#!/usr/bin/env bash # ============================================================================= # 10-secrets — idempotent, ADD-ONLY seeder for /srv/secrets/stacks.env # # Mirrors the arrsstack convention: one master file, three tiers. # [OPERATOR] secrets for operating the host; never emitted to a container # [VALUES] flat, deduplicated KEY=value # [MANIFEST] "#@stack = VAR VAR" lines consumed by genenv.sh # # GUARANTEES: # * An existing key is NEVER touched. Not its value, not its position, # not its comment. If KEY= exists, this script skips it entirely. # * A missing key is appended with either a generated value (for tokens we # can safely generate) or the literal FILL_ME (for anything a human must # supply). genenv.sh already fails closed on FILL_ME. # * Values are never printed. Only key names and add/skip decisions. # * Running this twice in a row produces zero changes the second time. # # This is a SEPARATE secrets file from arrsstack's. Per the project decision: # no shared or mounted secrets between hosts. Canonical copy is Vaultwarden. # ============================================================================= set -euo pipefail MASTER="/srv/secrets/stacks.env" REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" umask 077 install -d -m 0700 /srv/secrets added=0; skipped=0 # --- create the skeleton only if the file does not exist at all ------------- if [ ! -f "$MASTER" ]; then echo " creating $MASTER from template" install -m 0600 "$REPO_DIR/secrets/stacks.env.example" "$MASTER" fi # Ensure the tier markers exist, so insertion has something to anchor to. grep -q '^# ----- \[VALUES\]' "$MASTER" || printf '\n# ----- [VALUES] -----\n' >> "$MASTER" grep -q '^# ----- \[MANIFEST\]' "$MASTER" || printf '\n# ----- [MANIFEST] -----\n' >> "$MASTER" # --------------------------------------------------------------------------- # add_value # generator: "hex32" -> openssl rand -hex 32 # "fill" -> literal FILL_ME (human must supply) # anything else -> used as the literal default value # Inserts immediately BEFORE the [MANIFEST] marker so it lands inside [VALUES]. # --------------------------------------------------------------------------- add_value() { local key="$1" gen="$2" val if grep -qE "^${key}=" "$MASTER"; then printf ' skip %-32s (already present)\n' "$key" skipped=$((skipped+1)) return 0 fi case "$gen" in hex32) val="$(openssl rand -hex 32)" ;; fill) val="FILL_ME" ;; *) val="$gen" ;; esac # Insert before the [MANIFEST] header, preserving everything else byte-for-byte. local tmp; tmp="$(mktemp)" awk -v line="${key}=${val}" ' /^# ----- \[MANIFEST\]/ && !done { print line; print ""; done=1 } { print } ' "$MASTER" > "$tmp" install -m 0600 "$tmp" "$MASTER"; rm -f "$tmp" printf ' ADD %-32s (%s)\n' "$key" "$([ "$gen" = fill ] && echo 'needs a human' || echo generated)" added=$((added+1)) } # --------------------------------------------------------------------------- # add_manifest # Appends a "#@stack" line only if one does not already exist for that stack. # --------------------------------------------------------------------------- add_manifest() { local stack="$1"; shift if grep -qE "^#@stack[[:space:]]+${stack}[[:space:]]*=" "$MASTER"; then printf ' skip %-32s (manifest present)\n' "#@stack ${stack}" skipped=$((skipped+1)) return 0 fi printf '#@stack %s = %s\n' "$stack" "$*" >> "$MASTER" printf ' ADD %-32s\n' "#@stack ${stack}" added=$((added+1)) } echo " seeding $MASTER (add-only)" # --- [VALUES] --------------------------------------------------------------- add_value TZ "America/New_York" # Reuses the SAME token value that arrsstack's mediabox-mcp already serves, so # the Claude connector entry does not change when NPM host 42 is repointed. # Left as FILL_ME: copy it across by hand rather than minting a new one. add_value MEDIABOX_MCP_BEARER_TOKEN fill add_value PLEX_ADVERTISE_URL "http://10.0.1.20:32400" # --- [MANIFEST] ------------------------------------------------------------- add_manifest shell-mcp MEDIABOX_MCP_BEARER_TOKEN TZ add_manifest plex TZ PLEX_ADVERTISE_URL chmod 600 "$MASTER" chown root:root "$MASTER" echo " result: ${added} added, ${skipped} left untouched" # Report unfilled keys by NAME only — never values. if grep -qE '^[A-Za-z_][A-Za-z0-9_]*=FILL_ME$' "$MASTER"; then echo echo " keys still needing a value (fill by hand, then re-run):" grep -E '^[A-Za-z_][A-Za-z0-9_]*=FILL_ME$' "$MASTER" | cut -d= -f1 | sed 's/^/ - /' fi exit 0