Files
k3s-cluster/home-manager/deployment.yaml
2026-07-26 17:19:20 +02:00

174 lines
6.1 KiB
YAML

apiVersion: apps/v1
kind: Deployment
metadata:
name: hermes
namespace: home-manager
labels:
app: hermes
spec:
replicas: 1 # MUST be 1 — Hermes' /opt/data is single-writer.
strategy:
type: Recreate # never run two pods against the same PVC
selector:
matchLabels:
app: hermes
template:
metadata:
labels:
app: hermes
spec:
# No serviceAccountName — the agent has NO k8s API access. It manages the
# home via the Home Assistant REST API and notifies via Discord.
# Pin to the powerful amd64 node (image is linux/amd64; the NUC has 24 GiB).
nodeSelector:
kubernetes.io/arch: amd64
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: hardware
operator: In
values: ["high-memory"]
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: hermes
topologyKey: kubernetes.io/hostname
initContainers:
# Seed /opt/data with config.yaml + SOUL.md + .env on first boot only.
# ArgoCD owns the manifests; the PVC is runtime state and is NOT reconciled.
- name: seed-data
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
set -e
if [ ! -f /opt/data/config.yaml ]; then
echo "First boot: seeding /opt/data from ConfigMap + env..."
cp /seed/config.yaml /opt/data/config.yaml
cp /seed/SOUL.md /opt/data/SOUL.md
chmod 600 /opt/data/config.yaml
# Write .env from the injected Secret env vars so the s6 gateway
# finds API keys (the hermes container reads keys from /opt/data/.env).
: > /opt/data/.env
chmod 600 /opt/data/.env
for k in OPENAI_API_KEY OPENAI_BASE_URL HOMEASSISTANT_TOKEN \
DISCORD_BOT_TOKEN DISCORD_HOME_CHANNEL \
GATEWAY_ALLOW_ALL_USERS DISCORD_FREE_RESPONSE_CHANNELS \
HERMES_DASHBOARD HERMES_DASHBOARD_BASIC_AUTH_USERNAME \
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD HERMES_DASHBOARD_BASIC_AUTH_SECRET \
API_SERVER_KEY; do
eval "v=\${$k:-}"
[ -n "$v" ] && echo "$k=$v" >> /opt/data/.env
done
else
echo "/opt/data already initialized — leaving runtime state intact."
fi
mkdir -p /opt/data/home/.kube /opt/data/cron/output /opt/data/scripts
envFrom:
- secretRef:
name: hermes-env
volumeMounts:
- name: data
mountPath: /opt/data
- name: seed
mountPath: /seed
containers:
- name: hermes
image: nousresearch/hermes-agent:latest
imagePullPolicy: Always
# IMPORTANT: do NOT set `command:` — it would override the image's
# ENTRYPOINT (/init, s6-overlay), which sets up the hermes user, seeds
# config on first boot, and supervises the gateway.
args: ["gateway", "run"]
ports:
- name: gateway
containerPort: 8642
- name: dashboard
containerPort: 9119
envFrom:
- secretRef:
name: hermes-env
env:
- name: HERMES_HOME
value: /opt/data
# Home Assistant REST API base URL (in-cluster service). Non-secret, so
# it lives here rather than in the Secret. The SOUL.md uses $HOMEASSISTANT_URL.
- name: HOMEASSISTANT_URL
value: "http://home-assistant.home-assistant:80"
# Expose the Hermes gateway HTTP API on port 8642 (the Service routes
# 80 → 8642). Used by Open WebUI and other in-cluster clients to talk
# to the agent as an OpenAI-compatible endpoint.
- name: API_SERVER_ENABLED
value: "true"
# Bind the gateway API on all interfaces so other pods can reach it
# via the Service (default is 127.0.0.1/loopback, which is unreachable
# cross-pod). Auth is still enforced via API_SERVER_KEY.
- name: API_SERVER_HOST
value: "0.0.0.0"
# Distinct model id advertised on GET /v1/models. Without this both
# agents report "hermes-agent" and Open WebUI dedupes them, hiding one.
- name: API_SERVER_MODEL_NAME
value: "home-manager"
# NOTE: API_SERVER_KEY comes from the hermes-env Secret (via envFrom)
# and is also seeded into /opt/data/.env by the init container. Clients
# (e.g. Open WebUI) authenticate with `Authorization: Bearer <key>`.
# HERMES_WRITE_SAFE_ROOT is intentionally unset → defaults to HERMES_HOME
# (/opt/data). The home agent has no git workspace to write to, so the
# tighter default is correct (memory/skills/scripts live under /opt/data).
volumeMounts:
- name: data
mountPath: /opt/data
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "2Gi"
cpu: "1000m"
livenessProbe:
# Probe the dashboard port (9119, always enabled via HERMES_DASHBOARD=1
# and binds 0.0.0.0). The gateway API on 8642 is enabled via
# API_SERVER_ENABLED=true above.
tcpSocket:
port: 9119
initialDelaySeconds: 90
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 5
securityContext:
allowPrivilegeEscalation: false
volumes:
- name: data
persistentVolumeClaim:
claimName: hermes-data
- name: seed
configMap:
name: hermes-seed
---
apiVersion: v1
kind: Service
metadata:
name: home-manager
namespace: home-manager
spec:
type: ClusterIP
selector:
app: hermes
ports:
- name: gateway
port: 80
targetPort: 8642
- name: dashboard
port: 9119
targetPort: 9119