Skip to content

Commit 11eec58

Browse files
Merge pull request #62 from offendingcommit/worktree-feat+helm-chart
feat(helm): add Helm chart for self-hosted openconcho web UI
2 parents 3677575 + d81e7f1 commit 11eec58

18 files changed

Lines changed: 897 additions & 0 deletions

.github/workflows/docker-publish.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,34 @@ jobs:
4444
labels: ${{ steps.meta.outputs.labels }}
4545
cache-from: type=gha
4646
cache-to: type=gha,mode=max
47+
48+
publish-chart:
49+
name: Package & push Helm chart to GHCR
50+
runs-on: ubuntu-latest
51+
needs: [publish]
52+
if: startsWith(github.ref, 'refs/tags/')
53+
steps:
54+
- uses: actions/checkout@v4
55+
56+
- uses: azure/setup-helm@v4
57+
58+
- name: Derive chart version
59+
id: version
60+
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
61+
62+
- name: Log in to GHCR (Helm OCI)
63+
run: |
64+
echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io \
65+
--username "${{ github.actor }}" \
66+
--password-stdin
67+
68+
- name: Package chart
69+
run: |
70+
helm package charts/openconcho \
71+
--version "${{ steps.version.outputs.VERSION }}" \
72+
--app-version "${{ steps.version.outputs.VERSION }}"
73+
74+
- name: Push chart
75+
run: |
76+
helm push "openconcho-${{ steps.version.outputs.VERSION }}.tgz" \
77+
oci://ghcr.io/${{ github.repository_owner }}/charts

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Frontend UI for self-hosted Honcho instances — browse memories, peers, session
3838
| `packages/web/src/test/` | Vitest unit/integration tests + setup |
3939
| `packages/web/e2e/` | Playwright e2e specs |
4040
| `packages/desktop/` | Tauri shell that bundles the built web app |
41+
| `charts/openconcho/` | Helm 3 chart for self-hosting on Kubernetes (OCI artifact on GHCR) |
4142
| `.claude/rules/` | Coding conventions (auto-loaded; stack-agnostic, applies to all agents) |
4243
| `docs/` | Architecture and references |
4344

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,34 @@ profiles: `make up` runs the `dev` profile (`build: .`), `make prod` runs the
114114
(comma-separated host globs) for when you expose the proxy. Full details and env
115115
vars are in [`docs/docker.md`](docs/docker.md).
116116

117+
### Kubernetes (Helm)
118+
119+
The chart is published as an OCI artifact to GHCR on every tagged release.
120+
121+
```bash
122+
helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
123+
--version 0.14.0 \
124+
--create-namespace --namespace openconcho \
125+
--set honcho.defaultUrl=https://honcho.example.com
126+
```
127+
128+
Enable an Ingress and TLS:
129+
130+
```bash
131+
helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
132+
--version 0.14.0 \
133+
--create-namespace --namespace openconcho \
134+
--set honcho.defaultUrl=https://honcho.example.com \
135+
--set ingress.enabled=true \
136+
--set ingress.className=nginx \
137+
--set 'ingress.hosts[0].host=openconcho.example.com' \
138+
--set 'ingress.hosts[0].paths[0].path=/' \
139+
--set 'ingress.tls[0].secretName=openconcho-tls' \
140+
--set 'ingress.tls[0].hosts[0]=openconcho.example.com'
141+
```
142+
143+
Full chart documentation, configuration reference, and an ArgoCD Application example are in [`charts/openconcho/README.md`](charts/openconcho/README.md).
144+
117145
### Connecting to your instance
118146

119147
1. Enter the base URL of your Honcho instance (e.g. `http://localhost:8000`)

charts/openconcho/Chart.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: v2
2+
name: openconcho
3+
description: Self-hosted UI for Honcho — browse memories, peers, sessions, conclusions, and chat with memory context.
4+
type: application
5+
version: 0.14.0
6+
appVersion: "0.14.0"
7+
keywords:
8+
- honcho
9+
- memory
10+
- ai
11+
home: https://github.com/offendingcommit/openconcho
12+
sources:
13+
- https://github.com/offendingcommit/openconcho
14+
maintainers:
15+
- name: offendingcommit
16+
url: https://github.com/offendingcommit

charts/openconcho/README.md

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
# openconcho Helm Chart
2+
3+
Helm 3 chart for self-hosting the [openconcho](https://github.com/offendingcommit/openconcho) web UI on Kubernetes.
4+
5+
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.
6+
7+
## Prerequisites
8+
9+
- Kubernetes 1.25+
10+
- Helm 3.10+
11+
- A running [Honcho](https://github.com/plastic-labs/honcho) instance reachable from within the cluster (or via a configured ingress)
12+
13+
## Installing
14+
15+
Add the chart repository:
16+
17+
```bash
18+
helm registry login ghcr.io --username <github-username> --password <github-token>
19+
```
20+
21+
Install the chart:
22+
23+
```bash
24+
helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
25+
--version 0.14.0 \
26+
--set honcho.defaultUrl=https://honcho.example.com
27+
```
28+
29+
Or with a values file (recommended):
30+
31+
```bash
32+
helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
33+
--version 0.14.0 \
34+
-f my-values.yaml
35+
```
36+
37+
## Upgrading
38+
39+
```bash
40+
helm upgrade openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
41+
--version <new-version> \
42+
-f my-values.yaml
43+
```
44+
45+
## Uninstalling
46+
47+
```bash
48+
helm uninstall openconcho
49+
```
50+
51+
## Configuration
52+
53+
All values with their defaults are documented in [`values.yaml`](values.yaml). Key options:
54+
55+
| Value | Default | Description |
56+
|---|---|---|
57+
| `replicaCount` | `1` | Number of pod replicas |
58+
| `image.repository` | `ghcr.io/offendingcommit/openconcho-web` | Container image |
59+
| `image.tag` | `""` | Tag; defaults to chart `appVersion` |
60+
| `image.pullPolicy` | `IfNotPresent` | Image pull policy |
61+
| `honcho.defaultUrl` | `""` | Honcho URL pre-seeded in the UI |
62+
| `honcho.upstreamAllowlist` | `""` | SSRF guard (comma-separated host globs) |
63+
| `service.type` | `ClusterIP` | `ClusterIP` / `NodePort` / `LoadBalancer` |
64+
| `service.port` | `80` | Service port |
65+
| `ingress.enabled` | `false` | Enable Ingress resource |
66+
| `ingress.className` | `""` | IngressClass name |
67+
| `autoscaling.enabled` | `false` | Enable HorizontalPodAutoscaler |
68+
| `podDisruptionBudget.enabled` | `false` | Enable PodDisruptionBudget |
69+
| `networkPolicy.enabled` | `false` | Enable NetworkPolicy (same-namespace only) |
70+
| `resources.requests.memory` | `32Mi` | Memory request |
71+
| `resources.limits.memory` | `128Mi` | Memory limit |
72+
73+
## Examples
74+
75+
### Minimal (ClusterIP, no ingress)
76+
77+
```yaml
78+
honcho:
79+
defaultUrl: http://honcho.honcho.svc.cluster.local:8000
80+
```
81+
82+
### With Ingress and TLS (cert-manager)
83+
84+
```yaml
85+
honcho:
86+
defaultUrl: https://honcho.example.com
87+
88+
ingress:
89+
enabled: true
90+
className: nginx
91+
annotations:
92+
cert-manager.io/cluster-issuer: letsencrypt-prod
93+
hosts:
94+
- host: openconcho.example.com
95+
paths:
96+
- path: /
97+
pathType: Prefix
98+
tls:
99+
- secretName: openconcho-tls
100+
hosts:
101+
- openconcho.example.com
102+
```
103+
104+
### With autoscaling and disruption budget
105+
106+
```yaml
107+
replicaCount: 2
108+
109+
autoscaling:
110+
enabled: true
111+
minReplicas: 2
112+
maxReplicas: 10
113+
targetCPUUtilizationPercentage: 70
114+
115+
podDisruptionBudget:
116+
enabled: true
117+
minAvailable: 1
118+
```
119+
120+
### With NetworkPolicy
121+
122+
> **Note:** When `networkPolicy.enabled=true` and `ingress.enabled=true`, you must add
123+
> a policy that allows traffic from the ingress-controller namespace. Run
124+
> `helm status <release>` for the exact `kubectl edit` command after install.
125+
126+
```yaml
127+
networkPolicy:
128+
enabled: true
129+
130+
ingress:
131+
enabled: true
132+
className: nginx
133+
hosts:
134+
- host: openconcho.example.com
135+
paths:
136+
- path: /
137+
pathType: Prefix
138+
```
139+
140+
### Private registry
141+
142+
```yaml
143+
image:
144+
repository: registry.example.com/myorg/openconcho-web
145+
tag: "0.14.0"
146+
pullPolicy: Always
147+
148+
imagePullSecrets:
149+
- name: registry-credentials
150+
```
151+
152+
## ArgoCD Application
153+
154+
```yaml
155+
apiVersion: argoproj.io/v1alpha1
156+
kind: Application
157+
metadata:
158+
name: openconcho
159+
namespace: argocd
160+
spec:
161+
project: default
162+
source:
163+
repoURL: ghcr.io/offendingcommit/charts
164+
chart: openconcho
165+
targetRevision: 0.14.0
166+
helm:
167+
valuesObject:
168+
honcho:
169+
defaultUrl: https://honcho.example.com
170+
ingress:
171+
enabled: true
172+
className: nginx
173+
annotations:
174+
cert-manager.io/cluster-issuer: letsencrypt-prod
175+
hosts:
176+
- host: openconcho.example.com
177+
paths:
178+
- path: /
179+
pathType: Prefix
180+
tls:
181+
- secretName: openconcho-tls
182+
hosts:
183+
- openconcho.example.com
184+
destination:
185+
server: https://kubernetes.default.svc
186+
namespace: openconcho
187+
syncPolicy:
188+
automated:
189+
prune: true
190+
selfHeal: true
191+
syncOptions:
192+
- CreateNamespace=true
193+
```
194+
195+
> OCI chart sources require ArgoCD 2.10+ (OCI Helm support GA).
196+
197+
## Helm tests
198+
199+
After install, run the bundled tests to verify the deployment is healthy:
200+
201+
```bash
202+
helm test openconcho
203+
```
204+
205+
Two test pods run and exit 0 on success:
206+
207+
| Test | What it checks |
208+
|---|---|
209+
| `test-healthz` | `GET /healthz` body equals `ok` |
210+
| `test-spa-root` | `GET /` returns HTTP 200 |
211+
212+
Pass `--logs` to see output from failing pods:
213+
214+
```bash
215+
helm test openconcho --logs
216+
```
217+
218+
## Security posture
219+
220+
| Control | Value |
221+
|---|---|
222+
| Run as UID/GID | 101 (nginx-unprivileged) |
223+
| `runAsNonRoot` | `true` |
224+
| `readOnlyRootFilesystem` | `true` |
225+
| Linux capabilities | all dropped |
226+
| `seccompProfile` | `RuntimeDefault` |
227+
| `allowPrivilegeEscalation` | `false` |
228+
| `automountServiceAccountToken` | `false` |
229+
| Writable paths | `/var/cache/nginx`, `/var/run`, `/tmp` (tmpfs) |
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
OpenConcho {{ .Chart.AppVersion }} deployed to namespace {{ .Release.Namespace }}.
2+
3+
{{- if .Values.ingress.enabled }}
4+
Access:
5+
{{- range .Values.ingress.hosts }}
6+
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}
7+
{{- end }}
8+
{{- else if eq .Values.service.type "NodePort" }}
9+
Access (NodePort):
10+
export NODE_PORT=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "openconcho.fullname" . }} -o jsonpath="{.spec.ports[0].nodePort}")
11+
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
12+
echo "http://$NODE_IP:$NODE_PORT"
13+
{{- else if eq .Values.service.type "LoadBalancer" }}
14+
Access (LoadBalancer — IP may take a few minutes):
15+
export LB_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "openconcho.fullname" . }} --template '{{ "{{" }}range (index .status.loadBalancer.ingress 0){{ "}}" }}{{ "{{" }}.{{ "}}" }}{{ "{{" }}end{{ "}}" }}')
16+
echo "http://$LB_IP:{{ .Values.service.port }}"
17+
{{- else }}
18+
Access (port-forward):
19+
kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "openconcho.fullname" . }} 8080:{{ .Values.service.port }}
20+
Then open http://localhost:8080
21+
{{- end }}
22+
23+
Run Helm tests to verify the deployment:
24+
helm test {{ .Release.Name }}
25+
26+
{{- if and .Values.networkPolicy.enabled .Values.ingress.enabled }}
27+
28+
WARNING: NetworkPolicy + Ingress are both enabled.
29+
The default NetworkPolicy allows port {{ .Values.service.containerPort }} only from pods within
30+
namespace {{ .Release.Namespace }}. Ingress controllers typically run in a separate
31+
namespace (ingress-nginx, kube-system, etc.) and will be blocked.
32+
To allow ingress-controller traffic, add a namespaceSelector rule:
33+
34+
kubectl edit networkpolicy --namespace {{ .Release.Namespace }} {{ include "openconcho.fullname" . }}
35+
36+
# Under spec.ingress[0].from, add:
37+
- namespaceSelector:
38+
matchLabels:
39+
kubernetes.io/metadata.name: <ingress-controller-namespace>
40+
{{- end }}

0 commit comments

Comments
 (0)