From 411227046a3dee125a555a0d1a426afed0e74ec3 Mon Sep 17 00:00:00 2001 From: Offending Commit Date: Wed, 3 Jun 2026 10:51:38 -0500 Subject: [PATCH 01/14] =?UTF-8?q?feat(helm):=20chart=20scaffold=20?= =?UTF-8?q?=E2=80=94=20Chart.yaml,=20values,=20schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- charts/openconcho/Chart.yaml | 16 +++++ charts/openconcho/values.schema.json | 67 +++++++++++++++++++ charts/openconcho/values.yaml | 97 ++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 charts/openconcho/Chart.yaml create mode 100644 charts/openconcho/values.schema.json create mode 100644 charts/openconcho/values.yaml diff --git a/charts/openconcho/Chart.yaml b/charts/openconcho/Chart.yaml new file mode 100644 index 0000000..12a4099 --- /dev/null +++ b/charts/openconcho/Chart.yaml @@ -0,0 +1,16 @@ +apiVersion: v2 +name: openconcho +description: Self-hosted UI for Honcho — browse memories, peers, sessions, conclusions, and chat with memory context. +type: application +version: 0.14.0 +appVersion: "0.14.0" +keywords: + - honcho + - memory + - ai +home: https://github.com/offendingcommit/openconcho +sources: + - https://github.com/offendingcommit/openconcho +maintainers: + - name: offendingcommit + url: https://github.com/offendingcommit diff --git a/charts/openconcho/values.schema.json b/charts/openconcho/values.schema.json new file mode 100644 index 0000000..a0fd3d3 --- /dev/null +++ b/charts/openconcho/values.schema.json @@ -0,0 +1,67 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "type": "object", + "properties": { + "replicaCount": { + "type": "integer", + "minimum": 1 + }, + "image": { + "type": "object", + "required": ["repository", "pullPolicy"], + "properties": { + "repository": { "type": "string", "minLength": 1 }, + "tag": { "type": "string" }, + "pullPolicy": { + "type": "string", + "enum": ["Always", "IfNotPresent", "Never"] + } + } + }, + "service": { + "type": "object", + "required": ["type", "port", "containerPort"], + "properties": { + "type": { + "type": "string", + "enum": ["ClusterIP", "NodePort", "LoadBalancer"] + }, + "port": { "type": "integer", "minimum": 1, "maximum": 65535 }, + "containerPort": { "type": "integer", "minimum": 1, "maximum": 65535 } + } + }, + "honcho": { + "type": "object", + "properties": { + "defaultUrl": { "type": "string" }, + "upstreamAllowlist": { "type": "string" } + } + }, + "autoscaling": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "minReplicas": { "type": "integer", "minimum": 1 }, + "maxReplicas": { "type": "integer", "minimum": 1 }, + "targetCPUUtilizationPercentage": { + "type": "integer", + "minimum": 1, + "maximum": 100 + } + } + }, + "podDisruptionBudget": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "minAvailable": { "type": "integer", "minimum": 0 } + } + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" } + } + } + } +} diff --git a/charts/openconcho/values.yaml b/charts/openconcho/values.yaml new file mode 100644 index 0000000..6448e12 --- /dev/null +++ b/charts/openconcho/values.yaml @@ -0,0 +1,97 @@ +replicaCount: 1 + +image: + repository: ghcr.io/offendingcommit/openconcho-web + tag: "" + pullPolicy: IfNotPresent + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + automount: false + annotations: {} + name: "" + +podAnnotations: {} +podLabels: {} + +podSecurityContext: + runAsNonRoot: true + runAsUser: 101 + runAsGroup: 101 + fsGroup: 101 + seccompProfile: + type: RuntimeDefault + +securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: [ALL] + +tmpfsMounts: + - mountPath: /var/cache/nginx + - mountPath: /var/run + - mountPath: /tmp + +service: + type: ClusterIP + port: 80 + containerPort: 8080 + +ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: openconcho.example.com + paths: + - path: / + pathType: Prefix + tls: [] + +honcho: + defaultUrl: "" + upstreamAllowlist: "" + +resources: + requests: + cpu: 50m + memory: 32Mi + limits: + memory: 128Mi + +livenessProbe: + httpGet: + path: /healthz + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + +readinessProbe: + httpGet: + path: /healthz + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 5 + targetCPUUtilizationPercentage: 80 + +podDisruptionBudget: + enabled: false + minAvailable: 1 + +networkPolicy: + enabled: false + +topologySpreadConstraints: [] +nodeSelector: {} +tolerations: [] +affinity: {} From 8d41455e39db51617d7476e5cc48577eb7fff158 Mon Sep 17 00:00:00 2001 From: Offending Commit Date: Wed, 3 Jun 2026 10:56:37 -0500 Subject: [PATCH 02/14] fix(helm): use http://json-schema.org/draft-07/schema# for Helm compatibility --- charts/openconcho/values.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/openconcho/values.schema.json b/charts/openconcho/values.schema.json index a0fd3d3..2b15f7b 100644 --- a/charts/openconcho/values.schema.json +++ b/charts/openconcho/values.schema.json @@ -1,5 +1,5 @@ { - "$schema": "https://json-schema.org/draft-07/schema", + "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "replicaCount": { From 02682750ab766851570eae58eb0b92761b98724f Mon Sep 17 00:00:00 2001 From: Offending Commit Date: Wed, 3 Jun 2026 10:59:04 -0500 Subject: [PATCH 03/14] feat(helm): add _helpers.tpl with name, label, and imageTag partials --- charts/openconcho/templates/_helpers.tpl | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 charts/openconcho/templates/_helpers.tpl diff --git a/charts/openconcho/templates/_helpers.tpl b/charts/openconcho/templates/_helpers.tpl new file mode 100644 index 0000000..ef8fb6a --- /dev/null +++ b/charts/openconcho/templates/_helpers.tpl @@ -0,0 +1,46 @@ +{{- define "openconcho.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "openconcho.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{- define "openconcho.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "openconcho.labels" -}} +helm.sh/chart: {{ include "openconcho.chart" . }} +{{ include "openconcho.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{- define "openconcho.selectorLabels" -}} +app.kubernetes.io/name: {{ include "openconcho.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{- define "openconcho.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "openconcho.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{- define "openconcho.imageTag" -}} +{{- .Values.image.tag | default .Chart.AppVersion }} +{{- end }} From 514e1d46c0248bfae5da1f2ceb12ca8799a81468 Mon Sep 17 00:00:00 2001 From: Offending Commit Date: Wed, 3 Jun 2026 11:00:54 -0500 Subject: [PATCH 04/14] feat(helm): add Deployment template with read-only FS, tmpfs, probes --- charts/openconcho/templates/deployment.yaml | 80 +++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 charts/openconcho/templates/deployment.yaml diff --git a/charts/openconcho/templates/deployment.yaml b/charts/openconcho/templates/deployment.yaml new file mode 100644 index 0000000..b7cc438 --- /dev/null +++ b/charts/openconcho/templates/deployment.yaml @@ -0,0 +1,80 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "openconcho.fullname" . }} + labels: + {{- include "openconcho.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "openconcho.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "openconcho.selectorLabels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "openconcho.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ include "openconcho.imageTag" . }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.containerPort }} + protocol: TCP + env: + - name: OPENCONCHO_DEFAULT_HONCHO_URL + value: {{ .Values.honcho.defaultUrl | quote }} + - name: OPENCONCHO_UPSTREAM_ALLOWLIST + value: {{ .Values.honcho.upstreamAllowlist | quote }} + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + {{- range .Values.tmpfsMounts }} + - name: {{ .mountPath | trimPrefix "/" | replace "/" "-" }} + mountPath: {{ .mountPath }} + {{- end }} + volumes: + {{- range .Values.tmpfsMounts }} + - name: {{ .mountPath | trimPrefix "/" | replace "/" "-" }} + emptyDir: + medium: Memory + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} From d5a65d73b59378f5ce39bf76e0572da478cecbda Mon Sep 17 00:00:00 2001 From: Offending Commit Date: Wed, 3 Jun 2026 11:04:15 -0500 Subject: [PATCH 05/14] fix(helm): guard tmpfs blocks when empty, cap volume names at 63 chars --- charts/openconcho/templates/deployment.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/charts/openconcho/templates/deployment.yaml b/charts/openconcho/templates/deployment.yaml index b7cc438..900d459 100644 --- a/charts/openconcho/templates/deployment.yaml +++ b/charts/openconcho/templates/deployment.yaml @@ -51,17 +51,21 @@ spec: {{- toYaml .Values.readinessProbe | nindent 12 }} resources: {{- toYaml .Values.resources | nindent 12 }} + {{- if .Values.tmpfsMounts }} volumeMounts: {{- range .Values.tmpfsMounts }} - - name: {{ .mountPath | trimPrefix "/" | replace "/" "-" }} + - name: {{ .mountPath | trimPrefix "/" | replace "/" "-" | trunc 63 | trimSuffix "-" }} mountPath: {{ .mountPath }} {{- end }} + {{- end }} + {{- if .Values.tmpfsMounts }} volumes: {{- range .Values.tmpfsMounts }} - - name: {{ .mountPath | trimPrefix "/" | replace "/" "-" }} + - name: {{ .mountPath | trimPrefix "/" | replace "/" "-" | trunc 63 | trimSuffix "-" }} emptyDir: medium: Memory {{- end }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} From ee916eabc485f37cdc56ffbdd8d9004f33f3a7b7 Mon Sep 17 00:00:00 2001 From: Offending Commit Date: Wed, 3 Jun 2026 11:06:53 -0500 Subject: [PATCH 06/14] feat(helm): add Service and ServiceAccount templates --- charts/openconcho/templates/service.yaml | 15 +++++++++++++++ charts/openconcho/templates/serviceaccount.yaml | 13 +++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 charts/openconcho/templates/service.yaml create mode 100644 charts/openconcho/templates/serviceaccount.yaml diff --git a/charts/openconcho/templates/service.yaml b/charts/openconcho/templates/service.yaml new file mode 100644 index 0000000..a542306 --- /dev/null +++ b/charts/openconcho/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "openconcho.fullname" . }} + labels: + {{- include "openconcho.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "openconcho.selectorLabels" . | nindent 4 }} diff --git a/charts/openconcho/templates/serviceaccount.yaml b/charts/openconcho/templates/serviceaccount.yaml new file mode 100644 index 0000000..433c583 --- /dev/null +++ b/charts/openconcho/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "openconcho.serviceAccountName" . }} + labels: + {{- include "openconcho.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} From 9aa106cede7d5719ee2cbc48c1c677491deea568 Mon Sep 17 00:00:00 2001 From: Offending Commit Date: Wed, 3 Jun 2026 11:07:03 -0500 Subject: [PATCH 07/14] feat(helm): add optional Ingress template --- charts/openconcho/templates/ingress.yaml | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 charts/openconcho/templates/ingress.yaml diff --git a/charts/openconcho/templates/ingress.yaml b/charts/openconcho/templates/ingress.yaml new file mode 100644 index 0000000..e11a8a8 --- /dev/null +++ b/charts/openconcho/templates/ingress.yaml @@ -0,0 +1,37 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "openconcho.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "openconcho.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- toYaml .Values.ingress.tls | nindent 4 }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- end }} + {{- end }} +{{- end }} From b0b648bdcf64732c0a713bd8e45077c5f1b39ba6 Mon Sep 17 00:00:00 2001 From: Offending Commit Date: Wed, 3 Jun 2026 11:07:11 -0500 Subject: [PATCH 08/14] feat(helm): add optional HPA, PDB, and NetworkPolicy templates --- charts/openconcho/templates/hpa.yaml | 22 +++++++++++++++++++ .../openconcho/templates/networkpolicy.yaml | 20 +++++++++++++++++ charts/openconcho/templates/pdb.yaml | 18 +++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 charts/openconcho/templates/hpa.yaml create mode 100644 charts/openconcho/templates/networkpolicy.yaml create mode 100644 charts/openconcho/templates/pdb.yaml diff --git a/charts/openconcho/templates/hpa.yaml b/charts/openconcho/templates/hpa.yaml new file mode 100644 index 0000000..4f86d9a --- /dev/null +++ b/charts/openconcho/templates/hpa.yaml @@ -0,0 +1,22 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "openconcho.fullname" . }} + labels: + {{- include "openconcho.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "openconcho.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} +{{- end }} diff --git a/charts/openconcho/templates/networkpolicy.yaml b/charts/openconcho/templates/networkpolicy.yaml new file mode 100644 index 0000000..b7dfe4a --- /dev/null +++ b/charts/openconcho/templates/networkpolicy.yaml @@ -0,0 +1,20 @@ +{{- if .Values.networkPolicy.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "openconcho.fullname" . }} + labels: + {{- include "openconcho.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "openconcho.selectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + ingress: + - ports: + - port: {{ .Values.service.containerPort }} + protocol: TCP + from: + - podSelector: {} +{{- end }} diff --git a/charts/openconcho/templates/pdb.yaml b/charts/openconcho/templates/pdb.yaml new file mode 100644 index 0000000..b28e59a --- /dev/null +++ b/charts/openconcho/templates/pdb.yaml @@ -0,0 +1,18 @@ +{{- if .Values.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "openconcho.fullname" . }} + labels: + {{- include "openconcho.labels" . | nindent 4 }} +spec: + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if .Values.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- end }} + selector: + matchLabels: + {{- include "openconcho.selectorLabels" . | nindent 6 }} +{{- end }} From b4939bd57f2dba5ebca9efcd42901457512e70e4 Mon Sep 17 00:00:00 2001 From: Offending Commit Date: Wed, 3 Jun 2026 11:14:47 -0500 Subject: [PATCH 09/14] fix(helm): pdb mutual exclusion, ingress null rules guard, hpa nil utilization guard --- charts/openconcho/templates/hpa.yaml | 2 ++ charts/openconcho/templates/ingress.yaml | 4 +++- charts/openconcho/templates/pdb.yaml | 7 +++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/charts/openconcho/templates/hpa.yaml b/charts/openconcho/templates/hpa.yaml index 4f86d9a..43a9b83 100644 --- a/charts/openconcho/templates/hpa.yaml +++ b/charts/openconcho/templates/hpa.yaml @@ -12,6 +12,7 @@ spec: name: {{ include "openconcho.fullname" . }} minReplicas: {{ .Values.autoscaling.minReplicas }} maxReplicas: {{ .Values.autoscaling.maxReplicas }} + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} metrics: - type: Resource resource: @@ -19,4 +20,5 @@ spec: target: type: Utilization averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} {{- end }} diff --git a/charts/openconcho/templates/ingress.yaml b/charts/openconcho/templates/ingress.yaml index e11a8a8..27404eb 100644 --- a/charts/openconcho/templates/ingress.yaml +++ b/charts/openconcho/templates/ingress.yaml @@ -19,6 +19,7 @@ spec: tls: {{- toYaml .Values.ingress.tls | nindent 4 }} {{- end }} + {{- if .Values.ingress.hosts }} rules: {{- range .Values.ingress.hosts }} - host: {{ .host | quote }} @@ -26,7 +27,7 @@ spec: paths: {{- range .paths }} - path: {{ .path }} - pathType: {{ .pathType }} + pathType: {{ .pathType | default "Prefix" }} backend: service: name: {{ $fullName }} @@ -34,4 +35,5 @@ spec: number: {{ $svcPort }} {{- end }} {{- end }} + {{- end }} {{- end }} diff --git a/charts/openconcho/templates/pdb.yaml b/charts/openconcho/templates/pdb.yaml index b28e59a..de4b44b 100644 --- a/charts/openconcho/templates/pdb.yaml +++ b/charts/openconcho/templates/pdb.yaml @@ -6,11 +6,10 @@ metadata: labels: {{- include "openconcho.labels" . | nindent 4 }} spec: - {{- if .Values.podDisruptionBudget.minAvailable }} - minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} - {{- end }} - {{- if .Values.podDisruptionBudget.maxUnavailable }} + {{- if not (kindIs "invalid" .Values.podDisruptionBudget.maxUnavailable) }} maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- else }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable | default 1 }} {{- end }} selector: matchLabels: From ce211df48cc59dfe933eb7a1b1415591b0e9f7fa Mon Sep 17 00:00:00 2001 From: Offending Commit Date: Wed, 3 Jun 2026 11:16:23 -0500 Subject: [PATCH 10/14] feat(helm): add NOTES.txt with access instructions and NetworkPolicy/Ingress warning --- charts/openconcho/templates/NOTES.txt | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 charts/openconcho/templates/NOTES.txt diff --git a/charts/openconcho/templates/NOTES.txt b/charts/openconcho/templates/NOTES.txt new file mode 100644 index 0000000..7cc7ea7 --- /dev/null +++ b/charts/openconcho/templates/NOTES.txt @@ -0,0 +1,40 @@ +OpenConcho {{ .Chart.AppVersion }} deployed to namespace {{ .Release.Namespace }}. + +{{- if .Values.ingress.enabled }} +Access: +{{- range .Values.ingress.hosts }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }} +{{- end }} +{{- else if eq .Values.service.type "NodePort" }} +Access (NodePort): + export NODE_PORT=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "openconcho.fullname" . }} -o jsonpath="{.spec.ports[0].nodePort}") + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo "http://$NODE_IP:$NODE_PORT" +{{- else if eq .Values.service.type "LoadBalancer" }} +Access (LoadBalancer — IP may take a few minutes): + export LB_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "openconcho.fullname" . }} --template '{{ "{{" }}range (index .status.loadBalancer.ingress 0){{ "}}" }}{{ "{{" }}.{{ "}}" }}{{ "{{" }}end{{ "}}" }}') + echo "http://$LB_IP:{{ .Values.service.port }}" +{{- else }} +Access (port-forward): + kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "openconcho.fullname" . }} 8080:{{ .Values.service.port }} + Then open http://localhost:8080 +{{- end }} + +Run Helm tests to verify the deployment: + helm test {{ .Release.Name }} + +{{- if and .Values.networkPolicy.enabled .Values.ingress.enabled }} + +WARNING: NetworkPolicy + Ingress are both enabled. +The default NetworkPolicy allows port {{ .Values.service.containerPort }} only from pods within +namespace {{ .Release.Namespace }}. Ingress controllers typically run in a separate +namespace (ingress-nginx, kube-system, etc.) and will be blocked. +To allow ingress-controller traffic, add a namespaceSelector rule: + + kubectl edit networkpolicy --namespace {{ .Release.Namespace }} {{ include "openconcho.fullname" . }} + + # Under spec.ingress[0].from, add: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: +{{- end }} From ee4630e79ca588ee0f9cb167ac0f58ae4b8223cc Mon Sep 17 00:00:00 2001 From: Offending Commit Date: Wed, 3 Jun 2026 11:16:27 -0500 Subject: [PATCH 11/14] feat(helm): add test-healthz and test-spa-root helm test jobs --- .../templates/tests/test-healthz.yaml | 24 +++++++++++++++++++ .../templates/tests/test-spa-root.yaml | 23 ++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 charts/openconcho/templates/tests/test-healthz.yaml create mode 100644 charts/openconcho/templates/tests/test-spa-root.yaml diff --git a/charts/openconcho/templates/tests/test-healthz.yaml b/charts/openconcho/templates/tests/test-healthz.yaml new file mode 100644 index 0000000..5247baa --- /dev/null +++ b/charts/openconcho/templates/tests/test-healthz.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "openconcho.fullname" . }}-test-healthz" + labels: + {{- include "openconcho.labels" . | nindent 4 }} + annotations: + helm.sh/hook: test + helm.sh/hook-delete-policy: before-hook-creation +spec: + restartPolicy: Never + containers: + - name: healthz + image: busybox + command: + - sh + - -c + - | + RESPONSE=$(wget -qO- http://{{ include "openconcho.fullname" . }}:{{ .Values.service.port }}/healthz) + if [ "$RESPONSE" != "ok" ]; then + echo "FAIL: expected 'ok', got '$RESPONSE'" + exit 1 + fi + echo "PASS: /healthz returned 'ok'" diff --git a/charts/openconcho/templates/tests/test-spa-root.yaml b/charts/openconcho/templates/tests/test-spa-root.yaml new file mode 100644 index 0000000..9acb229 --- /dev/null +++ b/charts/openconcho/templates/tests/test-spa-root.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "openconcho.fullname" . }}-test-spa-root" + labels: + {{- include "openconcho.labels" . | nindent 4 }} + annotations: + helm.sh/hook: test + helm.sh/hook-delete-policy: before-hook-creation +spec: + restartPolicy: Never + containers: + - name: spa-root + image: busybox + command: + - sh + - -c + - | + if ! wget -qO /dev/null http://{{ include "openconcho.fullname" . }}:{{ .Values.service.port }}/; then + echo "FAIL: GET / did not return HTTP 200" + exit 1 + fi + echo "PASS: / returned HTTP 200" From 8fac5d060f45b68141917efad4afe499ca2fda56 Mon Sep 17 00:00:00 2001 From: Offending Commit Date: Wed, 3 Jun 2026 11:22:19 -0500 Subject: [PATCH 12/14] fix(helm): pin busybox:1.36, add -T 10 timeout, use --spider, add activeDeadlineSeconds --- charts/openconcho/templates/tests/test-healthz.yaml | 5 +++-- charts/openconcho/templates/tests/test-spa-root.yaml | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/charts/openconcho/templates/tests/test-healthz.yaml b/charts/openconcho/templates/tests/test-healthz.yaml index 5247baa..5a512e4 100644 --- a/charts/openconcho/templates/tests/test-healthz.yaml +++ b/charts/openconcho/templates/tests/test-healthz.yaml @@ -9,14 +9,15 @@ metadata: helm.sh/hook-delete-policy: before-hook-creation spec: restartPolicy: Never + activeDeadlineSeconds: 60 containers: - name: healthz - image: busybox + image: busybox:1.36 command: - sh - -c - | - RESPONSE=$(wget -qO- http://{{ include "openconcho.fullname" . }}:{{ .Values.service.port }}/healthz) + RESPONSE=$(wget -T 10 -qO- http://{{ include "openconcho.fullname" . }}:{{ .Values.service.port }}/healthz) if [ "$RESPONSE" != "ok" ]; then echo "FAIL: expected 'ok', got '$RESPONSE'" exit 1 diff --git a/charts/openconcho/templates/tests/test-spa-root.yaml b/charts/openconcho/templates/tests/test-spa-root.yaml index 9acb229..e6acda8 100644 --- a/charts/openconcho/templates/tests/test-spa-root.yaml +++ b/charts/openconcho/templates/tests/test-spa-root.yaml @@ -9,14 +9,15 @@ metadata: helm.sh/hook-delete-policy: before-hook-creation spec: restartPolicy: Never + activeDeadlineSeconds: 60 containers: - name: spa-root - image: busybox + image: busybox:1.36 command: - sh - -c - | - if ! wget -qO /dev/null http://{{ include "openconcho.fullname" . }}:{{ .Values.service.port }}/; then + if ! wget -T 10 -q --spider http://{{ include "openconcho.fullname" . }}:{{ .Values.service.port }}/; then echo "FAIL: GET / did not return HTTP 200" exit 1 fi From 4ebd4cc2110de8c915f518b378390d3e51722a2a Mon Sep 17 00:00:00 2001 From: Offending Commit Date: Wed, 3 Jun 2026 11:29:04 -0500 Subject: [PATCH 13/14] ci(helm): publish chart to ghcr oci on release tags --- .github/workflows/docker-publish.yml | 31 ++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 9e8fd80..9b0394d 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -44,3 +44,34 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max + + publish-chart: + name: Package & push Helm chart to GHCR + runs-on: ubuntu-latest + needs: [publish] + if: startsWith(github.ref, 'refs/tags/') + steps: + - uses: actions/checkout@v4 + + - uses: azure/setup-helm@v4 + + - name: Derive chart version + id: version + run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + + - name: Log in to GHCR (Helm OCI) + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io \ + --username "${{ github.actor }}" \ + --password-stdin + + - name: Package chart + run: | + helm package charts/openconcho \ + --version "${{ steps.version.outputs.VERSION }}" \ + --app-version "${{ steps.version.outputs.VERSION }}" + + - name: Push chart + run: | + helm push "openconcho-${{ steps.version.outputs.VERSION }}.tgz" \ + oci://ghcr.io/${{ github.repository_owner }}/charts From d81e7f17acb89f48ab845206f445fb18a4af8720 Mon Sep 17 00:00:00 2001 From: Offending Commit Date: Wed, 3 Jun 2026 16:32:28 -0500 Subject: [PATCH 14/14] docs(helm): annotate values.yaml, add chart README, ArgoCD example, update root README and AGENTS.md --- AGENTS.md | 1 + README.md | 28 +++++ charts/openconcho/README.md | 229 ++++++++++++++++++++++++++++++++++ charts/openconcho/values.yaml | 81 ++++++++++++ 4 files changed, 339 insertions(+) create mode 100644 charts/openconcho/README.md diff --git a/AGENTS.md b/AGENTS.md index 3a4d8cb..a802716 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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/e2e/` | Playwright e2e specs | | `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) | | `docs/` | Architecture and references | diff --git a/README.md b/README.md index 3b14bac..fb2dfd1 100644 --- a/README.md +++ b/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 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 1. Enter the base URL of your Honcho instance (e.g. `http://localhost:8000`) diff --git a/charts/openconcho/README.md b/charts/openconcho/README.md new file mode 100644 index 0000000..82a0529 --- /dev/null +++ b/charts/openconcho/README.md @@ -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 --password +``` + +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 \ + -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 ` 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) | diff --git a/charts/openconcho/values.yaml b/charts/openconcho/values.yaml index 6448e12..e27a03a 100644 --- a/charts/openconcho/values.yaml +++ b/charts/openconcho/values.yaml @@ -1,23 +1,40 @@ +# Number of pod replicas. Increase for high availability or use autoscaling instead. replicaCount: 1 image: + # Container image repository. Override to use a custom registry or fork. repository: ghcr.io/offendingcommit/openconcho-web + # Image tag. Defaults to the chart appVersion when left empty. tag: "" pullPolicy: IfNotPresent +# Secrets for pulling images from private registries. +# Example: [{ name: my-registry-secret }] imagePullSecrets: [] + +# Override the name portion used in resource names and labels. nameOverride: "" +# Override the full resource name (normally release-name + chart-name). fullnameOverride: "" serviceAccount: + # Create a dedicated ServiceAccount for the pod. create: true + # Disable automatic ServiceAccount token mounting — the app never calls the Kubernetes API. automount: false + # Annotations to add to the ServiceAccount (e.g. for IRSA, Workload Identity, Vault). annotations: {} + # Use a pre-existing ServiceAccount instead of creating one. Ignored when create is true. name: "" +# Annotations applied to every pod (not the Deployment). Useful for Prometheus scraping, +# Vault agent injection, Datadog unified service tagging, etc. podAnnotations: {} +# Extra labels applied to every pod. podLabels: {} +# Pod-level security context shared by all containers. +# UID/GID 101 matches the nginx-unprivileged base image — do not change without rebuilding. podSecurityContext: runAsNonRoot: true runAsUser: 101 @@ -26,37 +43,62 @@ podSecurityContext: seccompProfile: type: RuntimeDefault +# Container-level security context. securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: 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: - mountPath: /var/cache/nginx - mountPath: /var/run - mountPath: /tmp service: + # Kubernetes Service type. Options: ClusterIP | NodePort | LoadBalancer type: ClusterIP + # Port exposed by the Service (what the Ingress or other pods target). port: 80 + # Port the container actually listens on (nginx-unprivileged default). containerPort: 8080 ingress: enabled: false + # IngressClass name. Leave empty to accept the cluster default. + # Examples: nginx | traefik | alb | kong 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: {} hosts: - host: openconcho.example.com paths: - path: / pathType: Prefix + # TLS configuration. Provide a Secret containing the certificate. + # Example: + # - secretName: openconcho-tls + # hosts: + # - openconcho.example.com tls: [] 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: "" + # 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: "" +# 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: requests: cpu: 50m @@ -82,16 +124,55 @@ autoscaling: enabled: false minReplicas: 1 maxReplicas: 5 + # Scale up when average CPU utilization across pods exceeds this percentage. targetCPUUtilizationPercentage: 80 podDisruptionBudget: 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 + # 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 ` to see the reminder. networkPolicy: 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: [] + +# Constrain pods to nodes whose labels match these key/value pairs. +# Example: kubernetes.io/arch: amd64 nodeSelector: {} + +# Allow pods to be scheduled on tainted nodes. +# Example: +# - key: dedicated +# operator: Equal +# value: web +# effect: NoSchedule 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: {}