From c50095a0b6689540c3f07f2938a9c26a131d63d1 Mon Sep 17 00:00:00 2001 From: Morten Olsen Date: Thu, 1 Jan 2026 21:49:59 +0100 Subject: [PATCH] add forgejo runner --- apps/charts/forgejo/templates/deployment.yaml | 75 ++++++++ .../forgejo/templates/runner-deployment.yaml | 169 ++++++++++++++++++ apps/charts/forgejo/templates/runner-pvc.yaml | 12 ++ .../runner-secret-external-secrets.yaml | 3 + .../runner-secret-password-generators.yaml | 3 + .../forgejo/templates/woodpecker-secrets.yaml | 9 - apps/charts/forgejo/templates/woodpecker.yaml | 53 ------ apps/charts/forgejo/values.yaml | 51 +++++- 8 files changed, 309 insertions(+), 66 deletions(-) create mode 100644 apps/charts/forgejo/templates/runner-deployment.yaml create mode 100644 apps/charts/forgejo/templates/runner-pvc.yaml create mode 100644 apps/charts/forgejo/templates/runner-secret-external-secrets.yaml create mode 100644 apps/charts/forgejo/templates/runner-secret-password-generators.yaml delete mode 100644 apps/charts/forgejo/templates/woodpecker-secrets.yaml delete mode 100644 apps/charts/forgejo/templates/woodpecker.yaml diff --git a/apps/charts/forgejo/templates/deployment.yaml b/apps/charts/forgejo/templates/deployment.yaml index 4508e33..8d1d76d 100644 --- a/apps/charts/forgejo/templates/deployment.yaml +++ b/apps/charts/forgejo/templates/deployment.yaml @@ -1 +1,76 @@ +{{- if .Values.actions.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "common.fullname" . }} + labels: + {{- include "common.labels" . | nindent 4 }} +spec: + strategy: + type: {{ include "common.deploymentStrategy" . }} + replicas: {{ .Values.deployment.replicas | default 1 }} + {{- if .Values.deployment.revisionHistoryLimit }} + revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit }} + {{- end }} + selector: + matchLabels: + {{- include "common.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "common.selectorLabels" . | nindent 8 }} + spec: + {{- if .Values.deployment.serviceAccountName }} + serviceAccountName: {{ .Values.deployment.serviceAccountName | replace "{release}" .Release.Name | replace "{fullname}" (include "common.fullname" .) }} + {{- end }} + {{- if .Values.deployment.hostNetwork }} + hostNetwork: {{ .Values.deployment.hostNetwork }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy | default "IfNotPresent" }} + ports: +{{ include "common.containerPorts" . | indent 12 }} + {{- if .Values.container.healthProbe }} + livenessProbe: +{{ include "common.healthProbe" . | indent 12 }} + readinessProbe: +{{ include "common.healthProbe" . | indent 12 }} + {{- end }} + lifecycle: + postStart: + exec: + command: + - /bin/sh + - -c + - | + sleep 10 + su -c "forgejo forgejo-cli actions register --keep-labels --secret ${FORGEJO_RUNNER_SHARED_SECRET}" git || true + {{- if .Values.volumes }} + volumeMounts: +{{ include "common.volumeMounts" . | indent 12 }} + {{- end }} + {{- if or .Values.env .Values.globals.timezone }} + env: +{{ include "common.env" . | indent 12 }} + - name: FORGEJO_RUNNER_SHARED_SECRET + valueFrom: + secretKeyRef: + name: "{{ .Release.Name }}-runner-secrets" + key: shared-secret + {{- else }} + env: + - name: FORGEJO_RUNNER_SHARED_SECRET + valueFrom: + secretKeyRef: + name: "{{ .Release.Name }}-runner-secrets" + key: shared-secret + {{- end }} + {{- if .Values.volumes }} + volumes: + {{- include "common.volumes" . | nindent 8 }} + {{- end }} +{{- else }} {{ include "common.deployment" . }} +{{- end }} diff --git a/apps/charts/forgejo/templates/runner-deployment.yaml b/apps/charts/forgejo/templates/runner-deployment.yaml new file mode 100644 index 0000000..f8039b6 --- /dev/null +++ b/apps/charts/forgejo/templates/runner-deployment.yaml @@ -0,0 +1,169 @@ +{{- if .Values.actions.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ .Release.Name }}-runner" + labels: + app: "{{ .Release.Name }}-runner" +spec: + replicas: {{ .Values.actions.runner.replicas | default 1 }} + selector: + matchLabels: + app: "{{ .Release.Name }}-runner" + template: + metadata: + labels: + app: "{{ .Release.Name }}-runner" + spec: + hostname: docker + containers: + - name: docker-in-docker + image: "{{ .Values.actions.runner.dind.image.repository }}:{{ .Values.actions.runner.dind.image.tag }}" + imagePullPolicy: "{{ .Values.actions.runner.dind.image.pullPolicy }}" + env: + - name: DOCKER_TLS_CERTDIR + value: /certs + - name: DOCKER_HOST + value: docker-in-docker + securityContext: + privileged: true + ports: + - name: docker + containerPort: 2376 + protocol: TCP + volumeMounts: + - name: docker-certs + mountPath: /certs + - name: "{{ .Release.Name }}-runner" + image: "{{ .Values.actions.runner.image.repository }}:{{ .Values.actions.runner.image.tag }}" + imagePullPolicy: "{{ .Values.actions.runner.image.pullPolicy }}" + command: + - /bin/sh + - -c + - | + cd /data + # Wait for shared secret to be available + while [ -z "${FORGEJO_RUNNER_SHARED_SECRET}" ]; do + echo "Waiting for shared secret..." + sleep 1 + done + # Create runner file if it doesn't exist + if [ ! -f .runner ]; then + echo "Creating runner file..." + forgejo-runner create-runner-file \ + --connect \ + --instance "https://{{ .Values.subdomain }}.{{ .Values.globals.domain }}" \ + --name "{{ .Values.actions.runner.name | default "default" }}" \ + --secret "${FORGEJO_RUNNER_SHARED_SECRET}" || { + echo "Failed to create runner file, will retry..." + sleep 5 + exit 1 + } + # Set labels in runner file (matching docker-compose example) + {{- if .Values.actions.runner.labels }} + echo "Setting runner labels..." + LABELS_JSON='[{{- range $index, $label := .Values.actions.runner.labels }}{{- if $index }},{{- end }}"{{ $label }}"{{- end }}]' + sed -i "s|\"labels\": null|\"labels\": ${LABELS_JSON}|" .runner || \ + sed -i "s|\"labels\": \[\]|\"labels\": ${LABELS_JSON}|" .runner || true + {{- end }} + else + # Always update labels to match configuration + {{- if .Values.actions.runner.labels }} + LABELS_JSON='[{{- range $index, $label := .Values.actions.runner.labels }}{{- if $index }},{{- end }}"{{ $label }}"{{- end }}]' + echo "Updating runner labels to match configuration..." + # Use awk to replace the labels array (handles both single-line and multi-line JSON) + awk -v new_labels="${LABELS_JSON}" ' + BEGIN { in_labels = 0 } + /"labels":/ { + in_labels = 1 + print " \"labels\": " new_labels + next + } + in_labels && /^ \]/ { + in_labels = 0 + next + } + in_labels && /^ / { + next + } + { print } + ' .runner > .runner.tmp && mv .runner.tmp .runner || { + # Fallback to sed for single-line labels if awk fails + echo "Awk failed, trying sed fallback..." + sed -i "s|\"labels\": null|\"labels\": ${LABELS_JSON}|" .runner 2>/dev/null || true + sed -i "s|\"labels\": \[\]|\"labels\": ${LABELS_JSON}|" .runner 2>/dev/null || true + sed -i "s|\"labels\": \[[^]]*\]|\"labels\": ${LABELS_JSON}|" .runner 2>/dev/null || true + } + echo "Labels updated successfully" + {{- end }} + fi + # Generate config if it doesn't exist + if [ ! -f config.yml ]; then + echo "Generating config file..." + forgejo-runner generate-config > config.yml + # Update config for docker-in-docker + sed -i 's|network: .*|network: host|' config.yml || true + if ! grep -q "DOCKER_HOST" config.yml; then + awk '/^ envs:$/ { print; print " DOCKER_HOST: tcp://localhost:2376"; print " DOCKER_TLS_VERIFY: 1"; print " DOCKER_CERT_PATH: /certs/client"; next }1' config.yml > config.yml.tmp && mv config.yml.tmp config.yml || true + fi + sed -i 's|^ options:.*| options: -v /certs/client:/certs/client|' config.yml || true + if grep -q "valid_volumes: \[\]" config.yml; then + awk '/^ valid_volumes: \[\]$/ { print " valid_volumes:"; print " - /certs/client"; next }1' config.yml > config.yml.tmp && mv config.yml.tmp config.yml || true + fi + fi + # Wait for docker-in-docker to be ready + echo "Waiting for docker-in-docker to be ready..." + while ! nc -z localhost 2376 2>/dev/null; do + echo "Docker daemon not ready, waiting..." + sleep 2 + done + # Wait for TLS certificates to be available + while [ ! -f /certs/client/ca.pem ]; do + echo "Waiting for TLS certificates..." + sleep 1 + done + echo "Docker daemon and certificates ready" + # Run daemon + echo "Starting runner daemon..." + while : ; do + if [ -f .runner ] && [ -w .runner ]; then + forgejo-runner --config config.yml daemon || { + echo "Daemon exited, restarting in 5 seconds..." + sleep 5 + } + else + echo "Waiting for runner file..." + sleep 1 + fi + done + env: + - name: FORGEJO_INSTANCE_URL + value: "https://{{ .Values.subdomain }}.{{ .Values.globals.domain }}" + - name: FORGEJO_RUNNER_NAME + value: {{ .Values.actions.runner.name | default "default" | quote }} + - name: FORGEJO_RUNNER_SHARED_SECRET + valueFrom: + secretKeyRef: + name: "{{ .Release.Name }}-runner-secrets" + key: shared-secret + - name: DOCKER_HOST + value: tcp://localhost:2376 + - name: DOCKER_TLS_VERIFY + value: "1" + - name: DOCKER_CERT_PATH + value: /certs/client + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + volumeMounts: + - name: runner-data + mountPath: /data + - name: docker-certs + mountPath: /certs + volumes: + - name: runner-data + persistentVolumeClaim: + claimName: "{{ .Release.Name }}-runner-data" + - name: docker-certs + emptyDir: {} +{{- end }} diff --git a/apps/charts/forgejo/templates/runner-pvc.yaml b/apps/charts/forgejo/templates/runner-pvc.yaml new file mode 100644 index 0000000..0ebb62a --- /dev/null +++ b/apps/charts/forgejo/templates/runner-pvc.yaml @@ -0,0 +1,12 @@ +{{- if .Values.actions.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: "{{ .Release.Name }}-runner-data" +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.actions.runner.storage.size | default "10Gi" }} +{{- end }} diff --git a/apps/charts/forgejo/templates/runner-secret-external-secrets.yaml b/apps/charts/forgejo/templates/runner-secret-external-secrets.yaml new file mode 100644 index 0000000..ef969fb --- /dev/null +++ b/apps/charts/forgejo/templates/runner-secret-external-secrets.yaml @@ -0,0 +1,3 @@ +{{- if .Values.actions.enabled }} +{{ include "common.externalSecrets.externalSecrets" . }} +{{- end }} diff --git a/apps/charts/forgejo/templates/runner-secret-password-generators.yaml b/apps/charts/forgejo/templates/runner-secret-password-generators.yaml new file mode 100644 index 0000000..cfbf0a6 --- /dev/null +++ b/apps/charts/forgejo/templates/runner-secret-password-generators.yaml @@ -0,0 +1,3 @@ +{{- if .Values.actions.enabled }} +{{ include "common.externalSecrets.passwordGenerators" . }} +{{- end }} diff --git a/apps/charts/forgejo/templates/woodpecker-secrets.yaml b/apps/charts/forgejo/templates/woodpecker-secrets.yaml deleted file mode 100644 index 391f70e..0000000 --- a/apps/charts/forgejo/templates/woodpecker-secrets.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: homelab.mortenolsen.pro/v1 -kind: GenerateSecret -metadata: - name: "{{ .Release.Name }}-woodpecker-secret" -spec: - fields: - - name: WOODPECKER_AGENT_SECRET - encoding: hex - length: 64 diff --git a/apps/charts/forgejo/templates/woodpecker.yaml b/apps/charts/forgejo/templates/woodpecker.yaml deleted file mode 100644 index 33b30bb..0000000 --- a/apps/charts/forgejo/templates/woodpecker.yaml +++ /dev/null @@ -1,53 +0,0 @@ -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: "{{ .Release.Name }}-woodpecker" - - namespace: argocd -spec: - project: apps - source: - repoURL: https://woodpecker-ci.org/ - chart: woodpecker - targetRevision: 3.4.2 - helm: - releaseName: woodpecker - values: | - server: - extraSecretNamesForEnvFrom: - - {{ .Release.Name }}-woodpecker-secret - env: - WOODPECKER_HOST: "https://woodpecker.{{ .Values.globals.domain }}" - WOODPECKER_FORGEJO: "true" - WOODPECKER_FORGEJO_URL: "https://{{ .Values.subdomain }}.{{ .Values.globals.domain }}" - WOODPECKER_FORGEJO_CLIENT: "b3b42b15-3a60-4683-b548-b78fb3d82869" - WOODPECKER_FORGEJO_SECRET: "gto_xinhg5w3awf3moi64a4mqwzsbzv6mbnvb3ibyo6s7oo6d7j4n7da" - WOODPECKER_ADMIN: morten-olsen - WOODPECKER_BACKEND_K8S_STORAGE_CLASS: "{{ .Values.globals.environment }}" - WOODPECKER_BACKEND_K8S_STORAGE_RWX: "false" - WOODPECKER_PLUGINS_PRIVILEGED: plugins/docker - ingress: - enabled: false - persistentVolume: - enabled: true - accessModes: - - ReadWriteOnce - size: 10Gi - storageClass: "prod" - agent: - extraSecretNamesForEnvFrom: - - {{ .Release.Name }}-woodpecker-secret - env: - WOODPECKER_SERVER: "woodpecker-server:9000" - persistence: - storageClass: "{{ .Values.globals.environment }}" - destination: - server: https://kubernetes.default.svc - namespace: {{ .Release.Namespace }} - syncPolicy: - automated: - prune: true - selfHeal: true - syncOptions: - - CreateNamespace=true - - ServerSideApply=true diff --git a/apps/charts/forgejo/values.yaml b/apps/charts/forgejo/values.yaml index c01e181..9281f8b 100644 --- a/apps/charts/forgejo/values.yaml +++ b/apps/charts/forgejo/values.yaml @@ -22,7 +22,7 @@ container: protocol: TCP healthProbe: type: tcpSocket - port: http # Use named port + port: http # Use named port # Service configuration - multiple services service: @@ -37,7 +37,7 @@ service: targetPort: 22 protocol: TCP type: LoadBalancer - serviceName: ssh # Will be prefixed with release name: {release}-ssh + serviceName: ssh # Will be prefixed with release name: {release}-ssh # Volume configuration volumes: @@ -56,14 +56,14 @@ virtualService: gateways: public: true private: true - servicePort: 80 # Route to the http service port + servicePort: 80 # Route to the http service port # Environment variables env: USER_UID: "1000" USER_GID: "1000" FORGEJO__server__SSH_DOMAIN: - value: "ssh-{subdomain}.{domain}" # Will be templated: ssh-{subdomain}.{domain} + value: "ssh-{subdomain}.{domain}" # Will be templated: ssh-{subdomain}.{domain} FORGEJO__server__SSH_PORT: "2206" FORGEJO__service__REQUIRE_EXTERNAL_REGISTRATION_PASSWORD: "true" FORGEJO__service__ENABLE_PASSWORD_SIGNIN_FORM: "false" @@ -101,3 +101,46 @@ env: secretKeyRef: name: "{release}-pg-connection" key: password + # Actions configuration + FORGEJO__actions__ENABLED: "true" + FORGEJO__actions__ENABLED_FOR_REPOSITORIES: "true" + FORGEJO__actions__ENABLED_FOR_DEFAULT_BRANCH: "true" + FORGEJO__actions__SHARED_SECRET: + valueFrom: + secretKeyRef: + name: "{release}-runner-secrets" + key: shared-secret + +# External Secrets configuration for Actions runner +externalSecrets: + - name: "{release}-runner-secrets" + passwords: + - name: shared-secret + length: 20 + allowRepeat: true + noUpper: false + encoding: hex + secretKeys: + - shared-secret + +# Actions runner configuration +actions: + enabled: true + runner: + image: + repository: code.forgejo.org/forgejo/runner + tag: "12.3.1" + pullPolicy: IfNotPresent + dind: + image: + repository: code.forgejo.org/oci/docker + tag: dind + pullPolicy: IfNotPresent + name: default + replicas: 1 + storage: + size: 10Gi + labels: + - "ubuntu-latest:docker://gitea/runner-images:ubuntu-latest-full" + - "docker-cli:docker://code.forgejo.org/oci/docker:cli" + - "node-bookworm:docker://code.forgejo.org/oci/node:20-bookworm"