Files
homelab/docs/repository-review.md
T
forust 5c8bc15e60
renovate-ci / validate-renovate (push) Skipped
ci / lint-compose (push) Successful in 10s
ci / lint-actionlint (push) Successful in 7s
ci / lint-shellcheck (push) Successful in 9s
ci / lint-prettier (push) Successful in 19s
ci / lint-ruff (push) Successful in 7s
ci / lint-yaml (push) Successful in 10s
ci / lint-dockerfiles (push) Successful in 6s
ci / validate (push) Successful in 6s
ci / build (push) Skipped
ci / lint-compose (pull_request) Successful in 10s
ci / lint-actionlint (pull_request) Successful in 5s
ci / lint-shellcheck (pull_request) Successful in 8s
ci / lint-prettier (pull_request) Successful in 16s
ci / lint-ruff (pull_request) Successful in 7s
ci / lint-yaml (pull_request) Successful in 10s
ci / lint-dockerfiles (pull_request) Successful in 7s
ci / validate (pull_request) Successful in 7s
ci / build (pull_request) Skipped
renovate-ci / validate-renovate (pull_request) Successful in 9s
docs(reloader): describe workload opt-in and reload policy
2026-10-06 16:14:04 +02:00

12 KiB

Repository review

Reviewed the tracked tree at cc9c3de and read the live workstation state on 6 October 2026. Changes are split into documentation and individual fix branches, all based on that main commit. The original local checkout and its uncommitted monitoring changes were preserved. No deployment was performed.

Confirmed problems with prepared fixes

Priority Problem and consequence Fix branch
High APPLY_PRUNE=true is passed to each individual manifest apply. Each invocation sees only that file's desired objects and can delete other resources selected by the shared label. fix/deploy-prune-guard
High Deploy validates Compose with interpolation and env/path resolution disabled. Required settings can pass validation and then fail during apply after other workloads have changed. fix/deploy-validation
Medium Secret validation is text-based and compares names across all namespaces. A Secret elsewhere can hide a missing local Secret; mounted Secrets are also missed. fix/deploy-validation
Medium Compose CI misses postgres/shared-compose.yaml, netbird/client.compose.yaml, and renovate/renovate-compose.yaml. fix/deploy-validation
Medium NetBird Compose mounts entrypoint.sh, but it is absent. Its README also calls a missing setup.sh; a fresh checkout cannot start this stack as documented. fix/netbird-compose-runtime
Medium Glance's CSS mount uses glance-config, whose keys do not include user.css. That key is in glance-assets; the pod's subPath mount cannot be prepared correctly. fix/glance-assets
Medium The shared PostgreSQL initializer requires NETBOX_DB_PASSWORD, but the Compose env example omits it. Following the example leaves first initialization incomplete. fix/postgres-env-example
Medium EDU's Compose env example uses old credential names and full URL variables, while the code reads KEEPER_* and paths under EDU_URL_BASE. fix/session-keeper-reliability
Medium Session keeper HTTP calls have no timeouts. Its Redis cookie never expires, probes only check existence, and its logs include cookies. A hung or failed refresh can leave a stale session appearing ready. fix/session-keeper-reliability
Medium AdGuard's DoH and SearXNG's Compose rules put Boolean expressions inside Host(...). They are invalid router expressions despite valid YAML. fix/compose-router-rules

Traefik matchers should be combined as Host(a) || Host(b); the rule syntax is described in the Traefik rules documentation. The fix retains the DoH path constraint for both hostnames.

The prune fix deliberately rejects the unsafe option. It does not introduce automatic deletion under a different implementation. Prune defaults to false, and no tracked resource currently carries the selector label, so this is a latent defect rather than evidence of a live deletion incident.

The session fix bounds HTTP and Redis calls, validates required credentials, sets a cookie lifetime of two refresh intervals, and marks success only after publishing the verified cookie. With the default ten-minute interval, an outage longer than twenty minutes will make the existing Redis-key readiness checks fail. That is an intentional change from indefinite apparent readiness.

The deployment fix extracts required pod Secret references from rendered JSON, checks their namespaces, includes init containers, image-pull credentials, and mounted/projected Secrets, and honors optional references. Ingress TLS Secrets issued by cert-manager are not treated as pre-existing pod prerequisites. It checks existence/access, not every key's contents or application validity.

Live workstation observations

The SSH alias workstation is reachable. It has one Ready control-plane node, Kubernetes v1.35.4+k0s, and a Docker daemon alongside containerd. At inspection, no pods were Pending or in another non-running, non-completed phase. This is a point-in-time observation, not a complete application health test.

The deployment checkout at /srv/homelab is on main commit 2adf17c, behind the reviewed local commit. It has untracked host configuration and a separate userbot/ directory. It was not reset or cleaned.

Observed difference Implication
VictoriaMetrics and vmalert are running; the Prometheus StatefulSet has zero replicas. A monitoring migration is already in progress outside committed main. Deploying the old Helm values can overwrite those settings.
Homarr, Cloudflared, and Reloader are installed without their current Git active markers. Installed services and marker-selected services are different inventories. Missing markers do not establish that a service is stopped.
Cloudflare DDNS is running in both Docker and Kubernetes. Confirm which instance should own DNS updates and whether their domain lists overlap before retiring either one. Secret values were not inspected.
Traefik's LoadBalancer exposes port 8080 at 192.168.80.2. The direct API listener is deployed; its external reachability was not tested.
Default local-path has reclaim policy Delete, while many existing PVs have been changed to Retain. Current retention is partly live state. Recreating a claim can get a different policy from the old PV.
NetBird, NetBox media/reports/scripts, EDU Redis, Homarr, and VictoriaMetrics have Delete-policy PVs. Deleting their claims can delete important state. Plan backup and retention changes before namespace cleanup.

The monitoring files already modified in the user's local tree correspond to the live migration. They are excluded from these branches. Reconcile that work before using this review's baseline to deploy monitoring.

Remaining work

These need recovery design or infrastructure decisions rather than a small configuration correction:

  • SSH apply retries can replace the rollback baseline. ssh-run.sh retries exit 255, including apply-k8s; every new invocation publishes a fresh snapshot. If the first attempt already changed workloads, the retry snapshots that partial state. Preserve a run-specific original baseline and verify it across retries.
  • Rollback can exceed the job budget. Verification is parallel, but rollback_workloads is serial with a five-minute limit per workload. The thirty-minute job budget can expire before recovery finishes. Bound recovery concurrency and account for both phases before choosing a new timeout.
  • Snapshot collection is allowed to fail. Generation and workload snapshot errors are warnings; verify can fall back to all workloads. A snapshot failure must not permit unrelated workloads to be selected for automatic undo.
  • Rollback uses the previous revision, not the captured revision. rollout undo without an explicit revision cannot guarantee restoration to the snapshot after retries or intervening rollouts. First deployments also have no previous revision.
  • Manual deploy dispatch bypasses the CI-success trigger. Either validate the target commit's successful CI run or document manual dispatch as an operator override with its own required checks.
  • Direct Traefik API exposure is unauthenticated. The latest local commit explicitly added it for Homarr. Preserve that integration while choosing a cluster-internal authenticated path or a verified network restriction; do not simply disable an integration that is already in use.
  • Storage retention and backup are not reproducible as a whole. Defaults and several important PV policies are Delete. There is no repository-wide backup schedule. Existing PVC StorageClass changes require migration rather than an in-place YAML edit.
  • MeTube downloads are temporary on Kubernetes. /downloads is a 20 GiB emptyDir. Decide whether pod replacement should discard files or whether it should use persistent storage. Compose uses a host directory instead.
  • First-time activation needs a bootstrap path. Deploy validation dry-runs namespaced resources before the apply stage creates namespaces and installs selected charts. On a fresh cluster, missing namespaces and CRDs need separate preparation; activation is not a complete installer.

Validation

Baseline lint checks passed for Python, shell, workflows, YAML, standard Compose files, and Kubernetes resources with available schemas. Kubeconform found 347 resources in 174 files: 201 valid, 146 skipped CRDs, zero invalid resources. That skip count matters: passing schema validation does not validate Traefik rule strings or other controller-specific behavior.

Fix validation covers:

  • Compose discovery of manual entry points, rejection of required-variable gaps, namespace-scoped and optional Secret references, and API/render failures.
  • NetBird setup idempotence, preservation of existing keys, file permissions, runtime rendering, and rejection of invalid trusted proxy CIDRs.
  • Session refresh success and failure paths, timeouts, cookie expiry, log redaction, missing credentials, and nonpositive refresh intervals.
  • Correct Glance ConfigMap key selection and PostgreSQL initializer/env alignment.
  • YAML and Compose structure for the corrected router rules, compared with the documented Traefik grammar. They were not exercised on the live proxy.
  • Prune rejection before any cluster invocation.

All seven fix branches and the documentation branch merged together without conflicts in a disposable validation worktree. The combined tree passed the CI-equivalent local checks, Markdown formatting/lint and link checks, all 35 Compose structure checks, and 11 Python regression tests plus the shell validation regressions. CRD server-side validation and live rollout tests were not run.

Runtime tests use fixtures and mocks, not production credentials. Live checks read workload metadata, storage policies, chart versions, and container state only. They did not read Secret contents or change services.

Reloader follow-up

fix/reloader-integration adds the active marker and opt-in annotations to 28 application Deployments/StatefulSets that consume runtime ConfigMaps or Secrets. It corrects AdGuard's misplaced pod-template annotation. The Helm settings use annotation-based reloads, keep global auto-reload disabled, and ignore Jobs and CronJobs. PostgreSQL workloads are excluded because their credential variables and init scripts are only effective on an empty data directory.

The controller was already running on workstation when inspected. Its live configuration is unchanged by the branch: merge and deploy the integration to apply the new policy and application annotations. Configuration reload behavior was checked against the pinned chart, with Helm rendering and manifest validation; no production configuration was changed to provoke a test restart.