A `systemctl --user` unit runs under the per-user manager, which systemd starts at first login and stops when the user's last session ends — so closing the SSH session that started Skald killed it, and it never came up at boot. No crash and nothing in the journal: the whole cgroup is simply torn down. Both installers now enable lingering after installing the unit, and update.sh carries the same helper so an installation predating this fix is healed by an ordinary update. A failure to enable it only ever warns, with the manual command — it must not abort an install. Two things found on the way there: update.sh matched `case "$OS" in Linux) ... Darwin)`, but $OS had already been normalized to lowercase at the top of the file, so stop_service and start_service were both silent no-ops. None of the ordering the file documents at its head was executing: the tarball went over the running binary (ETXTBSY, aborting the update mid-way) and the safety-net restart in cleanup() was a no-op too, leaving the box down. Neither workflow published install.sh / install-nightly.sh to the web root, so the scripts served by builds.skaldagent.net were hand-copied and drifting from the repo — an installer fix would reach every existing box through update.sh but never a new one. Nightly publishes the nightly installer, release publishes the release one, both with the same atomic temp-and-rename the tarballs use. Also on the unit: dropped `After=docker.service`, which a user manager silently ignores rather than honouring advisorily, and moved `Restart=on-failure` to `always` — run.sh exits 0 on any graceful shutdown, including one nobody asked for, which on-failure reads as a clean stop. That is also what absorbs the boot race against Docker now that lingering makes us start at boot.
This commit is contained in:
+50
-2
@@ -130,6 +130,43 @@ stop_existing_service() {
|
||||
fi
|
||||
}
|
||||
|
||||
# ── systemd user lingering ────────────────────────────────────────────────────
|
||||
# A `systemctl --user` unit runs under the per-user manager (user@UID.service),
|
||||
# which systemd starts at first login and STOPS when the user's last session
|
||||
# ends — taking every user service down with it. So without lingering the server
|
||||
# dies the moment you close the SSH session that started it, and never comes up
|
||||
# at boot. Enabling it is the whole difference between "runs while I'm logged
|
||||
# in" and "is a daemon".
|
||||
enable_linger() {
|
||||
local target="${USER:-$(id -un)}"
|
||||
|
||||
if ! command -v loginctl >/dev/null 2>&1; then
|
||||
warn "loginctl not found — cannot enable lingering."
|
||||
echo " The server will stop when you log out of this machine."
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$(loginctl show-user "$target" --property=Linger 2>/dev/null || true)" in
|
||||
*=yes) info "✔ Lingering already enabled for ${target}"; return 0 ;;
|
||||
esac
|
||||
|
||||
# Enabling linger for yourself is normally allowed without elevation; fall
|
||||
# back to sudo, non-interactive first so `curl | bash` never blocks on a
|
||||
# password prompt it has no terminal to answer.
|
||||
if loginctl enable-linger "$target" 2>/dev/null \
|
||||
|| sudo -n loginctl enable-linger "$target" 2>/dev/null \
|
||||
|| { [ "$IS_INTERACTIVE" = true ] && sudo loginctl enable-linger "$target"; }; then
|
||||
info "✔ Lingering enabled — the server keeps running after you log out"
|
||||
else
|
||||
warn "Could not enable lingering for ${target}."
|
||||
echo " Without it, the server stops as soon as your last session ends"
|
||||
echo " and does not start at boot. Run this once, as an administrator:"
|
||||
echo ""
|
||||
echo " sudo loginctl enable-linger ${target}"
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Docker install helper ─────────────────────────────────────────────────────
|
||||
install_docker() {
|
||||
if [ "$OS" = "linux" ]; then
|
||||
@@ -354,13 +391,21 @@ if [ "$OS" = "linux" ] && [ -z "${NOSYSTEMD:-}" ]; then
|
||||
[Unit]
|
||||
Description=Skald Circle (release ${VERSION})
|
||||
Documentation=https://skaldagent.net
|
||||
After=network.target docker.service
|
||||
# No After=docker.service here: this is a *user* unit, and docker.service is a
|
||||
# system unit the user manager knows nothing about — the dependency would be
|
||||
# silently ignored. Docker may therefore still be starting when we do; the
|
||||
# server fails fast when the daemon is unreachable and Restart brings it back a
|
||||
# few seconds later, so boot ordering settles itself.
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=${INSTALL_DIR}/run.sh
|
||||
WorkingDirectory=${INSTALL_DIR}
|
||||
Restart=on-failure
|
||||
# always, not on-failure: run.sh exits 0 on any graceful shutdown, including one
|
||||
# nobody asked for (a stray SIGTERM to the server), which on-failure would treat
|
||||
# as a clean stop and leave the box down. An explicit "systemctl --user stop"
|
||||
# is unaffected — systemd never restarts after a requested stop.
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
Environment=SKALD_BIN=${INSTALL_DIR}/bin/skald
|
||||
Environment=SKALD_SETUP_BIN=${INSTALL_DIR}/bin/skald-setup
|
||||
@@ -373,6 +418,9 @@ SERVICE
|
||||
systemctl --user enable --now skald-circle.service
|
||||
|
||||
info "✔ Service installed and started"
|
||||
|
||||
enable_linger
|
||||
|
||||
echo ""
|
||||
echo " Status: systemctl --user status skald-circle"
|
||||
echo " Logs: journalctl --user -u skald-circle -f"
|
||||
|
||||
Reference in New Issue
Block a user