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.shon production —update-prod.shpins the prod overlays and
fails closed on an empty trust anchor or a non-HTTPS base URL.obscura updatecalls it. prod.envanddeploy/secrets/are gitignored and survivegit reset --hard. Never
git cleana 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.