localhost is wrong on a machine with a tailnet, and quietly so: it works from the machine itself and nowhere else, so the mistake surfaces on the first phone rather than during setup. And PUBLIC_URL is not decoration — gen:index bakes it into the page's OpenGraph tags, the task API hands it to scripts as OFFICER_API_HOST, and the CalDAV profile builder refuses without it. The tailnet is where Officer is actually reached, and it is the perimeter the whole security model rests on now that origin checking is gone. Its address is the honest default. Prefers the MagicDNS name over the raw 100.x address — both work, but the name survives a node being re-registered and is something a person can type. Falls back to localhost with no tailnet, which is right rather than merely tolerable: a machine with no private network has no better address to guess. Suggests http://officer-dev.ts.pastilhas.dev:9000 on this machine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
119 lines
3.9 KiB
Bash
119 lines
3.9 KiB
Bash
#!/bin/bash
|
|
# =============================================================================
|
|
# officer-setup — the environment file
|
|
# =============================================================================
|
|
#
|
|
# Definitions only.
|
|
#
|
|
# ── No secrets are written here ──
|
|
#
|
|
# Every encryption and signing key lives in the secret store — a 0600 SQLite file
|
|
# at $OFFICER_ROOT/secrets/officer-keys.db, one key per purpose, created on first
|
|
# use. See docs/secret-store.md and the Secrets section of officer-setup.sh.
|
|
#
|
|
# So this file holds no credential except POSTGRES_URL, which is a connection
|
|
# string to a database bound to loopback.
|
|
#
|
|
# ── 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}"
|
|
|
|
# Where Officer is reached from a browser. Not derivable — see the section.
|
|
PUBLIC_URL="${ENV_PUBLIC_URL}"
|
|
|
|
POSTGRES_URL="${POSTGRES_URL}"
|
|
|
|
ENVF
|
|
|
|
umask "$prior_umask"
|
|
|
|
chown "${USERNAME}:$(user_group)" "$dest"
|
|
chmod 600 "$dest"
|
|
return 0
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# A sensible default for PUBLIC_URL
|
|
# -----------------------------------------------------------------------------
|
|
#
|
|
# localhost is the wrong default on a machine with a tailnet, and quietly so:
|
|
# it works from the machine itself and from nowhere else, so the mistake shows up
|
|
# on the first phone, not during setup.
|
|
#
|
|
# The tailnet is where Officer is actually reached — it is the perimeter the
|
|
# whole security model rests on — so its address is the honest default.
|
|
#
|
|
# MagicDNS name preferred over the raw 100.x address. Both work, but the name
|
|
# survives the node being re-registered and reads as something a person can type,
|
|
# and PUBLIC_URL ends up baked into the page's OpenGraph tags by `bun gen:index`.
|
|
#
|
|
# Falls back to localhost when there is no tailnet, which is correct rather than
|
|
# merely tolerable: a machine with no private network has no other address that
|
|
# is any better a guess.
|
|
tailnet_hostname() {
|
|
local dns ip
|
|
dns="$(tailscale status --json 2>/dev/null | grep -oP '"DNSName":\s*"\K[^"]+' | head -1)"
|
|
dns="${dns%.}"
|
|
if [[ -n "$dns" ]]; then
|
|
echo "$dns"
|
|
return 0
|
|
fi
|
|
ip="$(tailscale ip -4 2>/dev/null | head -1)"
|
|
[[ -n "$ip" ]] && echo "$ip"
|
|
}
|
|
|
|
default_public_url() {
|
|
local host
|
|
host="$(tailnet_hostname)"
|
|
if [[ -n "$host" ]]; then
|
|
echo "http://${host}:${1}"
|
|
else
|
|
echo "http://localhost:${1}"
|
|
fi
|
|
}
|