Skip to content

Updating, Backups and Rollback

Updates arrive as a new distribution archive. There are two ways to apply one.

Terminal window
sudo scripts/tc-operator/tc-operator update --config operator.yaml

The node fetches the release manifest, compares it to the installed version and, if there is a newer one, downloads it, verifies its checksum, extracts it and re-runs the new archive’s installer. It needs no terminal, so you can schedule it.

The connection is outbound only — the node pulls, central never connects in — so this works from behind a firewall. The manifest’s signature is checked before any field in it is trusted, because the update path extracts an archive over the live install root and re-runs its installer as root. --force re-installs even when the node is already current.

You are given two files: the tarball and a checksum sidecar.

Terminal window
# 1. Verify the archive before touching anything
sha256sum -c trusted-codes-operator-<version>-<sha>.tar.gz.sha256
# 2. Extract over the existing tree, in place. Do not delete the directory
# first — the running containers bind-mount paths inside it.
sudo tar -xzf trusted-codes-operator-<version>-<sha>.tar.gz -C /opt/trusted-codes
# 3. Re-run install. --archive retains this build so you can roll back to it.
cd /opt/trusted-codes
sudo scripts/tc-operator/tc-operator install --config operator.yaml \
--archive ~/trusted-codes-operator-<version>-<sha>.tar.gz

Preserved untouched: your operator.yaml, everything under /etc/trusted-codes — secrets, mTLS identity, backups — and all data in the database. Secrets are reused on a re-run, never rotated.

Applied: new migrations only, since applied ones are skipped; reference data re-applied; the functions runtime restarted so it serves the new code; surfaces rebuilt and restaged; the node’s published information re-signed; the on-box README regenerated.

Recorded: the build, visible in status and at the head of the on-box README. With --archive, the tarball is kept under /etc/trusted-codes/archives — the last five — so a rollback needs no re-fetch.

Refused: installing an older archive over a newer node aborts, because it would roll the code back. --allow-downgrade proceeds if that is deliberate, and rollback sets it for you.

Not needed: finalize-tls. The proxy serves the restaged files from disk immediately. Run it again only if you changed domains.

Check tc-operator status after every update. It prints the build now running.

Terminal window
scripts/tc-operator/tc-operator rollback # list retained builds
sudo scripts/tc-operator/tc-operator rollback --to <version|sha>

It re-extracts that build and re-runs install, and asks you to type the target to confirm.

Rollback reverts code only — edge functions, surfaces, installer scripts. It does not revert the database schema. Migrations are forward-only, so the older code runs against the current schema. That is fine for a release that only added things. It is not fine for one that dropped or renamed something the old code reads.

A true full rollback is a restore from a database dump, not a code re-extract.

The installer takes a database dump automatically in exactly one case: when you uninstall. A live node needs your own schedule.

  1. The database — accounts, connections, data.
  2. /etc/trusted-codes/secrets.env — the node’s identity and encryption keys: the federation signing key, the JWT secret, the encryption key for personal data, the database password.

Restored data is unreadable without the original encryption key, and a node rebuilt without this file gets a brand new federation identity. Back it up alongside every dump and keep it offline. A dump on its own is not a backup.

Terminal window
source /etc/trusted-codes/secrets.env
docker exec -e PGPASSWORD="$POSTGRES_PASSWORD" tcnode-db \
pg_dump -U supabase_admin -d postgres \
| gzip > tc-backup-$(date -u +%Y%m%dT%H%M%SZ).sql.gz

The container is <project>-db; on a default install that is tcnode-db, and docker ps shows it. This is the same dump the uninstall takes. Copy the result and a current secrets.env off the box, and schedule it for any real deployment.

  1. Provision the box and get the archive onto it.

  2. Restore your saved secrets.env first, to /etc/trusted-codes/secrets.env, mode 0600. This preserves the node’s identity and the key that decrypts the data. Without the original, the restored rows are unreadable.

  3. Restore your operator.yaml and run tc-operator install. The stack comes up and replays the schema. There is no user data yet.

  4. Load the dump:

    Terminal window
    source /etc/trusted-codes/secrets.env
    gunzip -c tc-backup-<ts>.sql.gz \
    | docker exec -i -e PGPASSWORD="$POSTGRES_PASSWORD" tcnode-db \
    psql -U supabase_admin -d postgres

The dump is a full logical snapshot. Loading it over the freshly migrated schema prints harmless “already exists” notices for objects the migrations also created; the data rows are what restore your users. Verify with tc-operator status and a test login.

Rehearse this on a throwaway box before you have to do it for real. The step people get wrong is the second one, and they find out at the worst possible moment.