No description
  • YAML 64.9%
  • MARKDOWN 26.4%
  • SHELL 2%
  • HTML 1.5%
  • JavaScript 1.3%
  • Other 3.8%
Find a file
Ryan Grippeling 52d875602e
Some checks failed
e2e / Kyverno Chainsaw (KinD) (push) Successful in 21s
e2e / Validate Renovate config (push) Successful in 1m53s
e2e / Lint & static validation (push) Failing after 1m51s
e2e / Flux-local render (push) Successful in 2m1s
fix(ploeg): give OpenHands Runs a 45m idle timeout
The idle watchdog counts only harness output, and OpenHands prints
nothing while it works: bronze's builder for Glide #555 (Run 134) was
killed at the 15m default after US$0.24 of model calls. The 100m
harness timeout still bounds each Run; Glide ticket follows to count
model traffic as activity.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 19:55:49 +02:00
.claude fix(longhorn): retry a failed upgrade forward instead of rolling back 2026-09-27 11:33:58 +02:00
.forgejo/workflows feat(omnigraph): import the Obsidian vault into brain every 15 minutes 2026-09-27 17:24:33 +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 feat(envoy-gateway): upgrade chart v1.8.3 -> v1.9.1 for Kubernetes 1.36 2026-09-27 00:49:02 +02:00
catalog feat(ci): retire actions-runner-controller and both GitHub runner scale sets 2026-09-27 10:03:15 +02:00
docs/techdocs fix(mcp-vikunja): supergateway 4.0.0 reaps stateless children, alert on memory near limit 2026-09-27 19:45:07 +02:00
kubernetes fix(ploeg): give OpenHands Runs a 45m idle timeout 2026-09-27 19:55:49 +02:00
scripts feat(omnigraph): import the Obsidian vault into brain every 15 minutes 2026-09-27 17:24:33 +02:00
talos feat(talos): render machine configs with plain talosctl, retire talhelper 2026-09-27 00:43:36 +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 feat(access-plane): the model, its schema, and the module that reads it 2026-09-14 21:33:38 +02:00
.gitleaks.toml chore(security): gitleaks config; full-history scan clean (#6) 2026-06-13 10:05:35 +02:00
.lefthook.toml feat(ci): fail when a route claims synthetic coverage no Probe provides 2026-09-27 09:46:12 +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 feat(talos): render machine configs with plain talosctl, retire talhelper 2026-09-27 00:43:36 +02:00
.renovaterc.json5 feat(renovate): one PR per Glide release 2026-09-27 16:41:35 +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): adopt the estate no-comments rule 2026-09-04 19:17:29 +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(agents): fold CLAUDE.md into AGENTS.md, symlink the name 2026-09-01 09:45:53 +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(talos): render machine configs with plain talosctl, retire talhelper 2026-09-27 00:43:36 +02:00
LICENSE fix: WebGrip MIT, not onedr0p 2026-06-07 12:42:49 +02:00
README.md feat(ci): retire actions-runner-controller and both GitHub runner scale sets 2026-09-27 10:03:15 +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

  • cert-manager: ACME certificates for both envoy gateways.
  • cilium: eBPF networking, kube-proxy-free dataplane.
  • cloudflared + Cloudflare DNS: tunnel and DNS automation for *.webgrip.dev.
  • envoy-gateway: Provides envoy-internal/envoy-external Gateway API classes.
  • flux: Source, Kustomize, Helm, and notification controllers.
  • k8s-gateway: Split DNS responder for internal resolution.
  • kyverno: Policy engine for admission control, background scans, policy reports, and namespace guardrails.
  • metrics-server, reloader, spegel: telemetry, config reloads, and OCI image cache.

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.
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]
  Apps>Kustomization: freshrss] --> |Consumes| Gateways
  Apps --> |Consumes| TLS
  Flux --> |Reconciles| Net
  Flux --> |Reconciles| Certs
  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.