think
16px
820px

Deploying Obscura — the obscura CLI

One entry point for operating a deployment:

./deploy/obscura doctor      # what is wrong right now  (read-only, safe anywhere)
./deploy/obscura update      # the safe update pipeline
./deploy/obscura status      # what is running vs what is checked out
./deploy/obscura preflight   # does this host have the settings this build needs?
./deploy/obscura backup      # take a backup and wait for it to finalise
./deploy/obscura license     # the licence this deployment is running under
./deploy/obscura install     # first-time setup (wraps bootstrap-client.sh)
./deploy/obscura build <svc> # rebuild + restart one service

doctor and status never change anything. update is the only command that does.

Why this exists

Every problem below was a real incident, and every one of them was silent — health
checks stayed green while something was wrong:

Failure How it used to surface Now
update-prod.sh does not git pull deployed the old commit; noticed by chance doctor reports commits behind; update pulls
update.sh rebuilds only obscura+web sidecars ran stale code indefinitely doctor flags STALE images; update rebuilds them
A release adds a required setting AUDIT_CHAIN_KEY unset ⇒ audit chain forgeable, no warning preflight fails the deploy
A module is licensed but unconfigured office licensed, editing silently unavailable preflight + doctor report it
Deploy files hand-patched on the host a git reset silently reverted a live fix doctor fails on a dirty tracked tree

The three pieces

1. The binary declares what it needs

obscura-server -preflight evaluates the running environment and exits non-zero when a
required or security setting is unmet. -requirements emits the same as JSON.

Requirements live in go/internal/platform/config/requirements.go, next to the config they
describe, and are module-scoped: ONLYOFFICE_JWT_SECRET is a hard failure on a
deployment licensed for office and irrelevant on one that is not.

Severity is about consequence:

  • required — the deployment does not work.
  • security — it works, but a security control is inert. This is the dangerous
    middle that used to be invisible (AUDIT_CHAIN_KEY, BLOB_ENCRYPTION_KEY_FILE).
  • recommended — operational quality; reported, never blocking.

It cannot go stale. requirements_test.go reflects over Config and fails CI when a
new env var with no default is added without being either classified or explicitly waived
with a reason. Adding a setting now forces a decision about whether operators must be told.

2. doctor — one read-only pass

Source drift (behind/ahead/dirty) · configuration preflight (asks the running binary) ·
migration level vs checkout · image staleness · container health + /readyz · licence
status and licensed-but-unconfigured modules · backup freshness · disk.

Image staleness compares each service's image build time against the last commit
touching its sources
(from deploy/release.json). No state file, no image labels.

3. update — the safe pipeline

1  doctor          refuses to continue while a problem is open (--force to override)
2  backup          and waits for it to finalise           (--skip-backup to skip)
3  pull            git fetch + reset --hard origin/main
4  rebuild         only the sidecars whose sources changed
5  deploy          update-prod.sh (obscura + web, migrations, health gate)
6  verify          preflight, migrations, containers, licence  fails loudly

Step 4 is the one that used to be a human's job. Step 6 means a green deploy is actually
verified rather than merely finished.

deploy/release.json

Declares which services are buildable and which source paths feed each one. Update it when
a buildable service is added or its sources move — doctor uses it to decide what is stale.

Conventions that still apply

  • Never run bare update.sh on production — update-prod.sh pins the prod overlays and
    fails closed on an empty trust anchor or a non-HTTPS base URL. obscura update calls it.
  • prod.env and deploy/secrets/ are gitignored and survive git reset --hard. Never
    git clean a deployment host.
  • Secrets that must be vaulted outside the database: AUDIT_CHAIN_KEY (rotating it orphans
    every keyed audit row), BLOB_ENCRYPTION_KEY_FILE, STEGO_MASTER_KEY, and the licence
    signing key.