docs(helm): annotate values.yaml, add chart README, ArgoCD example, update root README and AGENTS.md
This commit is contained in:
@@ -38,6 +38,7 @@ Frontend UI for self-hosted Honcho instances — browse memories, peers, session
|
|||||||
| `packages/web/src/test/` | Vitest unit/integration tests + setup |
|
| `packages/web/src/test/` | Vitest unit/integration tests + setup |
|
||||||
| `packages/web/e2e/` | Playwright e2e specs |
|
| `packages/web/e2e/` | Playwright e2e specs |
|
||||||
| `packages/desktop/` | Tauri shell that bundles the built web app |
|
| `packages/desktop/` | Tauri shell that bundles the built web app |
|
||||||
|
| `charts/openconcho/` | Helm 3 chart for self-hosting on Kubernetes (OCI artifact on GHCR) |
|
||||||
| `.claude/rules/` | Coding conventions (auto-loaded; stack-agnostic, applies to all agents) |
|
| `.claude/rules/` | Coding conventions (auto-loaded; stack-agnostic, applies to all agents) |
|
||||||
| `docs/` | Architecture and references |
|
| `docs/` | Architecture and references |
|
||||||
|
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -114,6 +114,34 @@ profiles: `make up` runs the `dev` profile (`build: .`), `make prod` runs the
|
|||||||
(comma-separated host globs) for when you expose the proxy. Full details and env
|
(comma-separated host globs) for when you expose the proxy. Full details and env
|
||||||
vars are in [`docs/docker.md`](docs/docker.md).
|
vars are in [`docs/docker.md`](docs/docker.md).
|
||||||
|
|
||||||
|
### Kubernetes (Helm)
|
||||||
|
|
||||||
|
The chart is published as an OCI artifact to GHCR on every tagged release.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
|
||||||
|
--version 0.14.0 \
|
||||||
|
--create-namespace --namespace openconcho \
|
||||||
|
--set honcho.defaultUrl=https://honcho.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable an Ingress and TLS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
|
||||||
|
--version 0.14.0 \
|
||||||
|
--create-namespace --namespace openconcho \
|
||||||
|
--set honcho.defaultUrl=https://honcho.example.com \
|
||||||
|
--set ingress.enabled=true \
|
||||||
|
--set ingress.className=nginx \
|
||||||
|
--set 'ingress.hosts[0].host=openconcho.example.com' \
|
||||||
|
--set 'ingress.hosts[0].paths[0].path=/' \
|
||||||
|
--set 'ingress.tls[0].secretName=openconcho-tls' \
|
||||||
|
--set 'ingress.tls[0].hosts[0]=openconcho.example.com'
|
||||||
|
```
|
||||||
|
|
||||||
|
Full chart documentation, configuration reference, and an ArgoCD Application example are in [`charts/openconcho/README.md`](charts/openconcho/README.md).
|
||||||
|
|
||||||
### Connecting to your instance
|
### Connecting to your instance
|
||||||
|
|
||||||
1. Enter the base URL of your Honcho instance (e.g. `http://localhost:8000`)
|
1. Enter the base URL of your Honcho instance (e.g. `http://localhost:8000`)
|
||||||
|
|||||||
229
charts/openconcho/README.md
Normal file
229
charts/openconcho/README.md
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
# openconcho Helm Chart
|
||||||
|
|
||||||
|
Helm 3 chart for self-hosting the [openconcho](https://github.com/offendingcommit/openconcho) web UI on Kubernetes.
|
||||||
|
|
||||||
|
The chart deploys a single nginx-unprivileged container (port 8080, UID 101) that serves the React SPA and reverse-proxies Honcho API calls under `/api` to avoid browser CORS issues.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Kubernetes 1.25+
|
||||||
|
- Helm 3.10+
|
||||||
|
- A running [Honcho](https://github.com/plastic-labs/honcho) instance reachable from within the cluster (or via a configured ingress)
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
Add the chart repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm registry login ghcr.io --username <github-username> --password <github-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
Install the chart:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
|
||||||
|
--version 0.14.0 \
|
||||||
|
--set honcho.defaultUrl=https://honcho.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with a values file (recommended):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
|
||||||
|
--version 0.14.0 \
|
||||||
|
-f my-values.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upgrading
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm upgrade openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
|
||||||
|
--version <new-version> \
|
||||||
|
-f my-values.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uninstalling
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm uninstall openconcho
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
All values with their defaults are documented in [`values.yaml`](values.yaml). Key options:
|
||||||
|
|
||||||
|
| Value | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `replicaCount` | `1` | Number of pod replicas |
|
||||||
|
| `image.repository` | `ghcr.io/offendingcommit/openconcho-web` | Container image |
|
||||||
|
| `image.tag` | `""` | Tag; defaults to chart `appVersion` |
|
||||||
|
| `image.pullPolicy` | `IfNotPresent` | Image pull policy |
|
||||||
|
| `honcho.defaultUrl` | `""` | Honcho URL pre-seeded in the UI |
|
||||||
|
| `honcho.upstreamAllowlist` | `""` | SSRF guard (comma-separated host globs) |
|
||||||
|
| `service.type` | `ClusterIP` | `ClusterIP` / `NodePort` / `LoadBalancer` |
|
||||||
|
| `service.port` | `80` | Service port |
|
||||||
|
| `ingress.enabled` | `false` | Enable Ingress resource |
|
||||||
|
| `ingress.className` | `""` | IngressClass name |
|
||||||
|
| `autoscaling.enabled` | `false` | Enable HorizontalPodAutoscaler |
|
||||||
|
| `podDisruptionBudget.enabled` | `false` | Enable PodDisruptionBudget |
|
||||||
|
| `networkPolicy.enabled` | `false` | Enable NetworkPolicy (same-namespace only) |
|
||||||
|
| `resources.requests.memory` | `32Mi` | Memory request |
|
||||||
|
| `resources.limits.memory` | `128Mi` | Memory limit |
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Minimal (ClusterIP, no ingress)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
honcho:
|
||||||
|
defaultUrl: http://honcho.honcho.svc.cluster.local:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Ingress and TLS (cert-manager)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
honcho:
|
||||||
|
defaultUrl: https://honcho.example.com
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: nginx
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
hosts:
|
||||||
|
- host: openconcho.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls:
|
||||||
|
- secretName: openconcho-tls
|
||||||
|
hosts:
|
||||||
|
- openconcho.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### With autoscaling and disruption budget
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
replicaCount: 2
|
||||||
|
|
||||||
|
autoscaling:
|
||||||
|
enabled: true
|
||||||
|
minReplicas: 2
|
||||||
|
maxReplicas: 10
|
||||||
|
targetCPUUtilizationPercentage: 70
|
||||||
|
|
||||||
|
podDisruptionBudget:
|
||||||
|
enabled: true
|
||||||
|
minAvailable: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### With NetworkPolicy
|
||||||
|
|
||||||
|
> **Note:** When `networkPolicy.enabled=true` and `ingress.enabled=true`, you must add
|
||||||
|
> a policy that allows traffic from the ingress-controller namespace. Run
|
||||||
|
> `helm status <release>` for the exact `kubectl edit` command after install.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: nginx
|
||||||
|
hosts:
|
||||||
|
- host: openconcho.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
```
|
||||||
|
|
||||||
|
### Private registry
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
image:
|
||||||
|
repository: registry.example.com/myorg/openconcho-web
|
||||||
|
tag: "0.14.0"
|
||||||
|
pullPolicy: Always
|
||||||
|
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: registry-credentials
|
||||||
|
```
|
||||||
|
|
||||||
|
## ArgoCD Application
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: openconcho
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: ghcr.io/offendingcommit/charts
|
||||||
|
chart: openconcho
|
||||||
|
targetRevision: 0.14.0
|
||||||
|
helm:
|
||||||
|
valuesObject:
|
||||||
|
honcho:
|
||||||
|
defaultUrl: https://honcho.example.com
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: nginx
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
hosts:
|
||||||
|
- host: openconcho.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls:
|
||||||
|
- secretName: openconcho-tls
|
||||||
|
hosts:
|
||||||
|
- openconcho.example.com
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: openconcho
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
```
|
||||||
|
|
||||||
|
> OCI chart sources require ArgoCD 2.10+ (OCI Helm support GA).
|
||||||
|
|
||||||
|
## Helm tests
|
||||||
|
|
||||||
|
After install, run the bundled tests to verify the deployment is healthy:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm test openconcho
|
||||||
|
```
|
||||||
|
|
||||||
|
Two test pods run and exit 0 on success:
|
||||||
|
|
||||||
|
| Test | What it checks |
|
||||||
|
|---|---|
|
||||||
|
| `test-healthz` | `GET /healthz` body equals `ok` |
|
||||||
|
| `test-spa-root` | `GET /` returns HTTP 200 |
|
||||||
|
|
||||||
|
Pass `--logs` to see output from failing pods:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm test openconcho --logs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security posture
|
||||||
|
|
||||||
|
| Control | Value |
|
||||||
|
|---|---|
|
||||||
|
| Run as UID/GID | 101 (nginx-unprivileged) |
|
||||||
|
| `runAsNonRoot` | `true` |
|
||||||
|
| `readOnlyRootFilesystem` | `true` |
|
||||||
|
| Linux capabilities | all dropped |
|
||||||
|
| `seccompProfile` | `RuntimeDefault` |
|
||||||
|
| `allowPrivilegeEscalation` | `false` |
|
||||||
|
| `automountServiceAccountToken` | `false` |
|
||||||
|
| Writable paths | `/var/cache/nginx`, `/var/run`, `/tmp` (tmpfs) |
|
||||||
@@ -1,23 +1,40 @@
|
|||||||
|
# Number of pod replicas. Increase for high availability or use autoscaling instead.
|
||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
|
|
||||||
image:
|
image:
|
||||||
|
# Container image repository. Override to use a custom registry or fork.
|
||||||
repository: ghcr.io/offendingcommit/openconcho-web
|
repository: ghcr.io/offendingcommit/openconcho-web
|
||||||
|
# Image tag. Defaults to the chart appVersion when left empty.
|
||||||
tag: ""
|
tag: ""
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
|
# Secrets for pulling images from private registries.
|
||||||
|
# Example: [{ name: my-registry-secret }]
|
||||||
imagePullSecrets: []
|
imagePullSecrets: []
|
||||||
|
|
||||||
|
# Override the name portion used in resource names and labels.
|
||||||
nameOverride: ""
|
nameOverride: ""
|
||||||
|
# Override the full resource name (normally release-name + chart-name).
|
||||||
fullnameOverride: ""
|
fullnameOverride: ""
|
||||||
|
|
||||||
serviceAccount:
|
serviceAccount:
|
||||||
|
# Create a dedicated ServiceAccount for the pod.
|
||||||
create: true
|
create: true
|
||||||
|
# Disable automatic ServiceAccount token mounting — the app never calls the Kubernetes API.
|
||||||
automount: false
|
automount: false
|
||||||
|
# Annotations to add to the ServiceAccount (e.g. for IRSA, Workload Identity, Vault).
|
||||||
annotations: {}
|
annotations: {}
|
||||||
|
# Use a pre-existing ServiceAccount instead of creating one. Ignored when create is true.
|
||||||
name: ""
|
name: ""
|
||||||
|
|
||||||
|
# Annotations applied to every pod (not the Deployment). Useful for Prometheus scraping,
|
||||||
|
# Vault agent injection, Datadog unified service tagging, etc.
|
||||||
podAnnotations: {}
|
podAnnotations: {}
|
||||||
|
# Extra labels applied to every pod.
|
||||||
podLabels: {}
|
podLabels: {}
|
||||||
|
|
||||||
|
# Pod-level security context shared by all containers.
|
||||||
|
# UID/GID 101 matches the nginx-unprivileged base image — do not change without rebuilding.
|
||||||
podSecurityContext:
|
podSecurityContext:
|
||||||
runAsNonRoot: true
|
runAsNonRoot: true
|
||||||
runAsUser: 101
|
runAsUser: 101
|
||||||
@@ -26,37 +43,62 @@ podSecurityContext:
|
|||||||
seccompProfile:
|
seccompProfile:
|
||||||
type: RuntimeDefault
|
type: RuntimeDefault
|
||||||
|
|
||||||
|
# Container-level security context.
|
||||||
securityContext:
|
securityContext:
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
readOnlyRootFilesystem: true
|
readOnlyRootFilesystem: true
|
||||||
capabilities:
|
capabilities:
|
||||||
drop: [ALL]
|
drop: [ALL]
|
||||||
|
|
||||||
|
# Directories mounted as ephemeral tmpfs (in-memory) to satisfy nginx's write requirements
|
||||||
|
# when the root filesystem is read-only. Add entries for any additional writable paths.
|
||||||
tmpfsMounts:
|
tmpfsMounts:
|
||||||
- mountPath: /var/cache/nginx
|
- mountPath: /var/cache/nginx
|
||||||
- mountPath: /var/run
|
- mountPath: /var/run
|
||||||
- mountPath: /tmp
|
- mountPath: /tmp
|
||||||
|
|
||||||
service:
|
service:
|
||||||
|
# Kubernetes Service type. Options: ClusterIP | NodePort | LoadBalancer
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
|
# Port exposed by the Service (what the Ingress or other pods target).
|
||||||
port: 80
|
port: 80
|
||||||
|
# Port the container actually listens on (nginx-unprivileged default).
|
||||||
containerPort: 8080
|
containerPort: 8080
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
# IngressClass name. Leave empty to accept the cluster default.
|
||||||
|
# Examples: nginx | traefik | alb | kong
|
||||||
className: ""
|
className: ""
|
||||||
|
# Annotations forwarded verbatim to the Ingress resource.
|
||||||
|
# Example (cert-manager + nginx-ingress):
|
||||||
|
# kubernetes.io/ingress.class: nginx
|
||||||
|
# cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
annotations: {}
|
annotations: {}
|
||||||
hosts:
|
hosts:
|
||||||
- host: openconcho.example.com
|
- host: openconcho.example.com
|
||||||
paths:
|
paths:
|
||||||
- path: /
|
- path: /
|
||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
|
# TLS configuration. Provide a Secret containing the certificate.
|
||||||
|
# Example:
|
||||||
|
# - secretName: openconcho-tls
|
||||||
|
# hosts:
|
||||||
|
# - openconcho.example.com
|
||||||
tls: []
|
tls: []
|
||||||
|
|
||||||
honcho:
|
honcho:
|
||||||
|
# Default Honcho instance URL pre-populated in the UI on first load.
|
||||||
|
# Users can change or add instances at runtime; this only seeds the initial value.
|
||||||
|
# Example: https://honcho.example.com
|
||||||
defaultUrl: ""
|
defaultUrl: ""
|
||||||
|
# Optional SSRF guard: comma-separated host globs the nginx proxy is allowed to forward to.
|
||||||
|
# Leave empty to allow any upstream. Applies only when the proxy is publicly reachable.
|
||||||
|
# Example: honcho.example.com,*.internal.example.com
|
||||||
upstreamAllowlist: ""
|
upstreamAllowlist: ""
|
||||||
|
|
||||||
|
# CPU and memory requests / limits for the web container.
|
||||||
|
# The SPA is static HTML/JS, so memory is the primary concern and CPU is negligible at rest.
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 50m
|
cpu: 50m
|
||||||
@@ -82,16 +124,55 @@ autoscaling:
|
|||||||
enabled: false
|
enabled: false
|
||||||
minReplicas: 1
|
minReplicas: 1
|
||||||
maxReplicas: 5
|
maxReplicas: 5
|
||||||
|
# Scale up when average CPU utilization across pods exceeds this percentage.
|
||||||
targetCPUUtilizationPercentage: 80
|
targetCPUUtilizationPercentage: 80
|
||||||
|
|
||||||
podDisruptionBudget:
|
podDisruptionBudget:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
# Minimum number of pods that must remain available during voluntary disruptions
|
||||||
|
# (node drains, rolling upgrades). Set maxUnavailable instead to flip the direction.
|
||||||
minAvailable: 1
|
minAvailable: 1
|
||||||
|
# Uncomment to use maxUnavailable instead — cannot set both simultaneously.
|
||||||
|
# maxUnavailable: 1
|
||||||
|
|
||||||
|
# Restrict pod-to-pod traffic at the network layer. When enabled, only pods in the
|
||||||
|
# same namespace may reach the web container.
|
||||||
|
# WARNING: if ingress is also enabled, you must add a policy that allows traffic from
|
||||||
|
# the ingress-controller namespace — run `helm status <release>` to see the reminder.
|
||||||
networkPolicy:
|
networkPolicy:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
# Spread pods across failure domains (availability zones, nodes, etc.) to reduce
|
||||||
|
# the blast radius of a single-node or single-zone failure.
|
||||||
|
# Example (zone spread):
|
||||||
|
# - maxSkew: 1
|
||||||
|
# topologyKey: topology.kubernetes.io/zone
|
||||||
|
# whenUnsatisfiable: DoNotSchedule
|
||||||
|
# labelSelector:
|
||||||
|
# matchLabels:
|
||||||
|
# app.kubernetes.io/name: openconcho
|
||||||
topologySpreadConstraints: []
|
topologySpreadConstraints: []
|
||||||
|
|
||||||
|
# Constrain pods to nodes whose labels match these key/value pairs.
|
||||||
|
# Example: kubernetes.io/arch: amd64
|
||||||
nodeSelector: {}
|
nodeSelector: {}
|
||||||
|
|
||||||
|
# Allow pods to be scheduled on tainted nodes.
|
||||||
|
# Example:
|
||||||
|
# - key: dedicated
|
||||||
|
# operator: Equal
|
||||||
|
# value: web
|
||||||
|
# effect: NoSchedule
|
||||||
tolerations: []
|
tolerations: []
|
||||||
|
|
||||||
|
# Advanced pod affinity / anti-affinity rules.
|
||||||
|
# Example (soft anti-affinity — prefer spreading across different nodes):
|
||||||
|
# podAntiAffinity:
|
||||||
|
# preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
# - weight: 100
|
||||||
|
# podAffinityTerm:
|
||||||
|
# labelSelector:
|
||||||
|
# matchLabels:
|
||||||
|
# app.kubernetes.io/name: openconcho
|
||||||
|
# topologyKey: kubernetes.io/hostname
|
||||||
affinity: {}
|
affinity: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user