home-server
manager.sh
#!/bin/sh
apk add --no-cache openssh-client > /dev/null 2>&1
SSH="ssh -i /ssh/id_ed25519 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR user@127.0.0.1"
RUNNING=1
stop_all() {
RUNNING=0
echo 'Stopping Sunshine and SDDM/gamescope...'
# Single SSH round-trip instead of two sequential ones -- Docker's stop
# grace period only allows a fixed window (raised to 30s below, but this
# halves the round-trips regardless) before SIGKILL, and a prior stop
# left gamescope/SDDM running because the second command never got a
# chance to run (2026-09-12 incident: Portainer stop killed this
# container before "sudo systemctl stop sddm" ever executed).
$SSH 'systemctl --user stop sunshine.service; sudo systemctl stop sddm' &
wait $!
exit 0
}
trap stop_all TERM INT
start_session() {
echo 'Starting SDDM (autologin to gamescope-session)...'
$SSH 'sudo systemctl start sddm'
echo 'Waiting for gamescope to be ready on DP-1...'
for i in $(seq 1 60); do
$SSH 'pgrep -x gamescope-wl' > /dev/null 2>&1 && sleep 5 && break
sleep 1
done
echo 'Gamescope ready'
echo 'Starting Sunshine...'
$SSH 'systemctl --user reset-failed sunshine.service 2>/dev/null; systemctl --user start sunshine.service'
echo 'Sunshine started'
# Give the session time to fully settle before health checks begin --
# is-active reports "activating" for a bit right after start, and the
# first poll must not mistake that for a failure.
sleep 30
}
start_session
echo 'Streaming logs...'
tail -F /sunshine-config/sunshine.log &
# Health-check loop: gamescope or Sunshine can die independently of this
# container (a host reboot killing the session mid-stream is what motivated
# this fix -- see 2026-09-11 incident), and the old script just tailed a dead
# log file forever with no way to notice or recover. Poll every 30s and
# restart the whole session only on a definite failure state -- "activating"/
# "reloading"/"deactivating" are normal transients, not failures.
while [ "$RUNNING" = "1" ]; do
sleep 30 &
wait $!
SUNSHINE_STATE=$($SSH 'systemctl --user is-active sunshine.service' 2>/dev/null)
GAMESCOPE_ALIVE=$($SSH 'pgrep -x gamescope-wl' 2>/dev/null)
BAD=0
case "$SUNSHINE_STATE" in
failed|inactive|"") BAD=1 ;;
esac
[ -z "$GAMESCOPE_ALIVE" ] && BAD=1
if [ "$BAD" = "1" ] && [ "$RUNNING" = "1" ]; then
echo "Health check failed (gamescope pid: '${GAMESCOPE_ALIVE:-none}', sunshine: '$SUNSHINE_STATE') -- restarting session..."
$SSH 'sudo systemctl stop sddm' 2>/dev/null
sleep 3
start_session
fi
done