commit 4407d24ba6d446ff482898c2dc2cb4d9ff5a704a Author: 0xFEEDC0DE64 Date: Tue Oct 20 09:31:49 2026 +0200 Publish portable nextcloud-talk-hpb chart diff --git a/.gitea/workflows/chart.yml b/.gitea/workflows/chart.yml new file mode 100644 index 0000000..43d84c0 --- /dev/null +++ b/.gitea/workflows/chart.yml @@ -0,0 +1,71 @@ +name: Validate, publish and promote nextcloud-talk-hpb chart +on: + push: + pull_request: +jobs: + chart: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: azure/setup-helm@v4 + with: + version: v4.3.0 + - name: Validate chart + run: | + python3 -m venv "${RUNNER_TEMP}/chart-test" + "${RUNNER_TEMP}/chart-test/bin/pip" install PyYAML + PATH="${RUNNER_TEMP}/chart-test/bin:${PATH}" bash chart/test.sh + - name: Select immutable chart version + if: gitea.ref == 'refs/heads/main' + run: printf 'CHART_VERSION=%s\n' "$(./scripts/chart-version.sh)" >> "${GITHUB_ENV}" + - name: Publish chart + if: gitea.ref == 'refs/heads/main' + env: + PACKAGE_USERNAME: ${{ secrets.PACKAGE_USERNAME }} + PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }} + run: | + set -euo pipefail + test -n "${PACKAGE_USERNAME}" && test -n "${PACKAGE_TOKEN}" + mkdir -p .chart-packages + helm package chart/nextcloud-talk-hpb --destination .chart-packages --version "${CHART_VERSION}" + curl --fail --silent --show-error --user "${PACKAGE_USERNAME}:${PACKAGE_TOKEN}" \ + --request POST --upload-file ".chart-packages/nextcloud-talk-hpb-${CHART_VERSION}.tgz" \ + https://code.brunner.ninja/api/packages/feedc0de/helm/api/charts + - name: Check current main + id: current-main + if: gitea.ref == 'refs/heads/main' + env: + COMMIT_SHA: ${{ gitea.sha }} + run: | + git fetch --no-tags origin main + if test "$(git rev-parse FETCH_HEAD)" = "${COMMIT_SHA}"; then + echo 'promote=true' >> "${GITEA_OUTPUT}" + else + echo 'promote=false' >> "${GITEA_OUTPUT}" + fi + - uses: actions/checkout@v4 + if: steps.current-main.outputs.promote == 'true' + with: + repository: feedc0de/homelab-gitops + ref: main + token: ${{ secrets.GITOPS_TOKEN }} + path: homelab-gitops + - name: Promote chart + if: steps.current-main.outputs.promote == 'true' + working-directory: homelab-gitops + run: | + set -euo pipefail + ./scripts/update-chart-version.sh nextcloud-talk-hpb "${CHART_VERSION}" + git config user.name feedc0de + git config user.email feedc0de@users.noreply.code.brunner.ninja + git add bootstrap/nextcloud-talk-hpb.yaml + if git diff --cached --quiet; then exit 0; fi + git commit -m "Deploy nextcloud-talk-hpb chart ${CHART_VERSION}" + for attempt in 1 2 3; do + git push origin HEAD:main && exit 0 + test "${attempt}" = 3 && exit 1 + git fetch origin main + git rebase origin/main + done diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad14e04 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.chart-packages/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..adc9761 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Nextcloud Talk High Performance Backend + +This repository maintains a portable Helm chart for the Nextcloud Talk High Performance Backend integration. +It is used with the upstream Nextcloud Helm release. The application image is +`ghcr.io/nextcloud-releases/aio-talk:20260825_084538`; the chart selects that tested tag through +`Chart.yaml.appVersion`, while GitOps selects only the chart version. + +The chart references an existing Secret with TURN, signaling, and internal keys; keep those aligned with the Nextcloud Talk configuration. + +```sh +helm repo add brunner.ninja https://brunner.ninja/charts +helm repo update brunner.ninja +VALUES_FILE=values.yaml ./install.sh +``` + +The chart and its templates own the Kubernetes workload contract; set hostnames, +Ingress annotations, resources, and Secret names through values. The live +[homelab configuration](https://code.brunner.ninja/feedc0de/homelab-gitops/src/branch/main/apps/nextcloud) +is an example, not a portable default. Run `bash chart/test.sh` before +publishing. + +The Git tag follows the actual upstream release `20260825_084538`. Its first chart +package is `20260825.0.0-r084538`; later chart-only commits append the commit count +and Git hash. CI publishes the package to the indexed +`https://brunner.ninja/charts` repository and promotes only this chart's +Argo CD targetRevision. diff --git a/chart/nextcloud-talk-hpb/Chart.yaml b/chart/nextcloud-talk-hpb/Chart.yaml new file mode 100644 index 0000000..cac3cef --- /dev/null +++ b/chart/nextcloud-talk-hpb/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: nextcloud-talk-hpb +description: Nextcloud Talk High Performance Backend +type: application +version: 0.0.0 +appVersion: "20260825_084538" diff --git a/chart/nextcloud-talk-hpb/templates/resources.yaml b/chart/nextcloud-talk-hpb/templates/resources.yaml new file mode 100644 index 0000000..07b43b9 --- /dev/null +++ b/chart/nextcloud-talk-hpb/templates/resources.yaml @@ -0,0 +1,137 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }} + labels: + app.kubernetes.io/name: nextcloud-talk-hpb + app.kubernetes.io/instance: {{ .Values.nextcloudReleaseName }} +spec: + replicas: 1 + revisionHistoryLimit: 3 + selector: + matchLabels: + app.kubernetes.io/name: nextcloud-talk-hpb + app.kubernetes.io/instance: {{ .Values.nextcloudReleaseName }} + template: + metadata: + labels: + app.kubernetes.io/name: nextcloud-talk-hpb + app.kubernetes.io/instance: {{ .Values.nextcloudReleaseName }} + spec: + automountServiceAccountToken: false + containers: + - name: talk-hpb + image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}" + imagePullPolicy: IfNotPresent + env: + - name: NC_DOMAIN + value: {{ .Values.nextcloudHost | quote }} + - name: TALK_HOST + value: {{ .Values.ingress.host | quote }} + - name: TALK_PORT + value: '3478' + - name: TURN_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.integrationSecret.name }} + key: talk-turn-secret + - name: SIGNALING_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.integrationSecret.name }} + key: talk-signaling-secret + - name: INTERNAL_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.integrationSecret.name }} + key: talk-internal-secret + - name: TZ + value: {{ .Values.timezone | quote }} + ports: + - name: http + containerPort: 8081 + readinessProbe: + httpGet: + path: /api/v1/welcome + port: http + initialDelaySeconds: 10 + livenessProbe: + httpGet: + path: /api/v1/welcome + port: http + initialDelaySeconds: 30 + resources: + {{- toYaml .Values.resources | nindent 10 }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + volumeMounts: + - name: supervisor-run + mountPath: /run/supervisord + - name: supervisor-log + mountPath: /var/log/supervisord + - name: eturnal-run + mountPath: /opt/eturnal/run + - name: conf + mountPath: /conf + - name: tmp + mountPath: /tmp + securityContext: + seccompProfile: + type: RuntimeDefault + volumes: + - name: supervisor-run + emptyDir: + medium: Memory + - name: supervisor-log + emptyDir: + medium: Memory + - name: eturnal-run + emptyDir: + medium: Memory + - name: conf + emptyDir: + medium: Memory + - name: tmp + emptyDir: + medium: Memory +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }} +spec: + selector: + app.kubernetes.io/name: nextcloud-talk-hpb + app.kubernetes.io/instance: {{ .Values.nextcloudReleaseName }} + ports: + - name: http + port: 8081 + targetPort: http +--- +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }} + annotations: + {{- toYaml .Values.ingress.annotations | nindent 4 }} +spec: + ingressClassName: {{ .Values.ingress.className }} + rules: + - host: {{ .Values.ingress.host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }} + port: + name: http +{{- end }} diff --git a/chart/nextcloud-talk-hpb/values.yaml b/chart/nextcloud-talk-hpb/values.yaml new file mode 100644 index 0000000..1c90f9f --- /dev/null +++ b/chart/nextcloud-talk-hpb/values.yaml @@ -0,0 +1,13 @@ +image: + repository: ghcr.io/nextcloud-releases/aio-talk +nextcloudReleaseName: nextcloud +nextcloudHost: nextcloud.example.org +integrationSecret: + name: nextcloud-integrations +timezone: UTC +resources: {} +ingress: + enabled: false + className: "" + host: signaling.example.org + annotations: {} diff --git a/chart/test.sh b/chart/test.sh new file mode 100755 index 0000000..30efb01 --- /dev/null +++ b/chart/test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail +chart_name=$(basename "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/nextcloud-talk-hpb" && pwd)") +chart_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/nextcloud-talk-hpb" && pwd) +helm lint "$chart_dir" --strict +python3 - "$chart_dir" "$chart_name" <<'PY' +import subprocess,sys,yaml +chart,name=sys.argv[1:] +with open(chart+'/Chart.yaml') as f: metadata=yaml.safe_load(f) +def render(*args): + output=subprocess.check_output(['helm','template',name,chart,'--namespace','nextcloud',*args],text=True) + return {(x['kind'],x['metadata']['name']):x for x in yaml.safe_load_all(output) if x} +default=render() +enabled=render('--set','ingress.enabled=true','--set','monitoring.enabled=true') +assert ('Service',name) in default +assert ('Ingress',name) not in default +if name!='nextcloud-clamav': + assert ('Ingress',name) in enabled +if name=='nextcloud-notify-push': + assert ('ServiceMonitor',name) in enabled +else: + assert ('Deployment',name) in default + images=[c['image'] for c in default['Deployment',name]['spec']['template']['spec'].get('initContainers',[])+default['Deployment',name]['spec']['template']['spec']['containers']] + assert all(image.endswith(':'+metadata['appVersion']) for image in images) +if name=='nextcloud-clamav': + assert ('PersistentVolumeClaim',name) in default +assert not any(kind=='Secret' for kind,_ in enabled) +PY diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..c9b7d69 --- /dev/null +++ b/install.sh @@ -0,0 +1,14 @@ +#!/bin/sh +set -eu +chart_name=$(basename "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)") +namespace=${NAMESPACE:-nextcloud} +values_file=${VALUES_FILE:-values.yaml} +if [ ! -f "$values_file" ]; then + echo "Values file not found: $values_file" >&2 + exit 1 +fi +helm repo add brunner.ninja https://brunner.ninja/charts --force-update +helm repo update brunner.ninja +helm upgrade --install "$chart_name" "brunner.ninja/$chart_name" \ + --namespace "$namespace" --create-namespace --values "$values_file" \ + --wait --timeout 15m diff --git a/scripts/chart-version.sh b/scripts/chart-version.sh new file mode 100755 index 0000000..7868ea7 --- /dev/null +++ b/scripts/chart-version.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) +description=$(git -C "$repo_dir" describe --tags --long --dirty) +case "$description" in + *-dirty) echo 'Refusing to package a dirty tree.' >&2; exit 1 ;; +esac +if [[ "$description" =~ ^(.+)-([0-9]+)-g([0-9a-f]+)$ ]]; then + release_tag=${BASH_REMATCH[1]} + count=${BASH_REMATCH[2]} + hash=${BASH_REMATCH[3]} +else + echo "Unexpected git describe result: $description" >&2 + exit 1 +fi + +case "$release_tag" in + v[0-9]*) + version=${release_tag#v} + ;; + [0-9]*_[0-9]*) + date=${release_tag%%_*} + time=${release_tag#*_} + version="${date}.0.0-r${time}" + ;; + *) + echo "Unsupported application release tag: $release_tag" >&2 + exit 1 + ;; +esac +if [ "$count" -ne 0 ]; then + version="$version-$count-g$hash" +fi +printf '%s\n' "$version"