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.
This commit is contained in:
Thore Cimbal
2026-08-13 12:00:00 +00:00
parent c4e67244fe
commit 7164e4f61c
5 changed files with 39 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 590 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

+9
View File
@@ -87,3 +87,12 @@ configMapGenerator:
- wikijs-config.py
options:
disableNameSuffixHash: true
# Gemeinsame Branding-Assets (eine Quelle). Binärdateien -> kustomize legt sie als
# binaryData ab. MIT Namens-Hash: ändert sich ein Asset, zieht der Deployment-Verweis
# nach und der Pod startet mit dem neuen Bild neu. Kann später auch in Authentik/Element
# gemountet werden, um dieselbe Datei nicht mehrfach zu pflegen.
- name: platform-branding
namespace: matrix
files:
- branding/logo.png
- branding/alpenglow.jpg
+20
View File
@@ -183,6 +183,25 @@ def set_hide_local(jwt: str):
log("Login-Seite: local ausgeblendet (hideLocal=true; Break-Glass via /login?all)")
def ensure_theming(jwt: str):
# Branding kommt aus gemounteten statischen Dateien (/_assets/img/branding/, siehe
# wikijs.yaml + platform-branding ConfigMap): öffentlich ausgeliefert, kein read:assets
# für Guests, keine externe URL. Dark als Default (#0050).
logo = "/_assets/img/branding/logo.png"
bg = "/_assets/img/branding/alpenglow.jpg"
tc = gql('{theming{config{theme iconset darkMode tocPosition injectCSS injectHead injectBody}}}',
jwt)["theming"]["config"]
gql('mutation($t:String!,$i:String!,$d:Boolean!,$tp:String,$c:String,$h:String,$b:String){'
'theming{setConfig(theme:$t,iconset:$i,darkMode:$d,tocPosition:$tp,injectCSS:$c,injectHead:$h,'
'injectBody:$b){responseResult{succeeded message}}}}',
jwt, {"t": tc["theme"], "i": tc["iconset"], "d": True, "tp": tc["tocPosition"],
"c": tc["injectCSS"], "h": tc["injectHead"], "b": tc["injectBody"]})
gql('mutation($bg:String!,$logo:String!){site{updateConfig(authLoginBgUrl:$bg,logoUrl:$logo)'
'{responseResult{succeeded message}}}}',
jwt, {"bg": bg, "logo": logo})
log("Theming gesetzt (Dark-Default, Logo + Login-Hintergrund aus gemounteten Assets)")
def group_id(jwt: str, name: str):
for g in gql("{groups{list{id name}}}", jwt)["groups"]["list"]:
if g["name"] == name:
@@ -228,6 +247,7 @@ def main():
gql('mutation{groups{update(id:2,name:"Guests",redirectOnLogin:"/",permissions:[],pageRules:[]){responseResult{succeeded}}}}', jwt)
ensure_oidc(jwt)
set_hide_local(jwt)
ensure_theming(jwt)
log("fertig — Wiki.js konfiguriert")
+10
View File
@@ -59,6 +59,13 @@ spec:
volumeMounts:
- name: data
mountPath: /wiki/data
# Branding-Assets (Logo + Login-Hintergrund) als statische Dateien in den
# öffentlichen /_assets-Baum (express.static /wiki/assets). Dadurch ohne
# read:assets erreichbar (Guests bleiben gesperrt) und ohne externe URL —
# eine Quelle (ConfigMap platform-branding), Config zeigt auf /_assets/img/branding/.
- name: branding
mountPath: /wiki/assets/img/branding
readOnly: true
readinessProbe:
tcpSocket:
port: http
@@ -73,6 +80,9 @@ spec:
- name: data
persistentVolumeClaim:
claimName: wikijs-data
- name: branding
configMap:
name: platform-branding
---
apiVersion: v1
kind: Service