Release 0.2.0 #4
@@ -64,3 +64,18 @@ jobs:
|
||||
done
|
||||
echo "[nightly] Deployed:"
|
||||
ls -lh "$DEST/"
|
||||
|
||||
- name: Publish the nightly installer
|
||||
run: |
|
||||
cd "${GITHUB_WORKSPACE:-.}"
|
||||
# install-nightly.sh is served straight from the web root
|
||||
# (curl -fsSL https://builds.skaldagent.net/install-nightly.sh | bash),
|
||||
# so without this it stays whatever was copied there by hand and drifts
|
||||
# from the repo — a fix to the installer would reach every existing box
|
||||
# through update.sh but never a new one. Same atomic publish as the
|
||||
# tarballs: a client mid-download never sees a half-written script.
|
||||
ROOT=/var/www/builds.skaldagent.net
|
||||
cp install-nightly.sh "$ROOT/.install-nightly.sh.tmp"
|
||||
chmod 644 "$ROOT/.install-nightly.sh.tmp"
|
||||
mv -f "$ROOT/.install-nightly.sh.tmp" "$ROOT/install-nightly.sh"
|
||||
echo "[nightly] Published install-nightly.sh"
|
||||
|
||||
@@ -104,3 +104,18 @@ jobs:
|
||||
printf '%s\n' "$VERSION" > "$DEST/.LATEST.tmp"
|
||||
mv -f "$DEST/.LATEST.tmp" "$DEST/LATEST"
|
||||
echo "[release] Updated releases/LATEST → $VERSION"
|
||||
|
||||
- name: Publish the release installer
|
||||
run: |
|
||||
cd "${GITHUB_WORKSPACE:-.}"
|
||||
# install.sh is served straight from the web root
|
||||
# (curl -fsSL https://builds.skaldagent.net/install.sh | bash), so
|
||||
# without this it stays whatever was copied there by hand and drifts
|
||||
# from the repo — a fix to the installer would reach every existing box
|
||||
# through update.sh but never a new one. Published here rather than on
|
||||
# every push so the served installer always matches a real release.
|
||||
ROOT=/var/www/builds.skaldagent.net
|
||||
cp install.sh "$ROOT/.install.sh.tmp"
|
||||
chmod 644 "$ROOT/.install.sh.tmp"
|
||||
mv -f "$ROOT/.install.sh.tmp" "$ROOT/install.sh"
|
||||
echo "[release] Published install.sh"
|
||||
|
||||
@@ -98,6 +98,22 @@ systemd service → ExecStart=run.sh
|
||||
|
||||
**Fix**: removed `Requires=docker.service` from the user unit template in both install scripts. Kept `After=docker.service` (advisory, doesn't block if the unit isn't found).
|
||||
|
||||
**Follow-up**: `After=docker.service` was dropped too. It never did anything — a _user_ manager has no view of system units, so the ordering was silently ignored rather than merely advisory, and keeping it suggested a guarantee that was not there. What actually handles the boot race is `Restart` (see below): the server fails fast when the Docker daemon is unreachable, and systemd brings it back a few seconds later.
|
||||
|
||||
## Bug fix: the server dies when you log out ✅
|
||||
|
||||
**Problem**: `systemctl --user start skald-circle` worked, but closing the SSH session killed the server — and it never came up at boot. Not an application bug: a `--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**, tearing down every user service in the cgroup. No crash, no error in the journal — the whole cgroup is simply killed.
|
||||
|
||||
**Fix**: both installers now run `loginctl enable-linger $USER` after installing the unit (helper `enable_linger`, tried unprivileged first, then `sudo -n`, then interactive `sudo`, and only warns if all three fail — a missing linger must never abort an install). `update.sh` carries the same helper so an installation predating this fix is healed by an ordinary update.
|
||||
|
||||
**Also**: `Restart=on-failure` → `Restart=always`. `run.sh` exits 0 on _any_ graceful shutdown, including one nobody asked for (a stray SIGTERM to the server), which `on-failure` reads as a clean stop and leaves the box down. An explicit `systemctl --user stop` is unaffected — systemd never restarts after a requested stop. With lingering on, this is also what absorbs the boot race against Docker.
|
||||
|
||||
## Bug fix: update.sh never stopped or restarted the service ✅
|
||||
|
||||
**Problem**: `stop_service` and `start_service` matched `case "$OS" in Linux) … Darwin)`, but `$OS` had already been normalized to `linux`/`darwin` at the top of the script. Every branch fell through: both functions were no-ops. So the updater extracted the tarball **over the running binary** (`ETXTBSY` on Linux, aborting the update mid-way) and, when extraction did succeed, left the old build running in memory with the safety-net trap firing a restart that was itself a no-op. The careful stop → wait-for-exit → extract ordering the file documents at the top had not been executing at all.
|
||||
|
||||
**Fix**: matched the normalized lowercase values, with a comment at the seam saying why the capitalization is load-bearing.
|
||||
|
||||
## Bug fix: skald-setup non interattivo con curl | bash ✅
|
||||
|
||||
**Problem**: `skald-setup` controlla `isatty(0)`, ma con `curl ... | bash` stdin è un pipe, quindi saltava senza chiedere username/password. L'installer arrivava fino in fondo ma senza aver creato l'admin.
|
||||
|
||||
+50
-2
@@ -127,6 +127,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
|
||||
@@ -349,13 +386,21 @@ if [ "$OS" = "linux" ] && [ -z "${NOSYSTEMD:-}" ]; then
|
||||
[Unit]
|
||||
Description=Skald Circle (${DISPLAY_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
|
||||
@@ -368,6 +413,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"
|
||||
|
||||
+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"
|
||||
|
||||
@@ -43,6 +43,13 @@ fi
|
||||
|
||||
CHANNEL="$(tr -d '[:space:]' < "$CHANNEL_FILE")"
|
||||
|
||||
# ── Detect interactive stdin ──────────────────────────────────────────────────
|
||||
if [ -t 0 ]; then
|
||||
IS_INTERACTIVE=true
|
||||
else
|
||||
IS_INTERACTIVE=false
|
||||
fi
|
||||
|
||||
# ── Colours (if terminal) ─────────────────────────────────────────────────────
|
||||
if [ -t 1 ]; then
|
||||
RED='\033[0;31m'
|
||||
@@ -96,9 +103,15 @@ STOPPED=0 # set once the service has been stopped
|
||||
STARTED=0 # set once it has been (re)started
|
||||
|
||||
# ── Stop service ──────────────────────────────────────────────────────────────
|
||||
# NOTE: match the *normalized* OS values set above (linux/darwin), not uname's
|
||||
# capitalized output. Getting this wrong turns both stop_service and
|
||||
# start_service into silent no-ops, and then none of the ordering this file
|
||||
# documents at the top actually happens: the tarball is extracted over the
|
||||
# running binary (ETXTBSY on Linux, aborting the update mid-way), and the
|
||||
# safety-net restart in cleanup() is a no-op too, so the box stays down.
|
||||
stop_service() {
|
||||
case "$OS" in
|
||||
Linux)
|
||||
linux)
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
if systemctl --user is-active skald-circle.service >/dev/null 2>&1; then
|
||||
info "⏹️ Stopping service …"
|
||||
@@ -106,7 +119,7 @@ stop_service() {
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
Darwin)
|
||||
darwin)
|
||||
if command -v launchctl >/dev/null 2>&1; then
|
||||
if launchctl list com.skald.circle >/dev/null 2>&1; then
|
||||
info "⏹️ Stopping agent …"
|
||||
@@ -145,13 +158,13 @@ wait_until_stopped() {
|
||||
# ── Start service ─────────────────────────────────────────────────────────────
|
||||
start_service() {
|
||||
case "$OS" in
|
||||
Linux)
|
||||
linux)
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
info "▶ Starting service …"
|
||||
systemctl --user start skald-circle.service
|
||||
fi
|
||||
;;
|
||||
Darwin)
|
||||
darwin)
|
||||
if command -v launchctl >/dev/null 2>&1; then
|
||||
info "▶ Starting agent …"
|
||||
launchctl load "$HOME/Library/LaunchAgents/com.skald.circle.plist" 2>/dev/null || true
|
||||
@@ -160,6 +173,34 @@ start_service() {
|
||||
esac
|
||||
}
|
||||
|
||||
# ── systemd user lingering ────────────────────────────────────────────────────
|
||||
# Same helper the installers run, repeated here so an install predating it gets
|
||||
# healed by an ordinary update: a `systemctl --user` unit lives under the
|
||||
# per-user manager, which systemd stops when the user's last session ends —
|
||||
# so without lingering the server dies at logout and never starts at boot.
|
||||
# Idempotent, and a failure is only ever a warning: the update itself is fine.
|
||||
ensure_linger() {
|
||||
[ "$OS" = "linux" ] || return 0
|
||||
|
||||
local target="${USER:-$(id -un)}"
|
||||
|
||||
command -v loginctl >/dev/null 2>&1 || return 0
|
||||
|
||||
case "$(loginctl show-user "$target" --property=Linger 2>/dev/null || true)" in
|
||||
*=yes) return 0 ;;
|
||||
esac
|
||||
|
||||
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 now survives logout and starts at boot"
|
||||
else
|
||||
warn "Lingering is not enabled for ${target}."
|
||||
echo " The server stops when your last session ends. Run this once:"
|
||||
echo " sudo loginctl enable-linger ${target}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Cleanup + safety net ──────────────────────────────────────────────────────
|
||||
# Runs on every exit. Removes temp files and, if the update died after the
|
||||
# service was stopped but before it came back up, makes a best-effort restart so
|
||||
@@ -279,6 +320,7 @@ main() {
|
||||
fi
|
||||
|
||||
# ── Restart ────────────────────────────────────────────────────────────────
|
||||
ensure_linger
|
||||
start_service
|
||||
STARTED=1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user