Files
axion1337.chat-gitops/.gitlab-ci.yml
T
Thore CimbalandClaude Opus 5 e055ec511d ci: temporary probe that CANONIZE_TOKEN reaches the pipeline
Runs only on a web pipeline started with TEST_CANONIZE=1, pushes a throwaway ref
and deletes it again, and never touches main. Removed after the test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:00:00 +00:00

237 lines
12 KiB
YAML

# Leichter Verifikations-Job, portiert aus .gitea/workflows/deploy-on-push.yml
# (Gitea-CI-Rueckbau, CFGMON-11, erledigt 2026-08-01). Deployt nichts - Flux reconciled
# weiterhin selbststaendig aus dem Gitea-Mirror. Repo-Topologie: git.lab ist
# kanonisch, rohana/Gitea ist Push-Mirror und Flux-Quelle.
verify:
image: alpine:3.20
rules:
- if: $CI_COMMIT_BRANCH == "main"
changes:
- apps/**/*
- clusters/**/*
- .gitlab-ci.yml
script:
- apk add --no-cache git >/dev/null
- |
echo "YAML-Manifest-Check..."
find apps clusters -name "*.yaml" -type f | while read f; do
grep -q "^apiVersion:" "$f" || echo "WARN: $f enthaelt kein apiVersion - evtl. kein K8s-Manifest"
done
- |
echo "SOPS-Check der in diesem Push geaenderten Secret-Dateien..."
for f in $(git diff --name-only HEAD~1..HEAD -- '**/secret*.yaml' '**/credentials*.yaml' 2>/dev/null || true); do
if grep -q "ENC\[" "$f"; then echo "OK: $f ist verschluesselt"; else echo "WARNUNG: $f ist moeglicherweise NICHT verschluesselt!"; fi
done
- echo "Flux reconciled die Aenderungen innerhalb ~1 Minute (Quelle Gitea-Mirror)."
# ---------------------------------------------------------------------------
# TURN-Rotation kanonisieren (laeuft NUR als Pipeline-Schedule)
#
# Der Rotations-CronJob laeuft im Cluster und erreicht git.lab nicht - er pusht
# seinen Branch deshalb nach Gitea. Von dort muss die Rotation ueber git.lab
# zurueck, sonst ueberschreibt der naechste Mirror-Push sie und Flux spielt still
# das ALTE Shared Secret wieder ein. Frueher war das ein monatlicher Handgriff;
# dieser Job erledigt ihn.
#
# Einrichtung:
# 1. OFFEN: Project Access Token, Rolle Maintainer, Scope write_repository
# -> CI/CD-Variable CANONIZE_TOKEN (masked + protected). Nur sorb legt das
# an; ohne das Token laeuft der Leerlauf gruen durch, aber die erste
# echte Rotation bricht ab. Siehe gitops#51.
# 2. ERLEDIGT: Schedule "TURN-Rotation kanonisieren (taeglich)", 17:05 Europe/
# Berlin, angelegt 2026-08-02. Probelaeufe: Pipeline 159 deckte die
# fehlende Lab-CA auf, 161 lief gruen durch ("Keine offene Rotation").
#
# Warum taeglich statt monatlich zum Rotationstermin: ein monatlicher Schedule
# hat genau einen Versuch. Faellt der Runner an dem Tag aus, faellt die
# Kanonisierung ein Monat lang aus. Taeglich holt der Job jede offene Rotation
# spaetestens am Folgetag nach und ist ansonsten in Sekunden fertig.
# ---------------------------------------------------------------------------
canonize_rotation:
image: alpine:3.20
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
variables:
GIT_DEPTH: "0" # volle Historie: wir brauchen merge-base
script:
- apk add --no-cache git ca-certificates >/dev/null
- |
# Lab-CA. Der Runner stellt sie nur seinem eigenen Klon-Schritt bereit, nicht
# dem Job-Script - ein 'git fetch origin' im Script scheiterte deshalb an
# "unable to get local issuer certificate" (live gesehen, Pipeline 159).
# Deshalb liegt die Kette hier im Repo; sie ist ein oeffentliches Zertifikat,
# kein Geheimnis. CI_SERVER_TLS_CA_FILE zuerst, falls der Runner sie doch liefert.
if [ -n "$CI_SERVER_TLS_CA_FILE" ] && [ -f "$CI_SERVER_TLS_CA_FILE" ]; then
export GIT_SSL_CAINFO="$CI_SERVER_TLS_CA_FILE"
else
export GIT_SSL_CAINFO="$CI_PROJECT_DIR/ci/lab-ca-chain.crt"
fi
echo "CA: $GIT_SSL_CAINFO"
test -f "$GIT_SSL_CAINFO" || { echo "ABBRUCH: CA-Datei fehlt."; exit 1; }
- git config --global user.email "ci@axion1337.chat"
- git config --global user.name "TURN-Rotation (automatische Kanonisierung)"
- git remote add gitea https://rohana.axion1337.de/sorb/axion1337.chat-gitops.git
# --prune ist Pflicht, nicht Kosmetik: der Runner recycelt seinen Workspace
# (GIT_STRATEGY=fetch), und ohne prune bleiben remote-tracking-Refs geloeschter
# Zweige stehen. Der Job saehe einen laengst entfernten Rotationszweig sonst
# dauerhaft weiter - live beobachtet am 2026-08-18, Pipeline 492: beide Remotes
# waren nachweislich leer, der Job meldete den Zweig trotzdem.
- git fetch --quiet --prune gitea
# Kein 'fetch origin' noetig: der Runner hat main schon ausgecheckt (GIT_DEPTH 0),
# CI_COMMIT_SHA ist der Stand von main beim Anlegen der Pipeline.
- git checkout -B main "$CI_COMMIT_SHA"
- |
# Zeitpunkt der Rotation aus dem SOPS-Metadatenblock lesen. 'lastmodified'
# ist Metadatum, kein Geheimnis - es steht im Klartext neben den ENC[]-Werten.
rotationszeit() {
git show "$1:apps/production/coturn-secret.yaml" 2>/dev/null \
| sed -n 's/^[[:space:]]*lastmodified:[[:space:]]*"\(.*\)".*/\1/p' | head -1
}
MERGED=0
UEBERHOLT=""
for ref in $(git for-each-ref --format='%(refname:short)' 'refs/remotes/gitea/turn-secret-rotation-*'); do
SHA=$(git rev-parse "$ref")
if git merge-base --is-ancestor "$SHA" HEAD; then
echo "bereits kanonisiert: $ref"
continue
fi
# Ueberholte Zweige NICHT mergen. Ein Zweig, dessen Rotation aelter ist als
# der Stand in main, bringt nichts Neues, konfliktiert aber mit allem, was
# seither passiert ist - inklusive coturn-secret.yaml und
# synapse-turn-secret.yaml. Eine unbedachte Hand-Aufloesung wuerde das
# TURN-Shared-Secret ZURUECKDREHEN; Synapse und coturn waeren dann uneins
# und TURN tot. Real passiert: der Zweig vom 2026-07-28 blieb nach dem Merge
# auf Gitea liegen und hat diesen Job neun Tage lang taeglich rot gefaerbt -
# womit die rote Pipeline als Alarm wertlos wurde (AGENTS.md: sie IST der
# einzige Meldeweg). Deshalb ist ein Ueberbleibsel hier Aufraeumarbeit und
# kein Vorfall: gemeldet ja, rot nein.
NEU=$(rotationszeit "$SHA"); IST=$(rotationszeit HEAD)
if [ -n "$NEU" ] && [ -n "$IST" ] \
&& [ "$(printf '%s\n%s\n' "$NEU" "$IST" | sort | head -1)" = "$NEU" ]; then
echo "ueberholt: $ref traegt die Rotation vom $NEU, main steht auf $IST."
UEBERHOLT="$UEBERHOLT ${ref#gitea/}"
continue
fi
echo "kanonisiere: $ref ($SHA)"
SUBJ="chore(coturn): TURN-Rotation aus ${ref#gitea/} uebernommen"
BODY="Automatisch kanonisiert: der Rotations-CronJob im Cluster erreicht git.lab nicht und pusht nach Gitea; dieser Commit bringt die Rotation auf den kanonischen Weg zurueck, bevor der Mirror sie ueberschreiben kann."
if ! git merge --no-ff -m "$SUBJ" -m "$BODY" "$SHA"; then
echo "MERGE-KONFLIKT in $ref - es wird nichts gepusht."
echo "⚠️ Beim Aufloesen NIEMALS die aeltere Fassung von coturn-secret.yaml oder"
echo " synapse-turn-secret.yaml uebernehmen: beide muessen dasselbe Shared"
echo " Secret tragen, sonst faellt TURN aus. Im Zweifel main behalten."
echo "Siehe CLAUDE.md (Abschnitt Repo Topology)."
exit 1
fi
MERGED=1
done
if [ -n "$UEBERHOLT" ]; then
echo
echo "AUFRAEUMEN: diese Zweige sind ueberholt und koennen auf Gitea geloescht werden:"
for b in $UEBERHOLT; do echo " - $b"; done
echo "(Der Job bleibt gruen - liegengebliebene Zweige sind kein Vorfall.)"
fi
echo "MERGED=$MERGED" > .canonize_state
- |
. ./.canonize_state
if [ "$MERGED" = "0" ]; then
echo "Keine offene Rotation - nichts zu tun."
exit 0
fi
# Token erst hier pruefen, nicht am Jobanfang: solange nichts zu pushen ist,
# soll der taegliche Lauf gruen durchgehen und die Gitea-Strecke verifizieren.
# Fehlt er, wenn es wirklich etwas zu tun gibt, ist Abbruch richtig.
if [ -z "$CANONIZE_TOKEN" ]; then
echo "ABBRUCH: Es gibt eine offene Rotation, aber CANONIZE_TOKEN ist nicht gesetzt."
echo "Siehe Kopf dieser Datei, Einrichtungsschritt 1."
exit 1
fi
# Sicherheitsnetz: die Rotation fasst genau diese Secrets an. Kaeme hier
# etwas Unverschluesseltes durch, waere das Shared Secret im Klartext in Git.
for f in apps/production/coturn-secret.yaml apps/production/synapse-turn-secret.yaml; do
if ! grep -q "ENC\[" "$f"; then
echo "ABBRUCH: $f ist nicht SOPS-verschluesselt - es wird nichts gepusht."
exit 1
fi
echo "OK: $f ist verschluesselt"
done
git push "https://oauth2:${CANONIZE_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" HEAD:main
echo "Gepusht. Der Mirror traegt es nach Gitea zurueck, der PR schliesst sich dort selbst;"
echo "der Rotations-Branch existiert dann nur noch auf Gitea und wird vom Mirror entfernt."
# ---------------------------------------------------------------------------
# Wiki-Inhalt kanonisieren (laeuft NUR als Pipeline-Schedule, ADR-0015)
#
# Wiki.js laeuft im Cluster und erreicht git.lab nicht - es pusht seinen Inhalt
# deshalb nach Gitea (sorb/ThreadNetWiki, oeffentlich lesbar). Dieser Job spiegelt
# den Stand von dort nach git.lab axion1337.chat/threadnet-wiki, damit der
# kanonische Speicher wieder im Lab liegt. Gleiche Umkehrung wie die TURN-Rotation.
#
# Einrichtung:
# 1. ERLEDIGT: Project Access Token (Rolle Maintainer, Scope write_repository) auf
# axion1337.chat/threadnet-wiki -> gitops-CI/CD-Variable WIKI_CANONIZE_TOKEN
# (masked + protected).
# 2. Schedule: laeuft im bestehenden Tages-Schedule mit (CI_PIPELINE_SOURCE ==
# "schedule"). threadnet-wiki wurde leer angelegt -> der erste Push legt main an,
# danach nur noch Fast-Forward (kein Force, Branch-Protection bleibt).
canonize_wiki:
image: alpine:3.20
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
variables:
GIT_DEPTH: "1" # nur fuer ci/lab-ca-chain.crt aus dem ausgecheckten Repo
script:
- apk add --no-cache git ca-certificates >/dev/null
- |
# Lab-CA fuer den git.lab-Push; der Gitea-Klon nutzt die System-CA (oeffentliches Zert).
if [ -n "$CI_SERVER_TLS_CA_FILE" ] && [ -f "$CI_SERVER_TLS_CA_FILE" ]; then
LABCA="$CI_SERVER_TLS_CA_FILE"
else
LABCA="$CI_PROJECT_DIR/ci/lab-ca-chain.crt"
fi
test -f "$LABCA" || { echo "ABBRUCH: Lab-CA-Datei fehlt."; exit 1; }
if [ -z "$WIKI_CANONIZE_TOKEN" ]; then
echo "ABBRUCH: WIKI_CANONIZE_TOKEN nicht gesetzt (siehe Kopf, Einrichtungsschritt 1)."
exit 1
fi
- git config --global user.email "ci@axion1337.chat"
- git config --global user.name "ThreadNet-Wiki Kanonisierung"
- git clone --bare --quiet https://rohana.axion1337.de/sorb/ThreadNetWiki.git /tmp/wiki.git
- GIT_SSL_CAINFO="$LABCA" git -C /tmp/wiki.git push "https://oauth2:${WIKI_CANONIZE_TOKEN}@${CI_SERVER_HOST}/axion1337.chat/threadnet-wiki.git" 'refs/heads/main:refs/heads/main'
- echo "git.lab threadnet-wiki spiegelt jetzt Gitea sorb/ThreadNetWiki (main)."
# ---------------------------------------------------------------------------
# TEMPORAER (2026-08-19): beweist, dass CANONIZE_TOKEN in der Pipeline ankommt
# und schreiben darf - OHNE main anzufassen. Laeuft nur, wenn eine Web-Pipeline
# ausdruecklich mit TEST_CANONIZE=1 gestartet wird. Wird nach dem Test entfernt.
# ---------------------------------------------------------------------------
canonize_token_test:
stage: pruefen
image: alpine:3.20
rules:
- if: $CI_PIPELINE_SOURCE == "web" && $TEST_CANONIZE == "1"
script:
- apk add --no-cache git ca-certificates >/dev/null
- export GIT_SSL_CAINFO="$CI_PROJECT_DIR/ci/lab-ca-chain.crt"
- |
if [ -z "$CANONIZE_TOKEN" ]; then
echo "FEHLT: CANONIZE_TOKEN ist im Job nicht sichtbar."
echo "Ursache meist: Variable 'protected', Pipeline laeuft aber auf einem"
echo "ungeschuetzten Branch. Genau dann bliebe die Rotation liegen."
exit 1
fi
echo "Sichtbar: CANONIZE_TOKEN ist im Job gesetzt."
URL="https://oauth2:${CANONIZE_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
REF="canonize-token-probe-${CI_PIPELINE_ID}"
# Ausgabe wird gefiltert: git schreibt die URL bei Fehlern mit ins Log.
if git push "$URL" "HEAD:refs/heads/$REF" 2>&1 | sed 's#oauth2:[^@]*@#oauth2:***@#g'; then
echo "SCHREIBEN OK: Zweig $REF angelegt."
else
echo "SCHREIBEN FEHLGESCHLAGEN - Rolle oder Scope reichen nicht."
exit 1
fi
git push "$URL" --delete "refs/heads/$REF" 2>&1 | sed 's#oauth2:[^@]*@#oauth2:***@#g' \
&& echo "Aufgeraeumt: $REF wieder entfernt." \
|| echo "WARNUNG: $REF blieb liegen, bitte von Hand loeschen."