From 3df5071bd114dc72be3bc6c99528cd038bd6bbca Mon Sep 17 00:00:00 2001 From: Keith Jackson Date: Sun, 16 Aug 2026 14:52:08 -0500 Subject: [PATCH] Codify host firewall + Gitea bootstrap as IaC (hands-off rebuild) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per operator: the firewalld rule (VM->host:3000 for the Flux<-Gitea source) must be IaC, not a manual command (R-ENV-01, R-PLAT-04). - infra/ansible/playbooks/host-prereqs.yml: idempotent firewalld open on the libvirt zone (localhost play, become; no extra collection). - bootstrap/gitea/bootstrap-gitea.sh: now fully automates admin + public repo + push (was manual instructions) — idempotent; creds stay 0600 on host, never committed. Applying host-prereqs still needs host sudo (I lack non-interactive sudo); with passwordless become a destroy/rebuild is fully unattended. Co-Authored-By: Claude Opus 4.8 (1M context) --- DECISIONS-LOG.md | 9 ++-- bootstrap/gitea/bootstrap-gitea.sh | 53 +++++++++++++++++------- infra/ansible/playbooks/host-prereqs.yml | 39 +++++++++++++++++ 3 files changed, 84 insertions(+), 17 deletions(-) create mode 100644 infra/ansible/playbooks/host-prereqs.yml diff --git a/DECISIONS-LOG.md b/DECISIONS-LOG.md index e371829..2b14bcf 100644 --- a/DECISIONS-LOG.md +++ b/DECISIONS-LOG.md @@ -27,9 +27,12 @@ need the operator are called out. `platform/kyverno` (verified). Gate green. - **Flux INSTALLED** on the cluster (4 controllers Running). Host **Gitea working** (named-volume fix), repo pushed. GitRepository created → **BLOCKED**: cluster pods can't reach `10.73.0.1:3000` - (firewalld `libvirt` zone drops VM→host:3000). **Needs operator (one command, I lack sudo):** - `sudo firewall-cmd --zone=libvirt --add-port=3000/tcp --permanent && sudo firewall-cmd --reload` - Then Flux reconciles from Gitea (Phase-1 T-1.4 exit). + (firewalld `libvirt` zone drops VM→host:3000). + - **Now codified as IaC (per operator: no manual commands):** `infra/ansible/playbooks/host-prereqs.yml` + opens the port idempotently; `bootstrap/gitea/bootstrap-gitea.sh` now **fully automates** admin + + public repo + push (was manual). A rebuild is hands-off. To APPLY the firewall (I lack + non-interactive sudo): `ansible-playbook infra/ansible/playbooks/host-prereqs.yml --ask-become-pass` + (or passwordless host sudo for unattended rebuilds). Then Flux reconciles → Phase-1 T-1.4 exit. - **NOT deployed:** `platform`/`apps` Kustomizations were deliberately NOT created — the running cluster (Cilium etc.) is untouched. Only Flux controllers + the GitRepository source exist. diff --git a/bootstrap/gitea/bootstrap-gitea.sh b/bootstrap/gitea/bootstrap-gitea.sh index 38969c9..a3b923d 100755 --- a/bootstrap/gitea/bootstrap-gitea.sh +++ b/bootstrap/gitea/bootstrap-gitea.sh @@ -38,20 +38,45 @@ else "$GITEA_IMAGE" fi +# --- automated, idempotent setup (admin + PUBLIC repo + push) — NO manual steps (R-ENV-01) --- +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +ADMIN_CRED_FILE="${GITEA_ADMIN_CRED_FILE:-${HOME}/.config/home-k3s-bootstrap-gitea-admin.txt}" +BASE="http://localhost:${HTTP_PORT}" # host-side mgmt via localhost; the cluster uses HOST_NAT_IP + +echo "Waiting for Gitea to be healthy..." +curl -fsS --retry 40 --retry-delay 2 --retry-all-errors "${BASE}/api/healthz" >/dev/null + +# Admin credentials: generated once, stored 0600 on the host (never committed, never printed). +if [ ! -s "$ADMIN_CRED_FILE" ]; then + mkdir -p "$(dirname "$ADMIN_CRED_FILE")" + printf 'user=gitops\npass=%s\n' "$(openssl rand -base64 18 | tr -dc 'A-Za-z0-9' | head -c 22)" > "$ADMIN_CRED_FILE" + chmod 600 "$ADMIN_CRED_FILE" +fi +ADMIN_USER="$(sed -n 's/^user=//p' "$ADMIN_CRED_FILE")" +ADMIN_PW="$(sed -n 's/^pass=//p' "$ADMIN_CRED_FILE")" + +# Create the admin user if missing (idempotent). +if ! "$ENGINE" exec -u git "$NAME" gitea admin user list 2>/dev/null | grep -qw "$ADMIN_USER"; then + "$ENGINE" exec -u git "$NAME" gitea admin user create \ + --username "$ADMIN_USER" --password "$ADMIN_PW" --email "${ADMIN_USER}@home.local" \ + --admin --must-change-password=false +fi + +# Org + PUBLIC repo (idempotent — 'already exists' is fine; public => Flux needs no git creds). +curl -fsS -u "${ADMIN_USER}:${ADMIN_PW}" -H 'Content-Type: application/json' -X POST \ + "${BASE}/api/v1/orgs" -d "{\"username\":\"${ORG}\"}" -o /dev/null 2>/dev/null || true +curl -fsS -u "${ADMIN_USER}:${ADMIN_PW}" -H 'Content-Type: application/json' -X POST \ + "${BASE}/api/v1/orgs/${ORG}/repos" -d "{\"name\":\"${REPO}\",\"private\":false}" -o /dev/null 2>/dev/null || true + +# Push current HEAD -> the bootstrap source's main. +git -C "$REPO_ROOT" push "http://${ADMIN_USER}:${ADMIN_PW}@localhost:${HTTP_PORT}/${ORG}/${REPO}.git" HEAD:main + cat < --password --email --admin - 2. In the UI (or API) create org '${ORG}' and a **PUBLIC** repo '${REPO}' - (public => Flux needs no git credentials; this Gitea is NAT-only, not internet-exposed). - 3. Push this repo up: - git remote add bootstrap http://:@${HOST_NAT_IP}:${HTTP_PORT}/${ORG}/${REPO}.git - git push bootstrap main - -Flux's flux-system GitRepository url is already wired to: - http://${HOST_NAT_IP}:${HTTP_PORT}/${ORG}/${REPO}.git (clusters/dev) +Gitea bootstrap source ready (${DATA_DIR}). + UI (host): ${BASE}/${ORG}/${REPO} + Flux GitRepository (cluster): http://${HOST_NAT_IP}:${HTTP_PORT}/${ORG}/${REPO}.git (clusters/dev) + Admin creds (host, 0600): ${ADMIN_CRED_FILE} +Prerequisite: firewalld must allow VM->host:${HTTP_PORT} — + ansible-playbook infra/ansible/playbooks/host-prereqs.yml --ask-become-pass EOF diff --git a/infra/ansible/playbooks/host-prereqs.yml b/infra/ansible/playbooks/host-prereqs.yml new file mode 100644 index 0000000..7e54485 --- /dev/null +++ b/infra/ansible/playbooks/host-prereqs.yml @@ -0,0 +1,39 @@ +--- +# Host (hypervisor/workstation) prerequisites for the local dev/test cluster — IaC so the cluster +# can be destroyed & rebuilt with NO remembered manual commands (R-ENV-01, R-PLAT-04). +# +# Runs on the HOST itself (localhost), NOT the cluster nodes. Needs host privilege escalation: +# ansible-playbook playbooks/host-prereqs.yml --ask-become-pass +# (or configure passwordless sudo / a become password for fully unattended rebuilds). +# +# Idempotent: safe to re-run; only changes what isn't already in the desired state. Uses +# firewall-cmd directly (no extra Ansible collection dependency). +- name: Configure host prerequisites for the local dev cluster + hosts: localhost + connection: local + become: true + gather_facts: false + vars: + # The bootstrap Gitea (bootstrap/gitea/) runs on the host; the in-cluster Flux must reach it + # over the k3s-dev NAT gateway. firewalld's `libvirt` zone (which owns virbrN) drops + # VM->host: by default, so this must be opened or Flux cannot fetch the GitOps source. + gitea_bootstrap_port: 3000 + libvirt_firewalld_zone: libvirt + + tasks: + - name: Check whether the Gitea port is already allowed on the libvirt zone + ansible.builtin.command: "firewall-cmd --zone={{ libvirt_firewalld_zone }} --query-port={{ gitea_bootstrap_port }}/tcp" + register: gitea_port_check + changed_when: false + failed_when: false + + - name: Allow the bootstrap-Gitea port from the cluster VMs (permanent + runtime) + ansible.builtin.command: "{{ item }}" + loop: + - "firewall-cmd --zone={{ libvirt_firewalld_zone }} --add-port={{ gitea_bootstrap_port }}/tcp --permanent" + - "firewall-cmd --zone={{ libvirt_firewalld_zone }} --add-port={{ gitea_bootstrap_port }}/tcp" + when: "'yes' not in gitea_port_check.stdout" + changed_when: true + + # Tighter alternative (documented, not default): scope to the cluster subnet with a rich rule + # firewall-cmd --zone=libvirt --add-rich-rule='rule family=ipv4 source address=10.73.0.0/24 port port=3000 protocol=tcp accept' [--permanent]