From c6ca4d23fcd893ce21ee549fca762b38986bf3e8 Mon Sep 17 00:00:00 2001 From: Nik Afiq Date: Wed, 6 May 2026 19:50:06 +0900 Subject: [PATCH] Update .gitignore and add Home Service project documentation; enhance Homelab details --- .gitignore | 1 + content/projects/home-service.md | 83 ++++++++++++++++++++++++++++++++ content/projects/homelab.md | 36 ++++++++------ themes/terminal | 2 +- 4 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 content/projects/home-service.md diff --git a/.gitignore b/.gitignore index a460014..5b1649c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .env +.DS_Store .hugo_build.lock public/ resources/_gen/ \ No newline at end of file diff --git a/content/projects/home-service.md b/content/projects/home-service.md new file mode 100644 index 0000000..086173e --- /dev/null +++ b/content/projects/home-service.md @@ -0,0 +1,83 @@ +--- +title: "Home Service" +date: 2026-05-06 +draft: false +description: "Discord slash-command bot for home automation, built with Go microservices, gRPC, and a local LLM for natural language intent detection." +tags: ["go", "grpc", "protobuf", "microservices", "homeautomation", "ollama", "opentelemetry"] +github: "https://gitea.nik4nao.com/nik/home-services" +url: "" +--- + +## Overview + +A Go microservice system that controls Home Assistant devices through Discord slash commands. +Direct commands (`/light`, `/switch`) go straight to a typed gRPC gateway. Free-form +natural language (`/ai query`) routes through a local LLM (Ollama) for intent extraction +before dispatching the resolved action. All three services run on my homelab Kubernetes +cluster, deployed via ArgoCD. + +## Architecture + +``` +Discord user + │ + ├── /light, /switch ──(gRPC / mTLS)──▶ ha-gateway ──(REST)──▶ Home Assistant + │ + └── /ai query ────────(gRPC / mTLS)──▶ ai-gateway ──(HTTP)──▶ Ollama + │ (structured JSON intent) + └──(gRPC / mTLS)──▶ ha-gateway ──(REST)──▶ Home Assistant +``` + +Three services, each its own Go module, communicating over mTLS-secured gRPC. +Proto definitions live in a shared `gen` module compiled with `buf`. + +## Stack + +- **Language:** Go — multi-module workspace (`go.work`) +- **API layer:** gRPC + Protocol Buffers, schema managed with buf +- **Auth:** mTLS between all services — certificates provisioned by cert-manager +- **LLM:** Ollama (local, self-hosted) — structured JSON intent extraction +- **External APIs:** Home Assistant REST API, Discord Gateway API +- **Observability:** OpenTelemetry instrumentation in every service, traces exported to Tempo +- **CI/CD:** Gitea Actions — multi-arch image builds (amd64 + arm64) on every push to `main` +- **Deployment:** K3s via ArgoCD, credentials managed with Sealed Secrets + +## Architecture Pattern + +Each service follows a hexagonal (ports and adapters) layout: + +- `core/domain` — domain types (intents, lights, entities), no framework dependencies +- `core/ports` — interfaces the app layer depends on (driven ports) +- `adapters/primary` — inbound adapters: gRPC server, Discord slash-command handler +- `adapters/secondary` — outbound adapters: Home Assistant REST client, Ollama HTTP client, gRPC clients + +Dependencies point inward — adapters depend on ports, ports know nothing about adapters. + +## Highlights + +- `ai-gateway` prompts Ollama with user text and a cached list of known HA lights, parses + the model's structured JSON response as an intent, and executes it through `ha-gateway` — + unsupported or malformed responses return a plain explanation without taking action +- Light discovery results are cached in `ai-gateway` with a configurable TTL, reducing + round-trips to `ha-gateway` on every AI query +- mTLS enforced on all gRPC channels — `TLS_DIR` points each service to its cert bundle; + certificates are issued by the cluster's internal CA via cert-manager +- `discord-bot` tracks in-flight AI requests so graceful shutdown waits briefly before + terminating, avoiding dropped responses +- OpenTelemetry spans propagated across all three service boundaries — full distributed + traces visible in Grafana Tempo +- Multi-arch images (amd64 + arm64) built on every push via Gitea Actions and pushed to + the self-hosted container registry +- Test coverage at the domain and adapter layer in each service + +## Services + +| Service | Port | Responsibility | +|---|---|---| +| `ha-gateway` | 50051 | gRPC boundary for Home Assistant — entity, light, and switch RPCs | +| `ai-gateway` | 50052 | Intent extraction via Ollama, dispatches resolved actions to ha-gateway | +| `discord-bot` | — | Slash-command registration and routing (`/light`, `/switch`, `/ai`) | + +## Status + +Active and in daily use. diff --git a/content/projects/homelab.md b/content/projects/homelab.md index 86b4ccc..0943166 100644 --- a/content/projects/homelab.md +++ b/content/projects/homelab.md @@ -2,9 +2,9 @@ title: "Homelab Kubernetes Cluster" date: 2026-03-17 draft: false -description: "Self-hosted k3s cluster on bare-metal with Gitea CI/CD, multi-arch builds, Authentik SSO, and ~15 running workloads." -tags: ["kubernetes", "k3s", "homelab", "infrastructure", "traefik", "authentik"] -github: "" +description: "Self-hosted k3s cluster on bare-metal with ArgoCD GitOps, Authentik SSO, full observability stack, and ~20 running workloads." +tags: ["kubernetes", "k3s", "homelab", "infrastructure", "traefik", "authentik", "argocd", "ansible"] +github: "https://gitea.nik4nao.com/nik/homelab" url: "" --- @@ -19,36 +19,42 @@ operational patterns without a cloud bill. | Host | Role | Specs | |---|---|---| | Minisforum UM780 XTX | K3s control-plane | AMD Ryzen 7 8745H | -| HP ProDesk (nik-debian) | K3s storage agent | NFS server, mergerfs media pool | -| Mac Mini M2 | Standalone Docker host | ARM, outside the cluster | +| HP ProDesk (nik-debian) | K3s storage agent | NFS server, media pool | +| Mac Mini M2 | Standalone Docker host (Ollama, Watch Party) | ARM, outside the cluster | ## Stack - **Distribution:** k3s +- **GitOps:** Argo CD — app-of-apps pattern, all cluster state reconciled from git - **Ingress:** Traefik v3 -- **TLS:** cert-manager — Let's Encrypt (public) + internal CA (LAN) +- **TLS:** cert-manager — Let's Encrypt (public) + internal CA (`*.home.arpa`) - **Auth:** Authentik SSO — OIDC + forwardAuth proxy, TOTP MFA enforced - **DNS:** Pihole (primary + secondary, externalIPs) - **Storage:** NFS (Debian) + local-path dynamic provisioner +- **Secrets:** Sealed Secrets — encrypted at rest, committed to repo - **CI/CD:** Gitea Actions + act_runner, Docker buildx multiarch (amd64 + arm64) - **Registry:** Gitea built-in container registry -- **Observability:** Prometheus + Grafana + Loki + Promtail -- **IaC:** Ansible (host-level), Helm + raw manifests (cluster-level), all tracked in Gitea +- **Observability:** kube-prometheus-stack, Grafana, Loki, Tempo, OpenTelemetry Collector +- **VPN:** WireGuard +- **IaC:** Ansible (host bootstrap), Helm values + raw manifests (cluster-level), reconciled by Argo CD ## Highlights -- All cluster state is managed as code in a Gitea monorepo — single-file manifests per service, organised by concern -- Authentik SSO protects all web-facing services via Traefik forwardAuth; OIDC integrated with Gitea and Grafana +- All cluster state is declared in a Gitea monorepo and reconciled by Argo CD with `selfHeal: true` — changes flow through git, not kubectl +- App-of-apps pattern: a single root `Application` in Argo CD manages all child Applications, each pointing at a manifest directory or Helm values file +- Sealed Secrets used for all sensitive credentials committed to the repo — encrypted with the in-cluster controller public key, safe to store in git +- Authentik SSO protects all web-facing services via Traefik forwardAuth; OIDC integrated with Gitea, Grafana, and Argo CD +- Full distributed tracing with OpenTelemetry Collector + Tempo — home-services traces visible end-to-end in Grafana +- Dual-cert TLS strategy: internal CA for `*.home.arpa` services, Let's Encrypt for `*.nik4nao.com` public services; CA installer page serves `ca.crt` and an iOS/macOS mobileconfig profile - Multi-arch image builds (amd64 + arm64) via buildx on every push to `main`, pushed to the self-hosted registry -- Dual-cert TLS strategy: internal CA for `*.home.arpa` services, Let's Encrypt for `*.nik4nao.com` public services -- Pihole running as primary + secondary with externalIPs for LAN-wide DNS and ad-blocking - DDNS CronJob keeps the public A record in sync via the Porkbun API ## Running Workloads -Traefik, cert-manager, Pihole, Authentik, Gitea, Prometheus, Grafana, Loki, Promtail, -Jellyfin, qBittorrent, JDownloader, Photoview, Dashy, Glances, DDNS CronJob, this portfolio site. +Argo CD, Traefik, cert-manager, Sealed Secrets, Pihole, Authentik, Gitea, Prometheus, Grafana, +Loki, Tempo, OpenTelemetry Collector, Glances, Jellyfin, qBittorrent, JDownloader, Photoview, +Immich, Home Assistant, HA Gateway, AI Gateway, Discord Bot, Dashy, DDNS CronJob, this portfolio site. ## Status -Active and in daily use. \ No newline at end of file +Active and in daily use. diff --git a/themes/terminal b/themes/terminal index 5a2b4c0..4acd067 160000 --- a/themes/terminal +++ b/themes/terminal @@ -1 +1 @@ -Subproject commit 5a2b4c0f1fdb9180d525930b2c8f68a90221d245 +Subproject commit 4acd067c48195ac503541ba75f9259c7158d3792