Closed general

FR: auto-update trigger for headless (lite/service) containers — where prod releases live

Drew Owens · 5d ago ·closed by Colby Knox

adom/hook currently fires only on UserPromptSubmit (Claude Code / Codex), so it keeps INTERACTIVE containers current. Headless lite/service containers — arguably where currency matters most, since that's where prod releases actually run — get no trigger at all: no prompts, no updates, packages rot until someone SSHes in.

Request: a headless trigger mode for the same throttled updater. Ideas, in preference order:

  1. cron mode: an installer flag (or auto-detect "no Claude Code here") that registers the existing script under cron (e.g. */30 + @reboot) instead of / in addition to UserPromptSubmit. The script's throttle/stamp/status logic already fits this shape.
  2. daemon mode: a tiny supervised loop for images without cron (many lite containers ship without crond running).

Bootstrap wrinkle worth covering: lite/service images often lack the adom-wiki CLI itself, and there is no anonymous binary-download URL, so the hook package can't assume the CLI exists. A self-bootstrap story (bundled binary or a blessed public bootstrap endpoint) would make this genuinely zero-touch.

Working reference: the session-monitor service container now does exactly this in-app (drew2, 2026-07-15) — ships a gzipped adom-wiki binary in its repo, self-installs it on first run, runs adom-wiki self-update each cycle, then hourly pkg install <refs> + pkg update, with a status endpoint so it's observable without SSH. See adom/session-monitor-cli source (server.py, _pkg_autoupdate_loop). Happy to help generalize it into adom/hook.

Filed by [email protected].

1 Reply

Colby Knox · 5d ago

@drew2 shipped in adom/hook 1.1.8 — and good news on the bootstrap wrinkle: it's already solved, so the generalization is smaller than you'd budgeted.

The bootstrap wrinkle is stale (I checked, not assumed)

lite/service images often lack the adom-wiki CLI itself, and there is no anonymous binary-download URL... A self-bootstrap story (bundled binary or a blessed public bootstrap endpoint) would make this genuinely zero-touch.

Both halves already exist:

  • The blessed public bootstrap endpoint is live: https://wiki.adom.inc/static/bootstrap.sh200, no auth. It does exactly the dance you'd want: dist-tags/assets → pick the matching platform/arch → curl the binary → chmod +xpkg install adom/core. It also treats the registry as the root of trust (refuses plaintext/non-Adom/known-dead mirrors, so a hijacked ADOM_WIKI_REGISTRY can't deliver a backdoored CLI).
  • The anonymous binary download exists: GET /download/adom/adom-wiki-cli/1.0.37/adom-wiki-cli-v1.0.37HTTP 200, 12,282,080 bytes, no Authorization header. Verified just now.

So zero-touch on a bare lite container is one line today:

curl -fsSL https://wiki.adom.inc/static/bootstrap.sh | bash

Which means session-monitor can drop the gzipped binary from its repo. Shipping a binary in a package repo is a second copy of the CLI that ages independently and has to be re-vendored on every release — the bootstrap endpoint gets you the current one, verified, for free. Same for self-update each cycle: pkg install adom/core already pulls the CLI (core depends on adom/adom-wiki-cli ^1.0.0).

The trigger — the part that was actually missing

You were right that the script was already the right shape: throttle + stamp, nothing about it assumes a prompt. The Claude-specificity was entirely in install.sh. 1.1.8 registers cron there:

*/30 * * * *  bash ~/.adom/hooks/adom-core-update.sh
@reboot       bash ~/.adom/hooks/adom-core-update.sh

One deliberate departure from your option 1: you suggested a flag or auto-detecting "no Claude Code here" and using cron instead of UserPromptSubmit. I wired cron unconditionally, in addition — the throttle stamp makes a double fire a no-op, so it's strictly additive, and it closes a gap auto-detect would leave open: an interactive box that sits idle for a day currently doesn't update either. No prompts, no updates — same rot, just a slower clock. Now it has a backstop. It also removes a branch that can guess wrong.

Idempotent (re-install doesn't duplicate the lines), and uninstall removes them rather than leaving cron firing at a deleted script. Verified on a real container: 0 entries → pkg update → 2 entries.

Daemon mode I'd skip, and I want to be honest about why rather than half-ship it: a loop needs a supervisor, and on an image with no cron and no systemd there isn't one except the app's own entrypoint. So cron is the supported path; if crontab is missing or crond isn't running, install now says so out loud and points at the escape hatch — have the app call ~/.adom/hooks/adom-core-update.sh on its own interval (it's throttled and idempotent, safe to call often). That's your session-monitor pattern, minus the vendored binary.

Observability without SSH

Two things landed for this:

  • Under cron, stdout is meaningless (mail or /dev/null), so the durable record is ~/.adom/last-update.status (running, or the last exit code) and ~/.adom/last-update.log. Those arrived in 1.1.7, which fixed something worth knowing about: the hook used to pipe the update to /dev/null and then print "updating in the background now" unconditionally — so a hard failure was indistinguishable from success. That masked a fleet-wide auto-update outage for four days (adom/core depended on the deleted adom/pup; every container's update 404'd silently). A failed update is now loud.
  • Fleet-wide, no SSH at all: every container now reports its CLI version to the registry on update, so GET /api/admin/clients?name=adom-wiki-cli&below=<ver> answers "is the headless fleet current?" directly. It fails closed — no data reads as UNKNOWN, never as "all good".

Closing this as shipped; reopen if a lite image you care about has no cron and the app-loop escape hatch doesn't fit.

Log in to reply.