Files
platform/scripts/setup/officer-setup/lib/env.sh
T
pastilhasandClaude Opus 5 571d0a62ff delete the bug-report discord webhook, and the last dead HOME_DIR reads
DISCORD_BUG_REPORT_WEBHOOK is gone, with sendToDiscord and its helpers. Reports
still land in DATA_PATH/bug-reports — the disk write always happened first and
the webhook was only a ping about it, so nothing about the report is lost.

It was a personal notification channel living in deployment config, on a platform
whose owner is the only person who files reports. It was also never in
.env.example: the setup script wrote a variable nothing documented, which is the
same drift as PORT, in the other direction.

Note DISCORD_WEBHOOK_URL is a DIFFERENT variable — the notify sidecar's own
channel — and is untouched.

Then a parity sweep of setup / .env.example / what the code reads, which turned
up two leftovers from earlier today:

HOME_DIR was still read in six files, each with its own `?? homedir()` fallback.
Dead since nothing sets it, but a dead read is worse than none — it reads as a
supported override. They take homedir() directly now. user-instance.ts gets a
comment on why its line stays where it is: it sits above `process.env.HOME =
homeDir`, and homedir() reads $HOME, so a read moved below that assignment would
return whichever member was last spawned into. Two of the six had fallback chains
ending in process.cwd() and '' — the second would have silently disabled whatever
consumed it rather than failing.

VAULTWARDEN_URL was uncommented in .env.example among the variables setup writes,
though it is a plugin variable setup has never written. Commented out with the
other plugin entries.

The three files now agree: setup writes PORT, BROWSER_RELAY_PORT and
POSTGRES_URL; .env.example lists those plus JWT_SECRET and VAULT_STORE_KEY, which
are required by code and deliberately unwritten until the secret store lands.

Not typechecked (empty node_modules, frozen installs). Every changed file parses;
the setup section was run and writes three variables.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 00:23:32 +00:00

81 lines
2.6 KiB
Bash

#!/bin/bash
# =============================================================================
# officer-setup — the environment file
# =============================================================================
#
# Definitions only.
#
# ── What is NOT here ──
#
# JWT_SECRET and VAULT_STORE_KEY are not written. They are moving into the SQLite
# key store (docs/secret-store.md), and writing them here in the meantime would
# mean generating a value that the store then has to be reconciled with — two
# origins for one secret, which is the failure the store exists to end.
#
# The consequence is honest and deliberate: jwt.ts throws at module load without
# JWT_SECRET, so an install made by this script does not boot until the store
# lands. That sequencing was chosen rather than stumbled into.
#
# ── Derived, not asked ──
#
# DATA_PATH, OFFICER_ITEMS_DIR and HOME_DIR are gone too, and this time nothing
# replaces them. The platform derives the install root as the parent of its own
# working directory, so data/, capabilities/ and dockers/ follow from the layout
# on disk, and the owner's home comes from the OS. They were three environment
# variables that had to agree with each other and with the directory tree.
[[ -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)"
}
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}"
# The browser relay listens on its own port, separate from the app.
BROWSER_RELAY_PORT="${ENV_BROWSER_RELAY_PORT}"
POSTGRES_URL="${POSTGRES_URL}"
ENVF
umask "$prior_umask"
chown "${USERNAME}:$(user_group)" "$dest"
chmod 600 "$dest"
return 0
}