fix(hermes): write default's platform credentials to .env (Matrix/API down since #1405)
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful

Since #1405 the pod has six profiles, so the gateway multiplexes
(gateway.multiplex_profiles is written true on boot). In that mode every
profile, default included, reads its platform credentials only from its
own secret scope (/opt/data/.env plus secret sources), never from the pod
environment. After the #1405 rollout the gateway logged "matrix is enabled
but no profile ... provided a bot credential" and started neither Matrix
nor the API server (API_SERVER_KEY is not a global env var). The
GATEWAY_MULTIPLEX_PROFILES=false override does not avoid this: tested in
build.1792397861 with five profiles present, the API server still stays
down.

New init container `default-env` writes API_SERVER_KEY, the MATRIX_*
settings, MATRIX_ACCESS_TOKEN and HA_TOKEN into /opt/data/.env on every
start, through Hermes's own writer (hermes_cli.config.save_env_value
replaces an existing line in place and keeps the 0600 mode). A key whose
source is gone (for example the optional hermes-homeassistant Secret) is
removed from .env with remove_env_value, so a revoked token does not
linger. HA_TOKEN is written too, so default's ${HA_TOKEN} MCP header
resolves in its own scope.

Unlike the other init containers this one is fatal: if a Hermes bump
moves the writer, the pod should fail visibly (HermesDown) rather than
come up Ready with Matrix and the API server silently down.

The plain MATRIX_* settings move into a generated ConfigMap,
hermes-matrix-config, used by envFrom in both containers, so they exist
once. The hermes container keeps its env otherwise unchanged: s6 stage2
checks API_SERVER_KEY there, and expert kanban workers resolve HA_TOKEN
from it. A new profile does not copy the root .env (checked:
`hermes profile create` with credentials in the root .env leaves the new
profile's .env without them), so experts still get no platform
credentials.

Trade-off: these credentials now also sit in the PVC's .env (0600, next
to the keys entered in the dashboard) and in any PVC backup. The same uid
can already read them from the gateway's /proc/<pid>/environ, so this is
no new boundary, but the file is easier to stumble into.

Verified locally in build.1792397861 on a volume with extra profiles and
gateway.multiplex_profiles: true: the script exits 0 on repeated runs,
each key occurs once in .env, an unset HA_TOKEN is removed on the next
run, an unwritable HERMES_HOME exits 1, and the gateway then starts the API server
("API server listening on http://0.0.0.0:8642") and connects Matrix with
the token from .env (the test homeserver was unreachable on purpose).

After merge: check the gateway log for "matrix connected" and
"api_server connected", and that https://hermes-api.renner.uno answers.
This commit is contained in:
2026-10-20 22:48:24 +02:00
parent 507bacc5fe
commit a955cb9f9d
2 changed files with 59 additions and 13 deletions
+49 -13
View File
@@ -300,6 +300,53 @@ spec:
- name: experts
mountPath: /opt/hermes-experts
readOnly: true
# Multiplexed default reads platform credentials only from .env (rationale: commit message).
- name: default-env
image: gitea.renner.uno/konairius/hermes:build.1792397861 # {"$imagepolicy": "flux-system:hermes"}
# Fatal on a writer failure: without these Matrix and the API server stay down.
command:
- /opt/hermes/.venv/bin/python
- -c
- |
import os, sys
sys.path.insert(0, "/opt/hermes")
from hermes_cli.config import remove_env_value, save_env_value
for key in ("API_SERVER_KEY", "MATRIX_HOMESERVER", "MATRIX_USER_ID",
"MATRIX_ALLOWED_USERS", "MATRIX_E2EE_MODE",
"MATRIX_RECOVERY_KEY_OUTPUT_FILE", "MATRIX_ACCESS_TOKEN", "HA_TOKEN"):
value = os.environ.get(key)
if value:
save_env_value(key, value)
elif remove_env_value(key):
print(f"WARNING: {key} is no longer set; removed from .env", file=sys.stderr)
env:
- name: HERMES_HOME
value: /opt/data
- name: HOME
value: /opt/data
- name: HA_TOKEN
valueFrom:
secretKeyRef:
name: hermes-homeassistant
key: token
optional: true
envFrom:
- configMapRef:
name: hermes-matrix-config
- secretRef:
name: hermes-api-key
- secretRef:
name: hermes-matrix
securityContext:
runAsUser: 10000
runAsGroup: 10000
runAsNonRoot: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: data
mountPath: /opt/data
# tea login hermes-bot, written from the mounted token into an
# emptyDir at the agent's ~/.config/tea.
- name: tea-login
@@ -352,19 +399,6 @@ spec:
value: "true"
- name: API_SERVER_HOST
value: 0.0.0.0
# Matrix: bot @hermes (MAS account, compatibility token on device
# HERMESBOT in hermes-matrix); E2EE store under /opt/data/platforms/matrix.
- name: MATRIX_HOMESERVER
value: https://matrix.renner.uno
- name: MATRIX_USER_ID
value: "@hermes:matrix.renner.uno"
- name: MATRIX_ALLOWED_USERS
value: "@k.renner:matrix.renner.uno"
- name: MATRIX_E2EE_MODE
value: required
# One-time cross-signing bootstrap writes the generated recovery key here (0600).
- name: MATRIX_RECOVERY_KEY_OUTPUT_FILE
value: /opt/data/platforms/matrix/recovery-key.txt
# bao: log in with the pod's ServiceAccount token, role hermes
# (kubernetes/infrastructure/openbao/policies/hermes.hcl).
- name: BAO_ADDR
@@ -396,6 +430,8 @@ spec:
key: token
optional: true
envFrom:
- configMapRef:
name: hermes-matrix-config
- secretRef:
name: hermes-api-key
- secretRef:
+10
View File
@@ -11,6 +11,16 @@ resources:
- hermes.yaml
- ingress.yaml
configMapGenerator:
# Matrix: bot @hermes (MAS account, compatibility token on device HERMESBOT in
# hermes-matrix); E2EE store and the one-time recovery key under /opt/data/platforms/matrix.
- name: hermes-matrix-config
namespace: hermes
literals:
- MATRIX_HOMESERVER=https://matrix.renner.uno
- MATRIX_USER_ID=@hermes:matrix.renner.uno
- MATRIX_ALLOWED_USERS=@k.renner:matrix.renner.uno
- MATRIX_E2EE_MODE=required
- MATRIX_RECOVERY_KEY_OUTPUT_FILE=/opt/data/platforms/matrix/recovery-key.txt
- name: hermes-soul
namespace: hermes
files: