Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69915ce0dd | ||
|
|
4a05b82fe8 | ||
|
|
538a968edb | ||
|
|
47592bd308 | ||
|
|
2d2959f32a | ||
|
|
d153d3e62e | ||
|
|
de570ddf7c | ||
|
|
5a81930991 | ||
|
|
7f4ab6fc21 | ||
|
|
c659cc2bd6 | ||
|
|
13a7ad1c06 | ||
|
|
4b574b77bc | ||
|
|
f76b638dec | ||
|
|
67bc3a2f43 | ||
|
|
2b2fd3647b | ||
|
|
53996b0136 | ||
|
|
e7c5325aae | ||
|
|
853d2ba803 |
@@ -28,7 +28,7 @@
|
||||
## Review Focus
|
||||
|
||||
1. **Flux fetch across the transfer.** Flux's GitRepository fetches `k.renner/homelab.git` until the URL change lands. Documented behaviour says this works: source-controller v1.9.5 uses go-git v5.19.1 (its `go.mod`), whose `RedirectPolicy` "mirror[s] Git's http.followRedirects config" with the zero value `initial` (pkg.go.dev); git documents `initial` as following the redirect on the first request and using "the redirected URL as the base for the follow-up requests" (`git-config`, `http.followRedirects`). Gitea's 301 stays on the same host and scheme, so fetch and the IUA push both reach `konairius/homelab`. Fallback only, should that fail: Task 3 has a declared break-glass (suspend the `flux-system` Kustomization so it cannot re-apply the old URL, patch the GitRepository URL to the value the PR sets, resume once the source is Ready on the merge commit), checked by `flux get source git flux-system`.
|
||||
6. **Write access lost in the transfer.** Any writer that is a konairius org member loses its collaborator grant and keeps only its team permission (`flux` pushes for image automation; claude-bot, issue-fix-bot, security-bot, review-bot, argus-bot, renovate-bot write PRs/reviews). Expect: Task 3 Step 0 snapshot of collaborators, konairius teams and the branch-protection whitelists, compared after Step 1.
|
||||
6. **Write access lost in the transfer.** Any writer that is a konairius org member loses its collaborator grant and keeps only its team permission (`flux` pushes for image automation; claude-bot, issue-fix-bot, security-bot, review-bot, argus-bot, renovate-bot write PRs/reviews). Expect: Task 3 Step 0 snapshot of collaborators and the `master` protection, compared after Step 1 (memberships confirmed fine by the owner).
|
||||
2. **Image automation push between merge and rename (Cut-over 2).** The IUA pushes to `master`; after the Task 5 PR it expects `main`. Expect: IUA suspended for the whole window and resumed only after Flux reports `main`.
|
||||
3. **Org secrets after the Woodpecker move.** Homelab pipelines use the org secrets `claude_code_oauth_token` and `review_bot_token`; after the move they resolve in `konairius`. Expect: both exist in `konairius` before Cut-over 1 (Task 1 verification).
|
||||
4. **Crons on a vanished branch.** All 7 crons run on `master`. Expect: each switched to `main` in place (`woodpecker repo cron update --branch main`) inside the Cut-over 2 window, verified by `cron ls`.
|
||||
@@ -70,11 +70,11 @@
|
||||
|
||||
**Preconditions:** Task 1 verified; Task 2 PR green; no deploy pipeline running (`woodpecker pipeline ls k.renner/homelab --limit 5`); record `flux get source git flux-system` revision.
|
||||
|
||||
- [ ] **Step 0 (owner, needs admin):** snapshot `tea api --login renner.uno /repos/k.renner/homelab/collaborators`, `tea api --login renner.uno /orgs/konairius/teams` plus each team's members, and the `master` rule's push/merge/approvals whitelists (`tea api --login renner.uno /repos/k.renner/homelab/branch_protections`). Any writer that is a konairius member must get write access through its team, or be re-added as collaborator right after Step 1.
|
||||
- [ ] **Step 0 (claude-bot):** snapshot `tea api --login claude-bot /repos/k.renner/homelab/collaborators` (2026-10-19: security-bot, claude-bot, alert-monitor-bot, issue-fix-bot, update-audit-bot, review-bot, flux, renovate-bot, hermes-bot) and `/repos/k.renner/homelab/branches/master` (`protected: true`, `required_approvals: 1`). konairius memberships confirmed fine by the owner; whitelists need admin and are not snapshotted.
|
||||
- [ ] **Step 1 (owner):** `tea api --login renner.uno -X POST /repos/k.renner/homelab/transfer -f new_owner=konairius`. Expect `curl -sI https://gitea.renner.uno/k.renner/homelab` → 301.
|
||||
- [ ] **Step 2 (claude-bot):** Woodpecker move: `POST $WOODPECKER_SERVER/api/repos/1/move?to=konairius/homelab` → 204; `woodpecker repo ls` shows `konairius/homelab`.
|
||||
- [ ] **Step 3 (owner):** merge the Task 2 PR.
|
||||
- [ ] **Step 1a:** repeat the Step 0 reads against `konairius/homelab` and compare; re-add any lost writer as collaborator before continuing.
|
||||
- [ ] **Step 1a (claude-bot):** repeat the Step 0 reads against `konairius/homelab` and compare; a missing writer is re-added by the owner before continuing.
|
||||
- [ ] **Step 4:** `flux reconcile source git flux-system`. If it fails to fetch: break-glass — `flux suspend kustomization flux-system` (else it re-applies the last good artifact with the old URL), then `kubectl -n flux-system patch gitrepository flux-system --type merge -p '{"spec":{"url":"https://gitea.renner.uno/konairius/homelab.git"}}'` (the value the PR sets), wait for the source to be Ready on the Task 2 merge commit, then `flux resume kustomization flux-system`. Expect: revision = Task 2 merge commit, Ready.
|
||||
- [ ] **Step 5:** `flux reconcile image update homelab` after the next ImagePolicy change, or check the last IUA push reaches `konairius/homelab` (commit by Flux Image Automation on master).
|
||||
- [ ] **Step 6:** Run flux-git rotator once (`kubectl create job -n openbao --from=cronjob/flux-git-secret-rotate flux-git-move`); log shows `pull+push verified` against `konairius/homelab`.
|
||||
|
||||
@@ -295,8 +295,8 @@ data:
|
||||
name: llama-gate
|
||||
state: absent
|
||||
|
||||
# Retired: wiki-gate. wiki.renner.uno is gated by authentik-gate-sync
|
||||
# (kubernetes/apps/wiki/ingress.yaml).
|
||||
# Retired: wiki-gate. The wiki signs humans in itself (application
|
||||
# `wiki` in passkey-stepup.yaml).
|
||||
- model: authentik_outposts.outpost
|
||||
identifiers:
|
||||
name: wiki-gate
|
||||
@@ -1011,6 +1011,7 @@ data:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-profile]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-offline_access]]
|
||||
redirect_uris:
|
||||
- url: https://hermes.renner.uno/auth/callback
|
||||
matching_mode: strict
|
||||
@@ -1039,6 +1040,44 @@ data:
|
||||
negate: false
|
||||
failure_result: false
|
||||
|
||||
# Wiki (apps/wiki): its own OIDC login. Public client + PKCE; RS256
|
||||
# so the service verifies the ID token against the JWKS. No policy
|
||||
# binding: every Authentik user may sign in.
|
||||
- id: wiki-provider
|
||||
model: authentik_providers_oauth2.oauth2provider
|
||||
identifiers:
|
||||
name: Wiki
|
||||
state: present
|
||||
attrs:
|
||||
authentication_flow: null
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
client_type: public
|
||||
client_id: wiki
|
||||
# See the Matrix (MAS) provider: a blueprint-made provider starts
|
||||
# with no grant types.
|
||||
grant_types:
|
||||
- authorization_code
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-profile]]
|
||||
redirect_uris:
|
||||
- url: https://wiki.renner.uno/signin-oidc
|
||||
matching_mode: strict
|
||||
|
||||
# The slug is the issuer path: https://auth.renner.uno/application/o/wiki/
|
||||
- model: authentik_core.application
|
||||
identifiers:
|
||||
slug: wiki
|
||||
state: present
|
||||
attrs:
|
||||
name: Wiki
|
||||
slug: wiki
|
||||
provider: !KeyOf wiki-provider
|
||||
meta_launch_url: https://wiki.renner.uno
|
||||
meta_description: "Homelab wiki"
|
||||
|
||||
# OpenProject, decommissioned. Application first: Application.provider
|
||||
# is on_delete=PROTECT. Its policy binding cascades with it.
|
||||
- model: authentik_core.application
|
||||
|
||||
@@ -5,7 +5,7 @@ When you use a pronoun for someone whose pronouns haven't been stated, use they/
|
||||
# What you can touch
|
||||
|
||||
- `kubectl`: read-only cluster access (the `view` ClusterRole, no Secrets). You cannot change the cluster.
|
||||
- `talosctl`: read-only (`os:reader`) on the Talos nodes; `TALOSCONFIG` is set, the endpoint is `talos.default`. Pass nodes with `-n <InternalIP>` taken from `kubectl get nodes -o wide` at the time (e.g. `talosctl -n <ip> get members`).
|
||||
- `talosctl`: read-only (`os:reader`) on the Talos nodes; `TALOSCONFIG` is set, the endpoint is `talos.default`. Pass nodes by name with `-n` (e.g. `talosctl -n k8s-1 get members`, `-n k8s-1,k8s-2,k8s-3`).
|
||||
- `bao`: OpenBao at `BAO_ADDR`, read-only on your own `homelab/apps/hermes/*`. Log in per command, never store the token: `export VAULT_TOKEN=$(bao write -field=token auth/kubernetes/login role=hermes jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token)` then e.g. `bao kv list -mount=homelab apps/hermes`. Never print secret values.
|
||||
- Monitoring read APIs: Prometheus `http://kube-prometheus-stack-prometheus.monitoring.svc:9090`, Alertmanager `http://kube-prometheus-stack-alertmanager.monitoring.svc:9093`, Loki `http://loki.monitoring.svc:3100`.
|
||||
- The homelab wiki through the `homelab-wiki` skill (read, and deposit facts).
|
||||
|
||||
@@ -71,6 +71,30 @@ spec:
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /opt/data
|
||||
# WebSocket keepalive ping interval in seconds (default 20).
|
||||
- name: config-ws-ping
|
||||
image: gitea.renner.uno/konairius/hermes:build.1792397861 # {"$imagepolicy": "flux-system:hermes"}
|
||||
command:
|
||||
- /opt/hermes/.venv/bin/hermes
|
||||
- config
|
||||
- set
|
||||
- dashboard.ws_ping_interval
|
||||
- "60"
|
||||
env:
|
||||
- name: HERMES_HOME
|
||||
value: /opt/data
|
||||
- name: HOME
|
||||
value: /opt/data
|
||||
securityContext:
|
||||
runAsUser: 10000
|
||||
runAsGroup: 10000
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /opt/data
|
||||
# Skills from git (ConfigMap hermes-skills) beside the PVC's skills/.
|
||||
- name: skills-config
|
||||
image: gitea.renner.uno/konairius/hermes:build.1792397861 # {"$imagepolicy": "flux-system:hermes"}
|
||||
@@ -140,6 +164,9 @@ spec:
|
||||
value: https://auth.renner.uno/application/o/hermes/
|
||||
- name: HERMES_DASHBOARD_OIDC_CLIENT_ID
|
||||
value: hermes-dashboard
|
||||
# offline_access: Authentik issues a refresh token only when requested.
|
||||
- name: HERMES_DASHBOARD_OIDC_SCOPES
|
||||
value: openid profile email offline_access
|
||||
- name: API_SERVER_ENABLED
|
||||
value: "true"
|
||||
- name: API_SERVER_HOST
|
||||
|
||||
@@ -30,7 +30,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: controller
|
||||
image: gitea.renner.uno/gitea_admin/kuma-sync:2026-10-19-0646 # {"$imagepolicy": "flux-system:kuma-sync"}
|
||||
image: gitea.renner.uno/gitea_admin/kuma-sync:2026-10-19-0857 # {"$imagepolicy": "flux-system:kuma-sync"}
|
||||
env:
|
||||
- name: KUMA_URL
|
||||
value: https://kuma.renner.uno
|
||||
|
||||
@@ -24,7 +24,7 @@ metadata:
|
||||
namespace: ai
|
||||
annotations:
|
||||
# Keys, tokens and the Anthropic key arrive via envFrom; a rotated Secret rolls the pod.
|
||||
secret.reloader.stakater.com/reload: wiki-gitea,wiki-api-keys,wiki-typesafe,wiki-anthropic
|
||||
secret.reloader.stakater.com/reload: wiki-gitea,wiki-api-keys,wiki-typesafe,wiki-anthropic,wiki-api-basic-auth
|
||||
labels:
|
||||
app.kubernetes.io/name: wiki
|
||||
spec:
|
||||
@@ -53,7 +53,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: wiki
|
||||
image: gitea.renner.uno/konairius/wiki:build.1790322994 # {"$imagepolicy": "flux-system:wiki"}
|
||||
image: gitea.renner.uno/konairius/wiki:build.1792404861 # {"$imagepolicy": "flux-system:wiki"}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
@@ -72,11 +72,14 @@ spec:
|
||||
value: main
|
||||
- name: Wiki__Embeddings__Models__bge-m3__Url
|
||||
value: http://wiki-embed.ai.svc.cluster.local:8080
|
||||
# Pod networks: the CNPs admit only the gate outpost and Gitea.
|
||||
- name: Auth__ReverseProxy__TrustedProxies__0
|
||||
value: fd10:244::/48
|
||||
- name: Auth__ReverseProxy__TrustedProxies__1
|
||||
value: 10.244.0.0/16
|
||||
# Human login: the `wiki` application in apps/authentik/blueprints-configmap.yaml.
|
||||
- name: Auth__Oidc__Authority
|
||||
value: https://auth.renner.uno/application/o/wiki/
|
||||
- name: Auth__Oidc__ClientId
|
||||
value: wiki
|
||||
# wiki-index BasicAuth users (htpasswd), checked by the service for the read API.
|
||||
- name: Auth__Basic__UsersPath
|
||||
value: /var/run/secrets/wiki-basic-auth
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: wiki-gitea
|
||||
@@ -122,12 +125,18 @@ spec:
|
||||
mountPath: /data
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
- name: basic-auth
|
||||
mountPath: /var/run/secrets/wiki-basic-auth
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: wiki-data
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: basic-auth
|
||||
secret:
|
||||
secretName: wiki-api-basic-auth
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
|
||||
@@ -87,7 +87,7 @@ spec:
|
||||
key: apps/ai/wiki-anthropic
|
||||
property: api_key
|
||||
---
|
||||
# BasicAuth users for the machine door; same OpenBao key as the Python service used.
|
||||
# BasicAuth users for the machine door, read by Traefik and by the service; same OpenBao key as the Python service used.
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# wiki.renner.uno — LAN-only (no dns.renner.uno/public label). Gated by
|
||||
# authentik-gate-sync (ingress class authentik-gate); the service trusts
|
||||
# X-Authentik-Username only from pod addresses, and the generated CNP admits
|
||||
# only the gate outpost.
|
||||
# wiki.renner.uno — LAN-only (no dns.renner.uno/public label). Plain Traefik:
|
||||
# the service signs humans in itself with OIDC (application `wiki` in
|
||||
# apps/authentik/blueprints-configmap.yaml).
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
@@ -10,7 +9,7 @@ metadata:
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-cluster
|
||||
spec:
|
||||
ingressClassName: authentik-gate
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- wiki.renner.uno
|
||||
@@ -28,8 +27,7 @@ spec:
|
||||
number: 8080
|
||||
---
|
||||
# wiki-index.renner.uno — the machine door (bin/wiki_rank.py, wiki_ask.py):
|
||||
# Traefik BasicAuth, and the identity header stripped so only the outpost path
|
||||
# can carry X-Authentik-Username to the service.
|
||||
# Traefik BasicAuth; the service checks the same credentials itself.
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
@@ -40,16 +38,6 @@ spec:
|
||||
secret: wiki-api-basic-auth
|
||||
realm: wiki
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: wiki-api-strip-identity
|
||||
namespace: ai
|
||||
spec:
|
||||
headers:
|
||||
customRequestHeaders:
|
||||
X-Authentik-Username: ""
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
@@ -57,7 +45,7 @@ metadata:
|
||||
namespace: ai
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-cluster
|
||||
traefik.ingress.kubernetes.io/router.middlewares: ai-wiki-api-strip-identity@kubernetescrd,ai-wiki-api-basic-auth@kubernetescrd
|
||||
traefik.ingress.kubernetes.io/router.middlewares: ai-wiki-api-basic-auth@kubernetescrd
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
|
||||
@@ -4,7 +4,7 @@ metadata:
|
||||
name: gitea-mcp
|
||||
namespace: flux-system
|
||||
spec:
|
||||
image: docker.gitea.com/gitea-mcp-server@sha256:20701c3dcf3b71df384417d04f9f6382205b7f362380b258fb8e4dcf9599091f
|
||||
image: docker.gitea.com/gitea-mcp-server
|
||||
interval: 1h
|
||||
---
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
|
||||
@@ -140,10 +140,9 @@ spec:
|
||||
- port: "8080"
|
||||
protocol: TCP
|
||||
---
|
||||
# wiki: Traefik (the machine door wiki-index.renner.uno, BasicAuth with
|
||||
# X-Authentik-Username stripped), Gitea (push webhook) and Hermes Agent (API
|
||||
# key) may reach it; the gate outpost (humans, after Authentik) is admitted
|
||||
# by the CNP wiki-gate that authentik-gate-sync generates. Gitea and wiki-embed
|
||||
# wiki: Traefik (wiki.renner.uno, OIDC login in the service, and the machine
|
||||
# door wiki-index.renner.uno, BasicAuth), Gitea (push webhook) and Hermes Agent
|
||||
# (API key) may reach it. Gitea, wiki-embed and auth.renner.uno (via Traefik)
|
||||
# are reached through the namespace egress above; no OpenBao access.
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
|
||||
Reference in New Issue
Block a user