AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Multi-site Kubernetes homelab with BGP anycast (AS214304), Cilium CNI, and GitOps. 14 nodes across 4 countries.
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
Hybrid GitOps Infrastructure as Code for the Nuclear Lighters homelab.
Single source of truth for the entire Nuclear Lighters infrastructure across 4 countries (NL, GR, CH, NO) — managing Kubernetes clusters, Cisco network devices, Proxmox VMs/containers, 80+ Docker services, and two HA clusters (Nextcloud 9-layer, Home Assistant Pacemaker) through GitLab CI/CD pipelines.檢視完整 README ↗Hybrid GitOps Infrastructure as Code for the Nuclear Lighters homelab.
Single source of truth for the entire Nuclear Lighters infrastructure across 4 countries (NL, GR, CH, NO) — managing Kubernetes clusters, Cisco network devices, Proxmox VMs/containers, 80+ Docker services, and two HA clusters (Nextcloud 9-layer, Home Assistant Pacemaker) through GitLab CI/CD pipelines.
| Component | Technology | Management | Count |
|---|---|---|---|
| Kubernetes | K8s v1.34.2, Cilium v1.18.4 (eBPF) | Atlantis + Argo CD | 2 clusters, 14 nodes |
| Multi-Site | Cilium ClusterMesh + IPsec mesh | OpenTofu | NL primary + GR DR |
| Network | Cisco IOS/IOS-XE/ASA + FRR BGP | GitLab CI/CD (Netmiko) | 11 devices, AS214304 |
| Virtualization | Proxmox VE | GitLab CI/CD | 5 nodes, 116 LXC, 22 QEMU |
| Docker Services | Docker Compose | GitLab CI/CD (rsync) | 51 hosts, 80+ services |
| HA Clusters | Nextcloud (DRBD+OCFS2+NFS), Home Assistant (Pacemaker) | Native configs | 21 + 3 hosts |
| Monitoring | REDACTED_d8074874 + Thanos + LibreNMS | Helm + LXC | 163 alert rules, 137 monitored devices |
| Secrets | OpenBao (HA, 3 nodes) + External Secrets Operator | OpenTofu | JWT auth for CI, K8s sync |
| Storage | NFS + Synology iSCSI CSI + SeaweedFS S3 | OpenTofu | RWX + RWO + S3 |
| Logging | syslog-ng → Promtail → Loki → Grafana | K8s + LXC | Centralized log aggregation |
| TLS | cert-manager + Let's Encrypt (DNS-01 via Cloudflare) | OpenTofu | Wildcard certificates |
| Backup | Velero + SeaweedFS S3 | Argo CD | Daily 2AM + weekly Sunday 3AM |
┌─────────────────────────────────────────────────────────────┐
│ INTERNET │
│ │
│ AS34927 (iFog) AS56655 (Gigahost) │
│ │ │ │
│ │ eBGP │ eBGP │
│ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ │
│ │ CH Edge │ │ NO Edge │ │
│ │ Zürich │◄────IPsec────────►│ Oslo │ │
│ └────┬────┘ └────┬────┘ │
└─────────┼────────────────────────────┼──────────────────────┘
│ │
┌─────────────────────────┼────────────────────────────┼─────────────────────────┐
│ │ IPsec Mesh │ │
│ ┌───────────────┴────────────────────────────┴───────────────┐ │
│ ▼ ▼ │
│ ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │
│ │ NETHERLANDS (Primary) │ │ GREECE (DR) │ │
│ │ │ │ │ │
│ │ nlfrr01 ◄──iBGP──► nlfrr02 grfrr01 ◄──iBGP──► grfrr02 │
│ │ │ │ │ │ │ │
│ │ nlfw01 (ASA 5508-X) │ │ grfw01 (ASA 5508-X) │ │
│ │ │ eBGP (AS65000↔65001) │ │ │ eBGP (AS65000↔65001) │ │
│ │ ┌────▼────────────────┐ │ │ ┌────▼────────────────┐ │ │
│ │ │ K8s: nlcl01k8s │ │ │ │ K8s: grcl01k8s │ │ │
│ │ │ 7 nodes (3CP + 4W) │◄═ClusterMesh═►│ 7 nodes (3CP + 4W) │ │ │
│ │ └────────────────────┘ │ │ └────────────────────┘ │ │
│ │ │ │ │ │
│ │ Proxmox: pve01/02/03 │ │ Proxmox: pve01/02 │ │
│ │ Storage: Synology DS1621+ │ │ Storage: Local ZFS │ │
│ │ LXC: 116, QEMU: 22 │ │ LXC: 10+, QEMU: 10+ │ │
│ └─────────────────────────────────┘ └─────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────────────┘
| Site | Location | Role | Infrastructure | K8s Nodes |
|---|---|---|---|---|
| NL | Netherlands | Primary | 3x Proxmox, 2x Synology NAS, Cisco ASA/Switch/Router/LTE/4xAP | 7 (3 CP + 4 W) |
| GR | Greece | Disaster Recovery | 2x Proxmox, Local ZFS, Cisco ASA | 7 (3 CP + 4 W) |
| CH | Zürich, Switzerland | Edge (iFog VPS) | FRR BGP router | - |
| NO | Oslo, Norway | Edge (Gigahost VPS) | FRR BGP router | - |
| Node | Hardware | CPU | RAM | Storage | VMs |
|---|---|---|---|---|---|
| nl-pve01 | Venus Series Mini PC | i9-12900H (20T) | 96 GB | NVMe ZFS | 75 LXC, 8 QEMU |
| nl-pve02 | Synology DS1621+ VM | Ryzen V1500B (8C) | 16 GB | NAS iSCSI | 7 LXC |
| nl-pve03 | Dell Precision 3680 | i9-14900K (32T) | 128 GB | NVMe ZFS | 34 LXC, 14 QEMU |
| gr-pve01 | Venus Series Mini PC | i9-12900H (20T) | 96 GB | NVMe ZFS | — |
| gr-pve02 | Dell PowerEdge T110 II | Xeon E3-1270 V2 (8T) | 32 GB | SSD ZFS | — |
production/
├── k8s/ # Kubernetes (OpenTofu + Atlantis)
│ ├── main.tf # Root orchestrator — calls all modules
│ ├── variables.tf # Input variables (connection, sizing, feature flags)
│ ├── providers.tf # Kubernetes + Helm providers
│ ├── terraform.tfvars # Variable overrides
│ ├── _core/ # Platform infrastructure modules
│ │ ├── cilium/ # CNI, BGP, ClusterMesh, SPIRE mTLS, Hubble
│ │ ├── tetragon/ # eBPF security monitoring (5 TracingPolicies)
│ │ ├── ingress-nginx/ # Hardened ingress (ModSecurity WAF, HSTS)
│ │ ├── cert-manager/ # Let's Encrypt DNS-01 via Cloudflare
│ │ ├── external-secrets/ # ClusterSecretStore "openbao"
│ │ ├── nfs-provisioner/ # StorageClass "nfs-client"
│ │ ├── nl-nas01-csi/ # Synology iSCSI CSI (retain + delete classes)
│ │ ├── gitlab-agent/ # GitLab K8s agent (2 replicas)
│ │ ├── REDACTED_d97cef76/ # Dashboard (vendored chart)
│ │ └── pod-disruption-budgets/ # CoreDNS + Metrics Server PDBs
│ ├── namespaces/ # Application namespace modules
│ │ ├── monitoring/ # REDACTED_d8074874, Thanos, Goldpinger, BGPalerter, SNMP
│ │ ├── logging/ # Loki (single-binary, SeaweedFS S3) + Promtail (syslog)
│ │ ├── seaweedfs/ # S3 storage, filer.sync for NL↔GR replication
│ │ ├── argocd/ # Argo CD (2 replica server + repo-server)
│ │ ├── awx/ # AWX Operator (Postgres on iSCSI, projects on NFS)
│ │ ├── gatus/ # Status page with BGP/IPsec/network health monitoring
│ │ └── well-known/ # RFC 8615 security.txt, multi-domain
│ ├── argocd-apps/ # Argo CD application manifests (YAML)
│ │ ├── bentopdf/ # PDF converter
│ │ ├── echo-server/ # HTTP echo at echo.example.net
│ │ ├── pihole/ # DNS ad-blocker with Cilium network policy
│ │ └── velero/ # Backup (daily 2AM + weekly Sunday 3AM)
│ └── cluster-snapshots/ # Auto-generated daily (03:00 UTC)
│ ├── latest.md # Current cluster state
│ ├── cluster-context-lite.md # 3K token summary for quick troubleshooting
│ ├── cluster-context-full.md # 10K token deep analysis
│ └── history/ # 130+ daily snapshots since 2025-11-27
│
├── network/ # Cisco Network Automation
│ ├── configs/ # Raw running-config files (no extension)
│ │ ├── Router/ # nlrtr01, nl-lte01
│ │ ├── Switch/ # nlsw01
│ │ ├── Firewall/ # nlfw01
│ │ └── Access-Point/ # nlap01-04
│ ├── scripts/ # Python automation (11 scripts)
│ │ ├── detect_drift.py # Drift detection (device as source of truth)
│ │ ├── auto_sync_drift.py # Sync drifted configs to git
│ │ ├── pre_deploy_drift_gate.py # Block deploy on true drift
│ │ ├── validate_syntax.py # Syntax validation (hostname, no dangerous patterns)
│ │ ├── generate_diff.py # Hierarchical diff generator (JSON)
│ │ ├── generate_diff_ciscoconfparse.py # Alt diff generator
│ │ ├── direct_deploy.py # Deploy diffs via Netmiko SSH
│ │ ├── post_validate.py # Post-deploy health check
│ │ ├── sync_from_device.py # Pull device config → create MR
│ │ ├── filter_dynamic_content.py # Strip timestamps/checksums (library)
│ │ └── rebase-after-drift.sh # Rebase branch after drift MR merge
│ └── ansible/ # deploy.yml playbook (Netmiko backend)
│
├── pve/ # Proxmox VM/LXC Configs
│ ├── nl-pve01/
│ │ ├── lxc/ # 75 container configs (VMID.conf)
│ │ └── qemu/ # 8 VM configs (VMID.conf)
│ ├── nl-pve02/
│ │ └── lxc/ # 7 container configs
│ ├── nl-pve03/
│ │ ├── lxc/ # 34 container configs
│ │ └── qemu/ # 14 VM configs
│ ├── scripts/ # Drift detection + config normalization
│ └── templates/ # Empty (reserved)
│
├── docker/ # Docker Compose Services (51 hosts, 80+ services)
│ ├── nlgpu01/ # 21 GPU services (see Docker Services section)
│ ├── nl-matrix01/matrix/ # Matrix homeserver (see Matrix section)
│ ├── nlpinchflat01/ # YouTube archiver (see Pinchflat section)
│ └── ... # 50+ more hosts
│
├── native/ # Non-Docker HA Cluster Configs (read-only snapshots)
│ ├── ncha/ # Nextcloud HA: 9-layer stack, 21 hosts
│ │ ├── nlnc01/ # Apache, PHP, config.php, fstab, crontabs
│ │ └── nlnc02/ # Identical config (shared OCFS2 storage)
│ └── haha/ # Home Assistant HA: 3-node Pacemaker cluster
│ ├── nlcl01iot01/ # Pacemaker, Corosync, ESPHome, Z2M, configs
│ └── nlcl01iot02/ # Same (active/standby pair, shared NFS)
│
├── ci/ # Modular GitLab CI Pipeline Includes (8 files)
│ ├── cisco.yml # Cisco: drift → validate → deploy → verify
│ ├── docker.yml # Docker: rsync + compose pull/up, Matrix notifications
│ ├── k8s.yml # K8s: tofu fmt/validate + Argo CD bootstrap
│ ├── lxc.yml # Proxmox LXC: backup → stop → replace → start
│ ├── qemu.yml # Proxmox QEMU: same as LXC for VMs
│ ├── openbao.yml # Shared OpenBao JWT auth templates
│ ├── images.yml # CI runner image builds (6 images, manual trigger)
│ └── github-sync.yml # Sanitized public mirror to GitHub
│
├── images/ # CI Runner Image Build Contexts (6 images)
│ ├── cisco-ee/ # Python + Netmiko + Ansible Cisco collections (~1.1GB)
│ ├── docker-runner/ # Alpine + SSH + rsync (~90MB)
│ ├── pve-runner/ # Alpine + SSH + diff (~28MB)
│ ├── k8s-runner/ # Alpine + OpenTofu (pre-cached providers) + kubectl + helm (~347MB)
│ ├── hugo-runner/ # Debian + Hugo extended + MinIO client (~309MB, versioned tags)
│ └── github-sync-runner/ # Debian + git-filter-repo + gitleaks + trufflehog (~444MB)
│
├── ansible/playbooks/ # AWX-managed playbooks (synced from common repo)
│ ├── cert-manager/ # TLS cert sync to NPM
│ ├── docker/ # Docker project collection
│ ├── pve/ # Proxmox automation
│ ├── snmpd/ # SNMP daemon management
│ ├── ssh/ # SSH key distribution
│ └── updates/ # System updates
│
├── .gitlab-ci.yml # Main pipeline: 5 stages, 8 includes
├── atlantis.yaml # Atlantis project config (project: k8s)
└── renovate.json # Automated dependency updates
Note: native/ configs are read-only snapshots — there is no CI/CD pipeline for native services. To update: SSH to the VM, make changes, copy the updated config back, commit with chore(native): sync <service> config from <host>. Both NCHA and HAHA share the DRBD+OCFS2+NFS storage cluster (nlcl01file01/nlcl01file02/filearb01).
Every directory has its own CI/CD pipeline. Push to main triggers automatic deployment (except K8s, which requires MRs + Atlantis).
| Directory | Pipeline | Deploy Method | Trigger |
|---|---|---|---|
k8s/**/*.tf | ci/k8s.yml + Atlantis | tofu plan/apply via MR | MR + atlantis apply comment |
k8s/argocd-apps/ | ci/k8s.yml | kubectl apply + Argo CD sync | Push to main |
network/configs/ | ci/cisco.yml | Hierarchical diffs via Netmiko SSH | Push to main |
pve/**/lxc/*.conf | ci/lxc.yml | SCP + pct commands (causes downtime) | Push to main |
pve/**/qemu/*.conf | ci/qemu.yml | SCP + qm commands (causes downtime) | Push to main |
docker/<host>/<svc>/ | ci/docker.yml | rsync + docker compose up -d | Push to main |
kubectl apply, no helm install outside of OpenTofu. Read-only kubectl is allowed for debugging./var/lib/vz/backup/config-backups/. Auto-rollback on failure./srv/<service>/ on each host, then docker compose pull && up -d. Dockerfile changes trigger docker compose build --no-cache instead.drift-detection → validate → pre-deploy → deploy → verify
| Pipeline | Push to main | MR | Scheduled | Bot commits skipped |
|---|---|---|---|---|
| Docker | docker/**/* changes | Validation only | No | GitLab CI Auto-Sync |
| Cisco | network/configs/**/* changes | Never deploys | Drift detection only | NL Oxidized Bot, GitLab CI Auto-Sync |
| K8s | k8s/**/*.tf changes | Validation only | No | No |
| Argo CD | k8s/argocd-apps/**/* changes | Validation only | No | No |
| LXC | pve/**/lxc/*.conf changes | No | No | GitLab CI Auto-Sync |
| QEMU | pve/**/qemu/*.conf changes | No | No | GitLab CI Auto-Sync |
| GitHub Sync | Every push to main | Dry-run (manual) | Never | — |
| Images | Never (manual only) | Never | Never | — |
openbao.yml)Three shared templates — extend these, don't duplicate the auth logic:
| Template | Provides |
|---|---|
.openbao-auth | BAO_TOKEN + fetch_secret() helper |
.openbao-cisco | Above + CISCO_USER, CISCO_PASSWORD, CISCO_ENABLE_SECRET |
.openbao-cisco-with-git | Above + GITLAB_PUSH_TOKEN + git configured for push |
All use JWT auth (VAULT_ID_TOKEN with aud https://gitlab.example.net), role gitlab-ci, CA cert for TLS.
Docker (docker.yml):
docker-compose.yml or Dockerfiledocker compose ps for exited/restarting/dead/unhealthy containers.rsyncignore in a service dir excludes files from deployment syncCisco (cisco.yml):
detect_drift.py runs on schedule — device is source of truthpre_deploy_drift_gate.py blocks deployment if device has unreported manual changes, creates MRgenerate_diff.py fetches LIVE config from device, diffs against desired, outputs hierarchical JSON diff blocksdirect_deploy.py applies diffs via Netmiko with pre/post backups and write memory/tmp/ansible_deployment_lock (process-scoped)drift_report.txt, diffs/*.json, deploy logs (7-day retention)K8s (k8s.yml):
validate_k8s_opentofu: tofu fmt -check + tofu validate with temporary local backend overridevalidate_argocd_manifests: kubectl apply --dry-run=client on all YAML except application.yamlbootstrap_argocd_apps: applies application.yaml files on push to main (idempotent)verify_k8s_infrastructure: post-merge health check — nodes, pods, PVCs, servicesk8s. State backend: GitLab Terraform HTTP.LXC/QEMU (lxc.yml, qemu.yml):
/etc/pve/lxc/ vs /etc/pve/qemu-server/) and commands (pct vs qm)diff -q → start → cleanup| Image | Base | Tag | Size | Purpose |
|---|---|---|---|---|
cisco-ee | python:3.11-slim | latest | ~1.1GB | Netmiko, ciscoconfparse, Ansible cisco.ios/asa collections |
docker-runner | alpine:3.20 | latest | ~90MB | SSH, rsync, Python + PyYAML for Docker deployments |
pve-runner | alpine:3.20 | latest | ~28MB | SSH, diff, bash for Proxmox config management |
k8s-runner | alpine:3.20 | latest | ~347MB | OpenTofu (pre-cached providers), kubectl, helm |
hugo-runner | debian:bookworm-slim | versioned (e.g. 1.5) | ~309MB | Hugo extended, MinIO client, Docker buildx, OpenBao CA |
github-sync-runner | debian:bookworm-slim | latest | ~444MB | git-filter-repo, gitleaks, trufflehog, detect-secrets, ripgrep, yq |
Build contexts live in images/<name>/. Built via ci/images.yml (manual trigger on changes). Registry: registry.example.net/infrastructure/nl/production/<name>:<tag>.
| Class | Backend | Use Case |
|---|---|---|
nfs-client | Synology DS1621+ NFS → 10.0.X.X:/volume1/k8s | Shared/low-IOPS (Grafana, Pi-hole config, AWX projects, SPIRE data) |
REDACTED_b280aec5 | Synology iSCSI | Databases, metrics, stateful (Prometheus, Loki, Postgres, Thanos, SeaweedFS) |
REDACTED_4f3da73d | Synology iSCSI | Ephemeral stateful (Alertmanager, Gatus) |
| Category | Component | Version | Description |
|---|---|---|---|
| CNI | Cilium | v1.18.4 | eBPF networking, kube-proxy replacement, BGP, ClusterMesh |
| Security | Tetragon | — | eBPF monitoring (5 TracingPolicies: process exec, sensitive file access, privilege escalation, kubectl exec, network connections). All observe-only. |
| Security | ModSecurity WAF | — | DetectionOnly mode with OWASP CRS on ingress-nginx |
| Security | SPIRE mTLS | — | Cilium mutual TLS for pod-to-pod REDACTED_6fa691d2 |
| Ingress | NGINX Ingress | v1.14.1 | Hardened (HSTS, security headers, rate limiting) |
| Storage | Synology CSI | v1.1.4 | iSCSI block storage (RWO), retain + delete classes |
| Storage | NFS Provisioner | — | NFS shares from Synology DS1621+ (RWX) |
| Storage | SeaweedFS | v4.01 | S3-compatible, filer.sync for NL↔GR active-active replication |
| Monitoring | REDACTED_d8074874 | v79.10.0 | Prometheus (2 replicas, 200Gi each, 1095-day retention, site label nl) |
| Monitoring | Thanos | — | Query (2 replicas) + Store (2 replicas, SeaweedFS S3) + Compactor. GR store via ClusterMesh. |
| Monitoring | Goldpinger | — | DaemonSet for cross-node connectivity/latency testing |
| Monitoring | BGPalerter | — | Monitors AS214304 prefix for hijacks, route leaks, RPKI invalidity |
| Monitoring | SNMP Exporter | — | Polls Cisco ASA for BGP + IPsec metrics |
| Logging | Grafana Loki | v3.5.7 | Single-binary, 100Gi iSCSI, 30-day retention, SeaweedFS S3 for chunks |
| Logging | Promtail | — | Syslog receiver on LB .68:514 — all Docker containers send logs here |
| Secrets | External Secrets | v1.1.1 | ClusterSecretStore "openbao", 1h refresh, Kubernetes auth |
| TLS | cert-manager |
openbaosecret/k8s/<namespace>/<secret-name> or secret/k8s/shared/ for cross-namespaceScheduled CI job detect_k8s_drift runs tofu plan -detailed-exitcode on main. Uses OpenBao JWT auth for secrets. Exit 2 = drift warning.
Prometheus (163 rules: 150 REDACTED_d8074874 + 13 custom)
│
▼ Alertmanager webhook
n8n Prometheus Alert Receiver (24 nodes)
│ dedup by alertname:namespace, dual-store persistence
│ filters: Watchdog, InfoInhibitor, info severity
▼
Matrix #infra-nl-prod notification
│
▼
OpenClaw k8s-triage.sh (L1/L2: creates YT issue with Namespace/Pod/Alert Source fields,
│ kubectl investigation, posts findings as YT comment)
│ critical alerts auto-escalated
▼
Claude Code (L3: reads YT comments, plans fix, waits for human approval, executes)
Custom alert rules (13, in k8s/namespaces/monitoring/custom-alerts.tf):
ContainerOOMKilled, REDACTED_879bd353, REDACTED_02123891, REDACTED_a8a7eee8, REDACTED_67797f17, CiliumAgentNotReady, REDACTED_b94e0389, REDACTED_e52ce3d8, NFSMountStale, NFSMountHighLatency, ArgocdAppDegraded, ArgocdAppOutOfSync, HighPodRestartRate.
Triage policy: All non-info alerts trigger triage (no whitelist). Dedup by alertname:namespace prevents duplicate YT issues. Noisy alerts should be silenced at Alertmanager level, not filtered in the receiver.
AS214304 (public ASN, RIPE NCC assigned December 2025) with a three-tier design:
┌─────────────────────────────────────────────────────────────────────────────┐
│ EXTERNAL (eBGP) - AS214304 │
│ │
│ Internet ◄──► ch-edge (Zürich) ◄──► AS34927 (iFog GmbH) │
│ Internet ◄──► no-edge (Oslo) ◄──► AS56655 (Gigahost AS) │
│ Announces: 2a0c:9a40:8e20::/48 │
├─────────────────────────────────────────────────────────────────────────────┤
│ INTERNAL (iBGP) - AS65000 │
│ │
│ Full mesh between 6 FRR routers via IPsec tunnels: │
│ ch-edge ◄──► no-edge ◄──► nlfrr01 ◄──► nlfrr02 │
│ nlfw01 grfrr01 ◄──► grfrr02 │
│ grfw01 │
│ Route Reflectors: nlfrr01/02, grfrr01/02 │
│ Clients: ch-edge, no-edge, NL-ASA, GR-ASA │
├─────────────────────────────────────────────────────────────────────────────┤
│ KUBERNETES (eBGP) - AS65001 per site │
│ │
│ NL: ASA (AS65000) ◄──► Cilium nodes (AS65001) │
│ GR: ASA (AS65000) ◄──► Cilium nodes (AS65001) │
│ Advertises: Pod CIDRs, LoadBalancer IPs (10.0.X.X-126) │
│ Receives: Default route, internal networks │
└─────────────────────────────────────────────────────────────────────────────┘
2a0c:9a40:8e20::/48
├── :8e20::/52 → NL Site (4,096 /64s)
│ ├── :8e20::/56 → NL Infrastructure
│ └── :8e21::/56 → NL Kubernetes Pods
├── :8e30::/52 → GR Site
├── :8e40::/52 → CH Edge
├── :8e50::/52 → NO Edge
└── :8e60::/52 → Reserved
| ASN | Scope | Purpose | Nodes |
|---|---|---|---|
| AS214304 | Public | Internet peering, IPv6 announcements | ch-edge, no-edge |
| AS65000 | Private | Internal backbone iBGP mesh | All FRR routers, ASAs |
| AS65001 | Private | Kubernetes BGP (per site, isolated) | Cilium nodes |
| Device | Model | OS | IP | Role |
|---|---|---|---|---|
| nlfw01 | ASA 5508-X | ASA 9.16(4) | 10.0.X.X | Core firewall, NAT, VPN, BGP peer to K8s |
| nlsw01 | Catalyst 3850-12X48U | IOS-XE 16.12 | 10.0.X.X | Core L2 switch, 7 port-channels, 13+ VLANs |
| nlrtr01 | ISR 4321 | IOS 17.9 | 10.0.X.X | Primary router |
| nl-lte01 | C819G-LTE-MNA | IOS 15.6 | 10.0.X.X | LTE failover gateway |
| nlap01-04 | AIR-CAP3702I-E | IOS 15.3 | 10.0.X.X-96 | Wireless APs (8 SSIDs each) |
| Provider | Interface | Type |
|---|---|---|
| Freedom Internet | ASA Port-channel1.6 (VLAN 6, outside_freedom) | PPPoE, public 45.138.52-55.x — primary |
| Budget Internet (ex-XS4ALL) | ASA Port-channel1.2 (VLAN 2, outside_budget) → nlrtr01 | routed /30 10.0.X.X/30 — rtr01 terminates the Budget PPPoE on Dialer1 (145.53.163.13) — backup |
| LTE Failover | Cellular0 on nl-lte01 | NAT to 10.0.X.X/30 |
| VLAN | Name | Subnet | Security Level | Purpose |
|---|---|---|---|---|
| 2 | outside_budget | 10.0.X.X/30 | 0 | Budget Internet via nlrtr01 (routed /30; ex-outside_xs4all PPPoE) |
| 3 | outside_lte | 10.0.X.X/30 | 0 | LTE failover |
| 5 | inside_iot | 10.0.X.X/26 | 30 | IoT devices |
| 6 | outside_freedom | PPPoE | 0 | WAN uplink |
| 7-9 | rooms B/C/D | 192.168.17x.x | 40 | Guest rooms |
| 10 | inside_mgmt | 10.0.X.X/24 | 100 | Management (trusted) |
| 12 | cctv | 10.0.X.X/28 | — | CCTV cameras |
| 13 | guest | — | — | Guest WiFi |
| 14 | dmz_vpn01 | — | 0 | VPN DMZ |
| 85 | inside_k8s | 10.0.X.X/24 | 100 | Kubernetes nodes |
| 87-88 | corosync/nfs | — | 100 | Cluster comms + NFS storage |
| Po | Speed | Members | Destination |
|---|---|---|---|
| Po1 | 4 Gbps | 4x Gi | ASA firewall (trunk) |
| Po2 | 2 Gbps | 2x Gi | Synology NAS |
| Po3 | 20 Gbps | 2x 10G | PVE host (trunk) |
| Po5 | 4 Gbps | 4x Gi | Synology NAS |
| Po6 | 20 Gbps | 2x 10G | Synology NAS |
| Po7 | 20 Gbps | 2x 10G | MINISFORUM host (trunk) |
Each AP serves 8 SSIDs mapped to VLANs: IoT (5), Guest (13), CCTV (12), Mgmt (10), Room A (11), Room B (7), Room C (8), Room D (9). All WPA/AES-CCM.
| Script | Purpose | Args |
|---|---|---|
detect_drift.py | Check all/one device for drift | [device_type] [device_name] |
auto_sync_drift.py | Sync drifted configs to git | [device_type] [device_name] |
pre_deploy_drift_gate.py | Block deploy on true drift | <device_type> <device_name> |
validate_syntax.py | Syntax validation | <config_file> |
generate_diff.py | Create hierarchical diff JSON | <device_type> <device_name> <config_file> |
direct_deploy.py | Deploy diff to device | <device_type> <device_name> <diff_json> |
post_validate.py | Post-deploy health check | <device_type> <device_name> |
sync_from_device.py | Pull device config → create MR | <device_type> <device_name> |
filter_dynamic_content.py | Strip timestamps/checksums (library) | Imported by other scripts |
All Python scripts use CISCO_USER, CISCO_PASSWORD, CISCO_ENABLE_SECRET env vars (fetched from OpenBao in CI).
drift-detection → validate → pre-deploy-gate → generate-diff → deploy → verify
generate_diff.py parses configs into hierarchical blocks (interface, router, access-list) and generates only add/remove commands needed. Never replaces entire configs.filter_dynamic_content.py strips timestamps, checksums, uptime markers, NTP clock periods, and "Building configuration" headers before comparison.NL Oxidized Bot (automated backup) and GitLab CI Auto-Sync (drift sync) to prevent infinite loops.VMIDs follow a hierarchical 9-digit scheme: XYZABCDEF
| Segment | Digits | Meaning |
|---|---|---|
| XYZ | 1-3 | Host/cluster (101=nl-pve01, 102=nl-pve02, 103=nl-pve03) |
| ABC | 4-6 | Functional group (100=infra, 101=apps) |
| DEF | 7-9 | Instance number |
Exceptions: 666, 777 are IoT cluster nodes (special-purpose IDs).
LXC (typical):
nesting=1 (Docker-in-LXC support)QEMU (typical):
| Backend | Type | Used By | Purpose |
|---|---|---|---|
nl-pve01-local-zfs | Local ZFS | 72 LXC, 6 QEMU | Performance workloads |
nl-pve03-local-zfs | Local ZFS | 29 LXC, 10 QEMU | Performance workloads |
nlpvecl01-nfs | NFS (shared) | 15 LXC, 4 QEMU | Portable/shared VMs |
vmbr0 (all VMs)onboot: 1 on 80 LXC (production), onboot: 0 on 35 (test/lab)startup field controls ordering: 1-3 critical infra, 4-10 core services, 11+ apps| Name | VMID | Type | Host | CPU/RAM | Purpose |
|---|---|---|---|---|---|
| nlk8s-ctrl01-03 | 1011006xx | QEMU | nl-pve01+03 | 4C/4-8G | K8s control plane |
| nlk8s-node01-04 | 1011006xx, 1031006xx | QEMU | nl-pve01+03 | 4-8C/8G | K8s workers |
| nlpihole01 | 101100201 | LXC | nl-pve01 | 8C/4G | DNS/DHCP |
| nlnpm01 | 101100401 | LXC | nl-pve01 | 4C/4G | Reverse proxy (NPM) |
| nlpbs01 | 101100802 | LXC | nl-pve01 | 4C/8G | Proxmox Backup Server |
Communication: nl-matrix01 (Synapse + 8 bridges), librechat01, openwebui01 Media: nlgpu01/jellyfin (container absent on the host since 6 September 2026), navidrome01, lyrion01, feishin01, frigate01, pinchflat01, tautulli01 AI/GPU (all on nlgpu01, 21 services): Ollama, Stable Diffusion, Immich ML, Whisper, Piper, Milvus, LibreTranslate, Audiomuse, Viseron, Beszel, GPU-Hot, Onlogs Productivity: nlnc01/nlnc02 (Nextcloud), mealie01, docuseal01, excalidraw01, calibre01, audiobookshelf01, bookwyrm01, linkwarden02, twenty01 Infrastructure: nlhaproxy01/02, nlnpm01, redis01/02/03, influxdb01, proxysql01/02, atlantis01, sftpgo01 Monitoring: netalertx01, netvisor01 Finance: actualbudget01, ghostfolio01, wallos01
network_mode: host is default (databases, load balancers, GPU, infrastructure). Bridge for multi-container apps (BookWyrm, LibreChat, Immich).:latest). Version history tracked in .env files.unless-stopped (90%+), always (critical: Redis, InfluxDB), on-failure (workers: Celery)com.centurylinklabs.watchtower.scope=jellyfinudp://127.0.0.1:514) for Matrix stack → Promtail → Loki. JSON-file (max-size: 1m, max-file: 30) for most others.21 services sharing an RTX 3090 Ti:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1 # or "all"
capabilities: [gpu]
count: all — Ollama, Stable Diffusioncount: 1 — Immich ML/dev/dri/renderD128) — Frigate NVRAll other services use prebuilt public images.
Self-hosted Matrix stack at matrix.example.net on host nl-matrix01:
Internet → HAProxy (2 VPSes, BGP anycast) → nginx:443/6666 (TLS re-encryption)
├── mas.example.net → MAS:9090 (all paths)
└── matrix.example.net
├── auth (login/logout/refresh) → MAS:9090
├── /_matrix/push/v1/notify → ntfy:8880
├── /ntfy/ → ntfy:8880 (WebSocket)
├── /.well-known/* → static JSON (includes rtc_foci)
├── /_matrix|/_synapse/* → synapse:8008
├── /webhook/ → hookshot:9000 (localhost only)
└── / → element-web:8088
| Service | Image | Port | Status |
|---|---|---|---|
| postgres | postgres:15.13-alpine | 5432 | active |
| synapse | element-hq/synapse:v1.149.1 | 8008 | active |
| mas | element-hq/matrix-REDACTED_6fa691d2-service:1.13.0 | 9090 | active |
| element-web | vectorim/element-web:v1.12.12 | 8088 | active |
| nginx | nginx:1.29.6 | 443, 6666 | active |
| synapse-admin | synapse-admin:0.11.4 | 80 | active (localhost only) |
| matrix-hookshot | matrix-hookshot:7.3.2 | 9993, 9000 | active |
| hookshot-redis | redis:8.6.1-alpine | 6379 | active |
| ntfy | ntfy:v2.18.0 | 8880 | active |
| mautrix-signal | mautrix/signal:v0.2602.2 | 29328 | active |
| mautrix-whatsapp | mautrix/whatsapp:v0.2602.0 | 29329 | active |
synapse, mas, mm-matrix-bridge (disabled), mautrix_signal, mautrix_whatsapp
Both use megabridge v0.26+ format with encryption.appservice: true + MSC3202/MSC4190 support. Commands: !signal <cmd> / !wa <cmd>.
matrix_REDACTED_6fa691d2_servicematrix.org and integrations.ems.host onlylivekit-jwt.call.matrix.org, requires MSC3401+MSC4143Self-hosted YouTube video archiver on nlpinchflat01 with VPN protection and automatic self-healing.
┌────────────────────────────────────────────────────────────┐
│ nlpinchflat01 │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐ │
│ │ gluetun │◄───│pinchflat│ │watchtower│ │ selfheal │ │
│ │(ProtonVPN) │(archiver) │(auto-upd)│ │ (sidecar)│ │
│ │WireGuard│ │ │ └─────────┘ │ loop 15m │ │
│ └─────────┘ └─────────┘ └──────────┘ │
└────────────────────────────────────────────────────────────┘
pinchflat-selfheal.py)Runs as a Docker sidecar container (not cron), starts on docker compose up, loops every 15 minutes:
--flat-playlist --playlist-end 15 checks (seconds vs minutes per source)/tmp/yt-dlp-new then atomically mvs, clears corruption errors for retrysources — YouTube channels/playlists: id, collection_name, original_url, enabled, last_indexed_at
media_items — Individual videos: id, source_id, media_id, title, media_filepath (NULL = not downloaded), prevent_download, last_error
oban_jobs — Job queue (Elixir Oban): id, queue (media_collection_indexing / media_fetching), state (available/executing/completed/discarded/retryable), args, attempt, errors
9-layer architecture spanning 21 hosts across 3 Proxmox nodes:
DNS RR: nextcloud.example.net
├─ nlnpm01 (10.0.X.X, NL)
└─ grnpm01 (10.0.X.X, GR)
│
▼
NPM (Nginx Proxy Manager) — SSL termination, ~98 proxy configs
│
▼
HAProxy (L7, active/backup, Docker)
├─ nlhaproxy01 (pve01, 10.0.X.X) — nc01 PRIMARY, nc02 BACKUP
└─ nlhaproxy02 (pve03, 10.0.X.X) — nc02 PRIMARY, nc01 BACKUP (cross-site)
│
├── :443 → Nextcloud frontends
├── :6380 → Redis (TCP passthrough)
├── :9980 → Collabora CODE
└── :8404 → Stats dashboard
│
▼
Nextcloud Frontends (Apache 2.4.58 + PHP 8.4.18 + PHP-FPM)
├─ nlnc01 (QEMU, pve01, 10.0.X.X) — PRIMARY
└─ nlnc02 (QEMU, pve03, 10.0.X.X) — BACKUP
│
├── DB → ProxySQL (2x, 10.0.X.X+154, port 6033) → Galera MariaDB 11.8.8
├── Cache → HAProxy TCP :6380 → Redis Sentinel (3-node, master: redis02)
├── Files → NFS VIP 10.0.X.X (DRBD dual-primary + OCFS2, VLAN 88)
├── Auth → FreeIPA LDAP (sec.example.net)
├── Preview → imaginary01:9000
├── AI → gpu01 (face recognition :5000, chat :24002, LLM :24003, text2image :24004)
└── Storage → Synology NAS 10.0.X.X (homes + media via NFSv4.1)
Layer 1 — Entry Point (NPM):
| Host | VMID | PVE | IP | Role |
|---|---|---|---|---|
| nlnpm01 | 101100401 | nl-pve01 | 10.0.X.X | OpenResty 1.27.1, ~98 proxy configs |
| grnpm01 | — | gr-pve01 | 10.0.X.X | GR site entry point (DNS RR partner) |
Layer 2 — Load Balancer (HAProxy, Docker):
| Host | VMID | PVE | IP | Config |
|---|---|---|---|---|
| nlhaproxy01 | 101100402 | nl-pve01 | 10.0.X.X | HAProxy 3.3.5, nlnc01 PRIMARY |
| nlhaproxy02 | 103101007 | nl-pve03 | 10.0.X.X | HAProxy 3.3.5, nlnc02 PRIMARY (cross-site) |
Layer 3 — Nextcloud Application (Native Apache + PHP):
| Host | VMID | PVE | IPs | Version |
|---|---|---|---|---|
| nlnc01 | 101101206 | nl-pve01 | 10.0.X.X, 10.0.X.X | Nextcloud 32.0.6, PHP 8.4.18 |
| nlnc02 | 103101201 | nl-pve03 | 10.0.X.X, 10.0.X.X | Nextcloud 32.0.6, PHP 8.4.18 |
NFS mounts (both nodes, VLAN 88): 10.0.X.X:/mnt/ocfs2/nextcloud/nextcloud-app → /var/www/nextcloud, 10.0.X.X:/mnt/ocfs2/nextcloud/nextcloud-data → /mnt/nextcloud-data (NFSv4.2, nconnect=8)
Layer 4 — Database (Galera MariaDB + ProxySQL):
| Host | VMID | PVE | IP | Role |
|---|---|---|---|---|
| nlproxysql01 | 101101004 | nl-pve01 | 10.0.X.X | ProxySQL 2.7.2, port 6033 |
| nlproxysql02 | 101101008 | nl-pve03 | 10.0.X.X | ProxySQL 2.7.2, identical config |
| nlcl01mariadb01 | 101101002 | nlpve04 | 10.0.X.X | MariaDB 11.8.8 Galera, Primary (11.8 LTS since 2026-08-01) |
| nlcl01mariadb02 | 101101006 | nl-pve03 | 10.0.X.X | MariaDB 11.8.8 Galera, Primary (11.8 LTS since 2026-08-01) |
| nlcl01garbd01 | 101101007 | nl-pve02 | 10.0.X.X | Galera Arbitrator (quorum voter, no data) |
DNS: proxysql.example.net → RR .152 + .154 (Nextcloud connects directly, NOT via HAProxy)
Layer 5 — Cache (Redis Sentinel):
| Host | VMID | PVE | IP | Role |
|---|---|---|---|---|
| nlredis01 | 102100402 | nl-pve01 | 10.0.X.X | Redis 8.6.1, Slave |
| nlredis02 | 102100403 | nl-pve02 | 10.0.X.X | Redis 8.6.1, Master |
| nlredis03 | 102100404 | nl-pve03 | 10.0.X.X | Redis 8.6.1, Slave |
DNS: redis.example.net → RR .140 + .158 (HAProxy TCP proxy :6380→6379). Sentinel master: mymaster, quorum=2. Known issue: HAProxy uses PING health check only — can't detect Redis master. HAProxy has redis03 as PRIMARY; as of 2026-08-28 the actual master IS redis03 (failover after the pve02 shutdown), but they can diverge again after any failover.
Layer 6 — Shared Storage (DRBD + OCFS2 + NFS):
| Host | VMID | PVE | IPs | Role |
|---|---|---|---|---|
| nlcl01file01 | VM | nl-pve01 | 10.0.X.X, 10.0.X.X, VIP 10.0.X.X | DRBD Primary + OCFS2 + active NFS (Pacemaker-managed), 3.7TB |
| nlcl01file02 | VM | nl-pve03 | 10.0.X.X, 10.0.X.X | DRBD Primary + OCFS2, NFS passive (failover target) |
| nlcl01filearb01 | VM | nl-nas01 | 10.0.X.X, 10.0.X.X | Corosync/Pacemaker quorum voter only |
Pacemaker: 3 nodes, 7 resources. DRBD dual-Primary mode with OCFS2 (cluster filesystem). NFS floating IP 10.0.X.X. Export: /mnt/ocfs2 to *(rw,no_root_squash). This storage cluster is shared with HAHA — HAHA mounts /mnt/ocfs2/iot/.
Layer 7 — Backend Services:
| Host | IP | Service | Port |
|---|---|---|---|
| nlcode01 | 10.0.X.X | Collabora CODE (Docker) | 9980 |
| nlcode02 | 10.0.X.X | Collabora CODE (backup) | 9980 |
| nlimaginary01 | 10.0.X.X | Imaginary image processing | 9000 |
| nlwhiteboard01 | 10.0.X.X | Nextcloud Whiteboard | — |
| nlhpb01 | 10.0.X.X | Talk HPB signaling | 3478 (TURN), 8181 |
| nlgpu01 | 10.0.X.X | AI backends | 5000, 24002-24004 |
Layer 8 — Identity (FreeIPA):
| Host | IP | Role |
|---|---|---|
| nlfreeipa01 | 10.0.X.X | IPA 4.12.2, LDAP + Kerberos + DNS. Realm: SEC.NUCLEARLIGHTERS.NET |
| grfreeipa01 | — | GR site replica, DNS RR + LDAP replication |
Layer 9 — NAS Storage:
| Host | Type | IPs | Role |
|---|---|---|---|
| nl-nas01 | DS1621+ | 10.0.X.X, 10.0.X.X | NFS: homes, Media. Also DRBD arbitrator host. |
| nlsyno02 | DS1513+ | 10.0.X.X | Secondary NAS |
| Record | Resolves To | Purpose |
|---|---|---|
nextcloud.example.net | .43 (NL) + 10.0.X.X (GR) | User entry point (RR) |
redis.example.net | .140 + .158 (HAProxy) | Redis via HAProxy:6380 |
proxysql.example.net | .152 + .154 | DB via ProxySQL:6033 direct |
smtp.example.net | .71 (NL) + 10.0.X.X (GR) | Outbound email |
Key risk: nl-pve03 failure takes out half the HA cluster + ALL backend services. nl-pve01 failure takes out primary frontends + NFS server. nl-pve02 only has arbitrators — losing it doesn't cause outage but reduces quorum safety.
3-node Pacemaker cluster with STONITH fencing and automatic failover. All services run as Pacemaker-managed Docker containers on the active node.
IoT Devices (Zigbee, BLE, ESP, WiFi)
│
├─ TubesZB CC2652P7 (Ethernet Zigbee coordinator, PoE, Gi1/0/17, 10.0.X.X:6638)
├─ Olimex ESP32-POE-ISO (Ethernet Bluetooth proxy, PoE, Gi1/0/35, 10.0.X.X)
├─ M5Stack Atom Echo x2 (WiFi voice assistants, 10.0.X.X)
├─ HA Voice PE (WiFi voice assistant, ESP32-S3)
└─ 50+ Zigbee devices
│
▼
VIP: 10.0.X.X (Pacemaker-managed, floats between iot01/iot02)
│
├── :8123 → Home Assistant (4,142 entities, 82 automations)
├── :1883 → Mosquitto MQTT
├── :8099 → Zigbee2MQTT (50+ devices, channel 15, PAN ID 6754)
├── :6052 → ESPHome (7 devices)
├── :1880 → Node-RED
└── :80 → Emulated Hue (Google Home compatibility)
│
├── Files → NFS 10.0.X.X (file01 VIP) → /mnt/ocfs2/iot
├── Media → NFS 10.0.X.X (syno01) → /volume1/Media
├── AI → gpu01 Ollama (llama3.2:1b, RTX 3090 Ti)
└── External → https://homeassistant.example.net (via NPM)
| Host | VMID | PVE | IPs | Role |
|---|---|---|---|---|
| nlcl01iot01 | 666 | nl-pve01 | 10.0.X.X, 10.0.X.X | QEMU, 2C/2S, 4GB. Currently standby. |
| nlcl01iot02 | 777 | nl-pve03 | 10.0.X.X, 10.0.X.X | QEMU, 2C/2S, 4GB. Currently active. |
| nlcl01iotarb01 | — | nl-nas01 | 10.0.X.X | Synology VMM. Quorum voter + SBD + DC. |
ha_cluster (Corosync ring: iotcluster)no-quorum-policy=suicideresource-stickiness=100 (prefer current node after failover)migration-threshold=2, failure-timeout=60s, start-failure-is-fatal=false| Resource | Type | Target |
|---|---|---|
fence_iot01 | fence_pve | VMID 666 on nl-pve01, runs on iot02 |
fence_iot02 | fence_pve | VMID 777 on nl-pve03, runs on nlcl01iotarb01 |
sbd_stonith | external/sbd | /dev/sdb (all 3 nodes), pinned to nlcl01iotarb01 |
g_iot_stackResources start in order, stop in reverse. All colocated on same node. Never runs on nlcl01iotarb01.
| # | Resource | Type | Config |
|---|---|---|---|
| 1 | p_fs_iot | Filesystem | NFS 10.0.X.X:/mnt/ocfs2/iot → /mnt/iot |
| 2 | p_vip_iot | IPaddr2 | 10.0.X.X/24 |
| 3 | p_fs_media | Filesystem | NFS nl-nas01 Media → /mnt/iot/homeassistant/ha_nl-nas01 |
| 4 | p_fs_backup | Filesystem | NFS nl-nas01 Backup → backups/ |
| 5 | p_docker_home-assistant | docker | ghcr.io/home-assistant/home-assistant:stable, privileged, host network |
| 6 | p_docker_mosquitto | docker | eclipse-mosquitto:latest |
| 7 | REDACTED_9d35477e | docker | koenkk/zigbee2mqtt:latest |
| 8 | p_docker_esphome | docker | esphome/esphome:latest, privileged |
| 9 | p_docker_nodered | docker | nodered/node-red:latest |
Zigbee2MQTT (50+ devices), ESPHome (7 devices), Evohome/Lyric (Honeywell heating), Sonoff/Tuya/BleBox (WiFi devices), UniFi Protect (8 cameras), Nuki (smart locks), Enphase Envoy (solar), Proxmox VE monitoring, APC UPS, Ollama (conversation agent), Wyoming (voice pipeline), Piper TTS, Squeezebox/LMS (7 audio players), Philips TV, Frigate, Immich, CalDAV, HACS.
llama3.2:1b on nlgpu01| Brand | Count | Types |
|---|---|---|
| Sonoff | ~12 | Contact sensors, motion sensors, ZBMINIL2 switches, temp/humidity, wireless buttons |
| TUYA | ~12 | Presence detectors, illuminance sensors, smart plugs, LED bulbs, RGB strips |
| IKEA | ~5 | TRADFRI LED lights, signal repeaters, ON/OFF switch, Silverglans |
| Aqara/Xiaomi | ~4 | Cube T1 Pro, roller shade, motion sensors |
| Philips Hue | 1 | Infuse ceiling light |
| Other | ~5 | MOES IR remote + scene switch, Ecodim wall switch, b-parasite plant sensor |
| Device | Board | Network | Purpose |
|---|---|---|---|
| Olimex ESP32-POE-ISO | esp32-poe-iso | Ethernet (LAN8720) | Bluetooth proxy (BLE tracker) |
| M5Stack Atom Echo x2 | m5stack-atom | WiFi | Voice assistants with wake word |
| HA Voice PE | esp32-s3-devkitc-1 | WiFi | Voice assistant |
| AXA Remote | nodemcu (ESP8266) | WiFi | Window lock control (GPIO pulse) |
| M5 Atom TimerCam | esp32dev | WiFi | Camera 640x480 |
Both key peripherals connect via Ethernet+PoE to the Cisco switch, enabling clean Pacemaker failover without USB device rebinding:
| Device | Switch Port | IP | Purpose |
|---|---|---|---|
| TubesZB CC2652P7 | Gi1/0/17 | 10.0.X.X | Zigbee coordinator, TCP 6638 |
| Olimex ESP32-POE-ISO | Gi1/0/35 | 10.0.X.X | Bluetooth proxy |
Daily PoE power-cycle: cron resets both switch ports at 5:00/6:00 via hard_reset_tubeszb_olimex.sh (Netmiko SSH to switch).
/mnt/iot/ ← NFS from 10.0.X.X:/mnt/ocfs2/iot
├── homeassistant/
│ ├── ha_config/ ← HA config + automations
│ │ └── backups/ ← NFS from syno01:/volume1/Backup/habackup
│ └── ha_nl-nas01/ ← NFS from syno01:/volume1/Media
├── mosquitto/msqt_config/ ← Mosquitto config + password file
├── zigbee2mqtt/z2m_data/ ← Z2M config + device database
├── esphome/esphome_config/ ← ESPHome device configs (8 YAML files)
└── nodered/nodered_data/ ← Node-RED flows + settings
┌──────────────┐ ┌───────────────────┐ ┌──────────────────────┐
│ OpenBao │ ───► │ External Secrets │ ───► │ K8s Secrets │
│ (HA Cluster) │ │ Operator │ │ (per namespace) │
│ │ │ │ │ │
│ nl │ │ ClusterSecretStore │ │ • cloudflare-api │
│ openbao01/02│ │ "openbao" │ │ • grafana-admin │
│ gr │ │ │ │ • pihole-password │
│ openbao01 │ │ Refresh: 1h │ │ • velero-s3-creds │
│ │ │ Auth: Kubernetes │ │ • npm-credentials │
│ Paths: │ │ JWT auth for CI │ │ │
│ secret/k8s/ │ │ │ │ │
└──────────────┘ └───────────────────┘ └──────────────────────┘
openbao.yml shared templates), role gitlab-ci.env files alongside docker-compose.yml (private repo, intentional)CISCO_USER/CISCO_PASSWORD/CISCO_ENABLE_SECRET fetched from OpenBao at CI job runtimesecret/k8s/<namespace>/<secret-name> or secret/k8s/shared/ for cross-namespace| OS | Type | Count | Examples |
|---|---|---|---|
proxmox | server | 89 | PVE containers/VMs |
linux | server | 25 | Bare Linux (UniFi, custom) |
ping | — | 10 | Cameras, miners, K8s pihole |
ios | network | 5 | Cisco APs + LTE router |
iosxe | network | 2 | Cisco switch + router |
asa | firewall | 1 | Cisco ASA 5508-X |
apc | power | 2 | APC UPS + PDU |
dsm | storage | 2 | Synology DS1621+ / DS1513+ |
pfsense | firewall | 1 | pfSense VPN gateway |
nl{site="nl"} or {site="gr"}k8s/namespaces/monitoring/custom-alerts.tf): ContainerOOMKilled, REDACTED_879bd353, REDACTED_02123891, REDACTED_a8a7eee8, REDACTED_67797f17, CiliumAgentNotReady, REDACTED_b94e0389, REDACTED_e52ce3d8, NFSMountStale, NFSMountHighLatency, ArgocdAppDegraded, ArgocdAppOutOfSync, HighPodRestartRate| Exporter | Port | Metrics |
|---|---|---|
| FRR Exporter | :9342 | BGP sessions, prefix counts, peer state, uptime |
| IPsec Exporter | :9903 | Tunnel state, bytes in/out, packets, SA lifetime |
| SNMP Exporter | :9116 | Cisco ASA interface, CPU, memory |
| Cilium/Hubble | — | BGP peers, endpoints, network flows |
Selected content is automatically sanitized and synced to a public GitHub repository on every push to main.
git filter-repo --pathreplacements.txtREDACTED_<md5hash>| Category | Pattern | Replacement |
|---|---|---|
| Site prefixes | nl, gr | nl, gr |
| Domains | example.net (most specific first) | example.net |
| Private IPs | 192.168.x.x, 172.16-18.x.x | 10.0.X.X, 10.1.X.X |
| Public IPs | 45.138.5x.x, 185.44.82.x, 185.125.171.x | 203.0.113.X, 198.51.100.X (RFC 5737) |
| Node names | 15 explicit renames (PVE, OpenBao, FRR, storage, controllers) | Generic names |
| Secrets | SNMP communities, WiFi PSK, JWT tokens, passwords, API keys | REDACTED or REDACTED_<hash> |
| Nextcloud config | instanceid, passwordsalt, secret, dbpassword, API keys | REDACTED |
| Disk UUIDs | LVM UUIDs, fstab UUIDs | REDACTED |
| Locations | City names | Country only |
| CLAUDE.md files | All instances | Deleted |
README.md, LICENSE, atlantis.yaml, renovate.json, k8s/ (→ kubernetes/), network/configs/, network/scripts/, network/ansible/, ansible/playbooks/, ci/ (all 8 files), docker/nl-matrix01/matrix/ (→ docker/matrix/), pve/scripts/ (→ proxmox/scripts/), native/ncha/ (→ nextcloud-ha/), native/dbcluster/ (→ dbcluster/)
github-sync-runner (debian:bookworm-slim, ~444MB): git-filter-repo, gitleaks v8.21.2, trufflehog v3.82.13, detect-secrets, ripgrep, yq. Configs baked in at build time.
<type>(<scope>): <description>
Types: feat, fix, docs, refactor, test, chore
Scopes: k8s, argocd, cisco, pve, docker, ci, cilium, cert-manager, awx, thanos, clustermesh, matrix, pinchflat
docker/, network/, pve/ changes — pipelines auto-deploy on pushk8s/ changes — Atlantis handles plan/apply via MR commentsgithub-sync pipeline handles sanitization automatically.githooks/pre-commit auto-checks tofu fmt + tofu validate on staged .tf files. Install: git config core.hooksPath .githooks
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <[email protected]>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
| v1.17.1 |
| Let's Encrypt wildcard certs, DNS-01 via Cloudflare. Pushes wildcard cert to OpenBao via PushSecret for GR cluster. |
| GitOps | Argo CD | — | 4 apps (bentopdf, pihole, velero, echo-server). Chart 7.8.28, Matrix webhook → #alerts. Auto-sync with prune + selfHeal. |
| Backup | Velero | — | Daily 2AM + weekly Sunday 3AM, SeaweedFS S3 backend |
| DNS | Pi-hole | — | DNS ad-blocking with Cilium network policy |
| Automation | AWX | — | Ansible (cert sync, Docker inventory, system updates). Postgres on iSCSI, projects on NFS. |
| Dashboard | REDACTED_d97cef76 | — | Vendored chart (upstream archived) |