fix(hermes): restore isolated user features
Some checks failed
Tests / Declarative: Post Actions failed: 2, passed: 153
Some checks failed
Tests / Declarative: Post Actions failed: 2, passed: 153
This commit is contained in:
parent
cd1fb62d0f
commit
5159c98d64
@ -29,8 +29,8 @@ data:
|
|||||||
enabled: true
|
enabled: true
|
||||||
ttl_hours: 1
|
ttl_hours: 1
|
||||||
platform_toolsets:
|
platform_toolsets:
|
||||||
cli: [clarify, session_search, web]
|
cli: [clarify, file, memory, session_search, skills, todo, web]
|
||||||
api_server: [clarify, session_search, web]
|
api_server: [clarify, file, memory, session_search, skills, todo, web]
|
||||||
dashboard:
|
dashboard:
|
||||||
public_url: https://chat.hermes.bstein.dev
|
public_url: https://chat.hermes.bstein.dev
|
||||||
display:
|
display:
|
||||||
@ -43,15 +43,17 @@ data:
|
|||||||
with questions, writing, research, planning, and learning. Be direct,
|
with questions, writing, research, planning, and learning. Be direct,
|
||||||
thoughtful, and careful. Use public web research when freshness matters.
|
thoughtful, and careful. Use public web research when freshness matters.
|
||||||
|
|
||||||
This is a personal sandbox. Never attempt cluster administration, private
|
This is a personal sandbox. You may read and write files only in this
|
||||||
service access, repository modification, terminal execution, credentials,
|
user's private workspace and may use this user's private memory, skills,
|
||||||
or coordination of Brad's project agents. Those capabilities are not part
|
profiles, and task list. Never attempt cluster administration, private
|
||||||
of this chat product. The user's conversations and files must never be
|
service access, terminal execution, credentials, or coordination of Brad's
|
||||||
mixed with another Keycloak user's state.
|
project agents. The user's conversations and files must never be mixed with
|
||||||
|
another Keycloak user's state.
|
||||||
AGENTS.md: |
|
AGENTS.md: |
|
||||||
# Private Hermes chat
|
# Private Hermes chat
|
||||||
|
|
||||||
This runtime belongs to one authenticated Keycloak identity and one private
|
This runtime belongs to one authenticated Keycloak identity and one private
|
||||||
persistent volume. Provide conversational help with clarify, session search,
|
persistent volume. Provide conversational help with the private workspace,
|
||||||
and public web tools only. Do not claim access to Kubernetes, Vault, Gitea,
|
memory, skills, profiles, task list, session search, and public web tools.
|
||||||
Brad's projects, other users, the agent coordinator, or automated triage.
|
Do not claim access to Kubernetes, Vault, Gitea, Brad's projects, other
|
||||||
|
users, the agent coordinator, or automated triage.
|
||||||
|
|||||||
61
services/hermes/chat-pvcs.yaml
Normal file
61
services/hermes/chat-pvcs.yaml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# services/hermes/chat-pvcs.yaml
|
||||||
|
# Existing tenant claims are explicit so Flux can expand them with the template.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: home-hermes-chat-tenant-0
|
||||||
|
namespace: hermes
|
||||||
|
labels:
|
||||||
|
app: hermes-chat-tenant
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: astreae
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: home-hermes-chat-tenant-1
|
||||||
|
namespace: hermes
|
||||||
|
labels:
|
||||||
|
app: hermes-chat-tenant
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: astreae
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: home-hermes-chat-tenant-2
|
||||||
|
namespace: hermes
|
||||||
|
labels:
|
||||||
|
app: hermes-chat-tenant
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: astreae
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: home-hermes-chat-tenant-3
|
||||||
|
namespace: hermes
|
||||||
|
labels:
|
||||||
|
app: hermes-chat-tenant
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: astreae
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
@ -20,7 +20,7 @@ spec:
|
|||||||
app: hermes-chat-router
|
app: hermes-chat-router
|
||||||
annotations:
|
annotations:
|
||||||
ai.bstein.dev/role: privacy-preserving-chat-tenant-router
|
ai.bstein.dev/role: privacy-preserving-chat-tenant-router
|
||||||
ai.bstein.dev/config-rev: "20260808-webui-telegram-router-image"
|
ai.bstein.dev/config-rev: "20260808-tenant-personalization"
|
||||||
vault.hashicorp.com/agent-inject: "true"
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
vault.hashicorp.com/agent-pre-populate-only: "true"
|
vault.hashicorp.com/agent-pre-populate-only: "true"
|
||||||
vault.hashicorp.com/agent-init-first: "true"
|
vault.hashicorp.com/agent-init-first: "true"
|
||||||
@ -62,7 +62,7 @@ spec:
|
|||||||
values: [rpi5]
|
values: [rpi5]
|
||||||
containers:
|
containers:
|
||||||
- name: router
|
- name: router
|
||||||
image: registry.bstein.dev/bstein/hermes-chat-router@sha256:ccb3a7c7ceba317153ff64455777459a5cfd9497bc0e6f51687feedfbfd6f4ed
|
image: registry.bstein.dev/bstein/hermes-chat-router@sha256:d6b938bbde0a229b73dd2f1fc5c4607ea9391b21278a2ae0e43c135b83587534
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
ports:
|
ports:
|
||||||
- {name: http, containerPort: 8080, protocol: TCP}
|
- {name: http, containerPort: 8080, protocol: TCP}
|
||||||
|
|||||||
@ -6,10 +6,15 @@ metadata:
|
|||||||
namespace: hermes
|
namespace: hermes
|
||||||
labels:
|
labels:
|
||||||
app: hermes-chat-tenant
|
app: hermes-chat-tenant
|
||||||
|
annotations:
|
||||||
|
kustomize.toolkit.fluxcd.io/force: enabled
|
||||||
spec:
|
spec:
|
||||||
serviceName: hermes-chat-tenant
|
serviceName: hermes-chat-tenant
|
||||||
replicas: 4
|
replicas: 4
|
||||||
podManagementPolicy: Parallel
|
podManagementPolicy: Parallel
|
||||||
|
persistentVolumeClaimRetentionPolicy:
|
||||||
|
whenDeleted: Retain
|
||||||
|
whenScaled: Retain
|
||||||
updateStrategy:
|
updateStrategy:
|
||||||
type: RollingUpdate
|
type: RollingUpdate
|
||||||
selector:
|
selector:
|
||||||
@ -116,6 +121,7 @@ spec:
|
|||||||
mv /opt/data/.env.tmp /opt/data/.env
|
mv /opt/data/.env.tmp /opt/data/.env
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
rm -f /vault/secrets/anthropic-token /vault/secrets/chat-relay-key
|
||||||
chmod 0600 /opt/data/.env
|
chmod 0600 /opt/data/.env
|
||||||
chown -R 10000:10000 /opt/data
|
chown -R 10000:10000 /opt/data
|
||||||
securityContext:
|
securityContext:
|
||||||
@ -160,8 +166,10 @@ spec:
|
|||||||
- {name: api, containerPort: 8642, protocol: TCP}
|
- {name: api, containerPort: 8642, protocol: TCP}
|
||||||
env:
|
env:
|
||||||
- {name: HERMES_HOME, value: /opt/data}
|
- {name: HERMES_HOME, value: /opt/data}
|
||||||
- {name: HERMES_AUTH_FILE, value: /shared-auth/auth.json}
|
- {name: HERMES_AUTH_FILE, value: /opt/data/auth.json}
|
||||||
- {name: HOME, value: /opt/data/home}
|
- {name: HOME, value: /opt/data/home}
|
||||||
|
- {name: TERMINAL_CWD, value: /opt/data/workspace}
|
||||||
|
- {name: HERMES_WRITE_SAFE_ROOT, value: /opt/data/workspace}
|
||||||
- {name: PATH, value: /opt/data/home/.local/bin:/opt/hermes/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin}
|
- {name: PATH, value: /opt/data/home/.local/bin:/opt/hermes/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin}
|
||||||
- {name: HERMES_DASHBOARD, value: "0"}
|
- {name: HERMES_DASHBOARD, value: "0"}
|
||||||
- {name: API_SERVER_ENABLED, value: "true"}
|
- {name: API_SERVER_ENABLED, value: "true"}
|
||||||
@ -170,7 +178,7 @@ spec:
|
|||||||
- {name: API_SERVER_CORS_ORIGINS, value: https://chat.hermes.bstein.dev}
|
- {name: API_SERVER_CORS_ORIGINS, value: https://chat.hermes.bstein.dev}
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- {name: home, mountPath: /opt/data}
|
- {name: home, mountPath: /opt/data}
|
||||||
- {name: provider-auth, mountPath: /shared-auth}
|
- {name: provider-auth, mountPath: /opt/data/auth.json, subPath: auth.json, readOnly: true}
|
||||||
- {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py}
|
- {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py}
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
tcpSocket: {port: api}
|
tcpSocket: {port: api}
|
||||||
@ -206,7 +214,7 @@ spec:
|
|||||||
- {name: webui, containerPort: 8787, protocol: TCP}
|
- {name: webui, containerPort: 8787, protocol: TCP}
|
||||||
env:
|
env:
|
||||||
- {name: HERMES_HOME, value: /opt/data}
|
- {name: HERMES_HOME, value: /opt/data}
|
||||||
- {name: HERMES_AUTH_FILE, value: /shared-auth/auth.json}
|
- {name: HERMES_AUTH_FILE, value: /opt/data/auth.json}
|
||||||
- {name: HOME, value: /opt/data/home}
|
- {name: HOME, value: /opt/data/home}
|
||||||
- {name: HERMES_WEBUI_AGENT_DIR, value: /opt/hermes}
|
- {name: HERMES_WEBUI_AGENT_DIR, value: /opt/hermes}
|
||||||
- {name: HERMES_WEBUI_HOST, value: 0.0.0.0}
|
- {name: HERMES_WEBUI_HOST, value: 0.0.0.0}
|
||||||
@ -223,7 +231,7 @@ spec:
|
|||||||
- {name: HERMES_WEBUI_TRUST_FORWARDED_PROTO, value: "1"}
|
- {name: HERMES_WEBUI_TRUST_FORWARDED_PROTO, value: "1"}
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- {name: home, mountPath: /opt/data}
|
- {name: home, mountPath: /opt/data}
|
||||||
- {name: provider-auth, mountPath: /shared-auth, readOnly: true}
|
- {name: provider-auth, mountPath: /opt/data/auth.json, subPath: auth.json, readOnly: true}
|
||||||
- {name: tmp, mountPath: /tmp}
|
- {name: tmp, mountPath: /tmp}
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet: {path: /health, port: webui}
|
httpGet: {path: /health, port: webui}
|
||||||
@ -273,4 +281,4 @@ spec:
|
|||||||
storageClassName: astreae
|
storageClassName: astreae
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
storage: 2Gi
|
storage: 10Gi
|
||||||
|
|||||||
@ -10,6 +10,7 @@ resources:
|
|||||||
- chat-configmap.yaml
|
- chat-configmap.yaml
|
||||||
- rbac.yaml
|
- rbac.yaml
|
||||||
- pvc.yaml
|
- pvc.yaml
|
||||||
|
- chat-pvcs.yaml
|
||||||
- model-gate-rbac.yaml
|
- model-gate-rbac.yaml
|
||||||
- ariadne-handoff-rbac.yaml
|
- ariadne-handoff-rbac.yaml
|
||||||
- model-gate-state.yaml
|
- model-gate-state.yaml
|
||||||
|
|||||||
@ -45,7 +45,7 @@ spec:
|
|||||||
matchLabels:
|
matchLabels:
|
||||||
app: oauth2-proxy-hermes-triage
|
app: oauth2-proxy-hermes-triage
|
||||||
ports:
|
ports:
|
||||||
- {protocol: TCP, port: 9119}
|
- {protocol: TCP, port: 8787}
|
||||||
- from:
|
- from:
|
||||||
- namespaceSelector:
|
- namespaceSelector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
@ -72,7 +72,7 @@ spec:
|
|||||||
matchLabels:
|
matchLabels:
|
||||||
app: oauth2-proxy-hermes-agent
|
app: oauth2-proxy-hermes-agent
|
||||||
ports:
|
ports:
|
||||||
- {protocol: TCP, port: 9119}
|
- {protocol: TCP, port: 8787}
|
||||||
egress:
|
egress:
|
||||||
- to:
|
- to:
|
||||||
- namespaceSelector:
|
- namespaceSelector:
|
||||||
@ -310,5 +310,5 @@ spec:
|
|||||||
operator: In
|
operator: In
|
||||||
values: [hermes, hermes-agent, hermes-chat-router]
|
values: [hermes, hermes-agent, hermes-chat-router]
|
||||||
ports:
|
ports:
|
||||||
- {protocol: TCP, port: 9119}
|
- {protocol: TCP, port: 8787}
|
||||||
- {protocol: TCP, port: 8080}
|
- {protocol: TCP, port: 8080}
|
||||||
|
|||||||
@ -96,7 +96,7 @@ spec:
|
|||||||
- --cookie-samesite=lax
|
- --cookie-samesite=lax
|
||||||
- --cookie-refresh=1h
|
- --cookie-refresh=1h
|
||||||
- --cookie-expire=8h
|
- --cookie-expire=8h
|
||||||
- --upstream=http://hermes-agent.hermes.svc.cluster.local:9119
|
- --upstream=http://hermes-agent.hermes.svc.cluster.local:8787
|
||||||
- --http-address=0.0.0.0:4180
|
- --http-address=0.0.0.0:4180
|
||||||
- --skip-provider-button=true
|
- --skip-provider-button=true
|
||||||
- --reverse-proxy=true
|
- --reverse-proxy=true
|
||||||
@ -186,7 +186,7 @@ spec:
|
|||||||
- --cookie-samesite=lax
|
- --cookie-samesite=lax
|
||||||
- --cookie-refresh=1h
|
- --cookie-refresh=1h
|
||||||
- --cookie-expire=8h
|
- --cookie-expire=8h
|
||||||
- --upstream=http://hermes-triage.hermes.svc.cluster.local:9119
|
- --upstream=http://hermes-triage.hermes.svc.cluster.local:8787
|
||||||
- --http-address=0.0.0.0:4180
|
- --http-address=0.0.0.0:4180
|
||||||
- --skip-provider-button=true
|
- --skip-provider-button=true
|
||||||
- --reverse-proxy=true
|
- --reverse-proxy=true
|
||||||
|
|||||||
@ -50,9 +50,9 @@ var deniedPrefixes = []string{
|
|||||||
"/api/cron", "/api/curator", "/api/dashboard/config", "/api/env",
|
"/api/cron", "/api/curator", "/api/dashboard/config", "/api/env",
|
||||||
"/api/escape", "/api/extensions", "/api/file", "/api/folder",
|
"/api/escape", "/api/extensions", "/api/file", "/api/folder",
|
||||||
"/api/gateway", "/api/git", "/api/git-info", "/api/health/restart", "/api/kanban", "/api/logs", "/api/mcp",
|
"/api/gateway", "/api/git", "/api/git-info", "/api/health/restart", "/api/kanban", "/api/logs", "/api/mcp",
|
||||||
"/api/memory", "/api/messaging", "/api/notes", "/api/ops",
|
"/api/messaging", "/api/notes", "/api/ops",
|
||||||
"/api/oauth", "/api/onboarding/oauth", "/api/pairing", "/api/plugins", "/api/profiles", "/api/providers",
|
"/api/oauth", "/api/onboarding/oauth", "/api/pairing", "/api/plugins", "/api/providers",
|
||||||
"/api/rollback", "/api/shutdown", "/api/skills", "/api/terminal",
|
"/api/rollback", "/api/shutdown", "/api/terminal",
|
||||||
"/api/tools", "/api/updates", "/api/webhooks", "/api/wiki",
|
"/api/tools", "/api/updates", "/api/webhooks", "/api/wiki",
|
||||||
"/api/workspace", "/api/workspaces",
|
"/api/workspace", "/api/workspaces",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -95,6 +95,33 @@ func TestRouterProxiesWebUIAndAddsTelegramShortcut(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouterAllowsTenantScopedPersonalization(t *testing.T) {
|
||||||
|
backend := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
writer.WriteHeader(http.StatusNoContent)
|
||||||
|
}))
|
||||||
|
defer backend.Close()
|
||||||
|
router, err := newTenantRouter(filepath.Join(t.TempDir(), "state.json"), 1, func(slot int) string { return backend.URL })
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, path := range []string{
|
||||||
|
"/api/memory",
|
||||||
|
"/api/memory/write",
|
||||||
|
"/api/skills",
|
||||||
|
"/api/skills/save",
|
||||||
|
"/api/profiles",
|
||||||
|
"/api/profile/create",
|
||||||
|
} {
|
||||||
|
request := httptest.NewRequest(http.MethodGet, path, nil)
|
||||||
|
request.Header.Set("X-Forwarded-User", "subject")
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(response, request)
|
||||||
|
if response.Code != http.StatusNoContent {
|
||||||
|
t.Fatalf("GET %s: got %d, want %d", path, response.Code, http.StatusNoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWebUIModelAndReasoningOverridesAreProxied(t *testing.T) {
|
func TestWebUIModelAndReasoningOverridesAreProxied(t *testing.T) {
|
||||||
backend := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
backend := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||||
if request.Method != http.MethodPost {
|
if request.Method != http.MethodPost {
|
||||||
|
|||||||
@ -36,7 +36,7 @@ spec:
|
|||||||
port: 8642
|
port: 8642
|
||||||
targetPort: api
|
targetPort: api
|
||||||
- name: dashboard
|
- name: dashboard
|
||||||
port: 9119
|
port: 8787
|
||||||
targetPort: dashboard
|
targetPort: dashboard
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
@ -52,7 +52,7 @@ spec:
|
|||||||
app: hermes-agent
|
app: hermes-agent
|
||||||
ports:
|
ports:
|
||||||
- name: dashboard
|
- name: dashboard
|
||||||
port: 9119
|
port: 8787
|
||||||
targetPort: dashboard
|
targetPort: dashboard
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user