diff --git a/BACKLOG.md b/BACKLOG.md index e7e7f93..2cc4f73 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -26,9 +26,12 @@ Two single-writer sections (§6.1): Builder edits only `## Build backlog`; Adver CLAIMED 2026-05-26, awaiting Adversary. ### M2 — Drone online -- [ ] Drone server + exec runner via Nix; Gitea OAuth app -- [ ] hello-world .drone.yml runs green; logs in Drone UI -- [ ] Gate: M2 — push to cc-ci triggers visible green build +- [x] Drone server (coop-cloud recipe, reconcile oneshot) + exec runner via Nix; Gitea OAuth app. + Server healthz 200 via gateway; runner polling (capacity=2, type=exec). +- [x] hello-world .drone.yml runs green; logs visible (Drone UI + API). Build #1 success: clone + + hello (echo/whoami=root/abra 0.13.0-beta/swarm=active), both exit 0. +- [x] Gate: M2 — push to cc-ci triggers visible green build → CLAIMED 2026-05-26, awaiting Adversary. + OAuth link via one-time `scripts/bootstrap-drone-oauth.sh` (documented in install.md §2). ### M3 — Comment bridge - [ ] comment-bridge service: HMAC verify, !testme exact match, collaborator check, Drone API call diff --git a/JOURNAL.md b/JOURNAL.md index 51ca6cc..6c49392 100644 --- a/JOURNAL.md +++ b/JOURNAL.md @@ -245,3 +245,24 @@ cc-ci repo activated in Drone, which requires the bot's Gitea OAuth login (brows Drone a Gitea token (to sync repos + set the push webhook). Next tick: script the OAuth login to mint a Drone token, activate cc-ci, push .drone.yml, confirm green. (DRONE_USER_CREATE made autonomic-bot the admin.) + +## 2026-05-26 — M2 GATE MET: green build via push (Drone + exec runner) + +**Drone↔Gitea OAuth (scripted, the one manual bootstrap):** logged the bot into Gitea (CSRF cookie +→ form), drove Drone `/login` → Gitea authorize consent (POST `/login/oauth/grant` with _csrf+state+ +granted=true) → code callback → Drone `_session_`. Captured the whole flow in +`scripts/bootstrap-drone-oauth.sh` (reads bot creds from env; documented in install.md §2; one-time, +token persists in Drone's data volume). + +**Repo activation:** `GET /api/user` → autonomic-bot admin=true; `GET /api/user/repos?latest=true` +synced 12 repos; `POST /api/repos/recipe-maintainers/cc-ci` → active=true, config_path .drone.yml +(sets the Gitea push webhook). + +**Green build:** added `.drone.yml` (exec pipeline), pushed (0d89e28). Polled +`/api/repos/recipe-maintainers/cc-ci/builds` → build #1 pending→running→**success**. Steps: +clone success exit 0; hello success exit 0 — log shows `whoami=root`, `abra 0.13.0-beta-06a57de`, +`swarm=active` (ran on the host via the exec runner). **M2 gate met; CLAIMED.** + +**Next:** M3 — comment-bridge service: Gitea issue_comment webhook → verify HMAC + `!testme` exact + +collaborator → resolve PR head repo/SHA → trigger a parameterized Drone build; post a PR comment with +the run link. Need a Drone API token for the bridge (mint from the bot's Drone account). diff --git a/STATUS.md b/STATUS.md index 53a4824..d92c074 100644 --- a/STATUS.md +++ b/STATUS.md @@ -1,8 +1,8 @@ # STATUS — cc-ci Builder -**Phase:** M1 complete & CLAIMED → starting M2 (Drone). M0 PASS (Adversary @21:35Z). M1 awaiting verdict. -**In-flight:** M2 — Drone server + exec runner via Nix + Gitea OAuth app (first M2 task). -**Last updated:** 2026-05-26 (M1 claimed) +**Phase:** M2 complete & CLAIMED → starting M3 (comment bridge). M0+M1 PASS (Adversary). M2 awaiting verdict. +**In-flight:** M3 — comment-bridge service (!testme webhook → Drone build trigger). +**Last updated:** 2026-05-26 (M2 claimed, green build #1) ## Gates - **Gate: M0 — CLAIMED, awaiting Adversary** (2026-05-26). Evidence: flake rebuilds cc-ci from repo @@ -16,7 +16,12 @@ deployed by hand → HTTP 200 over HTTPS via gateway at cchtml1.ci.commoninternet.net with the wildcard cert; torn down clean (services/volumes/secrets/containers all 0). Repro: `scripts/deploy-proxy.sh` + `abra app new/deploy/undeploy`. Starting M2 as independent work; will - not flip M2's gate until M1 shows PASS. + not flip M2's gate until M1 shows PASS. → **M1 PASS** @2026-05-26T22:20Z. +- **Gate: M2 — CLAIMED, awaiting Adversary** (2026-05-26). Evidence: Drone server (coop-cloud recipe, + reconcile oneshot, Gitea SSO) healthz 200 via gateway; exec runner polling (capacity=2). cc-ci repo + activated (push webhook). Pushing `.drone.yml` triggered build #1 → **success** (clone + hello exec + steps, exit 0; ran abra/docker on the host). Repro: `nixos-rebuild switch` + one-time + `scripts/bootstrap-drone-oauth.sh`. Starting M3 as independent work; won't flip M3 gate until M2 PASS. ## Blocked - (none) diff --git a/docs/install.md b/docs/install.md index 20d29f4..ea6c24c 100644 --- a/docs/install.md +++ b/docs/install.md @@ -49,7 +49,22 @@ curl -ks --resolve drone.ci.commoninternet.net:443: \ > it survives a momentary drop, and **use the absolute flake path** (systemd units run with cwd `/`): > `systemd-run --unit=ccci-sw --property=Type=oneshot nixos-rebuild switch --flake /root/cc-ci#cc-ci` -## 2. (later milestones) comment-bridge, dashboard, recipe enrollment +## 2. One-time: link Drone ↔ Gitea (OAuth grant) + +The only manual post-rebuild step. Drone needs the bot's Gitea OAuth token (granted by an +interactive login) before it can sync/clone repos; this can't be Nix-declared without putting the +bot password on the box. The token then persists in Drone's `data` volume. + +```sh +GITEA_USERNAME=autonomic-bot GITEA_PASSWORD=… bash scripts/bootstrap-drone-oauth.sh +# -> "drone login ok (admin=true)" / "repo recipe-maintainers/cc-ci active=true" +``` + +Verify a build runs green: push any commit to the cc-ci repo and watch +`https://drone.ci.commoninternet.net` (or the API) — the push webhook (set on activation) triggers +the `.drone.yml` self-test on the exec runner. + +## 3. (later milestones) comment-bridge, dashboard, recipe enrollment See `docs/enroll-recipe.md` (D5), `docs/secrets.md` (D6), `docs/runbook.md`. Each new piece of infra is added as another idempotent reconcile oneshot, so this install stays a single `nixos-rebuild`. diff --git a/scripts/bootstrap-drone-oauth.sh b/scripts/bootstrap-drone-oauth.sh new file mode 100644 index 0000000..609a27b --- /dev/null +++ b/scripts/bootstrap-drone-oauth.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# One-time Drone<->Gitea OAuth bootstrap (the ONLY manual post-`nixos-rebuild` step). +# +# Drone can only sync/clone repos once it holds the bot's Gitea OAuth token, which is granted by +# an interactive OAuth login. This cannot be Nix-declared without putting the bot's Gitea password +# on cc-ci, so it's a one-time operator step. The token then persists in Drone's `data` volume, so +# reconcile redeploys keep working. Re-run only if that volume is wiped. +# +# Usage (run ON cc-ci, as root): +# GITEA_USERNAME=autonomic-bot GITEA_PASSWORD=… bash scripts/bootstrap-drone-oauth.sh +# Optionally ACTIVATE a repo: REPO=recipe-maintainers/cc-ci (default). +set -euo pipefail +: "${GITEA_USERNAME:?set GITEA_USERNAME}"; : "${GITEA_PASSWORD:?set GITEA_PASSWORD}" +GITEA="${GITEA:-https://git.autonomic.zone}" +DRONE="${DRONE:-https://drone.ci.commoninternet.net}" +CLIENT_ID="${CLIENT_ID:-ab4cdb9d-ee96-4867-875f-87384505fc52}" +REPO="${REPO:-recipe-maintainers/cc-ci}" +RES=(--resolve "drone.ci.commoninternet.net:443:127.0.0.1") +export PATH=/run/current-system/sw/bin:"$PATH" + +cj=$(mktemp); dj=$(mktemp); az=$(mktemp) +trap 'rm -f "$cj" "$dj" "$az"' EXIT + +# 1) Gitea web login (CSRF cookie -> form field). +curl -s -c "$cj" "$GITEA/user/login" -o /dev/null +gcsrf=$(awk '/_csrf/{v=$NF} END{print v}' "$cj") +curl -s -b "$cj" -c "$cj" -o /dev/null \ + --data-urlencode "_csrf=$gcsrf" \ + --data-urlencode "user_name=$GITEA_USERNAME" \ + --data-urlencode "password=$GITEA_PASSWORD" \ + "$GITEA/user/login" + +# 2) Drone /login -> Gitea authorize URL. +loc=$(curl -sk -c "$dj" -o /dev/null -D - "${RES[@]}" "$DRONE/login" \ + | awk 'tolower($1)=="location:"{print $2}' | tr -d '\r') +curl -sk -b "$cj" -c "$cj" -o "$az" "$loc" + +# 3) Grant consent -> code callback -> complete Drone login (sets Drone session). +acsrf=$(grep -oE 'name="_csrf" value="[^"]*"' "$az" | head -1 | sed -E 's/.*value="([^"]*)".*/\1/') +state=$(grep -oE 'name="state" value="[^"]*"' "$az" | head -1 | sed -E 's/.*value="([^"]*)".*/\1/') +cb=$(curl -sk -b "$cj" -c "$cj" -o /dev/null -D - \ + --data-urlencode "_csrf=$acsrf" --data-urlencode "client_id=$CLIENT_ID" \ + --data-urlencode "state=$state" --data-urlencode "scope=" --data-urlencode "nonce=" \ + --data-urlencode "redirect_uri=$DRONE/login" --data-urlencode "granted=true" \ + "$GITEA/login/oauth/grant" | awk 'tolower($1)=="location:"{print $2}' | tr -d '\r') +curl -sk -b "$dj" -c "$dj" -o /dev/null -L "${RES[@]}" "$cb" + +# 4) Verify + sync + activate the repo. +admin=$(curl -sk -b "$dj" "${RES[@]}" "$DRONE/api/user" | jq -r '.admin') +echo "drone login ok (admin=$admin)" +curl -sk -b "$dj" "${RES[@]}" "$DRONE/api/user/repos?latest=true" >/dev/null +active=$(curl -sk -b "$dj" "${RES[@]}" -X POST "$DRONE/api/repos/$REPO" | jq -r '.active') +echo "repo $REPO active=$active"