Updates & rollback
hal0 updates itself in one atomic symlink swap, backed by cosign-verified release tarballs. If an update goes wrong, one command rolls back to the previous version.
The layout
Section titled “The layout”The filesystem layout (PLAN §2) is FHS-aligned and deliberately separates the code from the data:
/usr/lib/hal0/current/ # symlink → /usr/lib/hal0-<version>//usr/lib/hal0-0.9.7/ # versioned install/usr/lib/hal0-0.9.8/ # versioned install (next)/etc/hal0/ # config, preserved across updates/var/lib/hal0/ # models, registry, OpenWebUI statehal0 update unpacks the new version next to the current one, then
swaps the current symlink in a single rename(2) call. The systemd
units point at /usr/lib/hal0/current/, so a systemctl restart
picks up the new version atomically. No partial state on disk, ever.
Check for an update
Section titled “Check for an update”hal0 update --checkReturns the latest version on the configured channel without applying it.
Apply an update
Section titled “Apply an update”hal0 update # stable channelhal0 update --channel stable # explicithal0 update --channel nightly # bleeding edgeThe flow:
- Fetch the release manifest from
hal0.dev. - Verify the manifest signature.
- Download the version tarball.
- Verify the tarball against
signer_identity+signer_issuer(GitHub OIDC, cosign). - Unpack to
/usr/lib/hal0-<new-version>/. - Swap
/usr/lib/hal0/current/to the new version (atomic). - Restart
hal0-api.service. - Slot units survive the restart — they keep serving traffic throughout.
Rollback
Section titled “Rollback”hal0 update --rollbackPoints current back at the previous versioned directory and
restarts. The data dirs (/etc/hal0, /var/lib/hal0) are untouched,
so your config, models, and registry are preserved.
Channels
Section titled “Channels”| Channel | What you get |
|---|---|
stable | Cut releases. The default; recommended. |
nightly | Every green main build. For testing only. |
Channels are configured in hal0.toml. Switch with
hal0 config edit or via the dashboard’s Settings view.
systemd does the lifecycle work
Section titled “systemd does the lifecycle work”Because every hal0 process is a systemd unit, the rest of the update mechanics are boring on purpose:
hal0-api.servicerestarts on the symlink swap.systemctl status hal0-apishows what version is now active.hal0-slot@*.serviceinstances survive the API restart; they keep serving traffic throughout.hal0-openwebui.servicerestarts alongside the API.- All four log into journald under their unit names, so a failed
update shows up in
journalctl -u hal0-api -n 100without ceremony.
systemctl is-active hal0-api is the single binary check a homelab
monitor (uptime-kuma, Prometheus blackbox, whatever) should hit after
an update.
Why atomic
Section titled “Why atomic”The naive approach (tar xf … && systemctl restart) has two failure
modes that bite at 2am:
- Partial unpack. Disk fills mid-tar; you have half a new version overlaid on the old one. Nothing works.
- Restart loop. New binary crashes on start; systemd retries; no
obvious way back without a second SSH session and a working
mv.
Atomic symlink swap solves both: the new version is fully unpacked
before the swap, and the previous version is still on disk for
--rollback to point back at.