Skip to main content

Secrets

The vault is running. SOPS is still the path deploys use.Both statements are true as of 2026-07-26, and the distinction matters more than either one on its own.

Intended model

OpenBao is the runtime source of truth for secrets. SOPS + age is the bootstrap and disaster-recovery backup. Deploy is vault-first with SOPS fallback, so a sealed or absent vault degrades to the encrypted file rather than failing.

What is actually true today

OpenBao was reinitialized on 2026-07-26 after being absent since the June 14 rebuild. It is up, healthy, initialized and unsealed; auto-unseal is proven to survive a container restart, including through the hill90-vault-unseal systemd unit that runs at boot. But the vault holds nothing. vault.sh setup and vault.sh seed have not been run, so there are no policies, no AppRoles and no KV data. Every deploy therefore still reads its secrets from SOPS — which is not an inference, it is what the green vault deploy logged:
So the vault is available infrastructure and SOPS is the operative store. Nothing reads a secret out of the vault today. Filling it is a deliberate next step that depends on the open decision below, not an oversight.

KV path map

The paths the intended model uses, once seeded:

What SOPS holds regardless

Three categories have no vault equivalent and live in SOPS by design:

Admin access is token-only

There is no single sign-on. OIDC through Keycloak was removed when the Keycloak stack was retired alongside the shelved application — the hill90-vault client was its only remaining consumer, and carrying a full identity provider for one operator was not worth the surface. This was a deliberate trade. Do not re-add vault.sh setup-oidc without first re-introducing an identity provider. Services authenticate by AppRole, never by root token.
Do not revoke the root token before the vault is configured. On OpenBao ≥ 2.5.3 the unauthenticated root-generation endpoints are disabled by default, so bao operator generate-root returns 403 — verified against 2.6.1. With no other sudo-capable token there is then no supported way back to root, and the vault cannot be configured at all.Correct order: initunsealsetupseedsetup-sync-token.

Auto-unseal

OpenBao starts sealed after every container restart. Three mechanisms handle it:
  1. Deploy-timedeploy.sh vault calls vault.sh auto-unseal after compose up.
  2. Boot-time — the hill90-vault-unseal systemd oneshot runs after docker.service, unsealing within roughly 60 seconds of a reboot.
  3. Manual fallbackvault.sh unseal.
The unseal key is stored on the VPS at /opt/hill90/secrets/openbao-unseal.key, mode 0600, owned by deploy.

Schema validation

platform/vault/secrets-schema.yaml is the canonical mapping between vault KV paths, SOPS keys and compose ${VAR} references. scripts/checks/check_secrets_schema.py runs in CI on every pull request and fails on drift in either direction — a compose reference with no schema entry, a schema key missing from the SOPS example, and three other checks.

The open question

Whether the vault earns its place at all is an open decision. The recorded recommendation is that SOPS stays the operative store until the vault has a concrete consumer, and that OpenBao’s file storage backend — deprecated, blocking any move past 2.6.x — sets a deadline on deciding rather than a reason to act immediately. The image tag is pinned, so nothing can break by surprise.

Source documents