valheim: restart only when a new Steam build ships
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
667a3debe1
commit
8312873c0d
2 changed files with 125 additions and 0 deletions
|
|
@ -125,6 +125,7 @@ in
|
|||
./services/redlib.nix
|
||||
./services/hardware-health.nix
|
||||
./services/wan-watchdog.nix
|
||||
./services/valheim-autoupdate.nix
|
||||
./services/prebuild.nix
|
||||
];
|
||||
|
||||
|
|
|
|||
124
services/valheim-autoupdate.nix
Normal file
124
services/valheim-autoupdate.nix
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
# services/valheim-autoupdate.nix — restart the Valheim server only when Steam
|
||||
# actually ships a new build.
|
||||
#
|
||||
# The Pelican egg already has AUTO_UPDATE=1, so steamcmd runs on every boot and
|
||||
# a restart *is* the update. The missing half is knowing when to bother, which
|
||||
# a plain nightly restart doesn't answer (it reboots players for nothing 364
|
||||
# nights out of 365). So, hourly:
|
||||
#
|
||||
# 1. installed buildid <- steamapps/appmanifest_896660.acf in the server volume
|
||||
# 2. latest buildid <- the container's own steamcmd, public branch
|
||||
# 3. differ, nobody playing -> POST restart to the wings daemon API
|
||||
#
|
||||
# Why the wings API and not `docker restart`: the egg's stop command is ^C, so
|
||||
# wings sends SIGINT and Valheim saves the world before exiting. docker's
|
||||
# SIGTERM would not. Auth reuses the node token already in /etc/pelican/config.yml
|
||||
# — read at *runtime*, so eval stays pure (see CLAUDE.md); no new secret, and
|
||||
# nothing to rotate by hand.
|
||||
#
|
||||
# Valheim has no console, no RCON and no broadcast command, so players cannot be
|
||||
# warned in-game — the only honest options are "restart on an empty server" or
|
||||
# "kick people mid-raid". This does the former and pushes an ntfy note instead.
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
# Pelican server "hawk" (panel server id 3). Both the volume path and the
|
||||
# container name are this uuid — wings names containers after the server.
|
||||
serverUuid = "185008d8-8a04-4077-be78-63853dc06c0e";
|
||||
steamAppId = "896660"; # valheim_server; 892970 is the client
|
||||
gamePort = "25569";
|
||||
|
||||
autoupdate = pkgs.writeShellScript "valheim-autoupdate" ''
|
||||
set -uo pipefail
|
||||
|
||||
uuid=${serverUuid}
|
||||
appid=${steamAppId}
|
||||
manifest=/var/lib/pelican/volumes/$uuid/steamapps/appmanifest_$appid.acf
|
||||
wings_cfg=/etc/pelican/config.yml
|
||||
state=/var/lib/valheim-autoupdate/announced
|
||||
secret=/var/secrets/ntfy-url
|
||||
|
||||
docker=${pkgs.docker}/bin/docker
|
||||
awk=${pkgs.gawk}/bin/awk
|
||||
curl=${pkgs.curl}/bin/curl
|
||||
|
||||
notify() { # body
|
||||
[ -f "$secret" ] || return 0
|
||||
url=$(${pkgs.coreutils}/bin/tr -d '\n' < "$secret")
|
||||
$curl -fsS --max-time 10 \
|
||||
-H "Title: Valheim server" -H "Tags: shield" \
|
||||
-d "$1" "$url" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# Nothing to restart, and no steamcmd to ask — a stopped server updates
|
||||
# itself whenever it next boots anyway.
|
||||
[ "$($docker inspect -f '{{.State.Running}}' "$uuid" 2>/dev/null)" = "true" ] || exit 0
|
||||
|
||||
installed=$($awk -F'"' '/"buildid"/{print $4; exit}' "$manifest" 2>/dev/null)
|
||||
latest=$($docker exec "$uuid" \
|
||||
./steamcmd/steamcmd.sh +login anonymous +app_info_update 1 \
|
||||
+app_info_print "$appid" +quit 2>/dev/null \
|
||||
| $awk '/"branches"/{b=1} b && /"public"/{p=1} p && /"buildid"/{gsub(/"/,"",$2); print $2; exit}')
|
||||
|
||||
# Steam unreachable, rate-limited, or output we don't recognise: do nothing
|
||||
# rather than guess. Next hour will try again.
|
||||
case "$latest" in ""|*[!0-9]*) exit 0 ;; esac
|
||||
case "$installed" in ""|*[!0-9]*) exit 0 ;; esac
|
||||
[ "$installed" = "$latest" ] && exit 0
|
||||
|
||||
# ponytail: UDP conntrack entries for the game port are the cheap stand-in
|
||||
# for a player count — no A2S challenge handshake to parse. They linger
|
||||
# ~2min past the last packet, so this reads as "played very recently",
|
||||
# which errs toward leaving people alone. Upgrade path if that's ever too
|
||||
# coarse: query A2S_INFO on the query port (game port + 1) and read the
|
||||
# players byte.
|
||||
if ${pkgs.gnugrep}/bin/grep -q "dport=${gamePort} " /proc/net/nf_conntrack 2>/dev/null; then
|
||||
if [ "$(${pkgs.coreutils}/bin/cat "$state" 2>/dev/null || true)" != "$latest" ]; then
|
||||
notify "Valheim build $installed -> $latest is out. Someone is online, so the update waits until the server is empty."
|
||||
${pkgs.coreutils}/bin/printf '%s' "$latest" > "$state"
|
||||
fi
|
||||
echo "valheim-autoupdate: $installed -> $latest available, players connected, deferring"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
token=$($awk '/^token:/{print $2}' "$wings_cfg")
|
||||
if [ -z "$token" ]; then
|
||||
echo "valheim-autoupdate: no wings token in $wings_cfg, cannot restart" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "valheim-autoupdate: $installed -> $latest, server empty, restarting"
|
||||
if $curl -fsS --max-time 30 -X POST \
|
||||
-H "Authorization: Bearer $token" -H 'Content-Type: application/json' \
|
||||
-d '{"action":"restart"}' \
|
||||
"http://127.0.0.1:8443/api/servers/$uuid/power" >/dev/null; then
|
||||
notify "Restarted for Valheim build $installed -> $latest."
|
||||
${pkgs.coreutils}/bin/printf '%s' "$latest" > "$state"
|
||||
else
|
||||
echo "valheim-autoupdate: wings power request failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
in
|
||||
{
|
||||
config = lib.mkIf (config.networking.hostName == "FredOS-Mediaserver") {
|
||||
systemd.services.valheim-autoupdate = {
|
||||
description = "Restart the Valheim server when a new Steam build ships";
|
||||
after = [ "docker-pelican-wings.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = autoupdate;
|
||||
StateDirectory = "valheim-autoupdate";
|
||||
# steamcmd's app_info_update round-trip is the slow part (~1min worst case).
|
||||
TimeoutStartSec = "5min";
|
||||
};
|
||||
};
|
||||
systemd.timers.valheim-autoupdate = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnBootSec = "10min"; # let wings bring the game container up first
|
||||
OnUnitActiveSec = "1h";
|
||||
RandomizedDelaySec = "5min"; # don't hit Steam on the exact hour
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue