[ ['label' => 'lab.sh', 'path' => 'lab.sh'], ['label' => 'cluster/main.tf', 'path' => 'bootstrap/cluster/main.tf'], ['label' => 'provisioning/main.tf', 'path' => 'bootstrap/provisioning/main.tf'], ], 'blog_stack_2' => [ ['label' => 'cluster/main.tf', 'path' => 'bootstrap/cluster/main.tf'], ['label' => 'provisioning/main.tf', 'path' => 'bootstrap/provisioning/main.tf'], ], 'blog_stack_3' => [ ['label' => 'lab.sh', 'path' => 'lab.sh'], ['label' => 'cluster', 'path' => 'bootstrap/cluster/main.tf'], ['label' => 'platform', 'path' => 'bootstrap/platform/main.tf'], ['label' => 'apps', 'path' => 'bootstrap/apps/main.tf'], ['label' => 'provisioning', 'path' => 'bootstrap/provisioning/main.tf'], ['label' => 'edge', 'path' => 'bootstrap/edge/main.tf'], ], 'blog_stack_4' => [ ['label' => 'platform/main.tf', 'path' => 'bootstrap/platform/main.tf'], ], 'blog_stack_5' => [ ['label' => 'apps/main.tf', 'path' => 'bootstrap/apps/main.tf'], ], 'blog_stack_6' => [ ['label' => 'edge/main.tf', 'path' => 'bootstrap/edge/main.tf'], ['label' => 'nginx template', 'path' => 'bootstrap/edge/templates/default.conf.tftpl'], ['label' => 'HAProxy template', 'path' => 'bootstrap/edge/templates/haproxy.cfg.tftpl'], ], 'blog_stack_7' => [ ['label' => 'cv-theme.js', 'path' => 'apps/website/cv-theme.js'], ['label' => 'cv.php', 'path' => 'apps/website/cv.php'], ['label' => 'styles.css', 'path' => 'apps/website/styles.css'], ], 'blog_stack_8' => [ ['label' => 'media-cruncher.js', 'path' => 'apps/demos-static/public/media-cruncher/media-cruncher.js'], ['label' => 'media-cruncher page', 'path' => 'apps/demos-static/public/media-cruncher/index.html'], ], 'blog_stack_9' => [ ['label' => 'demo catalog', 'path' => 'apps/demos-static/public/index.html'], ['label' => 'network-quality.js', 'path' => 'apps/demos-static/public/network-quality/network-quality.js'], ['label' => 'dev-toolbelt.js', 'path' => 'apps/demos-static/public/dev-toolbelt/dev-toolbelt.js'], ['label' => 'architecture-simulator.js', 'path' => 'apps/demos-static/public/architecture-simulator/architecture-simulator.js'], ], 'blog_stack_10' => [ ['label' => 'sentiment-sandbox.js', 'path' => 'apps/demos-static/public/sentiment-sandbox/sentiment-sandbox.js'], ['label' => 'model-drift.js', 'path' => 'apps/demos-static/public/model-drift/model-drift.js'], ['label' => 'privacy-redactor.js', 'path' => 'apps/demos-static/public/privacy-redactor/privacy-redactor.js'], ], 'blog_stack_11' => [ ['label' => 'demos Dockerfile', 'path' => 'apps/demos-static/Dockerfile'], ['label' => 'demos web-app.yaml', 'path' => 'apps/demos-static/web-app.yaml'], ['label' => 'website demos.php', 'path' => 'apps/website/demos.php'], ], 'blog_stack_12' => [ ['label' => 'provisioning/main.tf', 'path' => 'bootstrap/provisioning/main.tf'], ['label' => 'lab.sh Pimox pipeline', 'path' => 'lab.sh'], ['label' => 'provisioning README', 'path' => 'bootstrap/provisioning/README.md'], ], 'blog_stack_13' => [ ['label' => 'preseed.cfg', 'path' => 'bootstrap/provisioning/templates/preseed.cfg.tftpl'], ['label' => 'golden-node prepare', 'path' => 'bootstrap/provisioning/templates/golden-node-prepare.sh.tftpl'], ['label' => 'prepare-template', 'path' => 'bootstrap/provisioning/templates/prepare-template.sh.tftpl'], ], 'blog_stack_14' => [ ['label' => 'lab.sh worker storage guardrail', 'path' => 'lab.sh'], ['label' => 'README Pimox defaults', 'path' => 'README.md'], ], 'blog_stack_15' => [ ['label' => 'lab.sh OpenWrt pipeline', 'path' => 'lab.sh'], ['label' => 'README OpenWrt notes', 'path' => 'README.md'], ], 'blog_stack_16' => [ ['label' => 'platform/main.tf', 'path' => 'bootstrap/platform/main.tf'], ['label' => 'README monitoring', 'path' => 'README.md'], ], ]; function renderStackSourceLinks(string $stackKey, array $sourceLinks, string $sourceBase): void { if (!isset($sourceLinks[$stackKey])) { return; } echo ''; } ?> <?php echo $text['blog_title']; ?> - <?php echo $text['name']; ?>

Homelab architecture map Git push enters Gitea, Gitea Actions validates and builds app images, OpenTofu manages the cluster and provisioning layers, Debian serves PXE and preseed content, Pimox builds Debian VM templates and worker clones on NVMe storage, OpenWrt can run as an opt-in firewall VM, Argo CD syncs manifests, and the OCI edge routes traffic into Kubernetes services. Source, validation, and images Debian node 192.168.100.68 Edge access and workloads Developer laptop edit, test, push main Gitea repository https://lab2025.duckdns.org/git/ main is the release branch Gitea Actions runner Debian hosted runner runs app-only deploys Validation gates Gitleaks secret scan Trivy IaC and image posture Buildx image build linux/arm64 website + demos OpenTofu + lab.sh manual infra apply path apps command for CI deploys PXE + preseed service dnsmasq TFTP, nginx HTTP Debian 13 arm64 netboot golden-node prep scripts kubeadm control plane API server, scheduler, controller Calico pod networking GitOps mirror validated commit copied locally Argo CD reads deploy state Argo CD registry, gitea, monitoring website and demos-static apps Monitoring stack Prometheus, Grafana, Loki Mimir, Promtail, exporters Storage and backups OpenEBS retained PVs Gitea dumps and monitoring data OCI edge host nginx, HAProxy, Varnish, Squid TLS, routing, caching public DNS entry point Tailscale + NodePorts 30080 website, 30081 demos 30300 Gitea service path Raspberry Pi 192.168.100.89 arm64 Kubernetes worker website-production pods demos-static and lab apps Orange Pi 5 Plus Pimox VM 9000 template on local workers on nvme_thin_pool OVMF, virtio-scsi, qemu agent idempotent qm automation OpenWrt firewall VM VM 9050, opt-in only vmbr0 WAN, vmbr1 LAN simple firewall path DHCP optional, VLANs later Local registry :30500 php-website and demos-static pulled by arm64 workloads push workflow scan build manual infra validated Git serve boot join path Pimox template firewall VM sync apps secure tunnel service traffic image pulls

    $activityKey): ?>