244 Commits
Author SHA1 Message Date
Thore Cimbal d87c432f89 elementWeb auf v0.6.0
Release des Upstream-Anschlusses (ADR-0022). Gleicher Quellstand wie rc.3
(8ca03fe), das die Abnahme bestanden hat - nur unter Release-Nummer. Die Images
sind nicht bitgleich, weil der Build die Versionszeichenkette aus git describe
ins Artefakt backt.

Produktion laeuft damit erstmals auf einem Fork, der wieder an der
Upstream-Historie haengt.

Rueckhebel: Tag zurueck auf v0.5.4.
2026-08-19 12:00:00 +00:00
Thore Cimbal de347343b3 elementWeb auf v0.6.0-rc.3
Zweiter Anlauf des Upstream-Anschlusses (ADR-0022). Entfernt den Merge-Rest,
der rc.2 die Raumliste brach, und bringt einen typecheck-Job mit, den
docker_web als needs fuehrt - dieses Image ist das erste, das ohne bestandene
Typpruefung gar nicht haette entstehen koennen.

Abnahme steht aus, in dieser Reihenfolge: Raumliste laedt, ClamAV per Zip,
ClamAV per .png (der umgezogene Bild-Pfad), Call-Teilnehmerliste.

Rueckhebel: Tag zurueck auf v0.5.4.
2026-08-19 12:00:00 +00:00
Thore Cimbal b319f21133 Historie: rc.2-Vorfall am Tag-Kommentar festhalten
Der Block beschrieb rc.2 noch als laufenden Stand.
2026-08-19 12:00:00 +00:00
Thore Cimbal 1c3df5badb elementWeb zurueck auf v0.5.4 - rc.2 bricht die Raumliste
react-soft-crash bei sorb (Rageshake 2026-08-19 15:17, Safari):
"Setting 'feature_room_list_sections' does not appear to be a setting."
aus SettingsStore.getValue in RoomListItemViewModel.generateItemSync - also
bei jedem Raumlisteneintrag.

Fehler in der Merge-Aufloesung von ADR-0022: Upstream hat den Labs-Schalter
feature_room_list_sections entfernt (Sektionen laufen jetzt ueber
RoomList.showSections). Settings.tsx hat Upstreams Fassung uebernommen, in
RoomListItemViewModel.ts blieb die alte getValue-Zeile daneben stehen.

Der Build konnte das nicht fangen: getValue nimmt einen String, der Fehler
entsteht erst zur Laufzeit.

Kandidat kommt nach dem Fix als rc.3 zurueck.
2026-08-19 12:00:00 +00:00
Thore Cimbal 0f45266426 elementWeb auf v0.6.0-rc.2 (Upstream-Anschluss v1.12.26)
Kandidat, kein Release. Bringt den Merge aus ADR-0022 in Produktion, damit die
Abnahme an einem echten Client stattfinden kann.

Zu pruefen sind die zwei Patches, die der Merge verschieben musste:
ClamAV-Fehlermeldung im Bild-Pfad und die Call-Teilnehmerliste in der Raumliste.
Der Datei-Pfad (Zip) ist unberuehrt und diente heute als Ausgangswert - der
Scanner meldete die EICAR-Datei erwartungsgemaess zweimal, beim Senden und beim
Empfangen.

Rueckhebel: Tag zurueck auf v0.5.4.
2026-08-19 12:00:00 +00:00
Thore Cimbal d809b3f695 config(element): remove the interface path to a foreign homeserver
Decision sorb. Hides the edit button beside the server name, so the homeserver can no
longer be switched through the UI, and the 401/403 login error now names the server
rather than staying generic.

Honest about its reach, in the comment as well as here: it is a surface restriction.
MatrixChat still takes hs_url from the query string in two registration flows without
consulting this setting, so a crafted link is unaffected. Against
GHSA-wrcp-5v3v-3j6v - open since 2026-07-20, affecting everything below 1.12.22 while
we run 1.12.17 - it narrows the way in without closing it. The update in management
#0099 remains the actual fix.

The matching line went into the desktop client separately, since that one carries its
own config.json.
2026-08-19 12:00:00 +00:00
Thore Cimbal 3935f359f9 feat(synapse): close federation with an empty domain whitelist (#0060)
Decision sorb. Measured basis rather than preference: four months of operation with
zero destinations, zero remote users and zero rooms with outside participation, while
the federation API answered publicly - the delegation routes it over 443, so 8448
being shut never mattered.

An empty list federates with nobody and one entry opens it for exactly that domain,
so the capability stays one line away rather than gone.

The comment records what must not be done instead, because it is not obvious and it
would look correct: blocking /_matrix/federation at the edge. lk-jwt-service verifies
OpenID tokens through /_matrix/federation/v1/openid/userinfo and reaches it over the
public name - no hostAliases, ClusterFirst DNS - so a path-level block kills group
calls. Synapse serves that endpoint without an X-Matrix signature, so the whitelist
does not touch it.

Caught while validating: the first version of this edit split the auto_join block,
moving auto_join_rooms_for_guests under federation. Functionally identical after the
fragments merge, wrong to read, and fixed before pushing - the diff is now 20 added
lines and nothing moved.
2026-08-19 12:00:00 +00:00
Thore CimbalandClaude Opus 5 e7469a36b8 feat(netpol): deny pods the metadata service, leave everything else open (#0088)
First egress rule in matrix, authentik and monitoring. It allows DNS, the cluster
ranges and the whole internet, and denies only 169.254.0.0/16 - link-local, where
Hetzner serves instance metadata unauthenticated to any pod.

Deliberately narrow. The textbook cut, 0.0.0.0/0 except RFC1918, would have severed
two things here, both over 10.0.0.3 on the private Hetzner network: Alloy writes
metrics and logs there, and the TURN rotation reaches Gitea through a hostAlias to
that address. Private ranges therefore stay open.

The payoff is modest and should be stated as such: measured from a pod, the service
answers with instance-id, hostname, region, MAC and network config, while userdata
and public-keys are empty. No credentials are exposed here, unlike the AWS case this
hardening usually targets. It costs nothing though, and it closes the class.

Two preconditions checked rather than assumed, because both are the usual way this
breaks: kube-system carries kubernetes.io/metadata.name so the DNS rule actually
matches, and the cluster is IPv4-only so 0.0.0.0/0 really does cover everything.

Rollback is deleting the one policy per namespace.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:00:00 +00:00
Thore CimbalandClaude Opus 5 dfb88a375e feat(wiki): enrol every Authentik login into wiki-anwender
Decision sorb, and it was already made in #0049: normal Authentik users read the
user documentation, admins are admins. The role model was implemented; the way in
was not. selfRegistration created an account on first login and autoEnrollGroups
was empty, so the account landed in no group at all - and since Guests is stripped
of every permission, the user saw nothing and was told nothing about why. That is
#0103, and it happened to a real person.

Admins stay manual: membership in "authentik Admins" arrives through the groups
claim and is not affected by this baseline. betrieb/* keeps its default deny, so
the separation #0049 verified end to end still holds - it only stops applying to
people who were never let in at all.

The lookup aborts if wiki-anwender is missing rather than silently enrolling into
nothing, which would reproduce the exact failure this fixes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 0d9cc03bfd fix(element-web): roll out v0.5.4 — enforce the filtered send path
On Safari LiveKit silently skipped the sender track swap, so the raw microphone
stayed on the wire regardless of the suppression level. The fork now verifies
and enforces the swap and states the outcome in the console.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 55a06bc723 feat(element-web): roll out v0.5.3 — AI noise suppression released
The gate opens after the passed two-person acceptance: checkbox and slider are
back in the in-call audio settings. Rollback lever for any regression is the
gate in threadnet-call, not a deployment revert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 625d8fbb17 feat(element-web): roll out v0.5.2 — way-B filter attach, gate closed
Ships threadnet-call df4e5ee: the AI filter attaches to the microphone track
after publication with its own AudioContext on just that track. The feature
gate stays closed, so this behaves identically to v0.5.1 for every user; a
single test client opts in via two localStorage keys. The gate opens only
after the filter passes a two-person call - standing rule from #0054.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 0c9ffe5324 fix(element-web): roll out v0.5.1 — unmute incident from v0.5.0 resolved
Ships threadnet-call dcc8643: the AI filter's off-path is byte-identical to
upstream again (no processor key, noiseSuppression untouched) and the feature is
hard-gated off until the webAudioMix decision. The gate also covers clients that
still have the setting enabled in localStorage. Regression tests pin both cases
and were demonstrably red on the broken code.

Acceptance is a real two-person call after the rollout; v0.4.3 remains one
tag-revert away.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 450ad51c39 revert(element-web): diagnostic window closed, back to v0.4.3
The console pinned it: with the filter on, LiveKit refuses the processor because
Element Call constructs the room without webAudioMix, so no local audio track
ever carries an AudioContext. With the filter off the same build does publish
its track, so the opt-out path itself is intact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 b46040cae2 temp(element-web): v0.5.0 back up for a diagnostic window
sorb asked for it: three hypotheses about the broken unmute were disproven from
the outside, so the browser console is the only remaining source. Calls stay
broken while this runs. Goes back to v0.4.3 as soon as the console is captured.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 4cd23e8990 revert(element-web): back to v0.4.3 — v0.5.0 broke unmuting
Calls connect but no participant can unmute, and the SFU log shows not a single
published track. v0.5.0 is the first production image carrying the AI noise
suppression code in the audio capture path, and v0.4.3 is the last image calls
demonstrably worked on. Restoring service first; the cause is still open.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 25c5bb6286 feat(element-web): roll out v0.5.0 with AI noise suppression
Ships ADR-0018: DeepFilterNet3 as an opt-in filter in the call widget, default
off, checkbox plus slider, 35 % by default. The image now carries 23 MB of model
assets under /widgets/element-call/assets/dfn3/; they load when the user turns
the filter on, not on page load, so anyone leaving it off pays nothing.

The .7 package would have shipped a filter that was dead inside the widget and
nowhere else. Verified through the chain instead of trusting the green build:
npm package, node_modules, webpack output, and the CI artifact all carry the
assets at the path the widget requests. The last link — the running pod — gets
checked after this syncs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 83a14e1d67 feat(element): tell reporters where their report goes (#0045)
Reporting worked but ended in silence: report_event.admin_message_md was unset,
so a user who reported content saw no indication of whether it reached anyone or
whom to follow up with. For a moderated community that is an open edge.

sorb's decision is route B — reports stay in the server's event_reports store and
are reviewed through Element Admin; Draupnir deliberately does not get server
admin rights, which polling would have required. The message therefore names a
person rather than promising an automatism, and @sorb is the only admin who can
actually see the reports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 afc4ad3f20 feat(authentik): reject colliding usernames case-insensitively at registration
Authentik's own uniqueness is case-sensitive, so 'Boje' and 'boje' pass as
distinct while Matrix treats them as the same localpart. ADR-0011 closed the
takeover vector with on_conflict:fail, but that only bites at login: the user
registers happily and fails later with no explanation. This policy answers where
the mistake is made.

Deliberately reads only prompt_data and never request.user — the stage runs in an
anonymous enrollment context, which is exactly what the previously attached system
policies crashed on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 b4650dcef8 fix(coturn): pin the image, drop the last :latest tag
The running container was 4.10.0 while :latest had moved on to 4.17.2 — with
imagePullPolicy IfNotPresent the node keeps whatever it pulled once, so nobody
knew what was actually running and the next reschedule onto a fresh node would
have jumped seven minor versions silently. That is the concrete case #0052 is
about, and it also explains why the CVE scanner reported against a moving target.

Pinned to 4.17.2, which is both current and what :latest resolves to today, so the
scan results finally describe the thing that runs. The config uses only long-lived
core options (realm, use-auth-secret, relay-ip, cert/pkey), none of them removed
in that range. busybox in the init container goes 1.28 to 1.36, the version this
repo already uses elsewhere.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 b61dfd968e feat(backup): monthly restore drill that proves the backups are restorable
Restores the Borg archives into a throwaway postgres inside the pod and passes
only when rows actually land — the pg_restore exit code is not proof, counted
rows are. Production is never touched; the repos are only read.

Automated rather than a documented cadence: a check nobody performs is the same
mistake as an untested backup, one level up. Runs on the 4th at 04:20, after the
nightly jobs. Verified manually before commit (synapse 31908 rows, MAS 16085,
wiki 251).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 5f54fbef8e feat(wikijs): default new users to Europe/Berlin (fork patch)
Wiki.js auto-provisions SSO users in processProfile without a timezone, so
the DB column default (America/New_York) applies. Since there is no Wiki.js
image build pipeline, patch server/models/users.js at container startup to set
timezone: 'Europe/Berlin' on new-user creation. Idempotent (grep guard) and
fail-open (node starts even if the anchor is gone after an upgrade).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 2250969e4a feat(wikijs): German default locale, migrate content de, Berlin timezone
The content is German but was bound to the 'en' locale, the site default
language was English, and system accounts defaulted to America/New_York.

- ensure_locale: set default locale to 'de' (live via updateLocale) and
  migrate all pages en->de via pages.migrateToLocale, then rebuild page tree
  and search index only when something was migrated.
- Navigation tree now stored under locale 'de' so getTree finds it for the
  now-German pages.
- ensure_timezones: set guest + admin system accounts to Europe/Berlin
  (new auto-provisioned users still inherit the core-hardcoded NY default).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 9f8eed3193 fix(wikijs): make sidebar nav targets absolute
The default theme renders sidebar links with href=target verbatim, without
targetType or leading-slash handling. Page targets stored without a leading
slash resolved relatively (e.g. from /betrieb/x, 'Sicherheit' went to
/betrieb/betrieb/sicherheit -> 404), and the home item's empty target was a
dead link. Prefix page targets with '/' and point home at '/'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore Cimbal 56653ffd0c feat(wiki): add Sicherheit page to the betrieb navigation (M5) 2026-08-14 12:00:00 +00:00
Thore Cimbal 026c255a45 feat(wiki): bake custom navigation + HTML rendering + UX CSS into the config job
Make the wiki UX reproducible: set_navigation writes the custom static sidebar
(Home + Anwender + Betrieb, betrieb items visible only to admin groups 1/3);
ensure_html_rendering turns on markdownCore.allowHTML (v-encoded, other renderer
configs preserved) so the HTML card landing renders — note it needs a Wiki.js
restart to take effect on a fresh instance. The injectCSS is rebuilt: content links
get an underline + accent (the old broad primary--text coloring made non-links look
like links) plus the landing card/button styles. Page content lives in git-storage.
2026-08-13 12:00:00 +00:00
Thore Cimbal df72744d55 refs(wiki): shrink login background, undo the branding ConfigMap split
Downscale alpenglow.jpg 2560->1920px (604KB -> 400KB, visually identical on the
login screen). With the smaller background everything fits in one platform-branding
ConfigMap again (~752KB / 1MiB), so drop platform-branding-bg and its /_assets/img/bg
mount; authLoginBgUrl goes back to /_assets/img/branding/alpenglow.jpg. Favicons kept
as ThreadNet.
2026-08-13 12:00:00 +00:00
Thore Cimbal 2154f7fe77 fix(wiki): replace remaining Wiki.js favicons; split branding ConfigMap
The browser tab still showed Wiki.js because only favicon.ico + 16/32 were
replaced — the larger icons the tab picks (android-chrome-192, apple-touch-180,
mstile-150) were still the defaults. Add ThreadNet versions and subPath-mount
them. Adding these pushed the single branding ConfigMap to ~1.04 MB (the 1 MiB
etcd limit), so split the 604 KB background into its own platform-branding-bg
ConfigMap mounted at /_assets/img/bg; authLoginBgUrl follows.
2026-08-13 12:00:00 +00:00
Thore Cimbal cc189d3e15 feat(wiki): nightly postgres backup + TOC on the right (#0048/#0050)
Add wikijs-backup, a nightly Borg CronJob (03:30) that dumps the Wiki.js postgres
DB to the Storage Box — the content lives in git-storage, but comments, local
accounts and the search index do not, so the runtime state gets its own backup.
Mirrors authentik-backup (DB-only), reusing synapse-backup-credentials and
-known-hosts with a separate repo path; NetworkPolicy lets the backup pod reach
wikijs-postgres. Also flip tocPosition to right so the page TOC sits beside the
content instead of in the left rail.
2026-08-13 12:00:00 +00:00
Thore Cimbal acf38107f0 feat(wiki): reader access to home page for wiki-anwender (#0048/#0049)
The Betrieb/Anwender content structure is created (lives in git-storage, not the
job). Add a page rule so wiki-anwender can read the home page besides /anwender —
otherwise "/" would be denied for them. betrieb/* still matches no rule, so
Wiki.js' default-deny keeps it invisible to users (verified in checkAccess:
returns match && !deny). rule() gains a match param for the EXACT home rule.
2026-08-13 12:00:00 +00:00
Thore Cimbal ece80a8153 feat(wiki): accent color + favicon (#0050)
Set the blue Docusaurus-style accent (#2b6cb0 light / #63b3ed dark) via injectCSS
on the app UI — the login page is excluded because Wiki.js renders it without
custom CSS, so a dark login card is not themeable and is left as-is. Mount the
ThreadNet favicon (favicon.ico + favicon-16/32 PNGs the browser tab uses) from the
platform-branding ConfigMap via subPath, overlaying only those files.
2026-08-13 12:00:00 +00:00
Thore Cimbal 32ca547be3 feat(wiki): configurable site title via WIKI_SITE_TITLE
The site title (top-left + browser tab) was the hardcoded "Wiki.js". Drive it
from a deployment variable instead; the config job sets site.title from
WIKI_SITE_TITLE (default "Wiki"), set here to wiki.axion1337.chat.
2026-08-13 12:00:00 +00:00
Thore Cimbal 5d7301c4d2 feat(wiki): git-storage sync to Gitea (ADR-0015)
Wiki.js cannot reach git.lab from the cluster, so its content syncs to Gitea
(sorb/ThreadNetWiki) over HTTPS with a dedicated deploy PAT; a CI job canonizes
Gitea->git.lab (TURN-rotation pattern). The config job's ensure_git_storage
configures the git storage target (mode sync, config values {v:...}-encoded like
the auth strategy). Repo/user/branch/author are plain env; only the PAT lives in
the SOPS secret wikijs-git-secret, wired optional so the job still runs without it.
2026-08-13 12:00:00 +00:00
Thore Cimbal 7164e4f61c feat(wiki): brand login page via mounted static assets (#0050)
Serve the ThreadNet logo and the shared platform login background (alpenglow.jpg,
same file Authentik and Element use) as public static files under
/_assets/img/branding, mounted from a single platform-branding ConfigMap. This
avoids two bad patterns: linking the background via an external URL (runtime
dependency on axion1337.chat) and uploading the logo as a gated Wiki.js asset
(which 404/403s on the unauthenticated login page unless guests get read:assets).

Wiki.js serves /wiki/assets publicly at /_assets, so mounted files need no
read:assets — guests stay locked out of pages. The config job sets logoUrl and
authLoginBgUrl to the local paths and enables dark mode as default. The ConfigMap
uses a name hash so a branding change rolls the pod. It can later be mounted into
Authentik/Element too, keeping one source of truth for the shared assets.
2026-08-13 12:00:00 +00:00
Thore Cimbal c4e67244fe fix(wiki-config): hide local login via hideLocal, keep strategy enabled
Disabling local (previous approach) left the login page empty: Wiki.js needs a
form strategy to render the provider list, so with only OIDC the card was blank
and login hung. Keep local ENABLED and instead set the built-in authHideLocal
site config, which removes local from the provider list on the login page —
only "Login with Authentik" shows.

local stays usable because the config job logs in through it, and it is the
break-glass path, reachable via /login?all (Wiki.js honors ?all to bypass
hideLocal). Verified live end-to-end: job runs idempotently, both strategies
enabled, authHideLocal=true.
2026-08-13 12:00:00 +00:00
Thore Cimbal f034eabdd6 feat(wiki-config): disable local login after bootstrap (Variante B)
Nobody should log in manually. The job disables the local strategy as its final
step (with a still-valid JWT), so the login page offers only Authentik OIDC.
Re-runs without a DB reset find local disabled -> login returns None -> the job
exits cleanly (already configured). Break-glass = DB reset (finalize re-enables
local). Verified live: local login is BLOCKED after the run.
2026-08-12 12:00:00 +00:00
Thore Cimbal 81d66c25a0 fix(wiki-config): encode strategy config values as {v: ...}
The OIDC strategy failed at startup ('requires an issuer option') because the
config values never persisted: Wiki.js reads each via _.get(JSON.parse(value),
'v', null) (source-verified in server/graph/resolvers/authentication.js), so the
value MUST be {"v": <value>}. None of the earlier encodings had the 'v' key.
Fixed and verified live: strategy now loads [ OK ].
2026-08-12 12:00:00 +00:00
Thore Cimbal 026b499d1d feat(wiki): deployable Wiki.js config job (#0049) — headless setup + OIDC + roles
Idempotent GraphQL bootstrap job (verified live): /finalize with the random SOPS
admin, then the OIDC strategy and the groups 'authentik Admins' (full) and
'wiki-anwender' (read /anwender), Guests locked. Replaces the manual setup wizard
entirely. OIDC client_id/secret in a SOPS secret; NetworkPolicy lets the job reach
wikijs. Script as a fixed-name ConfigMap; re-run = delete the Job.
2026-08-12 12:00:00 +00:00
Thore Cimbal 3844f42f1f feat(wiki): randomized admin secret + wiki-anwender group (deployable #0049)
Random Wiki.js admin credentials (SOPS, never displayed) for the config Job to
bootstrap setup non-interactively. Authentik group wiki-anwender (curated readers
of /anwender); admin role = existing authentik Admins. Part of making Wiki.js
fully deployable - no manual setup wizard.
2026-08-12 12:00:00 +00:00
Thore Cimbal 3c6d9549fb feat(authentik): declare wiki-admin/wiki-betrieb/wiki-anwender groups (#0049)
Deployable group structure for the wiki roles/abschottung. Names match the
Wiki.js groups and flow to Wiki.js via the profile->groups claim. Members are
assigned in the Authentik UI (sorb), not here.
2026-08-12 12:00:00 +00:00
Thore Cimbal 64d60b4428 feat(authentik): set the wiki OIDC redirect_uri to the real Wiki.js callback
Replace the PROVIDERKEY placeholder with the actual Wiki.js OIDC strategy callback
(login/<strategy-key>/callback) so Authentik accepts the redirect back. Closes the
login loop for wiki.axion1337.chat.
2026-08-12 12:00:00 +00:00
Thore Cimbal 22da5a7910 fix(authentik): let Authentik generate the wiki OIDC client_id/secret
The wiki-oidc-provider blueprint referenced !Env AUTHENTIK_WIKI_OIDC_CLIENT_ID/
SECRET which were never set, so the blueprint failed (status: error) and no
provider was created. client_id isn't a secret and the client_secret is read
into Wiki.js manually anyway, so drop both !Env refs and let Authentik generate
them. Read the values from the Authentik UI into Wiki.js. Guide 10 design fix.
2026-08-12 12:00:00 +00:00
Thore Cimbal e110918d5f feat(wiki): wire up the wikijs-postgres-secret (created by sorb)
The SOPS-encrypted secret existed locally but was untracked and not in the
kustomization, so Flux never applied it and both wiki pods stayed in
CreateContainerConfigError (secret not found). Add it to resources and commit.
Values not read; age recipient matches .sops.yaml so Flux can decrypt.
2026-08-12 21:38:38 +02:00
Thore Cimbal ea01c0bc6d feat: add wiki oicd blueprint 2026-08-12 19:26:21 +00:00
Thore Cimbal 1744de074f feat(wiki): deploy Wiki.js in the suite (#0048) — waits on the DB secret
Wiki.js + a dedicated Postgres + public ingress for wiki.axion1337.chat, added to
the production kustomization so Flux applies it. Both pods intentionally wait on
the SOPS secret `wikijs-postgres-secret` (username/password) until sorb creates it
— same loud-but-visible pattern as the concierge bot. NetworkPolicy: Traefik ->
wikijs:http, wikijs -> wikijs-postgres:postgres (ingress-only, named container
ports). Ingress/Cert mirror the authentik pattern (letsencrypt-prod). Native OIDC
login (#0049, guide 10) and git-storage are configured post-start. All manifests
validated server-side (kubectl --dry-run=server).
2026-08-12 12:00:00 +00:00
Thore Cimbal 065b13086b feat: add authentik blueprint for forwarded wiki authentication 2026-08-12 15:39:13 +00:00
Thore Cimbal ef04d86bc4 fix(mas): reject upstream logins whose localpart already exists
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.
2026-08-11 12:00:00 +00:00
Thore Cimbal f6d2761025 feat: add the concierge bot for expiring guest invitations
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
2026-08-09 12:00:00 +00:00
Thore Cimbal f7412817c3 feat(client): restore the bug report button in local-only mode
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
2026-08-09 12:00:00 +00:00
Thore Cimbal f0174decc1 fix(authentik): give the identification stage its required fields
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
2026-08-07 12:00:00 +00:00