---
sidebar_position: 2
title: Upgrade & rollback
---

# Upgrade & rollback

## Check the running version

```bash
curl -s http://<host>:3001/selfhost/version
# {"version":"0.1.126"}
```

This reads the **immutable file `/app/SELFHOST_VERSION`**, baked into the image at build time from `selfhosted/package.json`. It ships inside the image layer, so it **always matches the code actually running** and cannot be spoofed by a runtime env / compose `.env` override. That is the point: if an upgrade did not actually swap the control-plane image (a silent-upgrade failure), the baked file still reports the old version — you *see* the mismatch instead of trusting a lying label.

The dashboard renders this same value as a small `v0.1.126` label in the sidebar, so the running release is always visible. (The dashboard has no upgrade button — upgrades are driven by the API endpoint or by re-running the installer, below.)

The latest published version is in the release manifest:

```bash
curl -s https://dl.zibby.app/selfhosted/latest/manifest.env
# ZIBBY_SELFHOSTED_VERSION=0.1.126
# IMAGE_TARBALL=zibby-selfhosted-0.1.126.tar.gz
# ...
```

## Two ways to upgrade

### 1. Re-run the installer (in place)

The installer is idempotent — re-running it pulls a newer release, verifies + `docker load`s the new images, re-pins `AGENT_IMAGE`, and **force-recreates** the control-plane container from the freshly loaded image:

```bash
curl -fsSL https://dl.zibby.app/selfhosted/latest/install.sh | bash
# or, from the install dir:  cd zibby-selfhosted && bash install.sh
```

Your `.env` (JWT_SECRET, ENCRYPTION_KEY, tokens) and data volumes are preserved.

### 2. The owner-only remote upgrade (recommended)

`POST /selfhost/upgrade` runs a **safe, self-driving upgrade** with an automatic backup and rollback — you can trigger it remotely and it survives the control-plane restart. It is **owner-only**: the endpoint requires the workspace-owner PAT/session or the admin project token (a run token or a non-owner member is refused `403`).

```bash
curl -s -X POST http://<host>:3001/selfhost/upgrade \
  -H "Authorization: Bearer <owner-or-admin-token>" \
  -H "Content-Type: application/json" \
  -d '{"version":"0.1.126"}'
# → 202 { "upgradeId": "...", "phase": "starting", ... }
```

Because a container can't recreate itself, the control plane launches a **detached helper** container (with the host docker socket) that outlives the control-plane bounce and drives the upgrade end to end:

1. **backup** — flush Scylla, tar the Scylla + object-store volumes, copy `.env`, into `<install-dir>/backups/upgrade-<id>/`.
2. **anchor** — pin the current control-plane image as `zibby-control-plane:rollback-<id>` so the new `docker load` can't garbage-collect the rollback target.
3. **download → install** — fetch the target release's `install.sh` (Ed25519 + checksum verified), `docker load`, re-pin `AGENT_IMAGE`, force-recreate.
4. **healthcheck** — poll the new control plane until container-healthy **and** `GET /selfhost/version` returns the target version.
5. **rollback** — on an unhealthy new control plane (or an installer that exited non-zero), re-tag the anchor back to `:local` and force-recreate → back to the working version.

Poll the status (owner-only, read-only):

```bash
curl -s http://<host>:3001/selfhost/upgrade/status \
  -H "Authorization: Bearer <owner-or-admin-token>"
# { "upgrade": { "phase": "install", ... }, "logTail": "...", "history": [...] }
```

Phases progress `starting → backup → anchor → download → install → healthcheck → success`. A failed upgrade ends at `rolled_back` (the box is back on the prior, healthy version) or, if even rollback fails, `rollback_failed` (manual restore needed — the backup dir and rollback-anchor tag are named in the status). The control plane restarts mid-upgrade, so the poll may briefly fail to connect — that is expected.

:::warning Pin the exact version, not `latest`
`{"version":"latest"}` resolves through the CDN, which can serve a **stale cached** `latest` for a while after a publish. For a deterministic upgrade, pass the exact version you verified against `manifest.env` (e.g. `{"version":"0.1.126"}`). The helper also treats "healthy but the version didn't advance to the pinned target" as a failure and rolls back — a guard you only get with a pinned version.
:::

## If a run fails with empty steps after an upgrade

Symptom: reviews/runs "fail" with `steps: []` and the control-plane log shows `pull access denied for zibby-agent … repository does not exist`. That is an **agent-image tag mismatch** — the executor is trying to launch a tag the reused `.env` points at but the tarball loaded a different one. Installer 0.1.53+ fixes this at the root (it pins `AGENT_IMAGE` to the loaded tag and aliases `zibby-agent:local`). Immediate operator unblock:

```bash
docker tag zibby-agent:slim zibby-agent:local
```
