Files
homelab/kubernetes/apps/hermes/hermes.yaml
T
hermes-bot d00ee70b23
review-bot reviewed: request changes
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
fix(hermes): run the Hermes image from the konairius org
2026-10-19 08:10:18 +00:00

222 lines
7.0 KiB
YAML

# All Hermes state: config.yaml, .env (keys entered in the dashboard), auth.json
# (the Anthropic OAuth login), sessions, memories, skills, cron. Mounted at
# /opt/data, the image's HERMES_HOME.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: hermes-data
namespace: hermes
spec:
accessModes:
- ReadWriteOnce
storageClassName: ceph-block
resources:
requests:
storage: 10Gi
---
# Hermes Agent: the gateway (messaging platforms, cron, API server on 8642) and
# the dashboard (9119, also the Hermes Desktop remote-gateway backend), both
# supervised by the image's s6-overlay. s6 must be PID 1 and starts as root,
# remapping and dropping to the `hermes` user (10000).
#
# One replica, Recreate: two gateways must never share one data directory.
apiVersion: apps/v1
kind: Deployment
metadata:
name: hermes
namespace: hermes
labels:
app.kubernetes.io/name: hermes
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: hermes
template:
metadata:
labels:
app.kubernetes.io/name: hermes
spec:
serviceAccountName: hermes
securityContext:
fsGroup: 10000
seccompProfile:
type: RuntimeDefault
initContainers:
# The dashboard trusts X-Forwarded-* only from listed peers; Traefik
# connects from the pod network. No env equivalent exists, so it is
# set in config.yaml on every start (idempotent).
- name: config
image: gitea.renner.uno/konairius/hermes:build.FIRST_BUILD_TS # {"$imagepolicy": "flux-system:hermes"}
command:
- /opt/hermes/.venv/bin/hermes
- config
- set
- dashboard.trusted_proxies
- '["10.244.0.0/16","fd10:244::/48"]'
env:
- name: HERMES_HOME
value: /opt/data
- name: HOME
value: /opt/data
securityContext:
runAsUser: 10000
runAsGroup: 10000
runAsNonRoot: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: data
mountPath: /opt/data
# Skills from git (ConfigMap hermes-skills) beside the PVC's skills/.
- name: skills-config
image: gitea.renner.uno/konairius/hermes:build.FIRST_BUILD_TS # {"$imagepolicy": "flux-system:hermes"}
command:
- /opt/hermes/.venv/bin/hermes
- config
- set
- skills.external_dirs
- '["/opt/hermes-skills"]'
env:
- name: HERMES_HOME
value: /opt/data
- name: HOME
value: /opt/data
securityContext:
runAsUser: 10000
runAsGroup: 10000
runAsNonRoot: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: data
mountPath: /opt/data
containers:
- name: hermes
image: gitea.renner.uno/konairius/hermes:build.FIRST_BUILD_TS # {"$imagepolicy": "flux-system:hermes"}
args: ["gateway", "run"]
env:
- name: HERMES_DASHBOARD
value: "1"
- name: HERMES_DASHBOARD_PUBLIC_URL
value: https://hermes.renner.uno
# Dashboard login: Authentik OIDC, public PKCE client (blueprint
# in apps/authentik/blueprints-configmap.yaml, group Hermes Users).
- name: HERMES_DASHBOARD_OIDC_ISSUER
value: https://auth.renner.uno/application/o/hermes/
- name: HERMES_DASHBOARD_OIDC_CLIENT_ID
value: hermes-dashboard
- name: API_SERVER_ENABLED
value: "true"
- name: API_SERVER_HOST
value: 0.0.0.0
# git as hermes-bot: the credential helper reads the mounted token.
- name: GIT_CONFIG_COUNT
value: "3"
- name: GIT_CONFIG_KEY_0
value: credential.https://gitea.renner.uno.helper
- name: GIT_CONFIG_VALUE_0
value: '!f() { test "$1" = get && echo username=hermes-bot && echo "password=$(cat /var/run/secrets/gitea/api-token)"; }; f'
- name: GIT_CONFIG_KEY_1
value: user.name
- name: GIT_CONFIG_VALUE_1
value: hermes-bot
- name: GIT_CONFIG_KEY_2
value: user.email
- name: GIT_CONFIG_VALUE_2
value: hermes-bot@konstantin-renner.de
envFrom:
- secretRef:
name: hermes-api-key
ports:
- name: dashboard
containerPort: 9119
- name: api
containerPort: 8642
# The dashboard is the user-facing surface; /api/status is public.
startupProbe:
httpGet:
path: /api/status
port: dashboard
periodSeconds: 10
failureThreshold: 30
readinessProbe:
httpGet:
path: /api/status
port: dashboard
periodSeconds: 15
livenessProbe:
httpGet:
path: /api/status
port: dashboard
periodSeconds: 60
timeoutSeconds: 10
failureThreshold: 3
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
memory: 4Gi
volumeMounts:
- name: data
mountPath: /opt/data
# Identity and house rules from git (SOUL.md beside this file).
- name: soul
mountPath: /opt/data/SOUL.md
subPath: SOUL.md
readOnly: true
- name: skills
mountPath: /opt/hermes-skills
readOnly: true
- name: wiki-key
mountPath: /var/run/secrets/wiki
readOnly: true
- name: gitea-token
mountPath: /var/run/secrets/gitea
readOnly: true
volumes:
- name: data
persistentVolumeClaim:
claimName: hermes-data
- name: soul
configMap:
name: hermes-soul
- name: skills
configMap:
name: hermes-skills
items:
- key: homelab-wiki-SKILL.md
path: homelab-wiki/SKILL.md
- key: homelab-wiki-wiki.py
path: homelab-wiki/scripts/wiki.py
- name: wiki-key
secret:
secretName: hermes-wiki
optional: true
- name: gitea-token
secret:
secretName: hermes-gitea
---
apiVersion: v1
kind: Service
metadata:
name: hermes
namespace: hermes
labels:
app.kubernetes.io/name: hermes
spec:
selector:
app.kubernetes.io/name: hermes
ports:
- name: dashboard
port: 9119
targetPort: dashboard
- name: api
port: 8642
targetPort: api