portfolio/content/projects/home-service.md
Nik Afiq c6ca4d23fc
All checks were successful
CI / build-check (push) Has been skipped
CI / build-and-push (push) Successful in 4m27s
Update .gitignore and add Home Service project documentation; enhance Homelab details
2026-05-06 19:50:06 +09:00

84 lines
3.9 KiB
Markdown

---
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.