a minimal blog cms for your pds
1#!/usr/bin/env bash
2# Bootstrap the local sandbox PDS for fair.
3#
4# What it does:
5# 1. Pre-flight: docker, openssl, curl, mkcert, fair binary
6# 2. Verify mkcert root CA is trusted by the system
7# 3. Generate sandbox/.env if missing (random secrets)
8# 4. Generate TLS cert for pds.localtest.me if missing
9# 5. docker compose up -d (PDS + Caddy)
10# 6. Poll https://pds.localtest.me/xrpc/_health until healthy
11# 7. Create test account via `goat` (bundled in the PDS image)
12# 8. Emit OAuth client metadata into sandbox/public/.well-known/
13#
14# Idempotent. Safe to re-run; only generates secrets/certs/account on
15# first run.
16
17set -euo pipefail
18
19cd "$(dirname "$0")"
20
21# --- Pre-flight -------------------------------------------------------------
22
23require() {
24 if ! command -v "$1" >/dev/null 2>&1; then
25 echo "missing prerequisite: $1" >&2
26 if [[ -n "${2:-}" ]]; then
27 echo " install: $2" >&2
28 fi
29 exit 1
30 fi
31}
32
33require docker
34require openssl
35require curl
36require mkcert "brew install mkcert"
37
38case "$(uname -s)" in
39 Darwin|Linux) ;;
40 *) echo "sandbox supports macOS and Linux only" >&2; exit 1 ;;
41esac
42
43# Verify mkcert root CA is in the system trust store. Without this,
44# `fair auth login` will fail because the browser won't trust the
45# pds.localtest.me cert when the PDS redirects to its auth page.
46CAROOT="$(mkcert -CAROOT)"
47if [[ ! -f "$CAROOT/rootCA.pem" ]]; then
48 echo "mkcert has not generated a CA yet" >&2
49 echo "run: sudo mkcert -install" >&2
50 exit 1
51fi
52
53# Check whether the CA is trusted by browsers. The Go CLI can trust
54# mkcert's CA explicitly without sudo, but the OAuth flow opens a
55# browser, and unverified-cert warnings there are a UX papercut.
56# Warn but don't fail.
57case "$(uname -s)" in
58 Darwin)
59 if ! security find-certificate -c "mkcert" /Library/Keychains/System.keychain >/dev/null 2>&1; then
60 echo "WARN: mkcert root CA not in system trust store." >&2
61 echo " Run once for browser trust: sudo mkcert -install" >&2
62 echo " The Go CLI will still work (it trusts the CA explicitly)." >&2
63 echo
64 fi
65 ;;
66esac
67
68# Resolve the fair binary; build if missing.
69FAIR_BIN="${FAIR_BIN:-$(realpath ../fair 2>/dev/null || echo "")}"
70if [[ -z "$FAIR_BIN" || ! -x "$FAIR_BIN" ]]; then
71 echo "building fair binary..."
72 (cd .. && go build -o fair ./cmd/fair)
73 FAIR_BIN="$(realpath ../fair)"
74fi
75
76# --- Generate .env if missing -----------------------------------------------
77
78if [[ ! -f .env ]]; then
79 echo "generating sandbox/.env with random secrets..."
80 cat > .env <<EOF
81PDS_JWT_SECRET=$(openssl rand -hex 32)
82PDS_ADMIN_PASSWORD=$(openssl rand -hex 16)
83PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=$(openssl rand -hex 32)
84EOF
85 chmod 600 .env
86fi
87
88# Pull admin password into our shell for the goat invocation later.
89# shellcheck disable=SC1091
90set -a; source .env; set +a
91
92# --- Generate TLS cert if missing -------------------------------------------
93
94mkdir -p caddy/certs
95if [[ ! -f caddy/certs/pds.localtest.me.pem ]]; then
96 echo "generating TLS cert for pds.localtest.me..."
97 (cd caddy && mkcert -cert-file certs/pds.localtest.me.pem -key-file certs/pds.localtest.me-key.pem pds.localtest.me)
98fi
99
100# --- Start PDS + Caddy ------------------------------------------------------
101
102echo "starting PDS + Caddy..."
103docker compose -f compose.yaml --env-file .env up -d
104
105echo -n "waiting for PDS to be healthy "
106HEALTHY=""
107# Trust mkcert's CA explicitly so curl works whether or not the user
108# has run `sudo mkcert -install`. The Go CLI does the same.
109ROOT_CA="$CAROOT/rootCA.pem"
110for _ in $(seq 1 60); do
111 if curl -sfo /dev/null --cacert "$ROOT_CA" https://pds.localtest.me/xrpc/_health; then
112 HEALTHY="yes"
113 echo " ready"
114 break
115 fi
116 echo -n "."
117 sleep 1
118done
119
120if [[ -z "$HEALTHY" ]]; then
121 echo >&2
122 echo "PDS did not become healthy at https://pds.localtest.me" >&2
123 echo "Check logs: docker compose -f sandbox/compose.yaml --env-file sandbox/.env logs" >&2
124 exit 1
125fi
126
127# --- Create test account (idempotent) ---------------------------------------
128
129# `test`, `admin`, etc. are on the PDS's reserved-handle list.
130HANDLE="${SANDBOX_HANDLE:-aparker.pds.localtest.me}"
131PDS_HANDLE_DOMAIN_NO_DOT=".pds.localtest.me"
132EMAIL="${SANDBOX_EMAIL:-aparker@aparker.io}"
133PASSWORD="${SANDBOX_PASSWORD:-sandbox}"
134
135if docker exec fair-sandbox-pds goat pds account list 2>/dev/null | grep -q "$HANDLE"; then
136 echo "account $HANDLE exists, skipping create"
137else
138 echo "creating test account $HANDLE..."
139 docker exec fair-sandbox-pds goat pds admin account create \
140 --admin-password "$PDS_ADMIN_PASSWORD" \
141 --handle "$HANDLE" \
142 --email "$EMAIL" \
143 --password "$PASSWORD"
144 echo
145 echo "Test account ready:"
146 echo " handle: $HANDLE"
147 echo " password: $PASSWORD"
148fi
149
150# --- Emit OAuth client metadata --------------------------------------------
151
152mkdir -p public
153"$FAIR_BIN" --profile sandbox emit-client-metadata --out public
154
155# --- Done -------------------------------------------------------------------
156
157echo
158echo "sandbox ready. Endpoints:"
159echo " PDS XRPC: https://pds.localtest.me"
160echo " Health: https://pds.localtest.me/xrpc/_health"
161echo " Client metadata: file://$(pwd)/public/.well-known/oauth-client-metadata.json"
162echo
163echo "Next: \`fair --profile sandbox auth login\` (Phase 3f, in progress)."