Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68e71d7e52 | ||
|
|
17bf12cc4f | ||
|
|
1fc8073218 | ||
|
|
161c04daff | ||
|
|
999e3fbac9 | ||
|
|
70208c42da | ||
|
|
94b35c3f64 | ||
|
|
5cbdb6e67f | ||
|
|
ee08edba3e | ||
|
|
96d15f19c8 | ||
|
|
0581fd461f | ||
|
|
f1024fa4b3 | ||
|
|
bd4553cc63 | ||
|
|
904475b89a | ||
|
|
c395a9fc67 | ||
|
|
bf3d58628e | ||
|
|
6edde49b48 | ||
|
|
94b6d77bac | ||
|
|
c7f6514e20 | ||
|
|
d865043eaa | ||
|
|
47fbc917fd |
@@ -1,11 +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/
|
||||
# (a `claude` run of the wiki-distill agent), then audit wiki/ with the vault's
|
||||
# own script (structural checks, no model). 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 agent and its retrieval scripts address the vault as ~/Notes, so the
|
||||
# distill 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
|
||||
@@ -29,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
|
||||
@@ -43,20 +66,25 @@ 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
|
||||
|
||||
@@ -67,6 +95,55 @@ steps:
|
||||
# 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:
|
||||
CLAUDE_CODE_OAUTH_TOKEN:
|
||||
from_secret: claude_code_oauth_token
|
||||
TYPESAFE_API_KEY:
|
||||
from_secret: typesafe_api_key
|
||||
WIKI_INDEX_BASIC_AUTH:
|
||||
from_secret: wiki_index_basic_auth
|
||||
commands:
|
||||
- |
|
||||
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"
|
||||
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
|
||||
# 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,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
|
||||
pull: true
|
||||
@@ -81,7 +158,11 @@ steps:
|
||||
git config user.name "Claude Bot"
|
||||
# Only the vault content is committed; anything an agent leaves elsewhere
|
||||
# in the checkout (run 10362: scratch scripts in the root) stays behind.
|
||||
if [ -z "$$(git status --porcelain -- wiki raw output)" ]; then
|
||||
# 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
|
||||
@@ -91,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 -- wiki raw output
|
||||
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")
|
||||
|
||||
@@ -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.
|
||||
@@ -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:*)",
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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"`.
|
||||
|
||||
@@ -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
|
||||
@@ -57,6 +57,12 @@ machine:
|
||||
extraConfig:
|
||||
imageGCHighThresholdPercent: 70
|
||||
imageGCLowThresholdPercent: 60
|
||||
# Default 110 leaves no N-1 headroom: with ~207 pods on three nodes, a
|
||||
# rolling drain puts the survivors at 105/110 and KubeletTooManyPods
|
||||
# fires (#937). The ceiling is IP space, not the kubelet: Cilium runs
|
||||
# ipam.mode=kubernetes on the node's /24 podCIDR (~253 usable after
|
||||
# cilium_host), so 200 keeps a margin for pods still terminating.
|
||||
maxPods: 200
|
||||
cluster:
|
||||
# etcd peers on node0, VLAN 46 (#576). Pinned: Talos never updates an
|
||||
# existing member's peer URLs, so changing this subnet means leave + rejoin
|
||||
|
||||
Reference in New Issue
Block a user