Files
home-network-k3s-impl/apps/gitea-bootstrap-patch.yaml
T
jakman85andClaude Opus 4.8 bc4519bc4c Phase-6: enable Gitea Actions, in-cluster act_runner, static CI gate
STEP 1 — enable Gitea Actions: pin GITEA__actions__ENABLED=true and
DEFAULT_ACTIONS_URL=github in the gitea bootstrap patch (was implicit upstream
default; make it a declared property).

STEP 2 — deploy + register an act_runner in a dedicated non-app namespace
(gitea-actions) so the privileged DinD executor sidecar is admitted without a
Kyverno policy exception (restrict-host-namespaces scopes to app namespaces only).
Docker executor + DinD sidecar over a shared TLS-cert emptyDir; runner state on a
Longhorn PVC (registers once). Registration token delivered as a SOPS-encrypted
Secret decrypted by Flux. Runner mounts no ServiceAccount token (R-SEC-02).

STEP 3 — author .gitea/workflows/gate.yml: the live enforced merge gate running
the STATIC layers (spec-immutability best-effort, lint, Kyverno L0, L1 render) via
`make -C ci lint l0 l1` with the pinned toolchain installed at job start. L2/L3
conformance is an if:false placeholder (needs a toolchain image + scoped cluster
creds — a design decision, not wired). merge-gate.yaml reduced to workflow_dispatch
(dormant full-pipeline blueprint). install-tools.sh gains check-jsonschema so the
schema stage validates in CI instead of skipping.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-25 23:51:52 -05:00

102 lines
5.3 KiB
YAML

---
# Gitea headless-install bootstrap patch (WS-APPS, Phase 6).
#
# WHY THIS IS A SUPPLEMENTARY MANIFEST (renderer follow-up):
# The ServiceDefinition schema/renderer (catalog/render.py) does not yet express per-container
# `env`, `envFrom`, `lifecycle`, or `initContainers`. Gitea needs all three to come up INSTALLED
# without the interactive web wizard and with a persistent SECRET_KEY. Rather than widen the
# renderer contract for a single app (out of scope for this task), this is a minimal, Gitea-only
# kustomize strategic-merge patch layered onto the rendered apps/gitea.yaml Deployment. It is
# fully GitOps-reconciled (Flux applies it), so nothing here is a manual pod hack Flux would revert.
# TODO(renderer): fold generic `env`/`envFrom`/`lifecycle` support into the ServiceDefinition
# schema + render.py, then delete this patch.
#
# WHAT IT DOES:
# - GITEA__security__INSTALL_LOCK=true -> skip the first-run web installer (headless install).
# - GITEA__security__SECRET_KEY (from the gitea-bootstrap SOPS Secret) -> a FIXED, persistent
# SECRET_KEY. It must NOT regenerate per restart or previously-encrypted data breaks; sourcing
# it from a committed SOPS secret pins it across pod restarts/reschedules.
# - GITEA__server__ROOT_URL / __DOMAIN -> correct public clone/redirect URLs for the git host
# that TLS-terminates at the shared Gateway (gitea.home.keithjackson.dev).
# - GITEA__actions__ENABLED=true -> turn on Gitea Actions explicitly (Phase 6 CI/CD, WS-GITOSS).
# Gitea 1.21+ defaults Actions ON, but pinning it here makes the CI merge gate a declared,
# reviewable property of the deployment rather than an implicit upstream default that a future
# version bump could flip. The in-cluster act_runner (ns gitea-actions) registers against it.
# - GITEA__actions__DEFAULT_ACTIONS_URL=github -> resolve `uses: <org>/<action>@ref` (e.g.
# actions/checkout) from github.com. The cluster HAS internet egress (LE/Cloudflare/ghcr already
# in use), so github-sourced actions work. A fully self-hosted/air-gapped value would require
# mirroring each action repo into this Gitea first; the cluster is not air-gapped, so `github`
# is the pragmatic, working choice (WS-GITOSS Phase-6 note).
# - A postStart lifecycle hook creates a BREAK-GLASS local admin idempotently (only if absent),
# so an IdP outage never locks the operator out of recovery tools (docs/15 availability caveat).
# All state (repos, SQLite DB, app.ini, INTERNAL_TOKEN) lands on the /data Longhorn PVC.
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitea
namespace: gitea
spec:
template:
spec:
containers:
- name: gitea
env:
- name: GITEA__security__INSTALL_LOCK
value: "true"
- name: GITEA__security__SECRET_KEY
valueFrom:
secretKeyRef:
name: gitea-bootstrap
key: secret-key
- name: GITEA__server__DOMAIN
value: gitea.home.keithjackson.dev
- name: GITEA__server__ROOT_URL
value: https://gitea.home.keithjackson.dev/
- name: GITEA__actions__ENABLED
value: "true"
- name: GITEA__actions__DEFAULT_ACTIONS_URL
value: github
- name: GITEA_ADMIN_USERNAME
valueFrom:
secretKeyRef:
name: gitea-bootstrap
key: admin-username
- name: GITEA_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: gitea-bootstrap
key: admin-password
lifecycle:
postStart:
exec:
command:
- /bin/sh
- -c
- |
# Idempotent break-glass admin bootstrap. Runs alongside the gitea entrypoint
# (which, with INSTALL_LOCK=true, headless-installs + migrates the DB on first
# boot). Waits for the DB, then creates the admin only if it does not exist.
set -u
export HOME=/data/git
i=0
until su-exec git gitea admin user list >/dev/null 2>&1; do
i=$((i + 1))
[ "$i" -ge 150 ] && { echo "[gitea-bootstrap] DB not ready after wait; skipping" >&2; exit 0; }
sleep 2
done
if su-exec git gitea admin user list --admin 2>/dev/null | awk 'NR>1{print $2}' | grep -qx "$GITEA_ADMIN_USERNAME"; then
echo "[gitea-bootstrap] admin '$GITEA_ADMIN_USERNAME' already present"
exit 0
fi
n=0
until su-exec git gitea admin user create \
--username "$GITEA_ADMIN_USERNAME" \
--password "$GITEA_ADMIN_PASSWORD" \
--email "[email protected]" \
--admin --must-change-password=false; do
n=$((n + 1))
[ "$n" -ge 10 ] && { echo "[gitea-bootstrap] admin create failed after retries" >&2; exit 0; }
sleep 3
done
echo "[gitea-bootstrap] created break-glass admin '$GITEA_ADMIN_USERNAME'"