#!/usr/bin/env bash # Idempotent operator account bootstrap for Forgejo (RAIL-HO-WP-0005 T05). # Does not commit secrets — uses API token from file or env. set -euo pipefail FORGEJO_API="${FORGEJO_API:-https://forgejo.coulomb.social/api/v1}" FORGEJO_ORG="${FORGEJO_ORG:-coulomb}" FORGEJO_OPERATOR_USER="${FORGEJO_OPERATOR_USER:-tegwick}" FORGEJO_OPERATOR_EMAIL="${FORGEJO_OPERATOR_EMAIL:-bernd.worsch@gmail.com}" FORGEJO_OPERATOR_SSH_KEY="${FORGEJO_OPERATOR_SSH_KEY:-$HOME/.ssh/id_gitea.pub}" FORGEJO_OPERATOR_SSH_TITLE="${FORGEJO_OPERATOR_SSH_TITLE:-workstation-automation}" TOKEN_FILE="${FORGEJO_TOKEN_FILE:-/tmp/forgejo-tegwick-api-token}" if [[ -n "${FORGEJO_ADMIN_TOKEN:-}" ]]; then TOKEN="${FORGEJO_ADMIN_TOKEN}" elif [[ -f "${TOKEN_FILE}" ]]; then TOKEN="$(cat "${TOKEN_FILE}")" else echo "Set FORGEJO_ADMIN_TOKEN or create ${TOKEN_FILE}" >&2 exit 1 fi AUTH=(-H "Authorization: token ${TOKEN}") user_status() { curl -sS -o /dev/null -w '%{http_code}' "${AUTH[@]}" "${FORGEJO_API}/users/${FORGEJO_OPERATOR_USER}" } echo "==> Forgejo operator bootstrap: ${FORGEJO_OPERATOR_USER}" case "$(user_status)" in 200) echo " user exists" ;; 404) echo " creating user ${FORGEJO_OPERATOR_USER}" FORGEJO_OPERATOR_USER="${FORGEJO_OPERATOR_USER}" \ FORGEJO_OPERATOR_EMAIL="${FORGEJO_OPERATOR_EMAIL}" \ curl -fsS -X POST "${FORGEJO_API}/admin/users" "${AUTH[@]}" \ -H "Content-Type: application/json" \ -d "$(python3 -c " import json, os print(json.dumps({ 'username': os.environ['FORGEJO_OPERATOR_USER'], 'email': os.environ['FORGEJO_OPERATOR_EMAIL'], 'password': os.urandom(18).hex(), 'must_change_password': False, 'send_notify': False, })) ")" >/dev/null ;; *) echo "Unexpected user lookup status for ${FORGEJO_OPERATOR_USER}" >&2 exit 1 ;; esac # Promote to site admin curl -fsS -X PATCH "${FORGEJO_API}/admin/users/${FORGEJO_OPERATOR_USER}" "${AUTH[@]}" \ -H "Content-Type: application/json" \ -d '{"admin":true,"prohibit_login":false}' >/dev/null echo " admin=true" # Org Owners team membership team_id="$(curl -fsS "${AUTH[@]}" "${FORGEJO_API}/orgs/${FORGEJO_ORG}/teams" \ | python3 -c "import sys,json; teams=json.load(sys.stdin); print(next((t['id'] for t in teams if t.get('name')=='Owners'), ''))")" if [[ -n "${team_id}" ]]; then curl -fsS -X PUT "${AUTH[@]}" \ "${FORGEJO_API}/teams/${team_id}/members/${FORGEJO_OPERATOR_USER}" >/dev/null 2>&1 \ && echo " org ${FORGEJO_ORG} Owners team" \ || echo " (already in Owners team or API skipped)" fi # SSH key for git+ssh if [[ ! -f "${FORGEJO_OPERATOR_SSH_KEY}" ]]; then echo "Missing SSH public key: ${FORGEJO_OPERATOR_SSH_KEY}" >&2 exit 1 fi existing="$(curl -fsS "${AUTH[@]}" "${FORGEJO_API}/users/${FORGEJO_OPERATOR_USER}/keys" \ | FORGEJO_OPERATOR_SSH_KEY="${FORGEJO_OPERATOR_SSH_KEY}" python3 -c " import json, os, sys want = open(os.environ['FORGEJO_OPERATOR_SSH_KEY']).read().strip() keys = json.load(sys.stdin) print(any((k.get('key') or '').strip() == want for k in keys)) ")" if [[ "${existing}" == "True" ]]; then echo " SSH key already present" else FORGEJO_OPERATOR_SSH_TITLE="${FORGEJO_OPERATOR_SSH_TITLE}" \ FORGEJO_OPERATOR_SSH_KEY="${FORGEJO_OPERATOR_SSH_KEY}" \ curl -fsS -X POST "${FORGEJO_API}/admin/users/${FORGEJO_OPERATOR_USER}/keys" "${AUTH[@]}" \ -H "Content-Type: application/json" \ -d "$(python3 -c " import json, os print(json.dumps({ 'title': os.environ['FORGEJO_OPERATOR_SSH_TITLE'], 'key': open(os.environ['FORGEJO_OPERATOR_SSH_KEY']).read().strip(), })) ")" >/dev/null echo " SSH key added (${FORGEJO_OPERATOR_SSH_TITLE})" fi echo "==> Operator bootstrap complete: ${FORGEJO_OPERATOR_USER}" echo " Test: ssh forgejo-remote # expect Hi there, ${FORGEJO_OPERATOR_USER}!"