chore(demo): one-shot host bootstrap (never merged)
Some checks failed
Demo bootstrap (one-shot) / bootstrap (push) Failing after 5s

Runs once on the demo-site runner while the org lead is away: writes
/opt/demo.runicgateway.com/.env from deploy/demo/.env.example with every
secret generated on the host and the rig's web token read through the
panel, and reports whether the host reaches the panel and the sidecar.
Never overwrites an existing .env and never prints a value. This branch
is deleted after the run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-27 03:56:30 -05:00
parent d48d8c473c
commit 7843207c24

View File

@@ -0,0 +1,94 @@
# ONE-SHOT, NEVER MERGED. Lives only on the chore/demo-bootstrap branch, which is
# deleted after it runs. It does the demo host's one-time setup while the org lead
# is away (deploy/demo/README.md): writes /opt/demo.runicgateway.com/.env from
# deploy/demo/.env.example with every secret generated ON THE HOST, and reports
# whether the host reaches the panel and the rig's sidecar.
#
# It never overwrites an existing .env, and it never prints a value: the log holds
# only yes/no facts and HTTP status codes.
name: Demo bootstrap (one-shot)
on:
push:
branches: [chore/demo-bootstrap]
jobs:
bootstrap:
runs-on: demo-site
env:
DEMO_DIR: /opt/demo.runicgateway.com
PTERODACTYL_URL: http://192.168.0.12
PTERODACTYL_SERVER: 6eb9d621
SIDECAR_URL: http://192.168.0.12:21004
steps:
- name: Check out the deploy files
uses: actions/checkout@v4
- name: Host facts
run: |
echo "host: $(hostname) user: $(id -un) ($(id -u))"
echo "docker: $(docker version --format '{{.Server.Version}}' 2>&1 | head -1)"
echo "compose: $(docker compose version --short 2>&1 | head -1)"
echo "node: $(node --version 2>&1 | head -1)"
echo "curl: $(command -v curl || echo missing)"
echo "sudo -n: $(sudo -n true 2>/dev/null && echo yes || echo no)"
echo "$DEMO_DIR exists: $(test -d "$DEMO_DIR" && echo yes || echo no)"
echo "$DEMO_DIR/.env exists: $(test -f "$DEMO_DIR/.env" && echo yes || echo no)"
- name: Reach the panel and the sidecar
run: |
code() { curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$@" || true; }
echo "panel $PTERODACTYL_URL/ -> $(code "$PTERODACTYL_URL/")"
echo "sidecar $SIDECAR_URL/health -> $(code "$SIDECAR_URL/health")"
- name: Write the .env
env:
PTERODACTYL_CLIENT_KEY: ${{ secrets.PTERODACTYL_CLIENT_KEY }}
run: |
set -euo pipefail
SUDO=""
if ! mkdir -p "$DEMO_DIR" 2>/dev/null || ! test -w "$DEMO_DIR"; then
sudo -n mkdir -p "$DEMO_DIR"
sudo -n chown "$(id -u):$(id -g)" "$DEMO_DIR"
fi
if test -f "$DEMO_DIR/.env"; then
echo "$DEMO_DIR/.env already exists; left untouched."
exit 0
fi
echo "PTERODACTYL_CLIENT_KEY secret present: $(test -n "$PTERODACTYL_CLIENT_KEY" && echo yes || echo no)"
# The rig's web token: the startup variable first, then sidecar.toml.
api() { curl -s --max-time 15 -H "Authorization: Bearer $PTERODACTYL_CLIENT_KEY" -H 'Accept: application/json' "$PTERODACTYL_URL/api/client/servers/$PTERODACTYL_SERVER/$1"; }
RIG_TOKEN=$(api startup | node -e "let s='';process.stdin.on('data',c=>s+=c).on('end',()=>{try{const v=JSON.parse(s).data.find(x=>x.attributes.env_variable==='RUSTLINK_WEB_TOKEN');process.stdout.write((v&&v.attributes.server_value)||'')}catch{}})") || true
echo "rig token from the startup variables: $(test -n "$RIG_TOKEN" && echo yes || echo no)"
if test -z "$RIG_TOKEN"; then
RIG_TOKEN=$(api 'files/contents?file=%2Frust-link%2Fsidecar.toml' | sed -n 's/^[[:space:]]*auth_token[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' | head -1) || true
echo "rig token from rust-link/sidecar.toml: $(test -n "$RIG_TOKEN" && echo yes || echo no)"
fi
if test -n "$RIG_TOKEN"; then
echo "sidecar /server with that token -> $(curl -s -o /dev/null -w '%{http_code}' --max-time 10 -H "Authorization: Bearer $RIG_TOKEN" "$SIDECAR_URL/server" || true)"
fi
hex() { head -c "$1" /dev/urandom | od -An -tx1 | tr -d ' \n'; }
f="$DEMO_DIR/.env.new"
cp deploy/demo/.env.example "$f"
chmod 600 "$f"
setkv() { K="$1" V="$2" awk 'index($0, ENVIRON["K"] "=") == 1 { $0 = ENVIRON["K"] "=" ENVIRON["V"] } { print }' "$f" > "$f.tmp"; mv "$f.tmp" "$f"; }
setkv DB_PASSWORD "$(hex 24)"
setkv DB_ROOT_PASSWORD "$(hex 24)"
setkv JWT_SECRET "$(hex 32)"
setkv SECRET_ENC_KEY "$(hex 32)"
setkv BOT_INTERNAL_KEY "$(hex 32)"
# The three published logins are shown on the site, so short is fine.
setkv DEMO_ADMIN_PASSWORD "demo-$(hex 5)"
setkv DEMO_MODERATOR_PASSWORD "demo-$(hex 5)"
setkv DEMO_PLAYER_PASSWORD "demo-$(hex 5)"
setkv PLAY_REVIEW_PASSWORD "$(hex 16)"
# The walks' stand-in id (76561199910000001); the default-set step gives it play data.
setkv DEMO_PLAYER_STEAM_ID 76561199910000001
setkv DEMO_RIG_SIDECAR_TOKEN "$RIG_TOKEN"
mv "$f" "$DEMO_DIR/.env"
chmod 600 "$DEMO_DIR/.env"
echo "wrote $DEMO_DIR/.env (mode 600). Empty keys left:"
grep -E '^[A-Z_]+=$' "$DEMO_DIR/.env" | cut -d= -f1 || true