cli: serving is an explicit verb; no arguments prints usage
server-release / image (push) Successful in 15s
server-test / test (push) Successful in 33s
server-release / release (push) Successful in 34s

Running an unfamiliar binary by name should tell you what it does, not bind a
dozen ports and start answering the internet. --serve (or --daemon) now does
that, and a bare invocation prints usage and exits 2 - non-zero on purpose, so a
service manager sees a failure rather than concluding the server ran and
finished cleanly.

The hazard this creates is worth spelling out, because it bites once and
silently: three places started the binary with no arguments - the systemd unit,
the unit template, and the Dockerfile - and --self-update replaces the binary
but never the unit. A routine update would therefore leave a service that cannot
start, discovered whenever the host next rebooted.

So the updater repairs it: after replacing the binary it appends --serve to an
ExecStart that has no flags, but only in a unit this program wrote (identified
by its description). Editing an operator's hand-written unit would be overreach;
leaving ours broken would be negligence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mrambossek
2026-08-01 17:42:20 +02:00
co-authored by Claude Fable 5
parent 80d2092f1b
commit 3cdbccee18
5 changed files with 99 additions and 2 deletions
+10
View File
@@ -9,6 +9,7 @@ package selfupdate
import (
"crypto/sha256"
"echo-lot.app/server/internal/system"
"encoding/hex"
"encoding/json"
"fmt"
@@ -132,6 +133,15 @@ func Run(api, currentVersion string) error {
os.Remove(tmp)
return fmt.Errorf("atomic replace failed (filesystem boundaries?): %w", err)
}
// Serving became an explicit verb, and a unit written before that change starts this binary
// with no arguments - which now prints usage and exits non-zero. The unit is not part of what
// an update replaces, so it is repaired here rather than left to fail at the next restart,
// which might be a reboot months from now.
if repaired, err := system.RepairExecStart(); err != nil {
fmt.Println("WARNING: could not update the systemd unit for --serve:", err)
} else if repaired {
fmt.Println("updated the systemd unit to pass --serve (serving is now an explicit verb)")
}
fmt.Printf("updated %s -> %s (%s); restart to run it\n", currentVersion, rel.TagName, self)
return nil
}