From 3a4cb1c327f4700ce200117e508dcbab6dc83e13 Mon Sep 17 00:00:00 2001 From: mrambossek Date: Sat, 1 Aug 2026 17:44:33 +0200 Subject: [PATCH] cli: survive the --serve transition when nobody is watching Deploying v0.8.0 broke fmr, and the reason is a flaw I should have seen: self-update is executed by the OLD binary, so the unit repair I put in the new binary's updater cannot fix the very update that installs it. The unit kept its argument-less ExecStart, the new binary answered that with usage and exit 2, and the service went into a restart loop. Fixed on fmr by hand, but that is not a fix for anyone else - and the whole premise of an unattended self-update is that nobody is watching when it happens. So: when started with no verb *and* systemd started us, the server repairs the unit and serves anyway, loudly. systemd sets INVOCATION_ID for every service invocation and nothing else does, so a person at a terminal still gets usage and a non-zero exit. Marked as a one-release shim to remove once no deployment predates --serve. Co-Authored-By: Claude Fable 5 --- server/cmd/echolot-server/main.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/server/cmd/echolot-server/main.go b/server/cmd/echolot-server/main.go index 8e923c2..43b0c9f 100644 --- a/server/cmd/echolot-server/main.go +++ b/server/cmd/echolot-server/main.go @@ -73,6 +73,26 @@ func run() error { switch { case actions.Help: + // Compatibility shim for one release. + // + // Serving became an explicit verb, but self-update is run by the *old* binary — so the + // repair added to the updater cannot fix the very update that installs the new one. A + // unit written before this change starts us with no arguments, and without this branch + // the service would simply stop working, unattended, on a host nobody is watching. + // + // Only when systemd started us: INVOCATION_ID is set by systemd for every service + // invocation and by nothing else, so a person at a terminal still gets usage. Remove + // this once no deployment predates --serve. + if os.Getenv("INVOCATION_ID") != "" { + slog.Warn("started by systemd with no verb — this unit predates --serve; " + + "repairing it and serving anyway") + if repaired, err := system.RepairExecStart(); err != nil { + slog.Error("could not repair the unit; fix ExecStart by hand", "err", err) + } else if repaired { + slog.Info("systemd unit updated to pass --serve") + } + return serve(cfg) + } config.Usage(os.Stderr) os.Exit(2) return nil