PORT=9000 JWT_SECRET="" POSTGRES_URL="postgres://postgres:password@localhost:5432/officer" PUBLIC_URL=http://localhost:9000 # Guards (CORS origin checks, rate limits, password-strength rules) are ON unless this is set to # "dev" or "development". Leave it unset or set it to "production" for a real deployment; only set # it to "dev" on a local machine you trust, since that disables all three. PUBLIC_BUILD_ENV=production # Origin checking is OFF unless this is explicitly "false" — an inversion of the usual rule, and one # that is only defensible when the tailnet is the perimeter. On a machine with no tailnet, set it to # false. Written explicitly rather than left to the default so the choice is visible. ALLOW_ANY_ORIGIN=true DATA_PATH=/path/to/data OFFICER_ITEMS_DIR=/path/to/officer-items HOME_DIR=/home/user BROWSER_RELAY_PORT=18792 # ── Sidecars ──────────────────────────────────────────────────────────────────────────────────── # Each sidecar owns its upstream's credentials; the platform API is only a thin auth+forward proxy # and never sees them. An unset upstream URL is not fatal — the sidecar logs a warning at boot and # answers 503 until it is set, so you can run Officer with any subset of these configured. # Transmission (officer-transmission) is configured from the app, not from here — Transmission → # Connection. The daemon URL, the optional RPC auth and the RPC path live in `service_connections`, # with the password encrypted, so nothing outside the sidecar can read it. # InvoiceShelf (officer-invoiceshelf) is configured from the app, not from here — Invoices → Connection. # Instances, their Sanctum tokens and the company each one is pinned to live encrypted in # `invoiceshelf_accounts`, so nothing outside the sidecar can read a token. # slskd (officer-slskd) is configured from the app, not from here — Soulseek → Connection. The # daemon URL and its API key live encrypted in `service_connections`; the sidecar injects the key as # X-API-Key on every forwarded request. # Vaultwarden (officer-vault). VAULTWARDEN_URL=http://127.0.0.1:8222 # VAULT_STORE_KEY is NOT Vaultwarden's, despite the name and where it sits — it is the platform's # at-rest key, and it encrypts every secret column in Postgres: Headscale admin API keys, app-store # service credentials, Jellyfin tokens, wallet node credentials, and the wallet seed envelope on top # of the owner passphrase that seals it. Any strong secret of 16+ chars works. # # CHANGING IT MAKES ALL OF THAT UNREADABLE AT ONCE, and for the seed that is unrecoverable: the # passphrase opens the inner envelope and this is the outer one. See docs/secret-store.md, which is # the design for moving this key out of here and making rotation a supported operation. VAULT_STORE_KEY="" # Anthropic proxy (officer-anthropic-proxy). Defaults to 5051; it holds the API credential, which # lives in the host env rather than here. # ANTHROPIC_PROXY_PORT=5051 # ReClip — the self-hosted yt-dlp service the download-media capability talks to. Defaults to # http://localhost:8899. # RECLIP_URL=http://localhost:8899 # ── Headscale (/api/vpn) ──────────────────────────────────────────────────────────────────────── # These drive the /api/vpn router, NOT the officer-headscale sidecar. The sidecar deliberately reads # neither, keeping its registered servers and their keys in Postgres so host env can never shadow # one. Set these only if you use /api/vpn. # HEADSCALE_URL=https://headscale.example.com # HEADSCALE_API_KEY="" # HEADSCALE_USER=officer # ── Bitcoin wallet (officer-wallet) ───────────────────────────────────────────────────────────── # The chain data source is NOT here — it is configured from the app, at Wallet → Settings → Chain # source, and stored per owner. Any Esplora-compatible API works (electrs, esplora, mempool.space); # it defaults to the public mempool.space until you set one. # WALLET_NETWORK=bitcoin # bitcoin | testnet | signet | regtest # # How long an unlocked wallet stays unlocked, in seconds. Default 900 (15 min). The root key is held # in the sidecar's memory for exactly this long after an unlock, then wiped. Shorter is safer. # WALLET_UNLOCK_TTL_SEC=900 # # NOTE: seed material is encrypted with VAULT_STORE_KEY (above) on top of the owner passphrase that # seals it. Both are required to spend. If you lose VAULT_STORE_KEY, every stored seed is # unrecoverable — back up the mnemonics separately, offline. # Immich (officer-photos) is configured from the app, not from here — Photos → Connection. Instances and # their API keys live encrypted in `photos_config`, so the platform never sees a key.