Files
platform/scripts/setup/officer-setup/lib/env.sh
T
pastilhasandClaude Opus 5 86079adb9a mail transport is configured in the app, not in .env
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>
2026-08-12 23:01:17 +00:00

123 lines
4.6 KiB
Bash

#!/bin/bash
# =============================================================================
# officer-setup — the environment file
# =============================================================================
#
# Definitions only.
#
# ── Two secrets that must never be regenerated ──
#
# JWT_SECRET signs every session token. Minting a new one logs everybody out of
# every device, silently — the symptom is people being signed out for no stated
# reason. The original regenerated it on every run that answered "yes" to
# regenerating .env.
#
# VAULT_STORE_KEY is worse, and the original never wrote it at all — so a
# scripted install had no key and the vault and wallet refused to store anything.
# It encrypts every upstream credential the platform holds (see docs/secret-store.md
# for the full list) and, on top of the owner passphrase, the BIP39 seed envelope.
# Changing it makes all of them unreadable, and for the seed that is unrecoverable:
# the passphrase opens the inner envelope, and the outer one is gone. Unless the
# mnemonic was written down offline, so are the coins.
#
# Both are read back from an existing .env and kept. Both are slated to move into
# the secret store — docs/secret-store.md — which is what makes changing them an
# operation rather than data loss.
#
# ── Derived, not asked ──
#
# DATA_PATH and OFFICER_ITEMS_DIR come from $OFFICER_ROOT. They were two separate
# questions in the original, which had to agree with each other and with where the
# app store looks.
[[ -n "${OFFICER_SETUP_ENV_LOADED:-}" ]] && return 0
OFFICER_SETUP_ENV_LOADED=1
env_file() { echo "$(platform_dir)/.env"; }
env_exists() { [[ -f "$(env_file)" ]]; }
# One value out of an existing .env, without sourcing it — the file holds
# secrets and arbitrary shell would run as root.
env_get() {
[[ -r "$(env_file)" ]] || return 0
awk -F= -v k="$1" '
$1 == k {
v = substr($0, index($0, "=") + 1)
gsub(/^"|"$/, "", v)
print v
exit
}' "$(env_file)"
}
# Long enough to be worth having, and stripped of characters that would need
# quoting in a file everything reads with a naive parser.
generate_secret() { openssl rand -base64 48 | tr -d '/+=\n' | head -c 48; }
# Origin checking is OFF unless this is explicitly false — CLAUDE.md is explicit
# that the inversion is deliberate and is only defensible because the tailnet is
# the perimeter. With no tailnet there is no perimeter, so the default stops
# being defensible and the value has to be written the other way.
tailnet_present() { ip link show tailscale0 &>/dev/null; }
write_env() {
local dest
dest="$(env_file)"
[[ -f "$dest" ]] && cp -a "$dest" "${dest}.before-officer-setup"
# Restrictive from the moment it exists rather than chmod'd afterwards, so the
# secrets are never briefly world-readable. Restored straight after: umask is
# not scoped to a function, and leaving it at 077 would quietly make every file
# a later section creates owner-only.
local prior_umask
prior_umask="$(umask)"
umask 077
cat >"$dest" <<ENVF
# Written by officer-setup.
#
# Everything Officer reads at runtime. Kept at 0600 and owned by ${USERNAME}: it
# holds the token-signing secret and the database credential.
PORT="${ENV_PORT}"
PUBLIC_URL="${ENV_PUBLIC_URL}"
PUBLIC_BUILD_ENV="production"
# The browser relay listens on its own port, separate from the app.
BROWSER_RELAY_PORT="${ENV_BROWSER_RELAY_PORT}"
# ── Do not regenerate either of these ──
#
# JWT_SECRET signs every session token. A new one logs everybody out, everywhere.
JWT_SECRET="${ENV_JWT_SECRET}"
# VAULT_STORE_KEY encrypts every upstream credential in Postgres, and encrypts
# the wallet's seed envelope on top of the owner passphrase. Changing it makes
# all of them unreadable — and for the seed that is unrecoverable, passphrase or
# not. Back it up with the same seriousness as the mnemonics.
VAULT_STORE_KEY="${ENV_VAULT_STORE_KEY}"
POSTGRES_URL="${POSTGRES_URL}"
# Derived from the install root — see scripts/setup/officer-setup/lib/layout.sh.
DATA_PATH="${OFFICER_ROOT}/data"
OFFICER_ITEMS_DIR="${OFFICER_ROOT}/capabilities"
# The owner's real login home, which is where terminals, chats and task runs
# actually execute — as opposed to the managed home under DATA_PATH.
HOME_DIR="${USER_HOME}"
# Origin checking. Off by default in the platform, which is only safe behind a
# tailnet; written explicitly here so the machine's actual situation decides it.
ALLOW_ANY_ORIGIN="${ENV_ALLOW_ANY_ORIGIN}"
DISCORD_BUG_REPORT_WEBHOOK="${ENV_DISCORD_WEBHOOK}"
ENVF
umask "$prior_umask"
chown "${USERNAME}:$(user_group)" "$dest"
chmod 600 "$dest"
return 0
}