The Authentik upstream provider used claims_imports.localpart.on_conflict:
add, which links a newly registered upstream identity to an EXISTING local
account when the derived localpart collides. Combined with Authentik's
case-sensitive username uniqueness, any holder of an invitation token could
register a case variant of an existing name and be linked into that account,
including service accounts (draupnir, alerts, maintenance-notify) that have
no upstream link at all.
Set on_conflict: fail so a colliding localpart aborts provisioning instead.
Existing links are unaffected.
Turns guest onboarding from an admin-only click in the Authentik UI into a traceable command a defined circle can run: !einladen creates a single-use invitation valid for three days, !verlaengern extends it twice at most, !freischalten makes it permanent, and expired accounts are deactivated automatically.
Authorisation is deliberately twofold - the Authentik group decides, the invite room makes it visible. A group alone leaves no trace of who invited whom; a room alone would authorise anyone who gets in.
Two deployment details matter: exactly one replica with Recreate, because a second instance would execute every command twice; and the script ConfigMap keeps its name hash so a change actually restarts the pod, avoiding the trap described in #50.
Endpoints and field names were taken from the running Authentik OpenAPI schema, not guessed. Refs axion1337.chat/axion1337.chat-gitops#48
Without bug_report_endpoint_url the whole section in Settings, Help and About is hidden, so a user has no way to obtain their logs at all. The element.io endpoint was deliberately removed on 2026-08-01 to stop logs flowing there.
The special value local brings the capability back without the outflow: the button reads Download logs, the dialog writes them to the device, and nothing is uploaded anywhere. Users can then attach them in Matrix, which is the path that is already in use.
Config only - no rebuild. Refs axion1337.chat/ThreadNet-Web#9
The actual root cause of the failing blueprint, visible only after the KeyOf references were gone: the entry set recovery_flow alone, but the serializer validates the whole object and rejected it with 'When no user fields are selected, at least one source must be selected'.
All other fields are listed on purpose even though they match the model defaults - whatever the serializer does not receive is reset to its default, so a blueprint naming a single field can silently flip unrelated settings. Values are taken from the live database.
Refs axion1337.chat/axion1337.chat-gitops#60
Adding name and title was necessary but not sufficient - the blueprint kept failing. Running ak apply_blueprint by hand showed why the real error was never visible: when authentik logs a failed entry it calls str() on the KeyOf object, whose __repr__ resolves against an EMPTY blueprint and raises the very same exception. The logging path masks the actual validation error.
KeyOf also couples every stage binding to that one entry succeeding in the same run. Find resolves against the database instead, which is what the neighbouring blueprints in this same ConfigMap already do, and the flow exists.
Refs axion1337.chat/axion1337.chat-gitops#60
The flow entry only set designation. FlowSerializer requires name and title as well (neither has a model default), so validation failed, the entry never got a model instance, and every following KeyOf matrix_recovery_flow reference failed with it. The blueprint had status error on every run.
Corroborated inside the same ConfigMap: matrix-invitation-flow sets name and title and applies successfully.
Values are taken from the live database so nothing changes on the page users see. The typo in mail-adress is left as is - changing user-facing text would be a separate decision.
Closesaxion1337.chat/axion1337.chat-gitops#60
Eigene Validate-Stage hinter der vorhandenen, gebunden an die Admin-Gruppe. Fasst KEIN Authentik-Standardobjekt an: not_configured_action haengt an der Stage statt an der Bindung, eine Umstellung am Standard haette alle Mitglieder getroffen.
not_configured_action=configure statt deny - wer keinen Faktor hat, wird beim Login durch die Einrichtung gefuehrt statt ausgesperrt. Bei einem Admin-Konto gaebe es nach einem deny keinen Weg zurueck ausser ueber den Cluster.
last_auth_threshold=hours=1 verhindert die doppelte Abfrage: die Standard-Stage auf Ordnung 30 validiert bereits, wer einen Faktor hat; diese hier ueberspringt sich dann.
BEWUSST auf einem Branch: die Gruppe ist mit dem Authentik-Standard "authentik Admins" angenommen und von sorb noch nicht bestaetigt. Trifft sie nicht zu, greift die Regel fuer niemanden - das waere schlimmer als keine Regel, weil es sich sicher anfuehlt.
Widget-Abhaengigkeit auf 0.19.2-threadnet.7. Umgesetzt ueber VITE_PRODUCT_NAME, ohne eine einzige geaenderte Quelldatei im Fork - upstream sieht die Variable an jeder Stelle vor.
Schliesst den dritten Punkt aus #54 ab.
Wichtigster Punkt: solange branding_logo hier steht, gewinnt der Blueprint. Wer das Logo in Authentiks Oberflaeche auswaehlt, sieht es bis zur naechsten Reconciliation - danach ist es wieder weg. Ein neues Logo gehoert in diese Zeile.
Dazu die Liste der oeffentlich erreichbaren Assets und der Hinweis, dass die vorhandene Wortmarke im wiki-Repo liegt und von Hetzner aus nicht erreichbar ist - sie muesste erst mit dem Client ausgeliefert werden.
Unser 512-px-PNG rendert in der Anmeldemaske viel zu gross: Authentiks Default ist ein SVG, das sich der Box anpasst, ein PNG dagegen nimmt seine Naturgroesse. sorb entwirft ein passendes Logo.
Wichtig: explizit auf den Default gesetzt, nicht die Zeile geloescht - ein Blueprint mit state: present setzt weggelassene Felder nicht zurueck, der alte Wert bliebe in der DB stehen. Titel, Favicon und Hintergrund bleiben.
Elements lake.jpg weicht dem Alpengluehen (John Towner, Unsplash License), Danksagung entsprechend umgeschrieben. Image rohana.axion1337.de/sorb/threadnet-web:v0.4.2 aus Pipeline 192.
Voraussetzung fuer den naechsten Schritt in gitops#54: die ThreadNet-Brand in Authentik referenziert genau diese Bilddatei und liefe vorher ins Leere.
branding_title/logo/favicon/default_flow_background auf dem bestehenden Brand-Eintrag (domain authentik-default) - kein zweiter Brand, sondern derselbe, um den die default_application schon haengt.
Die Assets werden von der Client-Auslieferung referenziert statt in Authentik hochgeladen: sie liegen dort ohnehin, und eine zweite Kopie ist genau die Quelle, aus der spaeter Abweichungen entstehen. Ein ConfigMap-Mount scheidet aus - die drei Dateien reissen base64-kodiert das 1-MiB-Limit.
BEWUSST auf einem Branch: alpenglow.jpg existiert erst nach dem naechsten Client-Deploy. Auf main gemergt wuerde Flux das sofort ausrollen und die Anmeldeseite haette einen 404 als Hintergrund. Reihenfolge: erst Client-Image, dann dieser Merge.
Ich hatte registration/passwordReset/deactivate auf false gesetzt mit der
Begruendung, der Web-Client zeige Schaltflaechen, die ins Leere fuehren.
Gemessen hatte ich nur zweierlei: dass UIFeature in der Config fehlt und dass
der Homeserver Registrierung ablehnt. Dass daraus sichtbare Schaltflaechen
folgen, habe ich NICHT geprueft - sorb hat auf /#/login nachgesehen, dort gibt
es keinen Registrieren-Button. Element blendet ihn im SSO-Flow ohnehin aus.
Die Aenderung loeste damit ueberwiegend ein Problem, das es nicht gab - und
deactivate: false nahm zusaetzlich die Konto-Loeschung aus den Einstellungen,
die vorher sichtbar war. Das hatte niemand verlangt.
Zurueck auf den Stand davor. Sollen einzelne dieser Schalter bewusst gesetzt
werden, ist das eine eigene Entscheidung mit eigener Begruendung.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
Der Web-Client zeigte Registrierung, Passwort-Reset und Konto-Loeschung an - alle
drei funktionieren auf dieser Instanz nicht:
- Registrierung ist am Homeserver abgeschaltet (gemessen: M_FORBIDDEN,
'Registration has been disabled. Only m.login.application_service
registrations are allowed.')
- Login laeuft ausschliesslich ueber SSO (Flows: m.login.sso, m.login.token) -
Passwoerter liegen in Authentik, nicht im Homeserver
- Konto-Loeschung laeuft ebenfalls ueber die Identitaetsverwaltung
Der Desktop-Client blendet die drei seit jeher aus; nur die Web-Config hatte
UIFeature gar nicht gesetzt. Damit sahen Web-Nutzer Angebote, die entweder in
einer Fehlermeldung enden oder am eigentlichen Weg vorbeifuehren - und
Registrierung widerspricht ausserdem der Vision-Entscheidung 'kontrolliert
wachsend, jeder Eintritt wird freigegeben'.
Chirurgisch eingefuegt, 17 Themes unberuehrt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
Nachtrag - der Bump war im vorigen Commit an einer falschen Einrueckung im
Suchtext gescheitert (8 statt 10 Zeichen, derselbe Fehler wie beim v0.4.0-Bump).
Diesmal zeilenbasiert statt ueber einen mehrzeiligen Suchtext.
Bringt: Tab-Titel ThreadNet, favicon.ico, Fehlerseite und Desktop-Hinweis mit
eigener Marke.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
Hebt Prod auf v0.4.1 (Tab-Titel, favicon.ico, Fehlerseite, Desktop-Hinweis).
Dazu logo_link_url: Elements Default zeigt auf element.io - ein Klick aufs Logo
haette also von unserer Anmeldemaske weggefuehrt. Zeigt jetzt auf die eigene
Instanz. Reine Config, wirkt sofort.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
Auf der Anmeldemaske stand weiterhin Elements gruenes Logo - meine Icon-Arbeit
deckte nur vector-icons/ ab, also Favicon, PWA und App-Icon. Das Logo IM Client
kommt aus einer anderen Quelle: AuthHeaderLogo.tsx und HomePage.tsx lesen
branding.auth_header_logo_url aus der Config und fallen sonst auf
themes/element/img/logos/element-logo.svg zurueck.
Das ist reine Konfiguration - kein Rebuild noetig, die ConfigMap reicht.
Als Ziel bewusst vector-icons/512.png statt einer neuen Datei: Die liegt bereits
ausgeliefert (verifiziert, HTTP 200) und stammt aus derselben Quelle wie alle
anderen Icons. Eine zweite Logo-Datei koennte davon abdriften - genau die Falle,
die in shared/branding.md steht.
Randbedingung, die die Wahl einschraenkt: webpack kopiert aus res/ nur themes/**
und vector-icons/** nach webapp. Ein Logo unter res/img/ waere gar nicht per URL
abrufbar.
Chirurgisch eingefuegt, YAML und eingebettetes JSON validiert, 17 Themes
unveraendert.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
Hebt das Image von v0.3.0 auf v0.4.0. Damit sind die zentrierten Icons, die
Markenfarbe #ed4f4c in der Browser-/PWA-Leiste und die About-Attribution
'ThreadNet — powered by Element' unter der Client-Version live.
Der Weg ist der in der ThreadNet-Web-CI dokumentierte: Tag pushen -> docker_web
baut und pusht rohana.axion1337.de/sorb/threadnet-web:v0.4.0 -> Tag-Bump hier
deployt es. docker_web lief in Pipeline 176 gruen (101 s).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
Die Datei ist der Ort, an dem jemand Themes aendert - und der Ort, an dem nicht
stand, woher die Farben kommen. Genau so entstand die erfundene Fassung. Drei
Zeilen Kommentar mit Quelle, Verweis auf shared/branding.md und Hinweis auf die
zweite Kopie in ThreadNet-Web.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
Grundlage sind jetzt die Definitionen aus Anthropics theme-factory-Skill
(github.com/anthropics/skills/skills/theme-factory) statt meiner Interpretation
der Namen. Meine erste Fassung traf bei fast allen daneben - am deutlichsten bei
Sunset Boulevard, wo ich kraeftiges Koralle/Pink baute statt der vorgegebenen
Terrakotta-Palette #e76f51/#f4a261/#e9c46a/#264653.
Ob ein Theme hell oder dunkel gemeint ist, steht in den Beschreibungen teils
widerspruechlich ('Warm Sand - backgrounds' bei einem Theme, dessen Showcase-Seite
dunkel ist). Deshalb aus theme-showcase.pdf gemessen: sieben der zehn sind hell,
nur Sunset Boulevard, Golden Hour und Desert Rose dunkel. Vorher hatte ich sechs
faelschlich als dunkel angelegt.
Ableitung je Theme: die vier Originalfarben als Akzent/Sekundaer/Highlight/Text,
Flaechenabstufungen daraus gemischt, Username-Farben als Mischungen derselben
Palette - damit bleibt jedes Theme in sich stimmig.
Chirurgisch: nur die colors-Bloecke und is_dark der zehn Themes (299 Zeilen gegen
299), YAML validiert, die uebrigen sieben Themes unberuehrt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
Meine erste Auslegung des Namens waren kraeftige Sonnenuntergangstoene (Koralle,
Pink, Weinrot) - gemeint war die Hollywood-Hills-Richtung. sorbs eigene Wahl in
der BookStack-Oberflaeche macht das eindeutig: fuenf seiner sieben Farbwerte sind
der Coolors-Satz #264653/#2A9D8F/#E9C46A/#F4A261/#E76F51.
Genau der liegt jetzt auch dem Element-Theme zugrunde: Charcoal als Grundton,
Burnt Sienna als Akzent, Sandy Brown und Saffron als Sekundaerfarben, Persian
Green fuer Auswahl und Hervorhebung, warmes Beige als Text.
Chirurgisch: nur der colors-Block dieses einen Themes ersetzt (31 Zeilen gegen
31), YAML validiert, die uebrigen 16 Themes unberuehrt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
aXion1337 Light ist das exakte helle Gegenstueck zum Lieblingstheme: 'aXion1337
Dark' ist Gruvbox Dark (Farben und die acht Username-Farben sind die
Gruvbox-Palette), also ist Gruvbox Light das stilistische Analogon - gleiche
Rollenverteilung, gleiche Akzentfamilie, nur die Helligkeitsachse gespiegelt.
Dazu zehn benannte Paletten mit vollem Schluesselsatz (24 Farben inkl.
username-colors): Ocean Depths, Sunset Boulevard, Forest Canopy, Modern
Minimalist, Golden Hour, Arctic Frost, Desert Rose, Tech Innovation, Botanical
Garden, Midnight Galaxy - vier davon hell.
Chirurgisch eingefuegt (Lehre vom 2026-07-30): die YAML wurde nicht geparst und
nicht neu serialisiert, nur Text vor der schliessenden Array-Klammer ergaenzt.
Diff-Bilanz 0 entfernte / 427 neue Zeilen, YAML und eingebettetes JSON validiert.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
Der naechtliche YAML-Redump (9aa42f9/e963203) hatte Kommentare und
Formatierung der Datei zerstoert - semantisch war er korrekt (verifiziert),
aber wartungsfeindlich. Jetzt: Original von c631b0a + exakt die 5 Zeilen
aus ThreadNet-Web#1 (4 Feature-Flags + setting_default). Effektive Config
byte-identisch mit dem bereits deployten Stand.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
Web-Deploy war Teilmenge der Desktop-Defaults: video_rooms, group_calls,
element_call_video_rooms, new_room_decoration_ui + setting_default
feature_group_calls ergaenzt. Discord-Raumliste war schon beidseitig an.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
Automatischer Rotations-PR (Gitea #46) darf seit der Topologie-Umstellung
nicht mehr auf dem Mirror gemergt werden - Branch vom Mirror geholt und
hier kanonisch gemergt; der Push-Mirror traegt den Stand zurueck.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
Scheduled rotation (Issue #38). New secret generated, re-encrypted with
the scoped rotation age key, checksum/rotated-at annotations bumped so
Flux restarts coturn + synapse-main on merge. Please review and merge.
Das prometheus-node-exporter-HelmRelease (hostNetwork) kollidierte auf
dem Host mit dem etablierten systemd-Exporter auf Port 9100 und konnte
seit Anlage nie binden (4883 Restarts, CrashLoopBackOff). Host-Metriken
kommen weiterhin vom systemd-Dienst, den CFGMON direkt via 10.0.0.2:9100
scrapt - das DaemonSet haette sie nur dupliziert. Alloy-Scrape auf den
verwaisten Service ebenfalls entfernt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
Der Service matrix-stack-synapse-main exponiert 8008/9093/8080/9001
(synapse-metrics = 9001) - Port 9000 existiert nicht, der Scrape war
seit Anlage tot. Aufgedeckt durch den ersten TargetDown-Alarm des neuen
Alertings (gitops#32) direkt nach dem Deploy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
First full rebuild deployed to production (previous releases used the
widgets/element-call/-only patch workaround, fixed but not cut over in
Issue #12). Includes the new client-side scan hooks (DecryptFile.ts,
ContentMessages.ts) that cover encrypted rooms, which the server-side
Synapse module (Issue #19) structurally cannot see.
Synapse's own media-scanning module (Issue #19) can never see E2EE
attachment content - a structural limit, not a bug. This adds a small
standalone HTTP wrapper around the same ClamAV instance, reachable
from browser JS at /_scan, so the ThreadNet-Web client fork can scan
plaintext both before encrypting/uploading and after downloading/
decrypting - covering both directions regardless of room encryption.
Auth via Synapse's own /whoami endpoint, no separate auth system.
Synapse runs on Twisted's reactor, not asyncio's event loop - the
original asyncio.open_connection/wait_for calls failed immediately
with "RuntimeError: no running event loop", silently fail-opening
every scan (confirmed live: EICAR test file passed through unscanned).
Rewritten using twisted.internet.endpoints.HostnameEndpoint/
connectProtocol and a custom Protocol for the INSTREAM conversation.
Deploys ClamAV and a small stdlib-only Synapse spam-checker module
implementing check_media_file_for_spam over clamd's INSTREAM protocol.
Unlike the originally-considered matrix-content-scanner proxy (which
needs client-side cooperation neither Element Web nor Element X
provide), this hooks Synapse's own module API directly - transparent
to every client for unencrypted media. No custom Synapse image needed:
the module is mounted via a ConfigMap onto PYTHONPATH using the ESS
chart's extraVolumes/extraVolumeMounts/extraEnv support. Fails open on
scanner errors so a ClamAV outage can't block all uploads.
Testing E2EE support for the management room per user request - marked
"not considered production safe" upstream, verifying no regressions
before deciding to keep it.
Confirmed by extracting dist/config.js from the actual image - v3.x's
getConfigPath() only checks --draupnir-config/--mjolnir-config CLI
flags, no longer NODE_CONFIG_DIR/node-config auto-discovery.
initialManager's automatic management-room creation (no manual room
setup needed) only works on v3.1.0+ - v2.9.0 crashed on first startup
trying to join a room that was never created.
Bot account registered via mas-cli, compatibility token issued and
stored via sops. Adds an explicit NetworkPolicy allow-rule since
Draupnir needs to reach Synapse's client-server API in-namespace,
which the existing default-deny policy would otherwise silently block.
Real testing (Safari + Firefox, fresh rejoins) confirmed VP9 selection
always falls back to VP8 - our fork's buildPublishOptions() forces
simulcast:true unconditionally, which breaks LiveKit's SVC negotiation
path that vp9/av1 require. This is the same failure mode originally
suspected in the 2026-07-28 incident; the "LiveKit handles this
automatically" assumption from doc research did not hold up in
practice. Reverting to the known-safe VP8/H.264/H.265 SFU allow-list
and widget image. Root cause documented in Issue #11 for a future,
scoped fix (conditional simulcast vs. scalabilityMode per codec).
Patches the re-enabled VP9/AV1 codec dropdown (threadnet-call) onto the
existing v0.1.0 base image. Test-only tag - to be reverted or made
permanent depending on real call test results.
Appends video/VP9 and video/AV1 to the SFU's room.enabled_codecs
allow-list via matrixRTC.sfu.additional, keeping all currently
negotiated codecs unchanged. Opt-in test per Issue #11 - codecs
become selectable, not the new default.
Live-verified via matrix-rtc-sfu logs: VP9 gets silently rejected and
falls back to VP8 (SFU's enabledPublishCodecs is [VP8, H264, H265], no
VP9/AV1). Dropdown now only offers those three; also fixed
screenShareCodec's default which was "vp9" (fork commit ac99cc65 in
threadnet-call).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The MediaQualitySettings labels moved into the Video tab had no German
translations at all (en/app.json had all 10 keys, de/app.json had none) -
fork commit f13c4b43 in threadnet-call.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Moved from behind the Developer-mode toggle into the regular Video
settings tab (fork commit f61650c0 in threadnet-call) - these are just
resolution/framerate/bitrate/codec pickers, not actually developer-only
functionality, just accidentally buried where most users would never
find them.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Live-tested Issue #38's rotation: after merging the automated rotation
PR, coturn restarted quickly (Kustomization-level, 1m interval), but
synapse-main lagged behind since it depends on this separate HelmRelease
with its own 5m interval - a real (self-healing, but avoidable) window
where coturn had the new TURN secret and Synapse still had the old one,
which would reject each other's credentials/relayed media. Matching the
interval to production-apps's 1m tightens that window without needing
any new automation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Scheduled rotation (Issue #38). New secret generated, re-encrypted with
the scoped rotation age key, checksum/rotated-at annotations bumped so
Flux restarts coturn + synapse-main on merge. Please review and merge.
Job hung indefinitely on git clone - public-internet reachability to
rohana.axion1337.de from inside pods is currently down (related to
Issue #41's firewall/private-network findings), and the node-level
/etc/hosts workaround only helps containerd's image pulls, not processes
running inside pods (they resolve via CoreDNS + their own /etc/hosts, not
the node's). hostAliases routes this job's git/API traffic over the
private Hetzner network directly - verified working with a real pod.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Closes issue #38's automation half (architecture fix + first rotation
already landed in earlier commits this session). Monthly CronJob
(rohana.axion1337.de/sorb/axion-secret-rotation:v1 - alpine + git/sops/
jq/age) that:
- generates a new secret
- re-encrypts coturn-secret.yaml and synapse-turn-secret.yaml using the
scoped rotation-only age key (added as an additional recipient in an
earlier commit) - never touches the repo's master sops-age key
- bumps the turn-secret-checksum (HelmRelease annotation) and rotated-at
(coturn Deployment annotation) so merging actually restarts both
consumers, reusing the existing checksum-annotation pattern already in
this repo rather than inventing a new mechanism
- opens a Pull Request rather than pushing straight to main - a human
reviews and merges, keeping a checkpoint before production picks up new
credentials while still automating the tedious coordination work
Needs a Gitea PAT (repo write scope) filled into
turn-secret-rotation-secret.yaml's gitea-token key before first use.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>