No description
  • YAML 66.1%
  • MARKDOWN 26.5%
  • SHELL 2.1%
  • HTML 1.8%
  • JavaScript 1.2%
  • Other 2.1%
Find a file
Ryan Grippeling 16fd0a49fb
All checks were successful
e2e / Validate Renovate config (push) Successful in 41s
e2e / Flux-local render (push) Successful in 34s
e2e / Lint & static validation (push) Successful in 44s
e2e / Kyverno Chainsaw (KinD) (push) Successful in 8s
[Workflow] On Documentation Change / techdocs (push) Successful in 41s
[Workflow] On Documentation Change / Generate (push) Successful in 0s
[Workflow] On Documentation Change / Publish to Garage S3 (push) Successful in 9s
[Workflow] On Documentation Change / Deploy (Backstage / Garage S3) (push) Successful in 0s
[Workflow] On Documentation Change / Build (Zensical) + sync to Garage web (push) Successful in 41s
[Workflow] On Documentation Change / Deploy (docs site / Garage web) (push) Successful in 0s
docs(rfc): correct the worker-2 kata claim — it took, and how
My previous commit concluded worker-2 could not take the extension
without a version bump. Wrong: a concurrent fix landed it by applying
the node's machine config before the upgrade (its live install.image
still pointed at the base schematic). All three workers now report
kata-containers 3.32.0. Verified live: extensions on .32 +
runtime.webgrip.io/kata=true on all three nodes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 18:51:55 +02:00
.claude fix(claude): policy-audit must query both dialects, and readiness differs 2026-08-05 10:23:48 +02:00
.forgejo/workflows feat(docs): cut the docs domain over to per-repo buckets (estate #2, phase C) 2026-08-12 19:35:16 +02:00
.github refactor(tooling): consolidate on just, remove task entirely 2026-08-02 10:08:02 +02:00
.vscode Backport upstream cluster-template workflow parity with just-first operator UX (#266) 2026-05-25 13:22:40 +02:00
bootstrap fix(container): update image quay.io/jetstack/charts/cert-manager (v1.21.0 ➔ v1.21.1) 2026-08-04 07:07:45 +00:00
catalog fix: docs and claude cleanup 2026-06-07 12:53:04 +02:00
docs/techdocs docs(rfc): correct the worker-2 kata claim — it took, and how 2026-08-13 18:51:55 +02:00
kubernetes fix(openbao): drop the single-replica PDB — it was unsatisfiable, not strict 2026-08-13 11:33:02 +02:00
scripts fix(kyverno): image-supply-chain-audit covered Pods only; gate the class of bug 2026-08-12 15:56:28 +02:00
talos docs: why worker-2 cannot take kata by retry (same-version UKI revert) + the buildx leak source 2026-08-13 18:50:35 +02:00
.editorconfig Initial commit 2025-12-08 06:27:14 +01:00
.gitattributes Initial commit 2025-12-08 06:27:14 +01:00
.gitignore chore(docs): register new techdocs in nav; stop tracking harvest scratch dir 2026-06-26 22:06:04 +02:00
.gitleaks.toml chore(security): gitleaks config; full-history scan clean (#6) 2026-06-13 10:05:35 +02:00
.lefthook.toml refactor(tooling): consolidate on just, remove task entirely 2026-08-02 10:08:02 +02:00
.lefthookrc fix(lefthook): source .lefthookrc so GUI git clients find mise 2026-07-29 22:12:42 +02:00
.lycheeignore feat(docs): estate wave 1 — inventory pages, estate search, tags, freshness, llms discovery, link-rot cron (items 4,5,6,10,12,15) 2026-08-11 14:49:00 +02:00
.mcp.json fix(mcp): docs server via streamable /mcp — verified transport (stateless, no session) 2026-08-11 15:34:42 +02:00
.mise.toml fix(mise): revert bao to 2.5.5 again — the guard rule never matched 2026-08-11 10:59:36 +02:00
.renovaterc.json5 fix(mise): revert bao to 2.5.5 again — the guard rule never matched 2026-08-11 10:59:36 +02:00
.shellcheckrc Initial commit 2025-12-08 06:27:14 +01:00
.sops.yaml chore: Initial commit 🚀 2025-12-08 10:28:40 +01:00
.worktreeinclude chore: track .worktreeinclude (gitignored files copied into Claude worktrees) 2026-06-25 06:00:41 +02:00
.yamlfmt.yaml Backport upstream cluster-template workflow parity with just-first operator UX (#266) 2026-05-25 13:22:40 +02:00
AGENTS.md docs(agents): stop transcribing board state into the contract 2026-08-01 07:14:52 +02:00
BLOGPOST-dark-devsecfinops.devto.md docs(blog): draft the Dark DevSecFinOps post from last night's run 2026-07-31 06:16:46 +02:00
BLOGPOST-dark-devsecfinops.draft-v1.md docs(blog): draft the Dark DevSecFinOps post from last night's run 2026-07-31 06:16:46 +02:00
BLOGPOST-dark-devsecfinops.hn.md docs(blog): draft the Dark DevSecFinOps post from last night's run 2026-07-31 06:16:46 +02:00
BLOGPOST-dark-devsecfinops.linkedin.md docs(blog): draft the Dark DevSecFinOps post from last night's run 2026-07-31 06:16:46 +02:00
BLOGPOST-dark-devsecfinops.md docs(blog): draft the Dark DevSecFinOps post from last night's run 2026-07-31 06:16:46 +02:00
BLOGPOST-dark-devsecfinops.plan.md docs(blog): the milestone gate is met — a second agent reviewed a PR 2026-07-31 10:08:10 +02:00
BLOGPOST-dark-devsecfinops.reddit.md docs(blog): draft the Dark DevSecFinOps post from last night's run 2026-07-31 06:16:46 +02:00
BLOGPOST-dark-devsecfinops.research.md docs(blog): correct the spend-attribution claim — it was wrong 2026-07-31 07:13:44 +02:00
BLOGPOST-dark-devsecfinops.review.md docs(blog): draft the Dark DevSecFinOps post from last night's run 2026-07-31 06:16:46 +02:00
BLOGPOST-dark-factory.devto.md feat(ci): flip DEFAULT_ACTIONS_URL to the local forge, wire runner to shared dind 2026-07-24 07:27:52 +02:00
BLOGPOST-dark-factory.draft-v1.md feat(ci): flip DEFAULT_ACTIONS_URL to the local forge, wire runner to shared dind 2026-07-24 07:27:52 +02:00
BLOGPOST-dark-factory.hn.md feat(ci): flip DEFAULT_ACTIONS_URL to the local forge, wire runner to shared dind 2026-07-24 07:27:52 +02:00
BLOGPOST-dark-factory.linkedin.md feat(ci): flip DEFAULT_ACTIONS_URL to the local forge, wire runner to shared dind 2026-07-24 07:27:52 +02:00
BLOGPOST-dark-factory.md feat(ci): flip DEFAULT_ACTIONS_URL to the local forge, wire runner to shared dind 2026-07-24 07:27:52 +02:00
BLOGPOST-dark-factory.reddit.md feat(ci): flip DEFAULT_ACTIONS_URL to the local forge, wire runner to shared dind 2026-07-24 07:27:52 +02:00
BLOGPOST-dark-factory.research.md feat(ci): flip DEFAULT_ACTIONS_URL to the local forge, wire runner to shared dind 2026-07-24 07:27:52 +02:00
BLOGPOST-dark-factory.review.md feat(ci): flip DEFAULT_ACTIONS_URL to the local forge, wire runner to shared dind 2026-07-24 07:27:52 +02:00
catalog-info.yaml fix: improved techdocs and catalog-info.yaml for backstage 2025-12-08 16:00:47 +01:00
CLAUDE.md docs(claude): main is protected now — record the rule and the lockout recovery 2026-08-05 14:00:20 +02:00
feat-ci-fast-lane-remaining.patch feat(ci): flip DEFAULT_ACTIONS_URL to the local forge, wire runner to shared dind 2026-07-24 07:27:52 +02:00
HANDOFF-storage-and-node-strategy.md docs: sweep every hardware reference for worker-2 and the retired box 2026-08-02 09:38:58 +02:00
justfile feat(just): add the kubescape recipe .mise.toml has referenced since August 2026-08-09 17:23:45 +02:00
LICENSE fix: WebGrip MIT, not onedr0p 2026-06-07 12:42:49 +02:00
README.md fix(docs): lychee gate — external-only scope, no bogus flag; fix dead README renovate link 2026-08-11 16:07:07 +02:00

🚀 Homelab Operations Repository 🚧

... managed with Flux, Renovate, GitHub Actions, and Talos

Discord   Talos   Kubernetes   Flux   Renovate

Status   Gateway   Alertmanager

Age Uptime Nodes Pods CPU Memory Alerts

💡 Overview

This is the living source of truth for the Talos-powered cluster behind webgrip.dev. Flux owns every namespace under kubernetes/apps, Renovate watches the whole repo for drift, and GitHub Actions runs validation plus flux diffs before anything merges. TechDocs (in docs/techdocs) surface runtime inventory, Talos node state, and runbooks inside Backstage so docs ship with the manifests.

🌱 Kubernetes

My cluster runs on three bare-metal Talos controllers (soyo-1..3) that also schedule workloads, plus two dedicated workers — fringe-workstation (10.0.0.30) and worker-1 (10.0.0.31) — that take write-heavy apps off the control-plane disks. worker-1 carries the Longhorn replicas. Everything runs kube-proxy-free via Cilium, with split-DNS gateways and Cloudflare tunnels for ingress. GitOps keeps the manifests authoritative while just/Mise make local development reproducible.

Core Components

GitOps

Flux watches the kubernetes/apps tree, reconciling each top-level kustomization.yaml it finds. Those Kustomizations in turn apply HelmReleases, Jobs, ConfigMaps, and SOPS secrets. Renovate opens PRs whenever container tags, Helm charts, or Actions workflows drift; GitHub Actions runs linting plus flux diff --cached against the target cluster before a merge. Secrets are committed only as Age-encrypted SOPS files (see kubernetes/components/sops/), so Flux can decrypt them once the controller pulls from this repo.

Category Namespace(s) Highlights
Platform control flux-system, kube-system Flux controllers, notification receiver, Weave GitOps UI, plus Cilium, CoreDNS, metrics-server, Spegel, and Reloader.
Networking & ingress network Envoy internal/external gateways, Cloudflare DNS + Tunnel, and k8s-gateway for split-horizon DNS.
PKI & security cert-manager, kyverno, components/sops ACME HTTP-01 + DNS-01 issuers for wildcard certs, Kyverno admission/reporting/generate policies, and shared secrets rendered into namespaces through the SOPS component.
CI infrastructure arc-systems Actions Runner Controller plus a Docker-in-Docker runner scale set so GitHub repos can burst jobs onto the homelab.
Applications default, freshrss, invoiceninja Echo sample service, FreshRSS HelmRelease with Bitnami bootstrap job, and Invoice Ninja 5.12.39 paired with an app-template-managed MariaDB 11.8.5 StatefulSet on Longhorn storage.

TechDocs tracks all of these via Backstage catalog entries under catalog/, so you can pivot from docs to manifests without leaving the repo.

Directories

📁 kubernetes
├── 📁 apps             # Applications managed by Flux
├── 📁 bootstrap        # Talos + Helmfile bootstrap resources
└── 📁 flux             # Flux controllers and sources
    ├── 📁 components   # Shared components (SOPS, networking)
    └── 📁 meta         # Repository definitions
📁 docs/techdocs        # MkDocs TechDocs (runtime inventory, Talos state)
📁 talos                # Generated Talos configs + patches
📁 scripts              # Helper scripts + common libraries

Flux Workflow

graph TD
  FS>Kustomization: flux-system] --> |Installs| Flux[Flux Controllers + Operator]
  Net>Kustomization: network] --> |Publishes| Gateways[Envoy + Cloudflare Tunnel]
  Net --> DNS[k8s-gateway + ExternalDNS]
  Certs>Kustomization: cert-manager] --> |Issues| TLS[Wildcard Certificates]
  Arc>Kustomization: arc-systems] --> |Deploys| Runners[ARC + gha-runner-scale-set]
  Apps>Kustomization: freshrss] --> |Consumes| Gateways
  Apps --> |Consumes| TLS
  Runners --> |Serve| GitHub
  Flux --> |Reconciles| Net
  Flux --> |Reconciles| Certs
  Flux --> |Reconciles| Arc
  Flux --> |Reconciles| Apps

🌐 Networking

graph TD
  A>Odido Fiber 1Gb/1Gb]
  A --> |Genexis ONT bridge| R[Protectli V1410 · OPNsense]
  B>WireGuard / Cloudflare Tunnel] --> |Remote access| R
  R --> |TL-SG108PE Port 1 uplink| S1[TP-Link TL-SG108PE]
  S1 --> |Port 3 → Q-Link| S2[Q-Link Switch]
  S1 --> |Port 2 → Zyxel · AP Port 4| W[Zyxel VMG8825-T50 Wi-Fi 5]
  S2 --> |Port 7 → soyo-1| K1([soyo-1])
  S2 --> |Port 6 → soyo-2| K2([soyo-2])
  S2 --> |Port 5 → soyo-3| K3([soyo-3])
  S2 --> |Port 2 → NAS| N([NAS])
  R --> |WAN · HTTPS 443| HZ([Hetzner garage-fsn1<br/>off-site S3 · Immich · uptime-kuma])
  W --> |Port 1 → Hue| H([Philips Hue bridge])
  W --> |Port 2 → Home Assistant| P([Raspberry Pi Home Assistant])
  W --> |SSID| W1([Main Wi-Fi])
  W --> |IoT SSID| W2([IoT devices])
  W --> |Guest SSID| W3([Guest access])

🏘️ Flat LAN

Device Role Address Notes
Protectli V1410 / OPNsense Router + firewall 10.0.0.1 DHCP scope 10.0.0.50-10.0.0.150, WireGuard termination, split DNS rules.
TL-SG108PE Managed switch 10.0.0.2 Port 1 uplinks to the Protectli WAN handoff, port 2 feeds the Wi-Fi bridge, and port 3 uplinks the Q-Link switch.
Zyxel VMG8825-T50 Wi-Fi bridge/AP 10.0.0.3 Bridge mode so SSIDs land on the same subnet as wired clients.

Static infrastructure (Talos nodes, Synology, Home Assistant) keeps IPs below .50 and is reserved in OPNsense Terraform so DHCP drift is impossible.

🌎 DNS

Three ExternalDNS deployments keep Cloudflare public records and k8s-gateway entries aligned. envoy-internal routes stay inside the LAN, while envoy-external hostnames are proxied through Cloudflare Tunnel. OPNsense runs split-horizon DNS—every *.webgrip.dev lookup hits the router, which forwards internally to the k8s-gateway LoadBalancer (10.0.0.26) so services stay reachable on-LAN without touching Cloudflare.

🏠 Home DNS

graph TD
  Clients -->|Queries| Router[OPNsense split DNS]
  Router -->|webgrip.dev| K8sGW[k8s-gateway LB 10.0.0.26]
  Router -->|Other domains| WAN[Upstream DNS]
  K8sGW -->|Routes hostnames| Envoy[envoy-internal / envoy-external]
  Envoy -->|Publishes| Cloudflare

☁️ Cloud Dependencies

Service Use Cost
Cloudflare Authoritative DNS, Zero Trust tunnels for *.webgrip.dev ~$50/yr
GitHub Repo hosting, Actions, container registry Free
Healthchecks.io Connectivity + job heartbeat monitoring Free tier
Fastmail Email + identity provider for alerts ~$56/yr

🖥️ Hardware

Canonical, always-measured inventory: Infrastructure at a glance — every machine, disk, label and address in one table. The summary below is a snapshot.

Num Device CPU RAM OS / Firmware Function
3 SOYO Mini PC M4 (Twin Lake N150) Intel N150 (4C/4T) 12 GB LPDDR5 Talos Linux v1.13.4 Control-plane + workloads; one 512 GB SATA SSD each, shared by etcd + OS + Longhorn; Wi-Fi5/BT5 disabled
1 HP Z230 Tower Workstation Intel Core i7-4770 (4C/8T, 3.4 GHz) 16 GB DDR3 (1 slot free) Talos Linux v1.13.4 Dedicated worker fringe-workstation (10.0.0.30) for write-heavy apps; 256 GB SATA SSD (OS) + 1 TB HDD
1 Gigabyte Z87X-D3H desktop Intel Core i5-4670K (4C/4T, 3.4 GHz) 24 GB DDR3-1600 Talos Linux v1.13.4 Worker worker-1 (10.0.0.31), added 2026-06-19; most RAM in the cluster; 1 TB Samsung SSD 870 — currently the only Longhorn storage node
1 worker-2 (reclaimed Proxmox box) Intel Core i7-6700K (4C/8T, 4.0 GHz) 16 GB (2 slots free) Talos Linux v1.13.7 Worker worker-2 (10.0.0.32), rejoined 2026-08-02; 2 TB Samsung 990 EVO Plus NVMe (install + Longhorn) plus 250 GB/1 TB SSD and 1 TB/2 TB HDD unused
1 Protectli V1410 Intel i5 8 GB OPNsense Router/firewall, DHCP 10.0.0.50-150, WireGuard, split DNS for webgrip.dev
1 TP-Link TL-SG108PE Managed firmware 8-port 1 GbE switch feeding downstream fan-out
1 Q-Link 1 GbE switch Unmanaged Directly uplinks Talos nodes for east-west traffic
1 Zyxel VMG8825-T50 Bridge/AP firmware Wi-Fi AP bridging onto the same flat LAN
1 Hetzner EX-line (Falkenstein, garage-fsn1) Intel Core i7-6700 (4C/8T, 3.4 GHz) 62 GB Debian 12 Off-site: Garage S3 (all backups), Immich, uptime-kuma. 2 × 512 GB Samsung NVMe in RAID1 (452 GB usable). Reachable only via Caddy on 443
1 NAS + Proxmox host Intel i7 32 GB Arch Linux + Proxmox Decommissioned 2026-08-02. Held Garage S3, Immich and uptime-kuma; all migrated off-site, then the machine was reclaimed as a Talos node
1 Raspberry Pi 4 (Home Assistant) Broadcom 4 GB Home Assistant OS Local automations + integrations

🔢 Cluster & Upstream IPs

Device / Endpoint Purpose Address
Protectli V1410 / OPNsense Router, DHCP, split DNS 10.0.0.1
TP-Link TL-SG108PE Managed switch 10.0.0.2
Zyxel VMG8825-T50 Wi-Fi bridge/AP 10.0.0.3
soyo-1 Talos controller / worker 10.0.0.20
soyo-2 Talos controller / worker 10.0.0.21
soyo-3 Talos controller / worker 10.0.0.22
fringe-workstation Talos worker 10.0.0.30
worker-1 Talos worker (Longhorn storage) 10.0.0.31
worker-2 Talos worker (Longhorn storage) 10.0.0.32
Kubernetes / Talos API VIP Control-plane endpoint 10.0.0.25
k8s-gateway LoadBalancer Split DNS responder 10.0.0.26
envoy-internal LoadBalancer LAN-only ingress 10.0.0.27
envoy-external / Cloudflare tunnel VIP Public ingress origin 10.0.0.28
garage-fsn1 (Hetzner, off-site) S3 backups, Immich, uptime-kuma 116.202.53.185

🙏 Thanks

Thanks to the Home Operations Discord, onedr0p for the original cluster-template inspiration, bjw-s for the app-template, and every maintainer building Talos, Flux, Renovate, and the CNCF projects that make GitOps homelabs straightforward.