#!/usr/bin/env bash # infra/authentik/bootstrap/bootstrap.sh # # Idempotent post-deploy bootstrap. Waits for the Authentik server to become # ready, then ensures the project blueprints (./blueprints/*.yaml) are loaded # and applied. The blueprint files themselves are also mounted into the # server/worker containers via docker-compose.yml — the worker auto-applies # them at startup. This script is a belt-and-braces re-trigger that: # 1. polls /-/health/ready/ until it returns 200, # 2. triggers /api/v3/managed/blueprints//apply/ for every blueprint # so they re-apply immediately rather than waiting for the next worker # tick, # 3. prints the operator-actionable next steps (rotate bootstrap token, # enable MFA on the admin account, etc.). # # Run this from the operator's machine OR inside a Coolify post-deploy hook: # # export AUTHENTIK_BASE_URL=https://auth.boiler.saas-ctrl.com # export AUTHENTIK_BOOTSTRAP_TOKEN=$(cat ~/.cli-tokens/authentik-bootstrap) # bash infra/authentik/bootstrap/bootstrap.sh set -Eeuo pipefail : "${AUTHENTIK_BASE_URL:?AUTHENTIK_BASE_URL must be set (e.g. https://auth.boiler.saas-ctrl.com)}" : "${AUTHENTIK_BOOTSTRAP_TOKEN:?AUTHENTIK_BOOTSTRAP_TOKEN must be set (see .env.example)}" WAIT_MAX_SECONDS="${WAIT_MAX_SECONDS:-300}" WAIT_INTERVAL_SECONDS="${WAIT_INTERVAL_SECONDS:-5}" log() { printf "\033[1;34m[authentik-bootstrap]\033[0m %s\n" "$*"; } err() { printf "\033[1;31m[authentik-bootstrap:ERROR]\033[0m %s\n" "$*" >&2; } die() { err "$*"; exit 1; } # ---------------------------------------------------------------------------- # Step 1: wait for /-/health/ready/ # ---------------------------------------------------------------------------- log "waiting for ${AUTHENTIK_BASE_URL}/-/health/ready/ (timeout ${WAIT_MAX_SECONDS}s)" deadline=$(( $(date +%s) + WAIT_MAX_SECONDS )) while true; do if curl -fsS -o /dev/null -w "%{http_code}" "${AUTHENTIK_BASE_URL}/-/health/ready/" | grep -q "^200$"; then log "server ready" break fi if [[ $(date +%s) -ge ${deadline} ]]; then die "timed out waiting for /-/health/ready/" fi sleep "${WAIT_INTERVAL_SECONDS}" done # ---------------------------------------------------------------------------- # Step 2: list blueprints and trigger re-apply # ---------------------------------------------------------------------------- auth_header="Authorization: Bearer ${AUTHENTIK_BOOTSTRAP_TOKEN}" log "listing managed blueprints" blueprints_json="$(curl -fsS -H "${auth_header}" "${AUTHENTIK_BASE_URL}/api/v3/managed/blueprints/?page_size=200")" || die "blueprint listing failed (check AUTHENTIK_BOOTSTRAP_TOKEN)" # Extract pk for any blueprint whose `path` starts with /blueprints/toolctrl/. ids="$(printf "%s" "${blueprints_json}" \ | python3 -c 'import sys, json; data=json.load(sys.stdin); [print(b["pk"]) for b in data.get("results", []) if b.get("path","").startswith("/blueprints/toolctrl/")]')" if [[ -z "${ids}" ]]; then log "no /blueprints/toolctrl/* blueprints found yet — the worker has not picked them up" log "this is expected on the very first boot; the worker auto-discovers them within ~30s" log "re-run this script in 1 minute to apply explicitly" exit 0 fi # ---------------------------------------------------------------------------- # Step 3: apply each blueprint # ---------------------------------------------------------------------------- for id in ${ids}; do log "applying blueprint ${id}" curl -fsS -X POST -H "${auth_header}" "${AUTHENTIK_BASE_URL}/api/v3/managed/blueprints/${id}/apply/" \ -o /dev/null \ || err "apply failed for blueprint ${id} (will be retried by the worker)" done log "bootstrap complete" cat <<'EONOTE' ---------------------------------------------------------------------- NEXT STEPS (operator) ---------------------------------------------------------------------- 1. Log into https://auth.boiler.saas-ctrl.com using AUTHENTIK_BOOTSTRAP_EMAIL + AUTHENTIK_BOOTSTRAP_PASSWORD. The first login forces a password change. 2. In the Authentik UI, enroll TOTP for your account (User Settings -> Authenticators -> Set up TOTP). Required because the toolctrl-require-mfa-totp-for-admin policy denies access to the admin OIDC application without a verified TOTP device. 3. Rotate AUTHENTIK_BOOTSTRAP_TOKEN — it grants full admin-API access and is no longer needed after this bootstrap. Generate a fresh personal admin token instead, and store it in Coolify-Vault. 4. Add real human admins by creating users in the Authentik UI and adding them to the `toolctrl-admin` group. Force TOTP enrollment on each. 5. Verify OIDC discovery from the admin-panel side once #82 is wired: curl -fsS https://auth.boiler.saas-ctrl.com/application/o/toolctrl-admin-panel/.well-known/openid-configuration | jq .issuer ---------------------------------------------------------------------- EONOTE