# 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. # Ohne workflow-Block legt GitLab auch dann eine Pipeline an, wenn KEIN Job auf sie # passt - und fuehrt sie als "failed". Das ist rot ohne Fehler, und rot ohne Fehler # gewoehnt einem das Hinsehen ab (management #0104). Real passiert am 2026-08-19, # Pipeline 518. Diese Regeln verhindern die leere Pipeline, statt sie hinterher zu # quittieren. # # Reihenfolge zaehlt: schedule zuerst, sonst faellt die taegliche Kanonisierung mit # heraus - sie aendert keine Pfade und wuerde von der changes-Regel nie erfasst. .pfade_mit_pipeline: &pfade_mit_pipeline - apps/**/* - clusters/**/* - .gitlab-ci.yml workflow: rules: - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_PIPELINE_SOURCE == "web" - if: $CI_PIPELINE_SOURCE == "api" - if: $CI_COMMIT_BRANCH == "main" changes: *pfade_mit_pipeline - when: never 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)."