acpmux: Linux hosts keep their daemon on the newest feat-cmux-next build by themselves

Merged
TE
10d ago
+1679-228

Lands after #24304. The update lock and the read of the daemon's own home and socket moved to #24304, which is in review; this PR keeps the self-update and its timer and gets a base merge once #24304 lands. The CI job that publishes the channel is #24256, whose merge is held for Leo's OK on trust and security (it gets the R2 credentials). Until #24256 lands, host self-update finds no channel and changes nothing.

Summary

Leo: "OUR SHIT IS HARDLY EVER GOING TO GO IDLE. IT JUST WONT. YOU'LL JUST HAVE TO ACCEPT IT BREAKING AT SOME POINT BUT IT RESUMES WHATEVER IT INTERRUPTED. END OF STORY FOR THE DAEMON UPDATE."

Leo: "BRO CANT YOU JUST LIKE SUSPEND SHIT AFTER A TOOL CALL/TURN THEN JUST GO AND AUTOUPDATE OUR TEAMMATES NEED ACPMUX UPDATING TOO..."

#24070 updates an ssh peer when a Mac running cmux NEXT connects to it. A Linux host that no app connects to stayed on its old acpmux. With this PR, Linux hosts keep their own daemon on the newest feat-cmux-next build:

  • Channel. #24256 points cmux-tui/channels/feat-cmux-next.json at each daemon-tested feat-cmux-next tree, forward only. The file holds the tree key, the commit and the sha256 of each cmux-tui-acpmux-<target>.
  • acpmux host self-update [--channel feat-cmux-next] (Linux). It reads the channel. When the installed ~/.local/bin/acpmux has another sha256, it downloads that host's binary over https from files.cmux.com and checks the sha256. Then it runs the same update as host update:
    • the daemon restarts with its agents kept (KillMode=process);
    • it is rolled back when the new daemon does not come up, and that build is not retried.
  • One update per host at a time. The self-update takes the same lease as host update (#24304), so a timer run and an update from the app never overlap. A run that finds the lease held changes nothing and tries again at the next tick.
  • Timer. host setup and every host update install acpmux-update.service and acpmux-update.timer (every 15 minutes, first run 5 minutes after boot) next to acpmux.service. A hand-written unit with either name is left alone.
  • Opt-out. host setup --no-auto-update writes ~/.acpmux/no-auto-update, which keeps the host off it.

Leo-ask: ask-48bbaec1d4
Leo-ask: ask-061e3574a6
Leo-ask: ask-98502f19a4

Changelog

  • Added: acpmux host self-update and an acpmux-update.timer keep a Linux host's acpmux daemon on the newest feat-cmux-next build (once #24256 publishes the channel). Agents keep running across the restart, and a build that fails to come up is rolled back. Opt out with acpmux host setup --no-auto-update.

Tests

  • host_self_update_tests.rs:
    • a host that already has the channel's binary is left alone;
    • a newer build is downloaded, checked and installed;
    • a download whose sha256 does not match the channel is never installed;
    • a channel without this host's target is refused;
    • a build whose daemon does not come back is rolled back and not tried again;
    • one self-update runs at a time, and the opt-out stops them;
    • a Cloud VM is refused before anything is read;
    • the timer units install once, and a unit someone else wrote is left alone;
    • a_host_update_and_a_self_update_of_one_host_never_run_at_once: each waits for the other's lock, and acpmux.prev stays the known-good binary (fails without the lock in host update);
    • a_daemon_whose_socket_a_drop_in_moved_is_checked_where_it_listens: the update and a rollback's check reach the drop-in's socket (fails without the environment read).
  • host_update_tests.rs updated for the shared update path.

Observed / Not run

  • Observed: at 05746cd7 on cmux16, cargo test -p acpmux --lib cli::host passes 94 of 94, six runs in a row, and cargo clippy -p acpmux --lib --tests -- -D warnings is clean.

  • Not run: no fleet host was updated by hand while developing, and no live daemon was restarted.

  • Size: about 2,090 changed lines until #24304 lands; after the base merge, about 1,400, of which about 570 are tests and about 300 are command.rs moving into command_host.rs (the Rust god-file limit).

  • Observed (adopted by daemon-autoupdate-2 on huge-blue): six Codex review rounds (gpt-6-astra xhigh, read-only) fixed an install that never restarted (finished by the next run), the rollback backup (only a binary the answering daemon runs replaces acpmux.prev), a rolled-back mark that cannot be written (said, and retried), unit files (staged, written beside and renamed; a failed write starts nothing) and the opt-out (stops only acpmux's own timer). Each fix has a regression test that failed without it, in the same fast-checks run as its green: runs 40265668516, 40266620552, 40267789646, 40272269777. On the final head 5fbfed5a (a base merge): cargo test -p acpmux --lib cli:: 203 passed, cargo test -p cmux-tui remote_cli_docs 5 passed, fmt and clippy (--lib, --lib --tests) clean, run 40273398483.

  • Not run: no live host update or restart with the timer; the app compile is CI's.

  • Declined review findings: a same-commit binary with another sha is not reinstalled (same code, as host update decides); XDG_CONFIG_HOME is ignored, as the base's acpmux.service is; a no-op host update on a current host does not install the timer (setup and the next real update do). Follow-up: the timer install's refusals go to stderr and are not yet in setup/update's result rows.

Takes back #24304: nothing of its behaviour. Its lease now also covers the timer's host self-update, so the doc comments and the "another update of {} is running" message that named only host update, host setup and host enroll --fix were reworded to say so.

No capture: no UI.

Agent session: Air-Blue

šŸ¤– Generated with Claude Code