--- # The docs site. `uv run mkdocs build --strict` renders docs/ into site/; in production # scripts/docs/build_docs.py runs the reference generators first and builds into the hostPath the # `docs` k3s role serves. # # docs_dir points at the EXISTING docs/ tree and the nav names those files in place. Moving # them to fit a navigation shape would break every reference to docs/secret-rotation.md and # its siblings across CLAUDE.md, the role docs and the skills. site_name: Homelab Docs site_description: Reference and runbooks for the daniel-box / daniel-server / daniel-pi homelab docs_dir: docs site_dir: site use_directory_urls: true theme: name: material features: - navigation.top - navigation.indexes - search.highlight - content.code.copy palette: - media: "(prefers-color-scheme: light)" scheme: default toggle: icon: material/brightness-7 name: Switch to dark mode - media: "(prefers-color-scheme: dark)" scheme: slate toggle: icon: material/brightness-4 name: Switch to light mode markdown_extensions: - admonition - attr_list - md_in_html - tables - toc: permalink: true - pymdownx.details - pymdownx.superfences - pymdownx.highlight: anchor_linenums: true # `--8<-- "assets/generated/fragments/.md"` splices a generated fact table into a # hand-written page, so the prose stays prose and the tunables it sits beside are re-read # from the tree by scripts/docs/gen_doc_fragments.py on every docs-refresh run. check_paths # turns an include naming a fragment nobody generates into a strict-build failure; without # it the include line renders as text and the page reads as if the table were missing. - pymdownx.snippets: base_path: [docs] check_paths: true plugins: - search # Resolves the `` placeholders the reference generators emit into links, # using the domain of the URL the page was served from, and resolves the nav's sentinel hosts # the same way. The generators cannot bake an FQDN in: they parse the tree statically and # `domain` is SOPS-sourced. See the file's own header. # Table overflow on the wide generated reference tables. The file's own header carries the # mechanism; the short version is that Material's stock table rules clip long cells instead of # wrapping them, and the scrollbar that would reach the clipped text sits several screens down. extra_css: - assets/extra.css extra_javascript: - assets/fqdn-links.js # A filter bar and sortable headers on the long tables. It runs in the browser because the # filter is the reader's choice, not a fact the build knows. The file's own header says # which tables get one; it sits after fqdn-links.js so a filter matches the resolved links. - assets/table-filter.js # Turns a repo path written as a code span -- `scripts/docs/service_catalog.py`, # `docs/secret-rotation.md` -- into a link to where this site documents it. Unlike the FQDN # links above this resolves at build time, because a path -> URL map is fully known then; the # file's own header carries what it links, what it deliberately does not, and why it injects # the Scripts page's row anchors itself rather than asking the generator for them. # The leading underscore keeps it out of the Scripts reference, which enumerates scripts/**. hooks: - scripts/docs/_mkdocs_repo_links.py # Appends to every hand-written page the date of its last commit and which of the repo # files it names changed after that -- the freshness signal the generated pages get from # their frontmatter. Computed from git at build time, so nothing is committed; # reference/freshness.md is the same data as one ranked table. - scripts/docs/_mkdocs_freshness.py # Pages that are BUILT unless named here. mkdocs serves everything under docs_dir whether or # not the nav links it, and it logs an unlinked page at INFO — so `--strict` does not catch # one, and neither does reading the nav. # # `superpowers/` is 11 design plans kept on disk and deliberately gitignored (.gitignore:5-6). # Untracked is not unpublished: the cron builds from the host's working tree, so they were # being served from an internet-facing site. They are the only pages here with a real exposure # delta — this repo is PUBLIC, so everything tracked is world-readable already and excluding it # protects nothing. `adr/template.md` is a fill-in-the-blanks stub, not a decision. exclude_docs: | superpowers/ adr/template.md # Snippet bodies, not pages: each is spliced into a page above by `--8<--`, and mkdocs # would otherwise build every one as an orphan page of one table. Excluding a file from # the build does not hide it from the snippets extension, which reads base_path from disk. assets/generated/fragments/ # M6's per-batch status records, hand-written and spliced into m6-retire-kuma.md the same # way. One file per batch so parallel PRs do not conflict over one status block (#6656). target-architecture/monitoring/m6-status/ # Built and served on purpose, but deliberately absent from the nav. `archive/` is the # historical record of executed work — other docs and CLAUDE.md link INTO it, so excluding it # would 404 those links, while listing every superseded planning doc in the nav would bury the # live pages. Its README is the one exception: the nav's History section names it, as the # index a reader browses the archive from. Declaring it here is what separates "decided to # leave unlinked" from "nobody noticed", which is the distinction the nav coverage test now # enforces. # # DECIDED: docs/archive stays; size was never the concern, and a PR number is not greppable # offline the way the linked record is. Deleting the archive in favour of PR numbers was # re-examined on 2026-09-28 (#2808) and kept. # # `reference/backlog.md` is the same case for a different reason: `findings.py list` is the # open register, this page is only its rendering, and `issue-claiming-and-fanout.md` and # CLAUDE.md link INTO it directly — so it stays built and reachable, just off the nav. not_in_nav: | archive/ reference/backlog.md # ENFORCED by scripts/tests/test_mkdocs_config.py, in both directions: every entry below must # resolve to a file, and every docs/**.md must appear here OR be matched by exclude_docs above. # A document absent from the nav is still built and served, but nothing links to it — which is # indistinguishable from it not existing. nav: - Home: index.md # A nav heading that is only a link, off the site to the artifacts browser. The host is a # sentinel: `domain` is SOPS-sourced, so nothing static can name the real one, and # assets/fqdn-links.js rewrites this href to the domain and tier the reader is already on # — the same derivation it does for the reference pages' `` spans. # # The degradation differs from those spans and is worse. A span that is never rewritten # still reads as its placeholder text; a nav anchor is a live link or a dead one, so a # JavaScript failure leaves this entry pointing at an unresolvable host. Accepted: the # same failure already costs every service link on the reference pages. - Artifacts: https://artifacts.local.invalid/ - Reference: - Services: reference/services.md - Hosts: reference/hosts.md - Secrets: reference/secrets.md - Scheduled jobs: reference/crons.md - State of the lab: reference/state.md - Networking: reference/networking.md - Scripts: reference/scripts.md - Topology: reference/topology.md - Doc freshness: reference/freshness.md - Decision markers: reference/decisions.md # template.md is deliberately absent — it is a form, not a document. The nav test only # checks docs/*.md, not subdirectories, so leaving it out costs nothing. - Decisions: - Index: adr/index.md - "ADR-0001 MkDocs site with generated reference": adr/0001-mkdocs-site-with-generated-reference.md - "ADR-0002 k3s over Docker Compose": adr/0002-k3s-over-docker-compose-for-the-cluster-nodes.md - "ADR-0003 SOPS with age for secrets": adr/0003-sops-with-age-for-secrets-at-rest.md - "ADR-0004 Authelia as the SSO layer": adr/0004-authelia-is-the-single-sign-on-layer.md - "ADR-0005 Traefik as the edge": adr/0005-traefik-is-the-edge-with-ingressroute-crds.md - "ADR-0006 Longhorn for cluster storage": adr/0006-longhorn-for-cluster-storage.md - "ADR-0007 Backup tiering: R2 daily, B2 weekly": adr/0007-backup-tiering-r2-daily-b2-weekly.md - "ADR-0008 16 MiB Longhorn blocks": adr/0008-16-mib-longhorn-blocks.md - "ADR-0009 NetworkPolicy default-deny": adr/0009-networkpolicy-default-deny-ingress.md - "ADR-0010 Pull-based GitOps over Argo and Flux": adr/0010-pull-based-gitops-over-argo-and-flux.md - "ADR-0011 One lock serialises every deploy path": adr/0011-one-lock-serialises-every-deploy-path.md - "ADR-0012 Zero-downtime rollout gates": adr/0012-zero-downtime-deploys-gate-on-rollout-and-restarts.md - "ADR-0013 daniel-pi stays on Docker": adr/0013-daniel-pi-stays-on-docker.md - "ADR-0014 Kopia retired, Longhorn owns B2": adr/0014-kopia-retired-longhorn-owns-the-b2-credentials.md - "ADR-0015 D2 for hand-authored diagrams": adr/0015-d2-for-hand-authored-diagrams.md - "ADR-0016 Code scanning stays on default setup": adr/0016-code-scanning-stays-on-default-setup.md - "ADR-0017 The tree lock guards the tree": adr/0017-the-tree-lock-guards-the-tree-not-the-cluster.md - "ADR-0018 The repository stays public, CI stays hosted": adr/0018-the-repository-stays-public-and-ci-stays-hosted.md - "ADR-0019 Fact support stops at the repo store": adr/0019-fact-support-stops-at-the-repo-store.md - "ADR-0020 Flux applies the cluster from a promoted artifact": adr/0020-flux-applies-the-cluster-from-a-promoted-artifact.md - "ADR-0021 The host-package builder trusts master plus green CI": adr/0021-the-host-package-builder-trusts-master-plus-green-ci.md - "ADR-0022 The cluster decrypts its own file with its own key": adr/0022-the-cluster-decrypts-its-own-file-with-its-own-key.md - "ADR-0023 Fact support is a diff notice": adr/0023-fact-support-is-a-diff-notice.md - "ADR-0024 Code scanning is off": adr/0024-code-scanning-is-off.md - "ADR-0025 SUC upgrades k3s, and homelab-k3s-restart restarts it": adr/0025-suc-upgrades-k3s-and-homelab-k3s-restart-restarts-it.md - "ADR-0026 One alert plane on Prometheus and Alertmanager; Uptime Kuma retired": adr/0026-one-alert-plane-on-prometheus-and-alertmanager.md - "ADR-0027 Fan-out batches stay on host cgroups": adr/0027-fanout-batches-stay-on-host-cgroups.md - "ADR-0028 The cluster render supports a closed Jinja subset": adr/0028-the-renderer-supports-a-closed-jinja-subset.md # The long-form documents three of the records above compress. They sit under Decisions # rather than in a section of their own: each one is the working-out behind a single # ADR, and a reader who wants that depth arrives from the record, not from a # top-level heading. - Background: - "Longhorn backup tiering (ADR-0007)": longhorn-backup-tiering.md - "NetworkPolicy default-deny (ADR-0009)": networkpolicy-default-deny.md # The per-slice record. It is # the answers each deploy settled by observation, and slice 5's four decisions govern # how the baseline selects pods — live content, not archive material. - "NetworkPolicy — what each slice settled": networkpolicy-slice-answers.md - "Argo and Flux evaluation (ADR-0010)": archive/gitops-argo-flux-evaluation.md # The measurement #3734 needs before initial_setup.yml loops over a setup_roles list. - "Looped include_role measurement (#3734)": setup-role-loop-measurement.md # Six groups, ordered day-to-day first and recovery last. An upgrade is both a driving and # a recovery procedure, so the upgrades sit with recovery, where a reader looking for "how do # I get the cluster back" finds them. A group is a nav heading only: every page keeps its # path, so moving one between groups breaks no link. - Operations: - Deploy and land: - Deploying by hand: deploying.md # The long form behind the `land-after-merge` skill. The skill carries the invocation # and the verdict decoder; this page carries the measurement behind each rule, which # keeps the skill short. - Landing a merged PR: landing.md - The GitOps pipeline: gitops-pipeline.md - Host packages: host-packages.md - The Flux deploy plane: flux-deploy-plane.md - The homelab-render renderer: homelab-render.md - Secret rotation: secret-rotation.md - Monitoring and alerting: - Healthchecks.io deadman: healthchecks-io-deadman.md - Uptime Robot monitors: uptime-robot-monitors.md # internals holds the module table, prerequisites and gate sets; the check record # holds one entry per live check. check.py and cli.py cite the internals path. - monitor-bridge internals: monitor-bridge-internals.md - monitor-bridge check record: monitor-bridge-checks.md - uptime-kuma and AutoKuma record: uptime-kuma-autokuma-record.md - observability record: observability-dashboards.md - autofix-bridge actuators: autofix-bridge-actuators.md - Access and host security: - WireGuard private access: wireguard-private-homelab-access.md - Security tools: security-tools.md - Claude tooling: - Claude tooling reference: claude-tooling.md - Claude shell permissions: claude-shell-permissions.md - Claude as its own UNIX user: claude-agent-user.md - claude-rc caps: claude-code-rc-caps.md - Claude memory store copy: claude-memory-sync.md - Alert-triggered diagnosis (no-go): alert-triggered-diagnosis.md - Issue claiming and fan-out: issue-claiming-and-fanout.md - Failure classes: failure-classes.md - Python code organization: python-code-organization.md # Each page below takes the working-out of one role CLAUDE.md: drill records, measured # traps, board provenance and design records. The role doc keeps the operating rules, # within the inject hook's character budget, and a session reads the page only when it # edits that code. - Role records: - Edge: - Traefik record: traefik-plugins-and-startup.md - CrowdSec record: crowdsec-waf-record.md - Authelia sessions record: authelia-sessions-and-crowdsec-init.md - Pi-hole DNS continuity record: pihole-dns-continuity-record.md - Hosts: - Host baseline record: host-baseline-record.md - Pi host tuning record: pi-host-tuning-record.md - k3s node-plane crons and incidents: k3s-node-plane-crons-and-incidents.md - hypervisor libvirt internals: hypervisor-libvirt-internals.md - Docker engine pins and runtime: docker-engine-pins-and-runtime.md - Storage: - volume-snapshot drill record: volume-snapshot-drills.md - volume-revert drill and sizing: volume-revert-drill-and-sizing.md - Renovate: - renovate_notify internals: renovate-notify-internals.md - renovate_agent bounds and digest: renovate-agent-bounds-and-digest.md - Services: - headlamp plugins and OIDC record: headlamp-oidc-and-plugins.md - homepage widgets and layout: homepage-widgets-and-layout.md - Jellyfin plugins: jellyfin-plugins.md - Jellyfin font attachment latency: jellyfin-attachment-latency.md - qbittorrent VPN and prefs: qbittorrent-vpn-and-prefs.md - n8n community node packages: n8n-community-packages.md - game-stats internals: game-stats-internals.md - Valheim modding: valheim-modding.md - Email to RSS: email-to-rss.md - artifacts star list: artifacts-star-list.md - Recovery and upgrades: - k3s control-plane upgrade: k3s-upgrade.md - Longhorn upgrade: longhorn-upgrade.md - Ubuntu 26.04 upgrade plan: ubuntu-26.04-upgrade-plan.md - Longhorn disaster recovery: longhorn-disaster-recovery.md - k3s etcd restore: k3s-etcd-restore.md - Registry push store collection: registry-push-store-gc.md # The last stop on the recovery ladder — total loss of daniel-box or the author's own # access. It is the entry point that links to every runbook above rather than one more # subsystem-specific procedure, so it sits after all of them. - Break glass: break-glass.md # The planned migration to Flux, one alert plane, per-host packages and generated facts. # Every page is a plan with Status: PLANNED, not a description of what runs. A slice moves # to archive/ once it ships, the way the k3s migration's slices did. - Target architecture: - Design: target-architecture/design.md - Contracts: target-architecture/contracts.md - Renderer: - R1 Shadow render: target-architecture/render/r1-shadow-render.md - R2 Secret placeholders: target-architecture/render/r2-secret-placeholders.md - R3 Remove live reads: target-architecture/render/r3-remove-live-reads.md - R4 Delete the k8s Ansible path: target-architecture/render/r4-delete-k8s-ansible-path.md - Cluster deploy plane: - D1 Flux installed: target-architecture/deploy/d1-flux-installed.md - D2 First leaf: target-architecture/deploy/d2-first-leaf.md - D3 Secrets: target-architecture/deploy/d3-secrets.md - D4 Ordering: target-architecture/deploy/d4-ordering.md - D5 Bulk move: target-architecture/deploy/d5-bulk-move.md - D6 Delete the old path: target-architecture/deploy/d6-delete-old-path.md - D7 Superseding promotions: target-architecture/deploy/d7-superseding-promotions.md - Monitoring: - M1 Alertmanager: target-architecture/monitoring/m1-alertmanager.md - M2 First bridge family: target-architecture/monitoring/m2-first-bridge-family.md - M3 Textfile timer: target-architecture/monitoring/m3-textfile-timer.md - M4 Blackbox ingress probes: target-architecture/monitoring/m4-blackbox-ingress.md - M5 Loki ruler: target-architecture/monitoring/m5-loki-ruler.md - M6 Retire Kuma: target-architecture/monitoring/m6-retire-kuma.md - Alertmanager UI and readers: target-architecture/monitoring/alertmanager-ui-and-readers.md - Host plane: - H1 Cluster config to Flux: target-architecture/host/h1-cluster-config-to-flux.md - H2 Shrink the host plane: target-architecture/host/h2-shrink-host-plane.md - H3 Pi pilot: target-architecture/host/h3-pi-pilot.md - H4 Secrets unit: target-architecture/host/h4-secrets-unit.md - H5 Pi Compose: target-architecture/host/h5-pi-compose.md - H6 k3s upgrades: target-architecture/host/h6-k3s-suc.md - H7 Delete Ansible: target-architecture/host/h7-delete-ansible.md - Docs guard: - G1 Diff notice: target-architecture/docs-guard/g1-diff-notice.md - G2 Retire the lock: target-architecture/docs-guard/g2-retire-lock.md - G3 Freshness reviewer: target-architecture/docs-guard/g3-freshness-reviewer.md - G4 Drop the size test: target-architecture/docs-guard/g4-drop-size-test.md - G5 Shrink root CLAUDE.md: target-architecture/docs-guard/g5-shrink-root-claude-md.md # Work that shipped, a spike that concluded and an incident that closed: kept as the record, # not as instructions. Operations holds only pages that describe the lab as it runs. # The archive index lists every archived plan and names where its live documentation went, # and the pages under it stay off the nav (see not_in_nav above). So this heading is the # index alone. - History: - Archive index: archive/README.md