docs(helm): annotate values.yaml, add chart README, ArgoCD example, update root README and AGENTS.md

This commit is contained in:
Offending Commit
2026-06-03 16:32:28 -05:00
parent 4ebd4cc211
commit d81e7f17ac
4 changed files with 339 additions and 0 deletions

View File

@@ -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 |

View File

@@ -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
View 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) |

View File

@@ -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: {}