Two defects blocked in-cluster Gitea from becoming the Flux source: 1. Storage mountPath: the renderer hard-coded /data/<name>, so Gitea's PVC mounted at /data/data while Gitea writes all state to /data — real repos/DB landed on the ephemeral rootfs. Add an OPTIONAL `mountPath` to the storage schema + render.py (default stays /data/<name>, back-compat). catalog/gitea.yaml sets data.mountPath: /data. Only gitea.yaml's rendered mount changes; every other app renders byte-identical. 2. Headless install: the instance was un-installed (INSTALL_LOCK=false, ephemeral SECRET_KEY, no admin, API 404). A Gitea-only supplementary manifest (apps/gitea-bootstrap-patch.yaml) patches the rendered Deployment with GITEA__security__INSTALL_LOCK=true, a FIXED SECRET_KEY from a persistent SOPS secret (apps/gitea-bootstrap-secret.sops.yaml), ROOT_URL/DOMAIN, and a postStart hook that idempotently creates a break-glass local admin. State persists on the now-correct /data Longhorn PVC. The env/lifecycle wiring is a supplementary kustomize patch because the ServiceDefinition schema/renderer does not yet express env/envFrom/lifecycle — noted as a renderer follow-up (TODO in the patch). Co-Authored-By: Claude Opus 4.8 <[email protected]>
89 lines
4.3 KiB
YAML
89 lines
4.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).
|
|
# - 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_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'"
|