Skip to content

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 current release (as of 2026-05-22) is v0.1.0-alpha.1.

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.1.0-alpha.1/ # versioned install
/usr/lib/hal0-0.1.0-alpha.2/ # versioned install (next)
/etc/hal0/ # config, preserved across updates
/var/lib/hal0/ # models, registry, OpenWebUI state

hal0 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.

Terminal window
hal0 update --check

Hits https://releases.hal0.dev/stable.json and prints the latest version on the channel without applying it. Same manifest the curl hal0.dev/install.sh | bash bootstrap consumes.

Terminal window
hal0 update # stable channel
hal0 update --channel stable # explicit

The flow:

  1. Fetch the release manifest from https://releases.hal0.dev/stable.json.
  2. Verify the manifest signature (cosign keyless against the cert_url from the manifest; cosign 3.x requires --certificate).
  3. Download the version tarball.
  4. Verify the tarball against signer_identity + signer_issuer (GitHub OIDC, cosign).
  5. Unpack to /usr/lib/hal0-<new-version>/.
  6. Swap /usr/lib/hal0/current/ to the new version (atomic).
  7. Restart hal0-api.service.
  8. Slot units survive the restart — they keep serving traffic throughout.

releases.hal0.dev/stable.json is a Cloudflare Pages middleware proxy that auto-syncs from the GitHub release: cut a v* tag on the hal0 repo, release.yml publishes the GH release, and the manifest URL reflects it within ~60s. No hal0-web deploy needed.

Terminal window
hal0 update --rollback

Points 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.

ChannelWhat you get
stableCut releases. The default and the only channel with a live publish pipeline today.
nightlySchema-reserved for every green main build. No publishing workflow yet.
devSchema-reserved for ad-hoc dev cuts. No publishing workflow yet.

Channels are configured in hal0.toml. Switch with hal0 config edit or via the dashboard’s Settings view. Selecting anything other than stable will currently fall back to stable — the schema is in place, the cron-scheduled workflow that would populate nightly.json / dev.json is not.

Because every hal0 process is a systemd unit, the rest of the update mechanics are boring on purpose:

  • hal0-api.service restarts on the symlink swap. systemctl status hal0-api shows what version is now active.
  • hal0-slot@*.service instances survive the API restart; they keep serving traffic throughout.
  • hal0-openwebui.service restarts alongside the API.
  • Every unit logs into journald under its unit name, so a failed update shows up in journalctl -u hal0-api -n 100 without ceremony.

systemctl is-active hal0-api is the single binary check a homelab monitor (uptime-kuma, Prometheus blackbox, whatever) should hit after an update.

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.