MAIL_TRANSPORT was a fallback left from the old registration flow that sent
confirmation mail. That flow is gone; the variable outlived it.
It was never the primary source anyway. getTransport reads server_config
('server-settings' → smtp) first, which already backs a full UI at Settings →
Server → SMTP and its API in api/server-settings/smtp.ts, supporting resend,
smtp and mailhog. The env var only answered when that was absent — which is a
second source of truth for something the owner can already set, with the failure
mode that a stale URL in .env silently answers for a server whose settings row
is simply empty.
Removed from transport.ts, .env.example and the setup script's Environment
section, which no longer asks for it. setup-old/ still mentions it; that is the
archive and is left alone.
Also split the try. It wrapped the read AND the transport construction and
swallowed both, so three different problems produced one message. Unreachable
database, nothing configured, and stored settings that do not build a transport
now say different things, because the fix for each is different and this message
is all the caller ever sees.
The two consumers — queue/engine.ts and auth/forgot-password.ts — now raise
until SMTP is set in the UI, which is the honest answer rather than a regression.
Not typechecked: node_modules is empty here and installs are frozen. transport.ts
parses under `bun build --no-bundle`; the setup script was run and no longer
prompts for or writes the variable.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
83 lines
5.1 KiB
Bash
83 lines
5.1 KiB
Bash
PORT=9000
|
|
JWT_SECRET="<generate with: openssl rand -base64 32>"
|
|
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="<generate with: openssl rand -base64 32>"
|
|
|
|
# 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 admin 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.
|