Merge pull request #62 from offendingcommit/worktree-feat+helm-chart
feat(helm): add Helm chart for self-hosted openconcho web UI
This commit is contained in:
31
.github/workflows/docker-publish.yml
vendored
31
.github/workflows/docker-publish.yml
vendored
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
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
|
||||
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`)
|
||||
|
||||
16
charts/openconcho/Chart.yaml
Normal file
16
charts/openconcho/Chart.yaml
Normal file
@@ -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
|
||||
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) |
|
||||
40
charts/openconcho/templates/NOTES.txt
Normal file
40
charts/openconcho/templates/NOTES.txt
Normal file
@@ -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: <ingress-controller-namespace>
|
||||
{{- end }}
|
||||
46
charts/openconcho/templates/_helpers.tpl
Normal file
46
charts/openconcho/templates/_helpers.tpl
Normal file
@@ -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 }}
|
||||
84
charts/openconcho/templates/deployment.yaml
Normal file
84
charts/openconcho/templates/deployment.yaml
Normal file
@@ -0,0 +1,84 @@
|
||||
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 }}
|
||||
{{- if .Values.tmpfsMounts }}
|
||||
volumeMounts:
|
||||
{{- range .Values.tmpfsMounts }}
|
||||
- name: {{ .mountPath | trimPrefix "/" | replace "/" "-" | trunc 63 | trimSuffix "-" }}
|
||||
mountPath: {{ .mountPath }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.tmpfsMounts }}
|
||||
volumes:
|
||||
{{- range .Values.tmpfsMounts }}
|
||||
- name: {{ .mountPath | trimPrefix "/" | replace "/" "-" | trunc 63 | trimSuffix "-" }}
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
{{- end }}
|
||||
{{- 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 }}
|
||||
24
charts/openconcho/templates/hpa.yaml
Normal file
24
charts/openconcho/templates/hpa.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
{{- 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 }}
|
||||
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
39
charts/openconcho/templates/ingress.yaml
Normal file
39
charts/openconcho/templates/ingress.yaml
Normal file
@@ -0,0 +1,39 @@
|
||||
{{- 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 }}
|
||||
{{- if .Values.ingress.hosts }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
pathType: {{ .pathType | default "Prefix" }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ $fullName }}
|
||||
port:
|
||||
number: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
20
charts/openconcho/templates/networkpolicy.yaml
Normal file
20
charts/openconcho/templates/networkpolicy.yaml
Normal file
@@ -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 }}
|
||||
17
charts/openconcho/templates/pdb.yaml
Normal file
17
charts/openconcho/templates/pdb.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
{{- if .Values.podDisruptionBudget.enabled }}
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: {{ include "openconcho.fullname" . }}
|
||||
labels:
|
||||
{{- include "openconcho.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not (kindIs "invalid" .Values.podDisruptionBudget.maxUnavailable) }}
|
||||
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
|
||||
{{- else }}
|
||||
minAvailable: {{ .Values.podDisruptionBudget.minAvailable | default 1 }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "openconcho.selectorLabels" . | nindent 6 }}
|
||||
{{- end }}
|
||||
15
charts/openconcho/templates/service.yaml
Normal file
15
charts/openconcho/templates/service.yaml
Normal file
@@ -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 }}
|
||||
13
charts/openconcho/templates/serviceaccount.yaml
Normal file
13
charts/openconcho/templates/serviceaccount.yaml
Normal file
@@ -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 }}
|
||||
25
charts/openconcho/templates/tests/test-healthz.yaml
Normal file
25
charts/openconcho/templates/tests/test-healthz.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
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
|
||||
activeDeadlineSeconds: 60
|
||||
containers:
|
||||
- name: healthz
|
||||
image: busybox:1.36
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
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
|
||||
fi
|
||||
echo "PASS: /healthz returned 'ok'"
|
||||
24
charts/openconcho/templates/tests/test-spa-root.yaml
Normal file
24
charts/openconcho/templates/tests/test-spa-root.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
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
|
||||
activeDeadlineSeconds: 60
|
||||
containers:
|
||||
- name: spa-root
|
||||
image: busybox:1.36
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
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
|
||||
echo "PASS: / returned HTTP 200"
|
||||
67
charts/openconcho/values.schema.json
Normal file
67
charts/openconcho/values.schema.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"$schema": "http://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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
178
charts/openconcho/values.yaml
Normal file
178
charts/openconcho/values.yaml
Normal file
@@ -0,0 +1,178 @@
|
||||
# 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
|
||||
runAsGroup: 101
|
||||
fsGroup: 101
|
||||
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
|
||||
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
|
||||
# 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 <release>` 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: {}
|
||||
Reference in New Issue
Block a user