diff --git a/scripts/gitea_setup.sh b/scripts/gitea_setup.sh new file mode 100755 index 0000000..34ebf4d --- /dev/null +++ b/scripts/gitea_setup.sh @@ -0,0 +1,202 @@ +#!/usr/bin/env bash +# Phase 01 Gitea scaffolding. Idempotent. +# +# Creates the two new repos under the continuous-intelligence org, pushes a +# placeholder index.html to acdl-evidence, and creates qa + prod branches on +# acdl-contracts. Running against existing repos / branches / files is a +# no-op (409 or 422 is treated as success). +# +# Usage: ACDL_GITEA_TOKEN= scripts/gitea_setup.sh +# Exit codes: 0 = success (created or already existed); 1 = unrecoverable error. + +set -euo pipefail + +GITEA_HOST="${GITEA_HOST:-https://git.cloudinit.dev}" +ORG="continuous-intelligence" +TOKEN="${ACDL_GITEA_TOKEN:?ACDL_GITEA_TOKEN is required}" +API="${GITEA_HOST}/api/v1" + +AUTH=(-H "Authorization: token ${TOKEN}" -H "Content-Type: application/json") + +log() { printf '[setup] %s\n' "$*"; } +warn() { printf '[setup][WARN] %s\n' "$*" >&2; } +err() { printf '[setup][ERROR] %s\n' "$*" >&2; } + +# --- helpers ---------------------------------------------------------------- + +# http_status_code URL +http_get_status() { + local url="$1" + curl -sS -o /dev/null -w "%{http_code}" "${AUTH[@]}" "$url" +} + +# repo_exists NAME -> 0 if exists, 1 otherwise +repo_exists() { + local name="$1" + local status + status=$(http_get_status "${API}/repos/${ORG}/${name}") + [ "$status" = "200" ] +} + +# create_repo NAME DESCRIPTION +create_repo() { + local name="$1" + local description="$2" + local body + body=$(python3 -c " +import json, sys +print(json.dumps({ + 'name': '${name}', + 'description': ${description@Q}, + 'private': True, + 'default_branch': 'main', + 'auto_init': True, + 'gitignores': 'Python', + 'license': '', + 'readme': 'Default' +})) +") + log "Creating repo ${ORG}/${name} (default_branch=main, auto_init=true)" + local status body_out + status=$(curl -sS -o /tmp/setup_repo_create.json -w "%{http_code}" \ + "${AUTH[@]}" -X POST -d "$body" \ + "${API}/orgs/${ORG}/repos") + case "$status" in + 201) log " created (HTTP 201)" ;; + 409) log " already exists (HTTP 409); skipping" ;; + *) + err "create_repo ${name} failed: HTTP ${status}" + cat /tmp/setup_repo_create.json >&2 || true + return 1 + ;; + esac +} + +# file_exists REPO PATH -> 0 if the file already exists on the default branch +file_exists_on_default() { + local repo="$1" + local path="$2" + local status + status=$(http_get_status "${API}/repos/${ORG}/${repo}/contents/${path}?ref=main") + [ "$status" = "200" ] +} + +# create_placeholder_index REPO +create_placeholder_index() { + local repo="$1" + local path="index.html" + local placeholder + placeholder=' + + + + ACDL Evidence + + + +

ACDL Evidence Stream

+

Evidence timeline will appear here in Phase 05.

+

Placeholder served via Gitea raw file URL (D-012; Gitea has no native Pages).

+ +' + + if file_exists_on_default "$repo" "$path"; then + log "index.html already exists on ${repo} main; skipping" + return 0 + fi + + local body + body=$(python3 -c " +import json, base64 +content = '''${placeholder}''' +print(json.dumps({ + 'content': base64.b64encode(content.encode('utf-8')).decode('ascii'), + 'message': 'Initial placeholder index.html (Phase 01, D-016)', + 'branch': 'main' +})) +") + log "Pushing placeholder index.html to ${repo} main" + local status + status=$(curl -sS -o /tmp/setup_index_push.json -w "%{http_code}" \ + "${AUTH[@]}" -X POST -d "$body" \ + "${API}/repos/${ORG}/${repo}/contents/${path}") + case "$status" in + 201) log " pushed (HTTP 201)" ;; + 409|422) log " already exists or conflict (HTTP ${status}); skipping" ;; + *) + err "create_placeholder_index on ${repo} failed: HTTP ${status}" + cat /tmp/setup_index_push.json >&2 || true + return 1 + ;; + esac +} + +# branch_exists REPO BRANCH -> 0 if exists +branch_exists() { + local repo="$1" + local branch="$2" + local status + status=$(http_get_status "${API}/repos/${ORG}/${repo}/branches/${branch}") + [ "$status" = "200" ] +} + +# create_branch REPO BRANCH FROM_REF +create_branch() { + local repo="$1" + local branch="$2" + local from_ref="$3" + if branch_exists "$repo" "$branch"; then + log "Branch ${branch} already exists on ${repo}; skipping" + return 0 + fi + local body + body=$(python3 -c " +import json +print(json.dumps({'new_branch_name': '${branch}', 'old_branch_name': '${from_ref}'})) +") + log "Creating branch ${branch} on ${repo} from ${from_ref}" + local status + status=$(curl -sS -o /tmp/setup_branch.json -w "%{http_code}" \ + "${AUTH[@]}" -X POST -d "$body" \ + "${API}/repos/${ORG}/${repo}/branches") + case "$status" in + 201) log " created (HTTP 201)" ;; + 409) log " already exists (HTTP 409); skipping" ;; + *) + err "create_branch ${branch} on ${repo} failed: HTTP ${status}" + cat /tmp/setup_branch.json >&2 || true + return 1 + ;; + esac +} + +# --- main ------------------------------------------------------------------- + +log "Host: ${GITEA_HOST}" +log "Org: ${ORG}" +log "Token: " + +# Step 1: create acdl-contracts +if ! repo_exists acdl-contracts; then + create_repo acdl-contracts "ACDL developer + agentic entry surface (contract.yaml + issue trigger)" || exit 1 +else + log "acdl-contracts already exists; skipping create" +fi + +# Step 2: create acdl-evidence +if ! repo_exists acdl-evidence; then + create_repo acdl-evidence "ACDL hash-chained audit timeline served as a static site via raw file URLs" || exit 1 +else + log "acdl-evidence already exists; skipping create" +fi + +# Step 3: push placeholder index.html to acdl-evidence +create_placeholder_index acdl-evidence || exit 1 + +# Step 4: create qa + prod branches on acdl-contracts (visible stand-in for +# the unsupported Gitea environments API; per D-013). +create_branch acdl-contracts qa main || exit 1 +create_branch acdl-contracts prod main || exit 1 + +log "Done. Run scripts/verify_phase01.sh to confirm success criteria." +exit 0 \ No newline at end of file