Completes the immich seed->apps move (same single-transfer pattern as paperless
#400). Restore-test passed first (im-pg backup restorable: 33316 rows in asset).
- git mv seed/immich/{cluster,objectstore,scheduled-backup,barman-creds-
externalsecret,hetzner-creds-externalsecret,pvcs}.yaml -> apps/immich/, add to
apps/immich/kustomization.yaml.
- immich KS: drop 'dependsOn: seed-immich', add
'dependsOn: infrastructure-cnpg-barman', add the CNPG Cluster healthCheckExprs.
- Delete clusters/homelab/seed-immich.yaml and the now-empty seed/immich/.
- e2e-test.sh: drop seed-immich, add immich to the app-KS readiness checks
(immich was previously only covered via seed-immich).
- Fix the last stale seed/immich comment path in
infrastructure/openbao/snapshot-pvc.yaml (seed/immich -> apps/immich).
Safe ownership transfer (nothing pruned): all 7 seed/immich objects carry
prune: disabled (5 CNPG from stage 1 #401, 2 PVCs pre-existing), so deleting
seed-immich skips them in GC and the immich KS adopts them in place. No CNPG
restart, no app restart.
Verified: kubectl kustomize apps/immich builds 17 objects incl. Cluster/im-pg,
exactly 7 prune-protected; bash -n e2e-test.sh OK; seed/immich fully removed.
Left intentionally: the hetzner-creds ExternalSecret still reads OpenBao key
seed/immich/... (vault path, not a repo path).
Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The restore-test drill in RECOVERY.md told you to omit spec.plugins from the
throwaway cluster ('NO spec.plugins / isWALArchiver here'). With the plugin-based
barman this homelab now uses (barmancloud.cnpg.io ObjectStore + the
barman-cloud.cloudnative-pg.io plugin), that is invalid: the instance manager
can't load the plugin it needs to replay WAL during restore and dies at startup
with 'Unknown plugin: barman-cloud.cloudnative-pg.io' — the cluster never becomes
healthy. A real cold-rebuild DR following this drill would have failed the same
way. Found + fixed by running the drill against im-pg on 2026-07-25 (restore
succeeded once the plugin was declared; 33316 rows in 'asset', 584 MB DB).
- Declare the barman plugin in spec.plugins with isWALArchiver: false — loaded so
it can READ WAL for the restore, but never archives (stays read-only against
the backup path). Update the intro prose to match.
- Verification: pg_stat_user_tables n_live_tup reads 0 right after a restore
(stats not gathered), so that view is not a data check — add pg_database_size
and note counting a real table instead.
- Teardown: the old 'reclaim the released PV afterwards' was too vague and leaks
the Retain PV's RBD image (issue #262). Give the explicit recipe: capture the
PV name, delete the cluster, then patch the released PV to Delete so the CSI
reclaims the RBD image.
The in-place cold-rebuild section (which keeps the live cluster's existing
spec.plugins isWALArchiver: true) was already correct and is unchanged.
Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses the 14aa6415 review.
- pick-model.sh: the picker relied on `--allowedTools ""` to be tool-less, but an
empirical check showed an empty allowlist does NOT block tools (the Write tool
still ran) -- it is treated as "unset" and the default toolset stays live. Add
`--permission-mode plan`, which IS verified to block Write while still producing
the selection JSON. --allowedTools "" + the Read deny-list remain as additional
layers. Corrected the comments in pick-model.sh and the picker settings.json
that claimed the empty allowlist made it tool-less.
- .woodpecker/ci-scripts-test.yml: new pull_request-triggered pipeline (homelab
only) that runs scripts/ci/test-model-selection.sh, so a PR weakening the
injection gate fails checks immediately instead of only on the next scheduled
e2e cron (closes the coverage-timing gap the review flagged).
- Pin a recorded real /v1/models response (scripts/ci/testdata/v1-models-sample.json)
and assert fetch-models.sh maps it correctly (opus effort=true, haiku
effort=false), documenting the verified response shape.
Deferred with rationale: hard-failing a pipeline when the degraded-schema warning
fires -- the warning already reaches the job log (curl/jq stderr is no longer
suppressed), and hard-failing on a benign API schema addition would break all
model selection; graceful degradation to name+date is the deliberate choice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A 24-min WAN outage to netbird.renner.uno on 2026-07-24 exceeded the
10-min ephemeral-peer lifetime, so management deregistered all three
routing peers. The client treats 'peer is not registered' as
unrecoverable and parks in NeedsLogin; that fails readiness but not the
'--check live' liveness probe, so the pods sat Running-but-NotReady for
~20 h until they were deleted by hand.
- liveness probe now fails on the dead auth states (NeedsLogin /
LoginFailed / SessionExpired) and only on those: a restart re-runs the
entrypoint, which re-enrolls with NB_SETUP_KEY. Transient
Disconnected/Connecting states still retry silently, so a management
outage does not kill the working WireGuard data plane.
- new PrometheusRule: warning when the DaemonSet runs degraded >10m,
critical when 0 peers are Ready >5m.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pr-review triage agent runs with an unscoped Write tool and its cwd contains
./.pr-review (-> $D), so a prompt-injected diff could make it overwrite
./.pr-review/models.txt and then name a smuggled token as its `model`. Validation
re-read models.txt FROM DISK after the triage ran, so the catalog-membership
check was defeated by the agent's own writeback -- letting an attacker force a
weaker real model, or a syntactically-valid fake id that makes the review's
`claude --model <fake>` fail and silently suppress the review entirely.
Validate against $MODELS_TABLE -- the catalog captured in a shell variable BEFORE
the triage runs -- instead of re-reading the file the triage can overwrite. This
mirrors pick-model.sh, which already validates against its in-memory table before
its (tool-less) picker runs. The triage still reads models.txt for its own pick;
tampering only misleads that pick, which is then rejected against the pristine
snapshot -> CLI default (so the review still runs, it can't be suppressed).
Verified: a token present only in a tampered on-disk file but absent from the
pre-triage snapshot is now rejected (it was accepted before). Added a regression
test to scripts/ci/test-model-selection.sh, and fixed the header comment to note
the complex-review override as another path to the CLI default.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses the a7890e0a review.
- pr-review.yml: strip REVIEW_TOKEN from the valid-model.sh call too (it runs a
script from $HOMELAB, branch-supplied for a homelab PR), and guard it with
`|| true` so a missing script can't kill the step -- restoring the stated "a
run never dies on selection" invariant. For a complex/security-sensitive
review, ignore the (injectable) triage pick and run on the CLI default
(strongest configured) model.
- valid-model.sh: reject a leading dash at the shape check (making the header
accurate) so an option-like value is refused even without a catalog.
- fetch-models.sh: --retry 2 --retry-connrefused so a transient blip doesn't
drop the run to the default; add a FETCH_MODELS_BODY_FILE test seam.
- pick-model.sh: pass --settings (new ci/agents/model-picker/settings.json,
Read deny-list) as belt-and-suspenders behind --allowedTools "".
- test-model-selection.sh: add fetch-models.sh mapping tests (well-formed body
-> non-null capability columns; degraded body -> loud warning) via the seam,
and wire the whole suite into .woodpecker/e2e.yml so it runs in CI.
- Condense the duplicated model-selection comment in the four fixed jobs to a
one-line pointer to pick-model.sh.
- PR description: state the cost / no-fixed-ceiling property change.
Deferred with rationale (in the PR thread): selected-model in the four crons'
posted artifacts (already logged per run), pagination past limit=1000 (API max;
has_more warns), and unit-testing pick-model.sh's JSON extraction (its output
always passes through the tested valid-model.sh gate, and the picker prompt is
developer-controlled, not attacker input).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stage 1 of folding seed-immich into the existing dedicated immich KS (same
pattern as paperless #399/#400). Adds prune: disabled, in place, to the 5 CNPG
DB objects under seed/immich/ so the ownership handoff can never prune them:
- Cluster/im-pg (pruning deletes its data PVCs)
- ObjectStore/im-pg-store, ScheduledBackup/im-pg-daily
- ExternalSecret/barman-cloud-creds, ExternalSecret/hetzner-s3-creds
The immich-library (300Gi) + ml-cache PVCs already carry the annotation.
Metadata-only; no CNPG restart. im-pg has 30 completed barman backups (last
~2.5h) as a backstop.
Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses the remaining pr-review items.
- Extract the untrusted-model validation out of the pipeline YAML into
scripts/ci/valid-model.sh (single safe-token + catalog-membership gate) and
reuse it from both pick-model.sh and pr-review.yml, so the security-critical
logic lives in one tested place. Add scripts/ci/test-model-selection.sh, an
offline fixture test (no network / claude / Woodpecker) that pins the
behaviour: valid ids pass; hallucinated, empty, space-, newline-, leading-
dash-, and metachar-injected values are rejected; empty catalog rejects all.
This is the regression test for the flag-injection fix.
- pr-review triage cost: hand the triage the COMPLETE changed-file list
(files.txt) plus a diff capped at 4000 lines (diff.triage.patch) instead of
the full up-to-20k review diff, so classifying a PR on the default model stays
cheap. stats.txt still carries the true totals; triage-prompt.md updated.
- Doc nits: note the new helpers are POSIX `sh` (not `bash` like the older
scripts/ci/*.sh), and that fetch-models.sh's json mode is a debug affordance.
Deferred with rationale (also in the PR thread):
- Model id in the four cron jobs' posted artifacts: selection (including the
fallback-to-default) is already logged per run in the CI output, the
operational surface for this; threading it through four different posted
formats is a separate observability follow-up.
- --settings sandbox on the picker: it is granted no tools (--allowedTools ""),
so there is nothing for a Read-deny sandbox to constrain -- the other agents'
sandboxes exist to scope tools they DO have.
- Pagination past limit=1000: that is the API's max page size; >1000 models is
not a real scenario, and the has_more warning turns even that into a visible
log line rather than silent truncation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the paperless seed->apps move. Unlike woodpecker this is a single
ownership transfer into the app's existing dedicated KS.
- git mv seed/paperless/{cluster,objectstore,scheduled-backup,barman-creds-
externalsecret,hetzner-creds-externalsecret,pvcs}.yaml -> apps/paperless/, and
add them to apps/paperless/kustomization.yaml.
- paperless KS: drop 'dependsOn: seed-paperless', add
'dependsOn: infrastructure-cnpg-barman', add the CNPG Cluster healthCheckExprs
(moved off seed-paperless), refresh the stale 'seed phase' comment.
- Delete clusters/homelab/seed-paperless.yaml and the now-empty
kubernetes/seed/paperless/.
- e2e-test.sh: drop the seed-paperless readiness check (the paperless app KS is
already checked).
- Fix two stale file-path comments that pointed at the old location:
apps/woodpecker/objectstore.yaml and infrastructure/openbao/snapshot-pvc.yaml
(seed/paperless -> apps/paperless).
Safe ownership transfer (nothing pruned): all 9 seed/paperless objects carry
prune: disabled (5 CNPG objects from stage 1 #399, 4 PVCs pre-existing), so
deleting seed-paperless skips them in GC and the paperless KS adopts them in
place. No CNPG restart (Cluster is not a Deployment), no app restart.
Verified: kubectl kustomize apps/paperless builds 40 objects incl. Cluster/
pprl-pg, exactly 9 prune-protected; bash -n e2e-test.sh OK; seed/paperless fully
removed; no stale file-path refs remain (only the dated migration log + the
OpenBao KV path pending the separate hetzner-creds decoupling).
Left intentionally: the hetzner-creds ExternalSecret still reads OpenBao key
seed/paperless/hetzner-creds (a vault path, not a repo path).
Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stage 1 of folding seed-paperless into the existing dedicated paperless KS.
Adds kustomize.toolkit.fluxcd.io/prune: disabled, in place, to the five CNPG DB
objects under seed/paperless/ so the ownership handoff can never prune them:
- Cluster/pprl-pg (HA, 2 instances — pruning deletes its data PVCs)
- ObjectStore/pprl-pg-store, ScheduledBackup/pprl-pg-daily
- ExternalSecret/barman-cloud-creds, ExternalSecret/hetzner-s3-creds
The 4 data/media/consume/redis PVCs already carry the annotation. Landing it via
the existing seed-paperless KS guarantees it's live before the move. Metadata-
only; no spec change, so CNPG and the running pprl-pg instances are untouched.
Unlike woodpecker this is a single ownership transfer (seed-paperless ->
paperless KS, both already dedicated), so no app-object commonAnnotations / CI
restart is involved. pprl-pg has healthy barman backups (last ~2.7h) as a
backstop.
Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pr-review triage agent reads the attacker-controlled PR diff and writes
triage.json; its Write tool is not path-scoped, so a prompt-injected diff can
make it emit any `model` string. The catalog validation used
`grep -qxF -- "$MODEL"`, and grep -F treats a MULTI-LINE operand as
newline-separated patterns: a value like
"claude-sonnet-5\n--dangerously-skip-permissions" matches on its first line,
passes validation, and -- via the unquoted `${MODEL:+--model $MODEL}` expansion
-- word-splits into an extra argv entry, injecting a CLI flag into the review
agent's own `claude` invocation (defeating its --allowedTools allowlist and Read
sandbox, with CLAUDE_CODE_OAUTH_TOKEN in that process's env).
Removing map_model() (which returned one of three literal ids) is what opened
this; restore the invariant with a single-token guard before the expansion:
`case "$MODEL" in ""|*[!A-Za-z0-9._-]*) MODEL= ;; esac`. Verified it turns the
newline/space/hallucinated payloads into a plain `claude -p` (CLI default) while
still passing real ids. Mirrored the guard in pick-model.sh as defence in depth
(its grep -oE already keeps CHOICE single-line, so it was not exploitable).
Also from the review:
- pr-review.yml: correct the HOMELAB comment -- the fetch-models.sh call is
env -u REVIEW_TOKEN'd, so CLAUDE_CODE_OAUTH_TOKEN (not REVIEW_TOKEN) is what
branch-supplied shell there can see.
- pick-model.sh / triage-prompt.md: stop asking the picker to weigh "cost" the
catalog does not expose; reason from the visible capability signals (context /
max_output / effort|thinking|code_exec) as the scale proxy instead.
- fetch-models.sh: drop 2>/dev/null on curl so its -S error reaches the log.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Final stage of the seed-woodpecker -> dedicated woodpecker KS move. The
commonAnnotations prune:disabled guard on apps/woodpecker/ has done its job: the
ownership handoff completed (verified — wp-pg + all app objects adopted by the
woodpecker KS, RESTARTS=0, nothing pruned), so the app objects no longer need
blanket prune protection and should be normally prunable by their owning KS again.
Removes only the commonAnnotations block. The 4 CNPG DB objects keep their
per-file prune: disabled (permanent data-guard on Cluster/wp-pg etc.) — verified
kubectl kustomize apps/woodpecker now annotates exactly those 4, not the 13 app
objects.
Rolls woodpecker-server + agent once more as the annotation leaves their pod
templates — the same accepted CI restart as stage 1.
Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- pick-model.sh: stop swallowing fetch-models.sh and claude stderr so the real
cause of a fallback (no token / HTTP 401 / parse error / auth) reaches the job
log instead of just "no catalog available"; time-bound the selection call with
a self-probing `timeout` (the wrapper is skipped unless a probe confirms the
image's timeout takes coreutils "SECS CMD" syntax, so it can never silently
route every run to the default); strip PUSH_TOKEN and REVIEW_TOKEN as well --
issue-fix binds its bot PAT a second time as PUSH_TOKEN, which survived the
original `env -u GITEA_TOKEN`; `grep -qxF --`.
- fetch-models.sh: warn loudly when every model returns a null context window,
so a /v1/models schema change surfaces as a message rather than silently
reducing both selectors to name+date guessing.
- pr-review.yml: `env -u REVIEW_TOKEN` on the fetch-models.sh call (it needs only
CLAUDE_CODE_OAUTH_TOKEN), matching the agent calls below it; `grep -qxF --`.
Deferred with rationale: hard-bounding the triage's diff input (kept as prompt
guidance -- a hard cap risks its security-sensitivity judgment) and an offline
fallback-path test harness (tracked as a follow-up).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- fetch-models.sh: request ?limit=1000 so older/cheaper models are not dropped
by the API's 20-item default page; warn on has_more. Add a --table mode so
the catalog format lives in ONE place (pick-model.sh and pr-review.yml now
consume it instead of each re-deriving the jq table). Clean up the HTTP-000
double-print and surface jq parse errors instead of hiding them.
- pick-model.sh: run the picker with GITEA_TOKEN stripped from its env and
without acceptEdits (it is granted no tools); consume fetch-models.sh --table.
- pr-review.yml: print model `CLI default` instead of an empty backtick pair on
the fallback path; document that the fetch-models.sh call runs branch-supplied
shell (not a new exposure -- Woodpecker already runs the pipeline YAML from
the PR branch).
- triage-prompt.md: read stats + the changed-file list first and pull the full
diff only when needed, so the triage (now on a capable default model) does not
read a 20k-line diff just to classify a trivial PR.
Verified against the live catalog: all 11 currently-listed ids run under
`claude --model`, and the capability fields (context/effort/thinking/...) are
present in the OAuth /v1/models response -- so the reviewer's blocking concern
(a listed-but-CLI-unusable id) has no live trigger, and the fallback-to-default
path still covers it if that ever changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restructures woodpecker into its own Flux Kustomization instead of splitting its
app across the catch-all apps KS and its DB across seed-woodpecker.
- Add clusters/homelab/woodpecker.yaml: dedicated KS over apps/woodpecker with
the infrastructure-cnpg-barman dep + CNPG Cluster healthCheckExprs (moved off
seed-woodpecker) so the CNPG concerns live here, not in the shared apps KS.
- Remove woodpecker/ from apps/kustomization.yaml (catch-all apps KS no longer
owns the woodpecker app objects).
- git mv seed/woodpecker/{cluster,objectstore,scheduled-backup,
hetzner-creds-externalsecret}.yaml + MIGRATION.md -> apps/woodpecker/, and add
the four DB manifests to apps/woodpecker/kustomization.yaml.
- Delete clusters/homelab/seed-woodpecker.yaml and the now-empty
kubernetes/seed/woodpecker/.
- bootstrap.sh: add a wait_kustomization woodpecker entry (it used to be covered
by 'wait apps'); e2e-test.sh: add woodpecker to the KS readiness checks.
Safe dual ownership transfer (nothing pruned): every object carries prune:
disabled from stage 1 (#394) — the app objects via commonAnnotations, the DB
objects per-file — so when the apps KS drops woodpecker/ and seed-woodpecker is
deleted, both GC sweeps skip these live objects and the new woodpecker KS adopts
them in place. No CNPG restart (Cluster is not a Deployment); no app restart
(manifests unchanged, only the owning KS label flips).
Verified: kubectl kustomize apps/woodpecker builds 17 objects (13 app + 4 DB
incl. wp-pg Cluster), all prune-protected; bash -n bootstrap.sh + e2e-test.sh
OK; wp-pg is the sole CNPG cluster in apps ns and hetzner-s3-creds is used only
by wp-pg-store; seed/woodpecker fully removed.
Stage 3 will drop the transient commonAnnotations (one more accepted app roll).
Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pr-review, update-audit, security-scan, alert-monitor and issue-fix
pipelines each pinned a dated model id (--model claude-sonnet-5, plus
pr-review's haiku/sonnet/opus map_model allowlist). Those go stale every
time a new model ships and bake in assumptions about which model families
exist, so a newly released model could never be chosen without a code edit.
Replace all of it with selection driven only by what is available right now:
- scripts/ci/fetch-models.sh queries the catalog the CLI itself uses
(Anthropic GET /v1/models, via the existing CLAUDE_CODE_OAUTH_TOKEN and the
required anthropic-beta: oauth-2025-04-20 header) and emits each model with
its objective capability signals -- context, max_output, effort, thinking,
code_exec, release date -- with no id patterns and no family names.
- scripts/ci/pick-model.sh lets a triage agent choose the best-fit id from
that catalog for a one-line task description, weighing every listed model
on its merits. pr-review's existing per-PR triage agent now does the same,
reading the live catalog instead of emitting a fixed haiku/sonnet/opus tier.
No model id -- and no model family -- is baked into any pipeline. A model
released tomorrow, of any family, becomes eligible the day the API lists it.
The bootstrap selection call runs on the CLI default model, and if the catalog
is ever unreachable --model is simply omitted (CLI default) rather than falling
back to a named model, so a run never dies over model selection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Folds the app-object protection into stage 1 now that a woodpecker CI restart is
acceptable. commonAnnotations adds kustomize.toolkit.fluxcd.io/prune: disabled to
every object under apps/woodpecker/ (the woodpecker-ci Namespace, server/agent
Deployments, Service, Ingress, PDB, NetworkPolicy, ResourceQuota/LimitRange,
RBAC, ExternalSecret), so when 'woodpecker/' is later removed from
apps/kustomization.yaml the catch-all apps KS can't prune them mid-handoff.
Trade-off accepted: kustomize propagates commonAnnotations into the two
Deployments' pod templates, so this rolls woodpecker-server + agent once on
apply (and once more when removed in the final stage). Confirmed harmless — the
DB (wp-pg) is a separate CNPG resource, untouched.
Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stage 1 of moving woodpecker off seed-woodpecker into a dedicated woodpecker KS.
Adds kustomize.toolkit.fluxcd.io/prune: disabled, in place, to the four
data-bearing DB objects in seed/woodpecker/ so the later ownership handoff can
never prune them:
- Cluster/wp-pg (pruning deletes the DB's data PVCs)
- ObjectStore/wp-pg-store (barman backup target)
- ScheduledBackup/wp-pg-daily
- ExternalSecret/hetzner-s3-creds (barman S3 creds)
Landing the annotation via the existing seed-woodpecker KS guarantees it is live
on the objects before any move. Metadata-only; no spec change, so CNPG/the
running wp-pg pod are untouched. The Cluster keeps the annotation permanently as
a data-guard; the others are incidental to the same handoff.
The woodpecker *app* objects (server/agent/Namespace/etc., currently under the
catch-all apps KS) are protected separately in the next stage — they need
per-object top-level annotations rather than commonAnnotations, which would
propagate into the Deployments' pod templates and needlessly restart live CI.
Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses the follow-up review on #390: three more comments still described the
removed influxdb-seed Job, some in the present tense (contradicting the note the
prior commit added).
- helmrelease.yaml adminUser comment: drop the 'sibling influxdb-seed Job reads
the secret / deletes the bucket' description; state the admin token comes from
the influxdb-bootstrap Secret (OpenBao via ESO).
- helmrelease.yaml resource/timeout rationale: reword 'after a partial seed' and
'batches from the seed Job' to the seed-independent conditions (large dataset /
large write batches) that still justify the 6Gi limit + 60s write timeout.
- ha-token-job.yaml: drop 'Same wait pattern as the seed Job's wait-for-influxd';
describe the two-stage wait directly.
Verified: kubectl kustomize apps/influxdb builds; the only remaining 'seed'
strings are accurate migration-rationale comments and the OpenBao KV path.
Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Without the suffix the app resolves devices only via its 2-minute mDNS
cache; the authoritative-DNS leg of MdnsConnectCallback is disabled and
the presence sweep falsely marks online always-on devices missing
(5 devices confirmed online but flagged, 2026-07-24).
Fixes#389
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addresses the doc-only suggestion on #390: explain in the new influxdb KS that
dropping seed-influxdb's infrastructure-cnpg-barman dependency is intentional
(copy-paste boilerplate; InfluxDB has no CNPG cluster, only a ceph-block PVC).
Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Step 2 of the InfluxDB seed->apps handoff. #388 already landed
prune: disabled on the live HelmRelease/HelmRepository/ExternalSecret, so
retiring seed-influxdb here cannot trigger a helm uninstall — GC skips the
annotated objects and the new influxdb KS adopts them in place.
- git mv kubernetes/seed/influxdb/ -> kubernetes/apps/influxdb/ (annotations
from #388 travel with the files).
- Add kubernetes/clusters/homelab/influxdb.yaml (KS 'influxdb', path
apps/influxdb, deps infrastructure/crds/ceph). Delete seed-influxdb.yaml.
Review fixes from the closed atomic PR (#387):
- bootstrap.sh: wait on 'influxdb' instead of the deleted 'seed-influxdb'
(would otherwise spin the full 600s timeout on cold bootstrap).
- e2e-test.sh: swap the seed-influxdb readiness check for 'influxdb'.
- Fix stale comment paths kubernetes/seed/influxdb/... -> apps/influxdb/... in
network-policies/home-assistant-ns.yaml and apps/monitoring/
helmrelease-prometheus.yaml.
- Trim the helmrelease.yaml comment that described the removed seed-job.yaml,
and the ha-token-job.yaml 'at cutover moves to apps/influxdb' note (this is
that cutover).
Left intentionally: externalsecret.yaml key 'seed/influxdb/bootstrap' is an
OpenBao KV path, not a repo path — moving it needs a vault-side secret
migration, out of scope here.
Verified: kubectl kustomize apps/influxdb builds all objects with the right
prune/force annotations; bash -n passes on bootstrap.sh + e2e-test.sh; no
remaining seed-influxdb references in the repo (only the OpenBao KV path).
Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Step 1 of a two-step seed->apps handoff for InfluxDB (the move itself follows
in a separate PR). Adds kustomize.toolkit.fluxcd.io/prune: disabled to the
HelmRelease, HelmRepository, and ExternalSecret *in place* under
kubernetes/seed/influxdb/, with no move and no KS changes.
Why split: doing the annotate + move + delete-old-KS in one commit races the
new KS's first reconcile (which stamps the annotation onto the live objects)
against the old KS's deletion-GC. If GC wins, it prunes the un-annotated
HelmRelease -> helm uninstall -> the running InfluxDB StatefulSet is torn down.
Landing the annotation via the existing seed-influxdb KS first guarantees it is
live on the objects before any prune can run, per Flux's documented pattern.
No behavioural change: only a kustomize metadata annotation is added, so
helm-controller performs no upgrade and the server is untouched. After merge,
verify the annotation is live on all three objects, then land the move PR.
Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Second step of dissolving the transitional 'seed' layer (after
music-assistant). Move the audiobookshelf config/metadata/books PVCs from
seed-audiobookshelf into the app's own Kustomization and retire the seed KS.
- git mv kubernetes/seed/audiobookshelf/pvcs.yaml -> kubernetes/apps/audiobookshelf/
and add it to the app kustomization.
- Drop 'dependsOn: seed-audiobookshelf' from the audiobookshelf KS (it now owns
the 3 PVCs directly; ceph dep already present, no CNPG involved).
- Delete kubernetes/clusters/homelab/seed-audiobookshelf.yaml and the now-empty
kubernetes/seed/audiobookshelf/.
- Remove seed-audiobookshelf from the e2e-test.sh Kustomization readiness checks
(it no longer exists post-merge) — same fix the review flagged on the
music-assistant PR, done proactively here.
Safe ownership transfer: all 3 PVCs carry kustomize.toolkit.fluxcd.io/prune:
disabled, so Flux GC skips them when seed-audiobookshelf is pruned; the
audiobookshelf KS adopts them on reconcile. Verified: PVCs Bound + prune-disabled
live; kubectl kustomize apps/audiobookshelf builds all 3.
Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses review on #385.
Blocking: e2e-test.sh asserted kustomization/seed-music-assistant Ready, but
this PR deletes that KS — the test would fail post-merge with 'not found'.
Remove it from the checked list (line 110).
Also add a clarifying note in MIGRATION-sops-openbao.md that
seed-music-assistant has since been retired (the 2026-07-14 log line is left
factually intact as a historical record).
Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First step of dissolving the transitional 'seed' layer: move the
music-assistant-data PVC from seed-music-assistant into the app's own
Kustomization and retire the seed KS.
- git mv kubernetes/seed/music-assistant/pvcs.yaml -> kubernetes/apps/music-assistant/
and add it to the app kustomization.
- Drop 'dependsOn: seed-music-assistant' from the music-assistant KS (it now
owns the PVC directly; ceph dep already present, no CNPG involved).
- Delete kubernetes/clusters/homelab/seed-music-assistant.yaml and the now-empty
kubernetes/seed/music-assistant/.
Safe ownership transfer: the PVC already carries
kustomize.toolkit.fluxcd.io/prune: disabled, so Flux GC skips it even when the
seed-music-assistant KS is pruned — the object is never torn down, and the
music-assistant KS adopts it on next reconcile. Verified: PVC is Bound and
prune-disabled live; kubectl kustomize apps/music-assistant builds with the PVC.
Pilot for the per-app seed->apps relocation; remaining apps (audiobookshelf,
influxdb, woodpecker, paperless, immich, authentik) follow the same pattern.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The monolithic `Kustomization/seed` was split into per-app `seed-<app>`
KSes; it was left suspended + prune-disabled only so its inventory could
be pruned by a follow-up commit. That inventory is now empty (ownership
fully migrated to the per-app KSes), so the object owns nothing and can
be removed cleanly — flux-system (prune: true) will garbage-collect the
now-absent Kustomization/seed, and its own prune: false + empty inventory
means zero live resources are torn down.
Also drop kubernetes/seed/kustomization.yaml, the aggregator that only the
monolith referenced (path ./kubernetes/seed); it was already stale (missing
woodpecker). The per-app KSes target ./kubernetes/seed/<app> subdirs and are
unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gitea 1.21.4 logs a deprecation warning for [server] LFS_CONTENT_PATH
(superseded by [lfs] PATH) and recommends DISABLE_QUERY_AUTH_TOKEN
(default from 1.23). Both surface as startup notices on the admin
dashboard.
Add the replacements as GITEA__ env overrides so they are tracked in
git. environment-to-ini can only add keys, so the stale
[server] LFS_CONTENT_PATH was also removed from the on-PVC app.ini by
hand (same path value -> existing data/lfs objects untouched).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>