84 lines
3.9 KiB
Markdown
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.
|