Compare commits

...
Author SHA1 Message Date
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
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
k.renner 17bf12cc4f Merge pull request 'fix(alertmanager): route InfoInhibitor to null and add the info inhibit rules' (#936) from fix/alertmanager-info-inhibitor into master
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#936
2026-09-23 19:24:22 +00:00
claude-botandClaude Opus 5.5 1fc8073218 fix(alertmanager): route InfoInhibitor to null and add the info inhibit rules
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
Our hand-written Alertmanager config replaced the chart's default and
dropped two pieces kube-prometheus relies on:

- a route sending InfoInhibitor to the null receiver
- the inhibit rules that let InfoInhibitor (and warning/critical)
  suppress severity=info alerts

So both the helper alert and the info alerts it exists to silence were
emailed, daily. InfoInhibitor fired over the last 7 days in crowdsec,
kube-system, woodpecker-ci and without a namespace, driven by
CPUThrottlingHigh (crowdsec-agent, whereabouts-reconciler),
KubeletTooManyPods and KubeQuota* in woodpecker-ci.

Route and the three inhibit rules are copied from kube-prometheus
manifests/alertmanager-secret.yaml. Info alerts now reach email only
while a warning or critical alert fires in the same namespace, which is
the upstream design. amtool check-config passes; amtool config routes
test sends InfoInhibitor to null.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 21:23:29 +02:00
k.renner 161c04daff Merge pull request 'feat(rook-ceph): allow only aes256k CephX keys (#527)' (#935) from feat/cephx-allowed-ciphers-aes256k into master
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#935
2026-09-23 19:15:33 +00:00
claude-botandClaude Opus 5.5 999e3fbac9 feat(rook-ceph): allow only aes256k CephX keys (#527)
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
Final step of the CVE-2025-30156 remediation: allowedCiphers
[aes256k], which Rook applies as auth_allowed_ciphers. That is step 10
of Ceph's CephX aes256k upgrade procedure and clears
AUTH_INSECURE_KEYS_ALLOWED; per Ceph's health-check reference that also
resolves AUTH_INSECURE_KEYS_CREATABLE.

Precondition from the procedure, checked without printing any key:
AUTH_INSECURE_CLIENT_KEY_TYPE and AUTH_INSECURE_SERVICE_KEY_TYPE are both
clear (ceph health detail lists only KEYS_ALLOWED and KEYS_CREATABLE).
Rook status: daemon keys and CSI/rbdMirrorPeer at generation 2, prior CSI
keys deleted (#934).

Lockout recovery if auth breaks (Rook cephx-key-rotation doc): set
allowedCiphers [aes, aes256k] and daemon.keyType aes, wait for the mons
to carry --mon-auth-emergency-allowed-ciphers, then remove
daemon.keyType.

Closes #527

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 21:12:43 +02:00
k.renner 70208c42da Merge pull request 'feat(rook-ceph): delete the prior aes CSI keys (#527)' (#934) from feat/cephx-drop-prior-csi-keys into master
Reviewed-on: k.renner/homelab#934
2026-09-23 19:04:44 +00:00
claude-botandClaude Opus 5.5 94b35c3f64 feat(rook-ceph): delete the prior aes CSI keys (#527)
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
keepPriorKeyCountMax 1 -> 0. Rook deletes the generation-1 CSI keys
(client.csi-*.1, aes), which clears AUTH_INSECURE_CLIENT_KEY_TYPE.

Every PVC has been remounted with the generation-2 aes256k keys: all
three nodes were drained and uncordoned after the rotation, and no
kernel session on any mon uses a .1 entity any more. The remaining .1
sessions are idle librados connections in the CSI nodeplugins' pool;
ceph-csi keys pool entries by user and keyfile, and the CSI secrets now
name the .2 users, so nothing reuses them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 21:04:04 +02:00
k.renner 5cbdb6e67f Merge pull request 'ci(wiki-maintenance): allow the ~/Notes spelling the agents actually use' (#932) from ci/wiki-consolidate-tilde-paths into master
ci/woodpecker/cron/alert-monitor Pipeline was successful
ci/woodpecker/manual/wiki-maintenance Pipeline failed
Reviewed-on: k.renner/homelab#932
2026-09-23 18:56:45 +00:00
claude-botandClaude Fable 5.1 ee08edba3e ci(wiki-maintenance): allow the ~/Notes spelling the agents actually use
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
Run 10608 aborted before its first edit: the agent calls
~/Notes/bin/wiki-snapshot.sh and git -C ~/Notes rev-parse/status, the
rules carried only /root/Notes. Both spellings are allowed for the vault
scripts and the read-only git subcommands on the vault checkout, config is
denied there too, and the consolidate step derives its --allowedTools
from the settings file instead of a second copy. Verified headless:
rev-parse, status and wiki-delete.sh allowed, config denied.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 20:53:23 +02:00
k.renner 96d15f19c8 Merge pull request 'ci(wiki-maintenance): run the consolidate pass in CI' (#927) from ci/wiki-consolidate-in-ci into master
ci/woodpecker/manual/wiki-maintenance Pipeline was successful
Reviewed-on: k.renner/homelab#927
2026-09-23 18:48:21 +00:00
k.renner 0581fd461f Merge pull request 'feat(rook-ceph): rotate CSI and rbd-mirror-peer keys to aes256k (#527)' (#931) from feat/cephx-csi-aes256k into master
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#931
2026-09-23 18:24:43 +00:00
claude-botandClaude Opus 5.5 f1024fa4b3 feat(rook-ceph): rotate CSI and rbd-mirror-peer keys to aes256k (#527)
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
The last five CephX entities on aes are the four CSI clients and
client.rbd-mirror-peer. The CSI keys waited on node kernel support:
Rook's key-rotation doc asks for kernel 7.0+ for aes256k over kernel
mounts. Talos v1.13.10 (now on all nodes) backports those libceph
patches to 6.18 (siderolabs/pkgs 2da2cdc); CONFIG_CRYPTO_KRB5 and
CONFIG_CRYPTO_KRB5ENC are set on every node. ceph-csi is v3.17.1, the
minimum the doc names; Rook v1.20.7.

csi: KeyGeneration 2, keyType aes256k, keepPriorKeyCountMax 1. Rotated
keys only apply to new mounts; the prior key stays valid for PVCs
mounted before the rotation, per the Rook doc.

rbdMirrorPeer: KeyGeneration 2, aes256k. No CephRBDMirror and no peers
exist, so rotating the peer token affects nothing.

allowedCiphers / mon_auth_allow_insecure_key are NOT changed here: that
is the lockout-capable step and follows only once ceph health shows no
aes entity left.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 20:20:52 +02:00
claude-botandClaude Fable 5.1 bd4553cc63 ci(wiki-maintenance): report the claude exit code when no result record exists
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
Review on #927: an empty or truncated /tmp/consolidate.json made jq the
failing command instead of the run. Commit subject of push-notes names
consolidate too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 20:19:45 +02:00
k.renner 904475b89a Merge pull request 'chore(talos): move to v1.13.10' (#930) from chore/talos-1.13.10 into master
ci/woodpecker/push/build-ci-images Pipeline was successful
Reviewed-on: k.renner/homelab#930
2026-09-23 18:18:14 +00:00
claude-botandClaude Opus 5.5 c395a9fc67 chore(talos): move to v1.13.10
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/push/build-ci-images Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
All three nodes run v1.13.10 (applied by hand, one node at a time:
drain, talosctl upgrade --image, wait for 217/217 active+clean). This
brings the repo in step so a rebuild or a new node installs the same
version.

Why this release: its pkgs include siderolabs/pkgs@2da2cdc, 'backport
aes256k support (Ceph)', the libceph patches from Linux 7.x on top of
6.18.48. Kernel config on every node now has CONFIG_CRYPTO_KRB5=y and
CONFIG_CRYPTO_KRB5ENC=y. That was the blocker for rotating the CSI
CephX keys to aes256k (#527).

Patch release within 1.13; Kubernetes stays v1.36.3. Schematic is
unchanged; factory installer and ISO verified to exist for the tag.
talosctl checksum matches the release's sha256sum.txt and a local
download.

Refs #527

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 19:26:53 +02:00
k.renner bf3d58628e Merge pull request 'feat(k8s): make single-instance workloads survive node drains' (#929) from feat/drain-tolerant-singletons into master
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#929
2026-09-23 17:07:42 +00:00
claude-botandClaude Opus 5.5 6edde49b48 feat(k8s): make single-instance workloads survive node drains
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
Draining k8s-3 for the Talos 1.13.10 upgrade stopped on four pods
(wp-pg-1, ha-pg-1, gf-pg-1, gitea-0) whose PDBs allow zero disruptions,
and each needed a manual --disable-eviction.

CNPG: ha-pg, gf-pg, wp-pg and tr-pg go to instances: 2. With a replica,
CNPG switches the primary over ahead of a drain, the same way ak-pg,
im-pg and pprl-pg already do. With one instance it creates a primary PDB
and 'will prevent draining the node where the instance is housed'
(CNPG docs, kubernetes_upgrade). smartShutdownTimeout 10 / stopDelay 60
as on ak-pg, which bound switchover time and the barman-plugin demote
hang (cloudnative-pg#9770). Network policies already allow intra-cluster
replication, operator :8000 and WAL-restore egress in all four
namespaces; nothing references a specific instance name.

Single-replica RWO workloads (gitea, woodpecker-server,
protonmail-bridge, syncthing, influxdb): drop the minAvailable:1 PDBs.
They cannot fail over, and eviction already stops the pod gracefully
with SIGTERM and its grace period; the PDB only forced a manual
--disable-eviction on every drain. influxdb's comes from the chart
(pdb.create, influxdb2 2.1.2 defaults).

The 'single-instance cluster, the archive is the only off-node copy'
comments no longer hold and are removed (im-pg's was already stale).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 19:06:16 +02:00
claude-botandClaude Fable 5.1 94b6d77bac ci(wiki-maintenance): per-subcommand git allow for the source clones, deletion via wiki-delete.sh
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
Review on #927: the git -C /root/source/ prefix let config and push
through; rm /root/Notes/wiki/ matched path traversal. Allowed git forms
are now log/show/diff/status/rev-parse/ls-files per repo (both the ~ and
/root spelling), config is denied for those forms too, rm is denied and
notes are deleted through the vault's wiki-delete.sh, which validates the
slug. Verified headless: log allowed, config denied, push not allowed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 19:04:24 +02:00
claude-botandClaude Fable 5.1 c7f6514e20 ci(wiki-maintenance): run the consolidate pass in CI
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
The pass that verifies notes against sources, repos and the live cluster
and edits them still ran on the workstation timer, with no TypeSafe key,
no cost record, and no run on days the machine was off. It joins the
nightly pipeline after the audit: full clones of homelab and homeassistant
under ~/source, read-only kubectl via the ci-diagnostics token, its own
permission scope, JSON output so the report and total_cost_usd land in
the step log. push-notes also commits the rotation state and pushes the
agent's own snapshot commits.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 18:22:02 +02:00
claude-botandClaude Fable 5.1 d865043eaa ci(wiki-maintenance): skip the distill agent when raw/ is empty
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline is running
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Every night with an empty queue started a full claude run that read the
directory and stopped, paying the agent's startup context and a model
pick for nothing (runs 10362, 10583).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 18:12:02 +02:00
k.renner 47fbc917fd Merge pull request 'ci(wiki-maintenance): run the audit as the vault's script, commit only vault content' (#925) from ci/wiki-audit-script into master
ci/woodpecker/manual/wiki-maintenance Pipeline was successful
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#925
2026-09-23 16:06:52 +00:00
claude-botandClaude Fable 5.1 de1530789e ci(wiki-maintenance): run the audit as the vault's script, commit only vault content
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
The audit agent had no executable path in CI (Bash limited to the two
retrieval tools), wrote itself scripts it could not run and reported one
of seven checks (runs 10262, 10362). The checks are mechanical, so
notes/bin/wiki_audit.py does them without a model. push-notes adds only
wiki/, raw/ and output/, so scratch files in the checkout root are no
longer committed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 18:05:41 +02:00
k.renner c92c1f4ab5 Merge pull request 'fix(k8s-gateway): pull the chart from the Codeberg OCI registry' (#924) from fix/k8s-gateway-oci-chart into master
Reviewed-on: k.renner/homelab#924
2026-09-23 16:01:24 +00:00
k.renner 6bb8ef4776 Merge pull request 'fix: [alert] OpenBaoQuorumLost (openbao) (#920)' (#921) from alert-fix/issue-920 into master
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#921
2026-09-23 15:59:33 +00:00
claude-botandClaude Opus 5.5 33a0079a20 fix(k8s-gateway): pull the chart from the Codeberg OCI registry
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
The project moved to Codeberg; GitHub is a read-only mirror and the old
chart index at k8s-gateway.github.io returns 404, so the HelmRelease has
had no artifact. The chart is now published as an OCI artifact in the
project's own Codeberg registry (oci://codeberg.org/k8s-gateway/charts/
k8s-gateway), alongside the image.

OCIRepository + chartRef as for spegel and mariadb-operator; semver
range unchanged. Chart 3.7.2 -> 3.7.3, image moves to
codeberg.org/k8s-gateway/k8s_gateway:1.8.3. The rendered Corefile is
identical to the live one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 17:57:20 +02:00
k.renner 80816d7433 Merge pull request 'docs(network): traffic classes as built, node-to-node on VLAN 46 (#576)' (#923) from docs/network-traffic-classes-as-built into master
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#923
2026-09-23 12:49:20 +00:00
issue-fix-bot 87083c46e7 fix: [alert] OpenBaoQuorumLost (openbao) (#920)
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
2026-09-23 12:42:58 +00:00
33 changed files with 558 additions and 160 deletions
+81 -23
View File
@@ -1,12 +1,13 @@
# Nightly wiki maintenance for the Notes vault (k.renner/notes), replacing the
# workstation's wiki-maintenance.timer: distill the raw/ backlog into wiki/,
# then audit wiki/. Each agent is its own `claude` run, not a subagent of an
# orchestrator, so nothing depends on background tasks in print mode (the
# workstation job died on their 600 s ceiling). The pipeline commits and pushes
# as Claude Bot; the push webhook then refreshes the wiki-index service.
# workstation's wiki-maintenance and wiki-consolidate timers: distill the raw/
# backlog into wiki/ (wiki-distill agent), audit wiki/ with the vault's own
# script (structural checks, no model), then consolidate (wiki-consolidate
# agent: verifies a cohort of notes against sources, repos and the live
# cluster and edits them). The pipeline commits and pushes as Claude Bot; the
# push webhook then refreshes the wiki-index service.
#
# The agents and their retrieval scripts address the vault as ~/Notes, so each
# agent step links $HOME/Notes to the checkout instead of rewriting them.
# The agents address the vault as ~/Notes and the repos as ~/source/<repo>, so
# each agent step links those into the workspace instead of rewriting them.
when:
- event: cron
cron: wiki-maintenance
@@ -30,6 +31,27 @@ steps:
git -c http.extraheader="AUTHORIZATION: token $$TOKEN" \
clone --depth 50 --branch main https://gitea.renner.uno/k.renner/notes.git notes
echo "raw backlog: $$(ls notes/raw/*.md | grep -v _index.md | wc -l) file(s)"
# Full history for the consolidate agent's `git log` checks; the
# workspace clone is shallow and tree-filtered.
mkdir -p sources
git clone --quiet https://gitea.renner.uno/k.renner/homelab.git sources/homelab
git -c http.extraheader="AUTHORIZATION: token $$TOKEN" \
clone --quiet https://gitea.renner.uno/k.renner/homeassistant.git sources/homeassistant
- name: fetch-diag
# Read-only live-cluster access for the consolidate agent, same mechanism
# as issue-fix.yml: ./.ci-diag/kubectl plus a 1h ci-diagnostics token.
image: gitea.renner.uno/gitea_admin/ci-k8s:2026-09-08
environment:
SOPS_AGE_KEY:
from_secret: sops_age_key
S3_ACCESS_KEY_ID:
from_secret: hetzner_s3_access_key
S3_SECRET_ACCESS_KEY:
from_secret: hetzner_s3_secret_key
commands:
- bash scripts/ci/fetch-kubeconfig.sh
- bash scripts/ci/make-diag-kubeconfig.sh
- name: distill
image: gitea.renner.uno/gitea_admin/ci-claude:2.1.280
@@ -44,24 +66,36 @@ steps:
commands:
- |
set -eu
# An empty queue must not start the agent: its startup context alone
# costs more than the whole audit.
BACKLOG=$$(ls notes/raw/*.md | grep -v _index.md | wc -l)
if [ "$$BACKLOG" -eq 0 ]; then
echo "distill: raw/ is empty, skipping the agent"
exit 0
fi
ln -sfn "$$PWD/notes" "$$HOME/Notes"
mkdir -p "$$HOME/.config/wiki-index"
printf '%s' "$$WIKI_INDEX_BASIC_AUTH" | tr -d '\r\n' > "$$HOME/.config/wiki-index/basic-auth"
chmod 600 "$$HOME/.config/wiki-index/basic-auth"
"$$HOME/Notes/bin/wiki_rank.py" --probe-index
- |
set -eu
MODEL=$$(sh scripts/ci/pick-model.sh "distill raw source notes into atomic wiki notes: read sources, check the wiki for existing coverage, write or merge notes; careful writing, moderate reasoning")
if [ -n "$$MODEL" ]; then echo "distill: selected model $$MODEL"; else echo "distill: using the CLI default model"; fi
cd notes
cat ../ci/agents/wiki-maintenance/distill.md | claude $${MODEL:+--model $$MODEL} \
--agent wiki-distill \
--permission-mode acceptEdits \
--allowedTools "Read,Edit,Write,Glob,Grep,Bash(/root/Notes/bin/wiki_rank.py:*),Bash(/root/Notes/bin/wiki_ask.py:*),Bash(mv /root/Notes/raw/:*),Bash(mkdir -p /root/Notes/:*)" \
--allowedTools "Read,Edit,Write,Glob,Grep,Bash(/root/Notes/bin/wiki_rank.py:*),Bash(/root/Notes/bin/wiki_ask.py:*),Bash(mv /root/Notes/raw/:*),Bash(mkdir -p /root/Notes/:*),Bash(~/Notes/bin/wiki_rank.py:*),Bash(~/Notes/bin/wiki_ask.py:*),Bash(mv ~/Notes/raw/:*),Bash(mkdir -p ~/Notes/:*)" \
--settings ../ci/agents/wiki-maintenance/settings.json \
-p
- name: audit
image: gitea.renner.uno/gitea_admin/ci-claude:2.1.280
pull: true
commands:
# Findings are report lines in output/, not a pipeline failure.
- python3 -B notes/bin/wiki_audit.py --vault "$$PWD/notes"
- name: consolidate
image: gitea.renner.uno/gitea_admin/ci-claude:2.1.280
pull: true
environment:
@@ -75,20 +109,40 @@ steps:
- |
set -eu
ln -sfn "$$PWD/notes" "$$HOME/Notes"
ln -sfn "$$PWD/sources" "$$HOME/source"
mkdir -p "$$HOME/.config/wiki-index"
printf '%s' "$$WIKI_INDEX_BASIC_AUTH" | tr -d '\r\n' > "$$HOME/.config/wiki-index/basic-auth"
chmod 600 "$$HOME/.config/wiki-index/basic-auth"
- |
set -eu
MODEL=$$(sh scripts/ci/pick-model.sh "structural audit of ~1700 markdown notes: frontmatter completeness, broken links, orphans; mechanical checks, little reasoning")
if [ -n "$$MODEL" ]; then echo "audit: selected model $$MODEL"; else echo "audit: using the CLI default model"; fi
if [ -f .ci-diag/kubeconfig ]; then
ln -sf "$$PWD/.ci-diag/kubectl" /usr/local/bin/kubectl
export KUBECONFIG="$$PWD/.ci-diag/kubeconfig"
else
echo "consolidate: no ci-diag kubeconfig, live cluster checks unavailable"
fi
cd notes
cat ../ci/agents/wiki-maintenance/audit.md | claude $${MODEL:+--model $$MODEL} \
--agent wiki-audit \
# JSON output: the agent's report and the run's cost come out of one
# record at the end instead of a 45-minute silent log.
# One source for the Bash allowlist: the settings file (run 10608: the
# agent writes ~/Notes, the rules carried only /root/Notes).
BASH_RULES=$$(jq -r '[.permissions.allow[] | select(startswith("Bash("))] | join(",")' ../ci/agents/wiki-maintenance/consolidate-settings.json)
rc=0
cat ../ci/agents/wiki-maintenance/consolidate.md | claude \
--agent wiki-consolidate \
--permission-mode acceptEdits \
--allowedTools "Read,Write,Glob,Grep,Bash(/root/Notes/bin/wiki_rank.py:*),Bash(/root/Notes/bin/wiki_ask.py:*)" \
--settings ../ci/agents/wiki-maintenance/settings.json \
-p
--allowedTools "Read,Edit,Write,Glob,Grep,Agent,$$BASH_RULES" \
--settings ../ci/agents/wiki-maintenance/consolidate-settings.json \
--output-format json \
-p > /tmp/consolidate.json || rc=$$?
# A killed or crashed claude leaves no valid JSON; report rc, not a jq error.
if [ -s /tmp/consolidate.json ] && jq -e . /tmp/consolidate.json > /dev/null; then
jq -r '.result // empty' /tmp/consolidate.json
jq -c '{duration_ms, num_turns, total_cost_usd, is_error}' /tmp/consolidate.json
else
echo "consolidate: no result record (exit $$rc)"; head -c 2000 /tmp/consolidate.json || true
fi
echo "consolidate: git log --oneline of the run:"
git log --oneline origin/main..HEAD
exit $$rc
- name: push-notes
image: gitea.renner.uno/gitea_admin/ci-claude:2.1.280
@@ -102,7 +156,13 @@ steps:
cd notes
git config user.email "claude-bot@renner.uno"
git config user.name "Claude Bot"
if git diff --quiet && git diff --cached --quiet && [ -z "$$(git ls-files --others --exclude-standard)" ]; then
# Only the vault content is committed; anything an agent leaves elsewhere
# in the checkout (run 10362: scratch scripts in the root) stays behind.
# The consolidate agent commits its own snapshots, so HEAD may already be
# ahead of origin/main with a clean tree.
git add -A -- wiki raw output .wiki-consolidation-state.json
git diff --cached --quiet || git commit -q -m "wiki maintenance $$(date -u +%F): distill + audit + consolidate (CI run $$CI_PIPELINE_NUMBER)"
if [ "$$(git rev-list --count origin/main..HEAD)" -eq 0 ]; then
echo "wiki-maintenance: no changes"
exit 0
fi
@@ -112,13 +172,11 @@ steps:
# leaves the PR open for a human and fails the run.
BRANCH="wiki-maintenance/$$(date -u +%F)-$$CI_PIPELINE_NUMBER"
git checkout -q -b "$$BRANCH"
git add -A
git commit -q -m "wiki maintenance $$(date -u +%F): distill + audit (CI run $$CI_PIPELINE_NUMBER)"
TOKEN=$$(printf '%s' "$$WIKI_BOT_TOKEN" | tr -d '\r\n')
git -c http.extraheader="AUTHORIZATION: token $$TOKEN" push origin "HEAD:$$BRANCH"
API="https://gitea.renner.uno/api/v1/repos/k.renner/notes"
jq -n --arg title "wiki maintenance $$(date -u +%F) (CI run $$CI_PIPELINE_NUMBER)" \
--arg body "Nightly distill + audit. Merged by the pipeline; open only if the merge failed." \
--arg body "Nightly distill + audit + consolidate. Merged by the pipeline; open only if the merge failed." \
--arg head "$$BRANCH" '{title: $$title, body: $$body, head: $$head, base: "main"}' > /tmp/pr.json
code=$$(curl -s -o /tmp/pr-resp.json -w '%{http_code}' -X POST \
-H "Authorization: token $$TOKEN" -H "Content-Type: application/json" --data @/tmp/pr.json "$$API/pulls")
-5
View File
@@ -1,5 +0,0 @@
You are running as the scheduled wiki-maintenance job in CI. The Notes vault is checked out at ~/Notes (the repository k.renner/notes).
Run the standard audit pass over ~/Notes/wiki/ and write the dated report to ~/Notes/output/. Do not modify any wiki note. Do not commit or push; the pipeline does that.
End with two lines: notes scanned, and issues found by category.
@@ -0,0 +1,120 @@
{
"//": "Permission scope for the wiki-consolidate agent in CI: read the vault and the",
"//2": "source repos cloned under /root/source, read the live cluster through the",
"//3": "ci-diagnostics kubeconfig, edit wiki/, output/ and the rotation state only.",
"//4": "Deny rules win; git and kubectl are read-only subcommands, no network tools.",
"permissions": {
"allow": [
"Read(//root/Notes/**)",
"Read(//root/source/**)",
"Edit(//root/Notes/wiki/**)",
"Edit(//root/Notes/output/**)",
"Edit(//root/Notes/.wiki-consolidation-state.json)",
"Bash(/root/Notes/bin/wiki-snapshot.sh:*)",
"Bash(/root/Notes/bin/wiki_rank.py:*)",
"Bash(/root/Notes/bin/wiki_ask.py:*)",
"Bash(~/Notes/bin/wiki-snapshot.sh:*)",
"Bash(~/Notes/bin/wiki_rank.py:*)",
"Bash(~/Notes/bin/wiki_ask.py:*)",
"Bash(~/Notes/bin/wiki-delete.sh:*)",
"Bash(git -C /root/Notes log:*)",
"Bash(git -C /root/Notes show:*)",
"Bash(git -C /root/Notes diff:*)",
"Bash(git -C /root/Notes status:*)",
"Bash(git -C /root/Notes rev-parse:*)",
"Bash(git -C /root/Notes ls-files:*)",
"Bash(git -C ~/Notes log:*)",
"Bash(git -C ~/Notes show:*)",
"Bash(git -C ~/Notes diff:*)",
"Bash(git -C ~/Notes status:*)",
"Bash(git -C ~/Notes rev-parse:*)",
"Bash(git -C ~/Notes ls-files:*)",
"Bash(kubectl get:*)",
"Bash(kubectl describe:*)",
"Bash(kubectl version:*)",
"Bash(kubectl api-resources:*)",
"Bash(kubectl explain:*)",
"Bash(git log:*)",
"Bash(git show:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(git rev-parse:*)",
"Bash(git ls-files:*)",
"Bash(git -C /root/source/homelab log:*)",
"Bash(git -C /root/source/homelab show:*)",
"Bash(git -C /root/source/homelab diff:*)",
"Bash(git -C /root/source/homelab status:*)",
"Bash(git -C /root/source/homelab rev-parse:*)",
"Bash(git -C /root/source/homelab ls-files:*)",
"Bash(git -C /root/source/homeassistant log:*)",
"Bash(git -C /root/source/homeassistant show:*)",
"Bash(git -C /root/source/homeassistant diff:*)",
"Bash(git -C /root/source/homeassistant status:*)",
"Bash(git -C /root/source/homeassistant rev-parse:*)",
"Bash(git -C /root/source/homeassistant ls-files:*)",
"Bash(git -C ~/source/homelab log:*)",
"Bash(git -C ~/source/homelab show:*)",
"Bash(git -C ~/source/homelab diff:*)",
"Bash(git -C ~/source/homelab status:*)",
"Bash(git -C ~/source/homelab rev-parse:*)",
"Bash(git -C ~/source/homelab ls-files:*)",
"Bash(git -C ~/source/homeassistant log:*)",
"Bash(git -C ~/source/homeassistant show:*)",
"Bash(git -C ~/source/homeassistant diff:*)",
"Bash(git -C ~/source/homeassistant status:*)",
"Bash(git -C ~/source/homeassistant rev-parse:*)",
"Bash(git -C ~/source/homeassistant ls-files:*)",
"Bash(/root/Notes/bin/wiki-delete.sh:*)",
"Bash(ls:*)",
"Bash(date:*)",
"Bash(wc:*)",
"Bash(stat:*)",
"Bash(head:*)",
"Bash(tail:*)"
],
"deny": [
"Read(//proc/**)",
"Read(//etc/**)",
"Read(//root/.config/**)",
"Read(//root/.kube/**)",
"Read(//woodpecker/**/.ci-diag/**)",
"Read(//root/Notes/.git/**)",
"Read(//root/source/*/.git/**)",
"Read(//root/Notes/_archive/**)",
"Edit(//root/Notes/raw/**)",
"Edit(//root/Notes/_archive/**)",
"Edit(//root/source/**)",
"Bash(kubectl apply:*)",
"Bash(kubectl delete:*)",
"Bash(kubectl patch:*)",
"Bash(kubectl edit:*)",
"Bash(kubectl scale:*)",
"Bash(kubectl rollout:*)",
"Bash(kubectl exec:*)",
"Bash(kubectl cp:*)",
"Bash(kubectl drain:*)",
"Bash(kubectl cordon:*)",
"Bash(kubectl label:*)",
"Bash(kubectl annotate:*)",
"Bash(git push:*)",
"Bash(git reset:*)",
"Bash(git checkout:*)",
"Bash(git clean:*)",
"Bash(git rebase:*)",
"Bash(git config:*)",
"Bash(git -C /root/Notes config:*)",
"Bash(git -C ~/Notes config:*)",
"Bash(git -C /root/source/homelab config:*)",
"Bash(git -C /root/source/homeassistant config:*)",
"Bash(git -C ~/source/homelab config:*)",
"Bash(git -C ~/source/homeassistant config:*)",
"Bash(rm:*)",
"Bash(rm -rf:*)",
"Bash(curl:*)",
"Bash(wget:*)",
"Bash(ssh:*)",
"Bash(tea:*)",
"Bash(woodpecker:*)"
]
}
}
@@ -0,0 +1,5 @@
You are running as the scheduled wiki-maintenance job in CI. The Notes vault is checked out at ~/Notes (the repository k.renner/notes); the homelab and homeassistant repositories are checked out under ~/source/. `kubectl` reads the live cluster with a read-only identity.
Run the wiki-consolidate agent now for today's scheduled consolidation pass. Follow its instructions exactly: snapshot the vault to git first, abort if that fails, select today's rotating cohort of 20 from the state file, verify claims against both their sources and the live systems, act on the verdicts, write the dated report to ~/Notes/output/, update the state file, and snapshot again at the end. Do not push; the pipeline pushes the run's commits.
Report the counts and the coverage progress when you are done.
+5 -1
View File
@@ -1,5 +1,5 @@
{
"//": "Permission scope for the wiki-maintenance agents (wiki-distill, wiki-audit) in CI.",
"//": "Permission scope for the wiki-maintenance agent (wiki-distill) in CI.",
"//2": "The step's environment holds a Gitea push token, a TypeSafe key and the wiki-index",
"//3": "client credential, so the Read tool is kept away from /proc (env dumps), /root outside",
"//4": "the vault symlink target, /etc and the checkout's .git/. The vault itself is the work.",
@@ -15,6 +15,10 @@
"Bash(/root/Notes/bin/wiki_ask.py:*)",
"Bash(mv /root/Notes/raw/:*)",
"Bash(mkdir -p /root/Notes/:*)",
"Bash(~/Notes/bin/wiki_rank.py:*)",
"Bash(~/Notes/bin/wiki_ask.py:*)",
"Bash(mv ~/Notes/raw/:*)",
"Bash(mkdir -p ~/Notes/:*)",
"Bash(ls:*)",
"Bash(date:*)",
"Bash(wc:*)",
+2 -2
View File
@@ -1,6 +1,6 @@
FROM alpine:3.21@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d
ARG TALOS_VERSION=v1.13.9
ARG TALOS_VERSION=v1.13.10
ARG VELERO_VERSION=v1.16.0
ARG KUBECTL_VERSION=v1.36.3
ARG FLUX_VERSION=2.9.5
@@ -18,7 +18,7 @@ RUN apk add --no-cache \
RUN curl -sfL -o /usr/local/bin/talosctl \
"https://github.com/siderolabs/talos/releases/download/${TALOS_VERSION}/talosctl-linux-amd64" \
&& echo "7e1d4b7d5846964bdcf63a794e3c8161bb6ef2983d5ace58ea5322f3bf32a27e /usr/local/bin/talosctl" | sha256sum -c - \
&& echo "28e9ccd9aebd690fa03d8cf2582221d87ea38b2a1704f411d75a03589c58f2aa /usr/local/bin/talosctl" | sha256sum -c - \
&& chmod +x /usr/local/bin/talosctl
RUN curl -sfL -o /usr/local/bin/kubectl \
+2 -2
View File
@@ -14,7 +14,7 @@ FROM alpine:3.21@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d650
# kubernetes/talos/patches/cluster.yaml. The phase reads the version from that
# file at run time and passes it to the provisioner; this pin only decides which
# client talks to it.
ARG TALOS_VERSION=v1.13.9
ARG TALOS_VERSION=v1.13.10
ARG KUBECTL_VERSION=v1.36.2
ARG FLUX_VERSION=2.9.2
ARG KOPIA_VERSION=0.23.1
@@ -49,7 +49,7 @@ RUN apk add --no-cache \
RUN curl -sfL -o /usr/local/bin/talosctl \
"https://github.com/siderolabs/talos/releases/download/${TALOS_VERSION}/talosctl-linux-amd64" \
&& echo "7e1d4b7d5846964bdcf63a794e3c8161bb6ef2983d5ace58ea5322f3bf32a27e /usr/local/bin/talosctl" | sha256sum -c - \
&& echo "28e9ccd9aebd690fa03d8cf2582221d87ea38b2a1704f411d75a03589c58f2aa /usr/local/bin/talosctl" | sha256sum -c - \
&& chmod +x /usr/local/bin/talosctl
RUN curl -sfL -o /usr/local/bin/kubectl \
+1 -1
View File
@@ -107,7 +107,7 @@ config, and the nodes are addressed by name.
## Step 1b — boot all three machines from install media
```
https://factory.talos.dev/image/0f6fb8bd8bb53013e9743b7c7b099d96fb9c32fd2464ced32693c2dc0ea5feb9/v1.13.9/metal-amd64.iso
https://factory.talos.dev/image/0f6fb8bd8bb53013e9743b7c7b099d96fb9c32fd2464ced32693c2dc0ea5feb9/v1.13.10/metal-amd64.iso
```
Write it to a USB stick and boot each MS-01 from it. Each comes up in
+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:
-1
View File
@@ -6,7 +6,6 @@ kind: Kustomization
# and verified. See MIGRATION-gitea-woodpecker.md.
resources:
- statefulset.yaml
- pdb.yaml
- service.yaml
- ingressroute-ssh.yaml
- ingress.yaml
-23
View File
@@ -1,23 +0,0 @@
---
# Eviction protection for the single-replica Gitea StatefulSet.
#
# Gitea runs on SQLite (single writer) and its repos + DB live on a single
# ReadWriteOnce PVC, so it is deliberately replicas:1 and cannot fail over.
# With no PDB, a node drain / maintenance evicts the pod abruptly, mid-write,
# with zero coordination. minAvailable:1 on a 1-replica workload makes the
# eviction API refuse *voluntary* evictions: `kubectl drain` blocks and
# surfaces this pod, forcing a conscious decision (drain with
# --disable-eviction, or scale down first) instead of yanking the live DB.
# It does NOT block involuntary disruptions (node crash, OOM).
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: gitea
namespace: gitea
labels:
app.kubernetes.io/name: gitea
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: gitea
+3 -1
View File
@@ -4,7 +4,9 @@ metadata:
name: gf-pg
namespace: monitoring
spec:
instances: 1
instances: 2
smartShutdownTimeout: 10
stopDelay: 60
imageName: ghcr.io/cloudnative-pg/postgresql:16
storage:
storageClass: ceph-block
@@ -30,7 +30,9 @@ metadata:
name: ha-pg
namespace: home-assistant
spec:
instances: 1
instances: 2
smartShutdownTimeout: 10
stopDelay: 60
imageName: ghcr.io/cloudnative-pg/postgresql:16
storage:
storageClass: ceph-block
@@ -124,8 +126,7 @@ spec:
# padding rather than data.
#
# The cost is RPO: up to 30min of WAL is unrecoverable if this cluster and
# its volume are lost outright. This is a single-instance cluster, so the
# archive is the only off-node copy.
# its volumes are lost outright.
archive_timeout: "30min"
# Fewer checkpoints => fewer full-page writes, since a page is only
# fully-logged on its first modification after a checkpoint.
+1 -2
View File
@@ -59,8 +59,7 @@ spec:
# padding rather than data.
#
# The cost is RPO: up to 30min of WAL is unrecoverable if this cluster and
# its volume are lost outright. This is a single-instance cluster, so the
# archive is the only off-node copy.
# its volumes are lost outright.
archive_timeout: "30min"
# Write-reduction tuning. These databases are largely idle, and the
# Postgres defaults favour throughput over storage that wears out.
@@ -61,6 +61,9 @@ spec:
enabled: true
storageClass: ceph-block
size: 50Gi
# A minAvailable:1 PDB on a single replica blocks every node drain.
pdb:
create: false
# Bootstrap admin/org/bucket on the fresh PVC. Admin password + token come
# from the influxdb-bootstrap Secret (OpenBao via ESO), deterministic across
# rebuilds so the bootstrap token is the live admin token. `influx setup`
@@ -57,10 +57,14 @@ spec:
# the ClusterSecretStore goes InvalidProviderConfig and no
# ExternalSecret can refresh.
#
# No window: below quorum the secret store is down and every
# ExternalSecret stalls with it, so waiting only delays the page.
# Short window: readiness is only a proxy for quorum. A one-second
# node NotReady flap makes the node lifecycle controller mark that
# node's pods not-ready for ~30s while raft keeps its leader and
# serves (issue #920, during a rolling reboot with one replica
# already evicted). A really sealed or crashed majority persists,
# and existing Secrets keep their last values, so 2m costs little.
expr: max by (namespace, statefulset) (kube_statefulset_status_replicas_ready{namespace="openbao", statefulset="openbao"}) < 2
for: 0m
for: 2m
labels:
severity: critical
annotations:
@@ -304,6 +304,10 @@ spec:
- matchers:
- alertname = "KubeCPUOvercommit"
receiver: 'null'
# Helper alert that only drives the info inhibit rule below.
- matchers:
- alertname = "InfoInhibitor"
receiver: 'null'
# Flux notification-controller posts info-severity events
# here (e.g. image-automation commits). Keep them visible
# but don't repeat-spam every 12h on the same event.
@@ -313,6 +317,23 @@ spec:
group_wait: 1m
group_interval: 10m
repeat_interval: 24h
# From kube-prometheus manifests/alertmanager-secret.yaml.
inhibit_rules:
- source_matchers:
- severity = "critical"
target_matchers:
- severity =~ "warning|info"
equal: ['namespace', 'alertname']
- source_matchers:
- severity = "warning"
target_matchers:
- severity = "info"
equal: ['namespace', 'alertname']
- source_matchers:
- alertname = "InfoInhibitor"
target_matchers:
- severity = "info"
equal: ['namespace']
receivers:
- name: email
email_configs:
@@ -11,7 +11,6 @@ resources:
- protonmail-bridge.yaml
- ftp-scanner.yaml
- syncthing.yaml
- pdb.yaml
- network-policy.yaml
# pprl-pg CNPG Postgres + data/media/consume/redis PVCs (moved here from the
# retired seed-paperless KS; each carries prune: disabled so ownership
-37
View File
@@ -1,37 +0,0 @@
---
# Eviction protection for the single-replica stateful paperless sidecars.
#
# protonmail-bridge and syncthing each hold state on a single RWO PVC and run
# replicas:1. minAvailable:1 makes the eviction API refuse voluntary evictions,
# so a node drain blocks and surfaces these pods instead of evicting them
# abruptly. Involuntary disruptions (node crash) are not affected.
#
# NOTE: the other single-replica paperless pods (paperless, redis, gotenberg,
# tika, ftp-scanner) are intentionally omitted — they are caches / stateless
# converters where a blocking PDB would only wedge node drains with no
# recoverability benefit.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: protonmail-bridge
namespace: paperless
labels:
app.kubernetes.io/name: protonmail-bridge
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: protonmail-bridge
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: syncthing
namespace: paperless
labels:
app.kubernetes.io/name: syncthing
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: syncthing
+2 -3
View File
@@ -4,7 +4,7 @@ metadata:
name: tr-pg
namespace: trmnl
spec:
instances: 1
instances: 2
smartShutdownTimeout: 10
stopDelay: 60
# PG 18, NOT the repo-standard 16. Terminus ships a committed
@@ -47,8 +47,7 @@ spec:
# padding rather than data.
#
# The cost is RPO: up to 30min of WAL is unrecoverable if this cluster and
# its volume are lost outright. This is a single-instance cluster, so the
# archive is the only off-node copy.
# its volumes are lost outright.
archive_timeout: "30min"
# Write-reduction tuning. These databases are largely idle, and the
# Postgres defaults favour throughput over storage that wears out.
+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:
+130
View File
@@ -0,0 +1,130 @@
---
# 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
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
+38
View File
@@ -0,0 +1,38 @@
---
# wiki.renner.uno — LAN-only (no dns.renner.uno/public label). Traefik BasicAuth
# with the wiki-index credential until the Authentik wiki-gate outpost takes over.
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: wiki-basic-auth
namespace: ai
spec:
basicAuth:
secret: wiki-index-basic-auth
realm: wiki
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wiki
namespace: ai
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
traefik.ingress.kubernetes.io/router.middlewares: ai-wiki-basic-auth@kubernetescrd
spec:
ingressClassName: traefik
tls:
- hosts:
- wiki.renner.uno
secretName: wiki-tls
rules:
- host: wiki.renner.uno
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: wiki
port:
number: 8080
+6
View File
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- externalsecrets.yaml
- deployment.yaml
- ingress.yaml
+4 -3
View File
@@ -22,7 +22,9 @@ metadata:
# migration deliberately withheld this while a rollback still existed.
kustomize.toolkit.fluxcd.io/prune: disabled
spec:
instances: 1
instances: 2
smartShutdownTimeout: 10
stopDelay: 60
imageName: ghcr.io/cloudnative-pg/postgresql:16
storage:
storageClass: ceph-block
@@ -91,8 +93,7 @@ spec:
# padding rather than data.
#
# The cost is RPO: up to 30min of WAL is unrecoverable if this cluster and
# its volume are lost outright. This is a single-instance cluster, so the
# archive is the only off-node copy.
# its volumes are lost outright.
archive_timeout: "30min"
resources:
requests:
@@ -3,7 +3,6 @@ kind: Kustomization
resources:
- rbac.yaml
- server.yaml
- pdb.yaml
- agent.yaml
- service.yaml
- ingress.yaml
@@ -22,7 +21,7 @@ resources:
# OpenBao access, no NetworkPolicy needed. server.yaml/agent.yaml keep their
# Reloader annotations; externalsecret.yaml keeps the 10m refreshInterval.
# --- namespace migration, stages 1-2 (issue #433) ---
# Stage 2 has moved the workloads above (server, agent, service, ingress, pdb,
# Stage 2 has moved the workloads above (server, agent, service, ingress,
# rbac, secrets, policies) into the `woodpecker` namespace and pointed the
# server's DSN at wp-pg-rw.woodpecker.svc. Stage 3 retired the `apps`
# originals; these four now describe the surviving cluster. History and the
-27
View File
@@ -1,27 +0,0 @@
---
# Eviction protection for the single-replica Woodpecker server StatefulSet.
#
# The server is SQLite-backed (single writer) on a single RWO PVC, so it is
# deliberately replicas:1. minAvailable:1 makes the eviction API refuse
# voluntary evictions so a node drain blocks and surfaces this pod rather
# than yanking the live CI DB mid-write. Involuntary disruptions are not
# affected.
#
# The selector MUST include app.kubernetes.io/component: server: the agent
# Deployment in this same namespace shares app.kubernetes.io/name: woodpecker
# (component: agent), and a name-only selector would wrongly also cover the
# stateless, disposable agents.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: woodpecker-server
namespace: woodpecker
labels:
app.kubernetes.io/name: woodpecker
app.kubernetes.io/component: server
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: woodpecker
app.kubernetes.io/component: server
+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
@@ -1,11 +1,16 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
kind: OCIRepository
metadata:
name: k8s-gateway
namespace: kube-system
spec:
interval: 24h
url: https://k8s-gateway.github.io/k8s_gateway/
url: oci://codeberg.org/k8s-gateway/charts/k8s-gateway
ref:
semver: ">=2.4.0"
layerSelector:
mediaType: "application/vnd.cncf.helm.chart.content.v1.tar+gzip"
operation: copy
---
# In-cluster authoritative resolver for renner.uno.
#
@@ -28,14 +33,10 @@ metadata:
namespace: kube-system
spec:
interval: 30m
chart:
spec:
chart: k8s-gateway
version: ">=2.4.0"
sourceRef:
kind: HelmRepository
name: k8s-gateway
namespace: kube-system
chartRef:
kind: OCIRepository
name: k8s-gateway
namespace: kube-system
install:
remediation:
retries: 3
@@ -149,6 +149,30 @@ spec:
- port: "8080"
protocol: TCP
---
# wiki: Traefik (LAN clients) and Gitea (push webhook) may reach it. 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": ingress
app.kubernetes.io/name: traefik
- 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.
@@ -21,17 +21,29 @@ spec:
modules:
- name: pg_autoscaler
enabled: true
# Rotates daemon keys to aes256k for CVE-2025-30156 (#527). Opt-in; disabled
# by default. Increment keyGeneration to rotate again.
#
# Daemon keys only — aes256k over the CSI mount path needs kernel 7.0+ and
# these nodes run 6.18, so CSI stays on aes and the client-key checks stay
# raised. keyType is unset so Rook picks it.
# CephX keys on aes256k for CVE-2025-30156 (#527). Increment keyGeneration
# to rotate again. CSI kernel mounts need aes256k in libceph: Talos
# >= v1.13.10 backports it to 6.18 (siderolabs/pkgs 2da2cdc).
security:
cephx:
# auth_allowed_ciphers. Every key must be aes256k first, or the cluster
# locks itself out; recovery is in Rook's cephx-key-rotation doc.
allowedCiphers:
- aes256k
daemon:
keyRotationPolicy: KeyGeneration
keyGeneration: 2
csi:
keyRotationPolicy: KeyGeneration
keyGeneration: 2
keyType: aes256k
# 0 deletes prior keys. Set 1 before the next rotation so PVCs
# mounted with the old key keep working until they are remounted.
keepPriorKeyCountMax: 0
rbdMirrorPeer:
keyRotationPolicy: KeyGeneration
keyGeneration: 2
keyType: aes256k
# CephFS cloner pool raised from the default 4. Velero clones a
# VolumeSnapshot per PVC, and 4 threads stall a multi-PVC backup with
# `rados: "all cloner threads are busy"`.
+1 -1
View File
@@ -24,7 +24,7 @@ machine:
# The Kubernetes version is a separate decision, moved by
# `talosctl upgrade-k8s`.
install:
image: factory.talos.dev/metal-installer/0f6fb8bd8bb53013e9743b7c7b099d96fb9c32fd2464ced32693c2dc0ea5feb9:v1.13.9
image: factory.talos.dev/metal-installer/0f6fb8bd8bb53013e9743b7c7b099d96fb9c32fd2464ced32693c2dc0ea5feb9:v1.13.10
features:
rbac: true
# stableHostname is NOT here: it is the v1alpha1 spelling of what the