Compare commits

...
Author SHA1 Message Date
claude-botandClaude Fable 5.1 ae5de9bc84 feat(wiki): Authentik wiki-gate in front of wiki.renner.uno
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Proxy provider, application and outpost blueprint; outpost egress CNP;
ExternalName mirror; the ingress backends the outpost and the BasicAuth
middleware is gone. The ai CNP admits the outpost and Gitea only, so the
X-Authentik-Username header the service trusts from pod addresses cannot
arrive from the LAN.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-24 12:48:22 +02:00
k.renner ad152d2d16 Merge pull request 'feat(wiki): deploy the .NET wiki service next to the Python wiki-index' (#940) from feat/wiki-service-manifests into master
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#940
2026-09-24 10:29:43 +00:00
claude-botandClaude Fable 5.1 0cfc4dc486 feat(wiki): deploy the .NET wiki service next to the Python wiki-index
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
PVC, Deployment (build.1790245544), Service, ExternalSecrets for the
shared Gitea token/webhook secret and the API keys, LAN-only ingress
wiki.renner.uno behind BasicAuth, Flux Kustomization, CNP admitting
Traefik and Gitea. No OpenBao access from the pod.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-24 12:28:17 +02:00
k.renner 50c62da2f4 Merge pull request 'docs(wiki): proposal for a local wiki web interface and agent API' (#939) from docs/proposal-wiki-web-api into master
Reviewed-on: k.renner/homelab#939
2026-09-24 10:26:17 +00:00
security-bot 476c48c0af chore(image-automation): bump twinproduction/gatus:v5.37.0
ci/woodpecker/cron/wiki-maintenance Pipeline was successful
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Updates managed by image-automation-controller.
2026-09-24 00:39:00 +00:00
security-bot 8200ce1898 chore(image-automation): bump ghcr.io/usetrmnl/terminus:0.75.0
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Updates managed by image-automation-controller.
2026-09-23 20:38:59 +00:00
12 changed files with 364 additions and 3 deletions
@@ -976,6 +976,66 @@ data:
kubernetes_ingress_secret_name: "authentik-outpost-tls"
kubernetes_json_patches: null
# wiki-gate: the wiki service (ns ai) behind the same passkey flows as
# llama-gate. The service trusts X-Authentik-Username only from pod
# addresses, and its CNP admits only this outpost, so the gate is the
# whole access control for wiki.renner.uno.
- id: wiki-gate-provider
model: authentik_providers_proxy.proxyprovider
identifiers:
name: wiki-gate
state: present
attrs:
authentication_flow: !Find [authentik_flows.flow, [slug, passkey-passwordless-authentication]]
authorization_flow: !Find [authentik_flows.flow, [slug, passkey-stepup-authorization]]
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
mode: proxy
internal_host: http://wiki.ai.svc.cluster.local:8080
internal_host_ssl_validation: false
external_host: https://wiki.renner.uno
cookie_domain: renner.uno
access_token_validity: hours=24
refresh_token_validity: days=30
intercept_header_auth: true
- model: authentik_core.application
identifiers:
slug: wiki-gate
state: present
attrs:
name: Wiki
slug: wiki-gate
provider: !KeyOf wiki-gate-provider
meta_launch_url: https://wiki.renner.uno
meta_description: "Notes wiki: search, notes, answers, maintenance"
- model: authentik_outposts.outpost
identifiers:
name: wiki-gate
state: present
attrs:
type: proxy
service_connection: !KeyOf k8s-conn
providers:
- !KeyOf wiki-gate-provider
config:
log_level: info
authentik_host: "http://authentik-server.authentik.svc.cluster.local"
authentik_host_insecure: false
authentik_host_browser: "https://auth.renner.uno/"
refresh_interval: "minutes=5"
container_image: null
object_naming_template: "ak-outpost-%(name)s"
kubernetes_namespace: authentik
kubernetes_replicas: 1
kubernetes_service_type: ClusterIP
kubernetes_disabled_components: ["ingress"]
kubernetes_image_pull_secrets: []
kubernetes_ingress_annotations: {}
kubernetes_ingress_class_name: null
kubernetes_ingress_secret_name: "authentik-outpost-tls"
kubernetes_json_patches: null
- model: authentik_outposts.outpost
identifiers:
name: authentik Embedded Outpost
@@ -16,6 +16,7 @@ resources:
- prometheus-gate-egress.yaml
- wordpress-gate-egress.yaml
- llama-gate-egress.yaml
- wiki-gate-egress.yaml
- ldap-outpost-cert.yaml
- ldap-outpost-lb.yaml
- ldap-outpost-cnp.yaml
@@ -0,0 +1,21 @@
# Lets the wiki-gate outpost reach the wiki service. The authentik namespace
# baseline CNP otherwise stops any authentik pod from entering ai. Scoped to
# this outpost's pod label; the matching ingress rule is in ai-ns.yaml.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: wiki-gate-egress
namespace: authentik
spec:
endpointSelector:
matchLabels:
goauthentik.io/outpost-name: wiki-gate
egress:
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": ai
app.kubernetes.io/name: wiki
toPorts:
- ports:
- port: "8080"
protocol: TCP
+1 -1
View File
@@ -46,7 +46,7 @@ spec:
type: RuntimeDefault
containers:
- name: gatus
image: twinproduction/gatus:v5.36.0 # {"$imagepolicy": "flux-system:gatus"}
image: twinproduction/gatus:v5.37.0 # {"$imagepolicy": "flux-system:gatus"}
securityContext:
allowPrivilegeEscalation: false
capabilities:
+2 -2
View File
@@ -134,7 +134,7 @@ spec:
# is a device-bricking class of risk. Bumps are a reviewed, manual
# act — see kubernetes/infrastructure/image-automation/README.md.
- name: terminus
image: ghcr.io/usetrmnl/terminus:0.74.0 # {"$imagepolicy": "flux-system:terminus"}
image: ghcr.io/usetrmnl/terminus:0.75.0 # {"$imagepolicy": "flux-system:terminus"}
ports:
- name: http
containerPort: 2345
@@ -197,7 +197,7 @@ spec:
capabilities:
drop: ["ALL"]
- name: sidekiq
image: ghcr.io/usetrmnl/terminus:0.74.0 # {"$imagepolicy": "flux-system:terminus"}
image: ghcr.io/usetrmnl/terminus:0.75.0 # {"$imagepolicy": "flux-system:terminus"}
command: ["bundle", "exec", "sidekiq", "-r", "./config/sidekiq.rb"]
envFrom:
- configMapRef:
+135
View File
@@ -0,0 +1,135 @@
---
# Index snapshots per embedding model and the document catalog. A lost volume
# costs one full sync from Gitea (~10 min).
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wiki-data
namespace: ai
spec:
accessModes:
- ReadWriteOnce
storageClassName: ceph-block
resources:
requests:
storage: 1Gi
---
# Wiki service (konairius/Wiki): mirrors the Notes repo from Gitea, embeds
# wiki/ via wiki-embed, serves /rank, /api/v1 and the MudBlazor UI. Adapters
# are selected in appsettings (Wiki__* env overrides); secrets arrive as env
# from ExternalSecrets, the pod has no OpenBao access.
apiVersion: apps/v1
kind: Deployment
metadata:
name: wiki
namespace: ai
labels:
app.kubernetes.io/name: wiki
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: wiki
template:
metadata:
labels:
app.kubernetes.io/name: wiki
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
fsGroup: 65534
seccompProfile:
type: RuntimeDefault
containers:
- name: wiki
# build.<unix-ts> tags from the Wiki repo's release pipeline (commit c4e193b).
image: gitea.renner.uno/konairius/wiki:build.1790245544
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
env:
- name: HOME
value: /tmp
- name: Wiki__Index__File__Directory
value: /data
- name: Wiki__NoteStore__Gitea__BaseUrl
value: https://gitea.renner.uno
- name: Wiki__NoteStore__Gitea__Repository
value: k.renner/notes
- name: Wiki__NoteStore__Gitea__Branch
value: main
- name: Wiki__Embeddings__Models__bge-m3__Url
value: http://wiki-embed.ai.svc.cluster.local:8080
# Pod networks: the CNP admits only the wiki-gate outpost and Gitea.
- name: Auth__ReverseProxy__TrustedProxies__0
value: fd10:244::/48
- name: Auth__ReverseProxy__TrustedProxies__1
value: 10.244.0.0/16
envFrom:
- secretRef:
name: wiki-gitea
- secretRef:
name: wiki-api-keys
ports:
- name: http
containerPort: 8080
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
# /health: the process serves (liveness). /ready: catalog synced once and the
# active index loaded; a cold start syncs the whole vault first (~10 min).
startupProbe:
httpGet:
path: /ready
port: http
periodSeconds: 10
failureThreshold: 120
readinessProbe:
httpGet:
path: /ready
port: http
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: http
periodSeconds: 60
timeoutSeconds: 10
failureThreshold: 3
volumeMounts:
- name: data
mountPath: /data
- name: tmp
mountPath: /tmp
volumes:
- name: data
persistentVolumeClaim:
claimName: wiki-data
- name: tmp
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: wiki
namespace: ai
labels:
app.kubernetes.io/name: wiki
spec:
selector:
app.kubernetes.io/name: wiki
ports:
- name: http
port: 8080
targetPort: http
+43
View File
@@ -0,0 +1,43 @@
---
# Gitea read token and webhook secret, shared with the Python wiki-index until
# that is retired. Secret keys are the configuration paths the host reads.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: wiki-gitea
namespace: ai
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: openbao
target:
name: wiki-gitea
data:
- secretKey: Wiki__NoteStore__Gitea__Token
remoteRef:
key: apps/ai/wiki-index
property: gitea_token
- secretKey: Wiki__Webhook__Secret
remoteRef:
key: apps/ai/wiki-index
property: webhook_secret
---
# One API key per machine identity: OpenBao apps/ai/wiki-api-keys, field = name.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: wiki-api-keys
namespace: ai
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: openbao
target:
name: wiki-api-keys
data:
- secretKey: Auth__ApiKeys__workstation
remoteRef:
key: apps/ai/wiki-api-keys
property: workstation
+20
View File
@@ -0,0 +1,20 @@
# ExternalName mirror so the ai-namespace Ingress can backend to the wiki-gate
# outpost in the authentik namespace.
#
# browser → traefik (wiki.renner.uno)
# → ak-outpost-wiki-gate (ExternalName → authentik ns)
# → outpost validates the session + passkey step-up, sets X-Authentik-Username
# → http://wiki.ai.svc.cluster.local:8080
#
# In-cluster clients (CI agents, the Gitea webhook) address the Service directly.
apiVersion: v1
kind: Service
metadata:
name: ak-outpost-wiki-gate
namespace: ai
spec:
type: ExternalName
externalName: ak-outpost-wiki-gate.authentik.svc.cluster.local
ports:
- port: 9000
targetPort: 9000
+26
View File
@@ -0,0 +1,26 @@
# wiki.renner.uno — LAN-only (no dns.renner.uno/public label). Traefik backends
# the wiki-gate outpost, never the Service; the ai CNP admits only that outpost.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wiki
namespace: ai
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
ingressClassName: traefik
tls:
- hosts:
- wiki.renner.uno
secretName: wiki-tls
rules:
- host: wiki.renner.uno
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ak-outpost-wiki-gate
port:
number: 9000
+7
View File
@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- externalsecrets.yaml
- deployment.yaml
- gate-service.yaml
- ingress.yaml
+22
View File
@@ -0,0 +1,22 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: wiki
namespace: flux-system
spec:
interval: 10m
retryInterval: 1m
# First start on an empty PVC syncs and embeds the whole vault before /ready is 200.
timeout: 25m
sourceRef:
kind: GitRepository
name: flux-system
path: ./kubernetes/apps/wiki
prune: true
wait: true
dependsOn:
- name: infrastructure
- name: infrastructure-crds
- name: infrastructure-ceph-cluster
# wiki-embed (the embedding backend) lives in the llama-server app.
- name: llama-server
@@ -149,6 +149,32 @@ spec:
- port: "8080"
protocol: TCP
---
# wiki: only the wiki-gate outpost (humans, after Authentik) and Gitea (push
# webhook) may reach it; Traefik is not admitted, so the X-Authentik-Username
# header the service trusts cannot arrive from the LAN. Gitea and wiki-embed
# are reached through the namespace egress above; no OpenBao access.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: wiki
namespace: ai
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: wiki
ingress:
- fromEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": authentik
goauthentik.io/outpost-name: wiki-gate
- matchLabels:
"k8s:io.kubernetes.pod.namespace": gitea
app.kubernetes.io/name: gitea
toPorts:
- ports:
- port: "8080"
protocol: TCP
---
# wiki-index: Traefik (LAN clients behind BasicAuth) and Gitea (push webhook)
# may reach it; it reaches OpenBao for its credentials. Gitea itself is reached
# through Traefik, which the namespace egress above already allows.