b953fd4a8e
---ci--- phase: 1 milestone: v1.0 status: complete requirements: covered: [REQ-01, REQ-09] partial: [REQ-10, REQ-12] ---/ci--- Squash merge of phase/01-repo-scaffolding into milestone/v1.0-initial. Phase 01 ships the three-repo scaffold (acdl, acdl-contracts, acdl-evidence), the placeholder index.html on acdl-evidence (D-012/D-016 raw-URL substitute for unsupported Gitea Pages), the qa + prod branches on acdl-contracts (D-013 stand-in for unsupported Gitea environments), the workflow skeletons (pipeline.yml + issue-to-contract.yml), and the idempotent setup + verify scripts. REQ-10/12 remain partial pending Phase 04 full implementation.
228 lines
6.8 KiB
Bash
Executable File
228 lines
6.8 KiB
Bash
Executable File
#!/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=<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
|
|
}
|
|
|
|
# set_repo_visibility REPO VISIBILITY (public|private)
|
|
set_repo_visibility() {
|
|
local repo="$1"
|
|
local visibility="$2"
|
|
local body
|
|
body=$(python3 -c "
|
|
import json
|
|
is_private = ('${visibility}' == 'private')
|
|
print(json.dumps({'private': is_private, 'visibility': '${visibility}'}))
|
|
")
|
|
log "Setting ${repo} visibility to ${visibility}"
|
|
local status
|
|
status=$(curl -sS -o /tmp/setup_vis.json -w "%{http_code}" \
|
|
"${AUTH[@]}" -X PATCH -d "$body" \
|
|
"${API}/repos/${ORG}/${repo}")
|
|
case "$status" in
|
|
200) log " ok (HTTP 200)" ;;
|
|
*) warn "set_repo_visibility ${repo} -> ${visibility} returned HTTP ${status} (continuing)"; cat /tmp/setup_vis.json >&2 || true ;;
|
|
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='<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>ACDL Evidence</title>
|
|
<style>body{font-family:system-ui,sans-serif;margin:2rem;color:#333}</style>
|
|
</head>
|
|
<body>
|
|
<h1>ACDL Evidence Stream</h1>
|
|
<p>Evidence timeline will appear here in Phase 05.</p>
|
|
<p>Placeholder served via Gitea raw file URL (D-012; Gitea has no native Pages).</p>
|
|
</body>
|
|
</html>'
|
|
|
|
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: <set, ${#TOKEN} chars>"
|
|
|
|
# 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 2b: make acdl-evidence public so the Phase 05 UI (index.html) can
|
|
# fetch audit.json from a browser without exposing the API token (D-012
|
|
# raw-URL approach). acdl-contracts stays private.
|
|
set_repo_visibility acdl-evidence public
|
|
|
|
# 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 |