commit 90fd37101b092e93c07836d85fb2937b492b3eaf Author: bootstrap Date: Wed May 6 17:35:56 2026 +0300 Initial Gitea chart with smoke CI diff --git a/.gitea/workflows/trash-ci.yaml b/.gitea/workflows/trash-ci.yaml new file mode 100644 index 0000000..be937ec --- /dev/null +++ b/.gitea/workflows/trash-ci.yaml @@ -0,0 +1,19 @@ +name: trash-ci + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + smoke: + runs-on: linux-shell + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Smoke + run: | + echo "runner-ok" + pwd + ls -la diff --git a/.helm/.helmignore b/.helm/.helmignore new file mode 100755 index 0000000..0e8a0eb --- /dev/null +++ b/.helm/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/.helm/Chart.lock b/.helm/Chart.lock new file mode 100644 index 0000000..a085f4d --- /dev/null +++ b/.helm/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: universal-chart + repository: oci://cr.yandex/crp3ccidau046kdj8g9q/charts + version: 0.1.9 +- name: postgresql-preprod + repository: oci://cr.yandex/crp3ccidau046kdj8g9q/charts + version: 16.4.8 +digest: sha256:2fff848510e52c012ae7d941e38fbed2a017c94011e4b3dbca294e672f45a686 +generated: "2026-05-06T12:00:56.461784+03:00" diff --git a/.helm/Chart.yaml b/.helm/Chart.yaml new file mode 100644 index 0000000..7a5b48d --- /dev/null +++ b/.helm/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v2 +name: gitea +description: Gitea with act_runner, PostgreSQL, S3 backups, and restore jobs. +type: application +version: 1.0.0 +appVersion: "1.22.6" +dependencies: + - name: universal-chart + repository: oci://cr.yandex/crp3ccidau046kdj8g9q/charts + version: 0.1.9 + - name: postgresql-preprod + alias: postgresql + repository: oci://cr.yandex/crp3ccidau046kdj8g9q/charts + version: 16.4.8 diff --git a/.helm/charts/postgresql-preprod-16.4.8.tgz b/.helm/charts/postgresql-preprod-16.4.8.tgz new file mode 100644 index 0000000..6a2ad71 Binary files /dev/null and b/.helm/charts/postgresql-preprod-16.4.8.tgz differ diff --git a/.helm/charts/universal-chart-0.1.9.tgz b/.helm/charts/universal-chart-0.1.9.tgz new file mode 100644 index 0000000..6a417ee Binary files /dev/null and b/.helm/charts/universal-chart-0.1.9.tgz differ diff --git a/.helm/restore-values.example.yaml b/.helm/restore-values.example.yaml new file mode 100644 index 0000000..20a3655 --- /dev/null +++ b/.helm/restore-values.example.yaml @@ -0,0 +1,42 @@ +restore: + enabled: true + name: gitea-restore + files: + enabled: true + postgresql: + enabled: true + host: postgresql + s3: + bucket: gitops-gitea + endpointUrl: https://storage.yandexcloud.net + region: ru-central1 + giteaFilesKey: gitops-backups/gitea-files/gitea-files-2026-05-06-141255.tar.gz + postgresqlDumpKey: gitops-backups/postgresql/gitea-postgresql-2026-05-06-111204.sql.gz + scaleGitea: + enabled: true + serviceAccountName: gitea-restore + deploymentName: gitea + waitTimeoutSeconds: 300 + scaleUp: + enabled: true + replicas: 1 + waitForRestoreTimeoutSeconds: 1800 + images: + kubectl: + repository: bitnamilegacy/kubectl + tag: "1.30.6" + pullPolicy: IfNotPresent + awsCli: + repository: amazon/aws-cli + tag: "2.15.57" + pullPolicy: IfNotPresent + postgres: + repository: postgres + tag: "17" + pullPolicy: IfNotPresent + busybox: + repository: busybox + tag: "1.36" + pullPolicy: IfNotPresent + verify: + enabled: true diff --git a/.helm/templates/_helpers.tpl b/.helm/templates/_helpers.tpl new file mode 100644 index 0000000..834936a --- /dev/null +++ b/.helm/templates/_helpers.tpl @@ -0,0 +1,45 @@ +{{- define "gitea.name" -}} +{{- default .Chart.Name .Values.global.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "gitea.fullname" -}} +{{- if .Values.global.fullnameOverride -}} +{{- .Values.global.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- include "gitea.name" . -}} +{{- end -}} +{{- end -}} + +{{- define "gitea.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "gitea.labels" -}} +helm.sh/chart: {{ include "gitea.chart" . }} +app.kubernetes.io/name: {{ include "gitea.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- with .Values.global.labels }} +{{ toYaml . }} +{{- end }} +{{- end -}} + +{{- define "gitea.secretName" -}} +{{- default "gitea-secret" .Values.giteaSecret.name -}} +{{- end -}} + +{{- define "gitea.giteaPvcName" -}} +{{- if .Values.persistence.gitea.existingClaim -}} +{{- .Values.persistence.gitea.existingClaim -}} +{{- else -}} +{{- default "gitea-data" .Values.persistence.gitea.name -}} +{{- end -}} +{{- end -}} + +{{- define "gitea.runnerPvcName" -}} +{{- if .Values.persistence.runner.existingClaim -}} +{{- .Values.persistence.runner.existingClaim -}} +{{- else -}} +{{- default "gitea-runner-data" .Values.persistence.runner.name -}} +{{- end -}} +{{- end -}} diff --git a/.helm/templates/backup-cronjobs.yaml b/.helm/templates/backup-cronjobs.yaml new file mode 100644 index 0000000..373c6d8 --- /dev/null +++ b/.helm/templates/backup-cronjobs.yaml @@ -0,0 +1,170 @@ +{{- if and .Values.backup.enabled .Values.backup.giteaFiles.enabled (eq (default "sidecar" .Values.backup.giteaFiles.mode) "cronjob") }} +apiVersion: batch/v1 +kind: CronJob +metadata: + name: gitea-files-backup + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} +spec: + schedule: {{ .Values.backup.giteaFiles.schedule | quote }} + {{- with .Values.backup.timeZone }} + timeZone: {{ . | quote }} + {{- end }} + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: {{ .Values.backup.giteaFiles.successfulJobsHistoryLimit }} + failedJobsHistoryLimit: {{ .Values.backup.giteaFiles.failedJobsHistoryLimit }} + jobTemplate: + spec: + {{- with .Values.backup.giteaFiles.ttlSecondsAfterFinished }} + ttlSecondsAfterFinished: {{ . }} + {{- end }} + template: + spec: + restartPolicy: OnFailure + containers: + - name: backup + image: "{{ .Values.backup.giteaFiles.image.repository }}:{{ .Values.backup.giteaFiles.image.tag }}" + imagePullPolicy: {{ .Values.backup.giteaFiles.image.pullPolicy }} + command: + - /bin/sh + - -ec + - | + case "${AWS_ACCESS_KEY_ID:-}" in ""|GENERATED_*) echo "AWS_ACCESS_KEY_ID is not configured" >&2; exit 1;; esac + case "${AWS_SECRET_ACCESS_KEY:-}" in ""|GENERATED_*) echo "AWS_SECRET_ACCESS_KEY is not configured" >&2; exit 1;; esac + test -n "${S3_BUCKET:-}" || { echo "S3_BUCKET is not configured" >&2; exit 1; } + + timestamp="$(date +%F-%H%M%S)" + archive_name="gitea-files-${timestamp}.tar.gz" + tar -C /data -czf "/tmp/${archive_name}" . + aws --endpoint-url "${AWS_ENDPOINT_URL}" \ + s3 cp "/tmp/${archive_name}" "s3://${S3_BUCKET}/${S3_PREFIX}/gitea-files/${archive_name}" + env: + - name: S3_BUCKET + value: {{ .Values.backup.s3.bucket | quote }} + - name: S3_PREFIX + value: {{ .Values.backup.s3.prefix | quote }} + - name: AWS_DEFAULT_REGION + value: {{ .Values.backup.s3.region | quote }} + - name: AWS_ENDPOINT_URL + value: {{ .Values.backup.s3.endpointUrl | quote }} + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ include "gitea.secretName" . | quote }} + key: aws-access-key-id + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ include "gitea.secretName" . | quote }} + key: aws-secret-access-key + volumeMounts: + - name: gitea-data + mountPath: /data + readOnly: true + resources: + {{- toYaml .Values.backup.giteaFiles.resources | nindent 16 }} + volumes: + - name: gitea-data + persistentVolumeClaim: + claimName: {{ include "gitea.giteaPvcName" . | quote }} + readOnly: true +{{- end }} +{{- if and .Values.backup.enabled .Values.backup.postgresql.enabled }} +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: gitea-postgresql-backup + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} +spec: + schedule: {{ .Values.backup.postgresql.schedule | quote }} + {{- with .Values.backup.timeZone }} + timeZone: {{ . | quote }} + {{- end }} + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: {{ .Values.backup.postgresql.successfulJobsHistoryLimit }} + failedJobsHistoryLimit: {{ .Values.backup.postgresql.failedJobsHistoryLimit }} + jobTemplate: + spec: + {{- with .Values.backup.postgresql.ttlSecondsAfterFinished }} + ttlSecondsAfterFinished: {{ . }} + {{- end }} + template: + spec: + restartPolicy: OnFailure + initContainers: + - name: dump + image: "{{ .Values.backup.postgresql.dumpImage.repository }}:{{ .Values.backup.postgresql.dumpImage.tag }}" + imagePullPolicy: {{ .Values.backup.postgresql.dumpImage.pullPolicy }} + command: + - /bin/sh + - -ec + - | + timestamp="$(date +%F-%H%M%S)" + dump_name="gitea-postgresql-${timestamp}.sql.gz" + until pg_isready -h "${POSTGRES_HOST}" -U "${POSTGRES_USER}" -d "${POSTGRES_DB}"; do + sleep 5 + done + pg_dump -h "${POSTGRES_HOST}" -U "${POSTGRES_USER}" "${POSTGRES_DB}" | gzip -9 > "/backup/${dump_name}" + printf "%s" "${dump_name}" > /backup/dump-name + env: + - name: POSTGRES_HOST + value: {{ .Values.backup.postgresql.host | quote }} + - name: POSTGRES_DB + value: {{ .Values.postgresql.auth.database | quote }} + - name: POSTGRES_USER + value: {{ .Values.postgresql.auth.username | quote }} + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.databaseSecret.name | quote }} + key: {{ .Values.databaseSecret.passwordKey | quote }} + volumeMounts: + - name: backup + mountPath: /backup + containers: + - name: upload + image: "{{ .Values.backup.postgresql.uploadImage.repository }}:{{ .Values.backup.postgresql.uploadImage.tag }}" + imagePullPolicy: {{ .Values.backup.postgresql.uploadImage.pullPolicy }} + command: + - /bin/sh + - -ec + - | + case "${AWS_ACCESS_KEY_ID:-}" in ""|GENERATED_*) echo "AWS_ACCESS_KEY_ID is not configured" >&2; exit 1;; esac + case "${AWS_SECRET_ACCESS_KEY:-}" in ""|GENERATED_*) echo "AWS_SECRET_ACCESS_KEY is not configured" >&2; exit 1;; esac + test -n "${S3_BUCKET:-}" || { echo "S3_BUCKET is not configured" >&2; exit 1; } + + dump_name="$(cat /backup/dump-name)" + aws --endpoint-url "${AWS_ENDPOINT_URL}" \ + s3 cp "/backup/${dump_name}" "s3://${S3_BUCKET}/${S3_PREFIX}/postgresql/${dump_name}" + env: + - name: S3_BUCKET + value: {{ .Values.backup.s3.bucket | quote }} + - name: S3_PREFIX + value: {{ .Values.backup.s3.prefix | quote }} + - name: AWS_DEFAULT_REGION + value: {{ .Values.backup.s3.region | quote }} + - name: AWS_ENDPOINT_URL + value: {{ .Values.backup.s3.endpointUrl | quote }} + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ include "gitea.secretName" . | quote }} + key: aws-access-key-id + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ include "gitea.secretName" . | quote }} + key: aws-secret-access-key + volumeMounts: + - name: backup + mountPath: /backup + resources: + {{- toYaml .Values.backup.postgresql.resources | nindent 16 }} + volumes: + - name: backup + emptyDir: {} +{{- end }} diff --git a/.helm/templates/gitea-deployment.yaml b/.helm/templates/gitea-deployment.yaml new file mode 100644 index 0000000..ce1c054 --- /dev/null +++ b/.helm/templates/gitea-deployment.yaml @@ -0,0 +1,211 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gitea + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + app: gitea + service: gitea + annotations: + owner: "platform" +spec: + replicas: {{ .Values.gitea.replicaCount }} + revisionHistoryLimit: 10 + strategy: + type: Recreate + selector: + matchLabels: + app: gitea + template: + metadata: + labels: + {{- include "gitea.labels" . | nindent 8 }} + app: gitea + service: gitea + annotations: + traffic.sidecar.istio.io/excludeOutboundPorts: "4317,4318,9411" + spec: + containers: + - name: gitea + image: "{{ .Values.gitea.image.repository }}:{{ .Values.gitea.image.tag }}" + imagePullPolicy: {{ .Values.gitea.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.gitea.service.targetPort }} + protocol: TCP + - name: ssh + containerPort: 22 + protocol: TCP + env: + - name: USER_UID + value: {{ .Values.gitea.uid | quote }} + - name: USER_GID + value: {{ .Values.gitea.gid | quote }} + - name: GITEA__database__DB_TYPE + value: postgres + - name: GITEA__database__HOST + value: postgresql:5432 + - name: GITEA__database__NAME + value: {{ .Values.postgresql.auth.database | quote }} + - name: GITEA__database__USER + value: {{ .Values.postgresql.auth.username | quote }} + - name: GITEA__database__PASSWD + valueFrom: + secretKeyRef: + name: {{ .Values.databaseSecret.name | quote }} + key: {{ .Values.databaseSecret.passwordKey | quote }} + - name: GITEA__server__DOMAIN + value: {{ .Values.gitea.domain | quote }} + - name: GITEA__server__SSH_DOMAIN + value: {{ .Values.gitea.sshDomain | quote }} + - name: GITEA__server__ROOT_URL + value: {{ .Values.gitea.rootUrl | quote }} + - name: GITEA__server__HTTP_PORT + value: {{ .Values.gitea.httpPort | quote }} + - name: GITEA__server__SSH_PORT + value: {{ .Values.gitea.sshPort | quote }} + - name: GITEA__server__SSH_LISTEN_PORT + value: {{ .Values.gitea.sshListenPort | quote }} + - name: GITEA__security__INSTALL_LOCK + value: "true" + - name: GITEA__actions__ENABLED + value: "true" + - name: TZ + value: {{ .Values.gitea.timezone | quote }} + startupProbe: + tcpSocket: + port: http + initialDelaySeconds: {{ .Values.gitea.probes.startup.initialDelaySeconds }} + periodSeconds: {{ .Values.gitea.probes.startup.periodSeconds }} + timeoutSeconds: {{ .Values.gitea.probes.startup.timeoutSeconds }} + failureThreshold: {{ .Values.gitea.probes.startup.failureThreshold }} + readinessProbe: + tcpSocket: + port: http + initialDelaySeconds: {{ .Values.gitea.probes.readiness.initialDelaySeconds }} + periodSeconds: {{ .Values.gitea.probes.readiness.periodSeconds }} + livenessProbe: + tcpSocket: + port: http + initialDelaySeconds: {{ .Values.gitea.probes.liveness.initialDelaySeconds }} + periodSeconds: {{ .Values.gitea.probes.liveness.periodSeconds }} + volumeMounts: + - name: gitea-data + mountPath: /data + resources: + {{- toYaml .Values.gitea.resources | nindent 12 }} + {{- if and .Values.backup.enabled .Values.backup.giteaFiles.enabled (eq (default "sidecar" .Values.backup.giteaFiles.mode) "sidecar") }} + - name: gitea-files-archive + image: "{{ .Values.backup.giteaFiles.archiveImage.repository }}:{{ .Values.backup.giteaFiles.archiveImage.tag }}" + imagePullPolicy: {{ .Values.backup.giteaFiles.archiveImage.pullPolicy }} + command: + - /bin/sh + - -ec + - | + run_archive() { + timestamp="$(date +%F-%H%M%S)" + archive_name="gitea-files-${timestamp}.tar.gz" + tmp_path="/backup/${archive_name}.tmp" + archive_path="/backup/${archive_name}" + marker_path="/backup/${archive_name}.ready" + tar -C /data -czf "${tmp_path}" . + mv "${tmp_path}" "${archive_path}" + printf "%s" "${archive_name}" > "${marker_path}" + } + + last_run_file="/backup/.gitea-files-backup-last-run" + if [ "${RUN_ON_START}" = "true" ]; then + run_archive || true + date +%F > "${last_run_file}" + fi + + while true; do + current_time="$(date +%H:%M)" + current_day="$(date +%F)" + last_run="$(cat "${last_run_file}" 2>/dev/null || true)" + if [ "${current_time}" = "${BACKUP_TIME}" ] && [ "${last_run}" != "${current_day}" ]; then + if run_archive; then + echo "${current_day}" > "${last_run_file}" + fi + fi + sleep 60 + done + env: + - name: BACKUP_TIME + value: {{ .Values.backup.giteaFiles.time | quote }} + - name: RUN_ON_START + value: {{ ternary "true" "false" .Values.backup.giteaFiles.runOnStart | quote }} + - name: TZ + value: {{ .Values.backup.timeZone | quote }} + volumeMounts: + - name: gitea-data + mountPath: /data + readOnly: true + - name: gitea-files-backup + mountPath: /backup + resources: + {{- toYaml .Values.backup.giteaFiles.resources | nindent 12 }} + - name: gitea-files-upload + image: "{{ .Values.backup.giteaFiles.uploadImage.repository }}:{{ .Values.backup.giteaFiles.uploadImage.tag }}" + imagePullPolicy: {{ .Values.backup.giteaFiles.uploadImage.pullPolicy }} + command: + - /bin/sh + - -ec + - | + credentials_ready() { + case "${AWS_ACCESS_KEY_ID:-}" in ""|GENERATED_*) echo "AWS_ACCESS_KEY_ID is not configured" >&2; return 1;; esac + case "${AWS_SECRET_ACCESS_KEY:-}" in ""|GENERATED_*) echo "AWS_SECRET_ACCESS_KEY is not configured" >&2; return 1;; esac + test -n "${S3_BUCKET:-}" || { echo "S3_BUCKET is not configured" >&2; return 1; } + } + + while true; do + for marker_path in /backup/*.ready; do + [ -e "${marker_path}" ] || continue + archive_name="$(cat "${marker_path}")" + archive_path="/backup/${archive_name}" + [ -f "${archive_path}" ] || continue + + if credentials_ready; then + aws --endpoint-url "${AWS_ENDPOINT_URL}" \ + s3 cp "${archive_path}" "s3://${S3_BUCKET}/${S3_PREFIX}/gitea-files/${archive_name}" + rm -f "${archive_path}" "${marker_path}" + else + sleep 300 + fi + done + sleep 60 + done + env: + - name: S3_BUCKET + value: {{ .Values.backup.s3.bucket | quote }} + - name: S3_PREFIX + value: {{ .Values.backup.s3.prefix | quote }} + - name: AWS_DEFAULT_REGION + value: {{ .Values.backup.s3.region | quote }} + - name: AWS_ENDPOINT_URL + value: {{ .Values.backup.s3.endpointUrl | quote }} + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ include "gitea.secretName" . | quote }} + key: aws-access-key-id + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ include "gitea.secretName" . | quote }} + key: aws-secret-access-key + volumeMounts: + - name: gitea-files-backup + mountPath: /backup + resources: + {{- toYaml .Values.backup.giteaFiles.resources | nindent 12 }} + {{- end }} + volumes: + - name: gitea-data + persistentVolumeClaim: + claimName: {{ include "gitea.giteaPvcName" . | quote }} + {{- if and .Values.backup.enabled .Values.backup.giteaFiles.enabled (eq (default "sidecar" .Values.backup.giteaFiles.mode) "sidecar") }} + - name: gitea-files-backup + emptyDir: {} + {{- end }} diff --git a/.helm/templates/gitea-service.yaml b/.helm/templates/gitea-service.yaml new file mode 100644 index 0000000..bb74c1e --- /dev/null +++ b/.helm/templates/gitea-service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: gitea + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + app: gitea + service: gitea +spec: + type: {{ .Values.gitea.service.type }} + selector: + app: gitea + ports: + - name: http + port: {{ .Values.gitea.service.port }} + targetPort: http + protocol: TCP diff --git a/.helm/templates/pvc.yaml b/.helm/templates/pvc.yaml new file mode 100644 index 0000000..9f8f8d5 --- /dev/null +++ b/.helm/templates/pvc.yaml @@ -0,0 +1,45 @@ +{{- if and .Values.persistence.gitea.create (not .Values.persistence.gitea.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "gitea.giteaPvcName" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + {{- with .Values.persistence.gitea.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + {{- toYaml .Values.persistence.gitea.accessModes | nindent 4 }} + {{- with .Values.persistence.gitea.storageClass }} + storageClassName: {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.gitea.size }} +{{- end }} +{{- if and .Values.persistence.runner.create (not .Values.persistence.runner.existingClaim) }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "gitea.runnerPvcName" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + {{- with .Values.persistence.runner.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + {{- toYaml .Values.persistence.runner.accessModes | nindent 4 }} + {{- with .Values.persistence.runner.storageClass }} + storageClassName: {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.runner.size }} +{{- end }} diff --git a/.helm/templates/restore-job.yaml b/.helm/templates/restore-job.yaml new file mode 100644 index 0000000..d67f8a2 --- /dev/null +++ b/.helm/templates/restore-job.yaml @@ -0,0 +1,182 @@ +{{- if and .Values.restore .Values.restore.enabled }} +{{- $restoreFiles := default false .Values.restore.files.enabled }} +{{- $restorePostgresql := default false .Values.restore.postgresql.enabled }} +{{- if not (or $restoreFiles $restorePostgresql) }} +{{- fail "restore.enabled=true requires restore.files.enabled=true or restore.postgresql.enabled=true" }} +{{- end }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Values.restore.name | quote }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "0" + helm.sh/hook-delete-policy: before-hook-creation +spec: + backoffLimit: 1 + template: + spec: + restartPolicy: Never + initContainers: + - name: download + image: "{{ .Values.restore.images.awsCli.repository }}:{{ .Values.restore.images.awsCli.tag }}" + imagePullPolicy: {{ .Values.restore.images.awsCli.pullPolicy }} + command: + - /bin/sh + - -ec + - | + case "${AWS_ACCESS_KEY_ID:-}" in ""|GENERATED_*) echo "AWS_ACCESS_KEY_ID is not configured" >&2; exit 1;; esac + case "${AWS_SECRET_ACCESS_KEY:-}" in ""|GENERATED_*) echo "AWS_SECRET_ACCESS_KEY is not configured" >&2; exit 1;; esac + test -n "${S3_BUCKET:-}" || { echo "S3_BUCKET is not configured" >&2; exit 1; } + {{- if $restoreFiles }} + test -n "${GITEA_FILES_KEY}" + aws --endpoint-url "${AWS_ENDPOINT_URL}" s3 cp "s3://${S3_BUCKET}/${GITEA_FILES_KEY}" /restore/gitea-files.tar.gz + {{- end }} + {{- if $restorePostgresql }} + test -n "${POSTGRESQL_DUMP_KEY}" + aws --endpoint-url "${AWS_ENDPOINT_URL}" s3 cp "s3://${S3_BUCKET}/${POSTGRESQL_DUMP_KEY}" /restore/postgresql.sql.gz + {{- end }} + env: + - name: S3_BUCKET + value: {{ .Values.restore.s3.bucket | quote }} + - name: GITEA_FILES_KEY + value: {{ default "" .Values.restore.s3.giteaFilesKey | quote }} + - name: POSTGRESQL_DUMP_KEY + value: {{ default "" .Values.restore.s3.postgresqlDumpKey | quote }} + - name: AWS_DEFAULT_REGION + value: {{ .Values.restore.s3.region | quote }} + - name: AWS_ENDPOINT_URL + value: {{ .Values.restore.s3.endpointUrl | quote }} + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ include "gitea.secretName" . | quote }} + key: aws-access-key-id + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ include "gitea.secretName" . | quote }} + key: aws-secret-access-key + volumeMounts: + - name: restore + mountPath: /restore + {{- if $restoreFiles }} + - name: restore-files + image: "{{ .Values.restore.images.busybox.repository }}:{{ .Values.restore.images.busybox.tag }}" + imagePullPolicy: {{ .Values.restore.images.busybox.pullPolicy }} + command: + - /bin/sh + - -ec + - | + rm -rf /data/* /data/.[!.]* /data/..?* + tar -C /data -xzf /restore/gitea-files.tar.gz + volumeMounts: + - name: restore + mountPath: /restore + - name: gitea-data + mountPath: /data + {{- end }} + {{- if $restorePostgresql }} + - name: restore-postgresql + image: "{{ .Values.restore.images.postgres.repository }}:{{ .Values.restore.images.postgres.tag }}" + imagePullPolicy: {{ .Values.restore.images.postgres.pullPolicy }} + command: + - /bin/sh + - -ec + - | + export PGPASSWORD="${POSTGRES_PASSWORD}" + until pg_isready -h "${POSTGRES_HOST}" -U "${POSTGRES_USER}" -d postgres; do + sleep 5 + done + + escaped_user="$(printf "%s" "${POSTGRES_USER}" | sed 's/"/""/g')" + escaped_db="$(printf "%s" "${POSTGRES_DB}" | sed 's/"/""/g')" + + if ! psql -h "${POSTGRES_HOST}" -U "${POSTGRES_USER}" -d postgres -tAc "select 1 from pg_database where datname = '${POSTGRES_DB}'" | grep -q 1; then + createdb -h "${POSTGRES_HOST}" -U "${POSTGRES_USER}" -O "${POSTGRES_USER}" "${POSTGRES_DB}" + fi + + psql -h "${POSTGRES_HOST}" -U "${POSTGRES_USER}" -d postgres -v ON_ERROR_STOP=1 \ + -c "ALTER DATABASE \"${escaped_db}\" OWNER TO \"${escaped_user}\";" + + psql -h "${POSTGRES_HOST}" -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -v ON_ERROR_STOP=1 \ + -c "DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public AUTHORIZATION \"${POSTGRES_USER}\"; GRANT ALL ON SCHEMA public TO \"${POSTGRES_USER}\";" + + gunzip -c /restore/postgresql.sql.gz | psql -h "${POSTGRES_HOST}" -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -v ON_ERROR_STOP=1 + env: + - name: POSTGRES_HOST + value: {{ .Values.restore.postgresql.host | quote }} + - name: POSTGRES_DB + value: {{ .Values.postgresql.auth.database | quote }} + - name: POSTGRES_USER + value: {{ .Values.postgresql.auth.username | quote }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.databaseSecret.name | quote }} + key: {{ .Values.databaseSecret.passwordKey | quote }} + volumeMounts: + - name: restore + mountPath: /restore + {{- end }} + containers: + {{- if .Values.restore.verify.enabled }} + - name: verify + image: "{{ .Values.restore.images.postgres.repository }}:{{ .Values.restore.images.postgres.tag }}" + imagePullPolicy: {{ .Values.restore.images.postgres.pullPolicy }} + command: + - /bin/sh + - -ec + - | + {{- if $restoreFiles }} + test -d /data + objects="$(find /data -mindepth 1 -maxdepth 2 | head -n 1)" + test -n "${objects}" + {{- end }} + {{- if $restorePostgresql }} + tables="$(psql -h "${POSTGRES_HOST}" -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -tAc "select count(*) from information_schema.tables where table_schema = 'public'")" + test "${tables}" -gt 0 + core_tables="$(psql -h "${POSTGRES_HOST}" -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -tAc "select count(*) from information_schema.tables where table_schema = 'public' and table_name in ('user', 'repository', 'version')")" + test "${core_tables}" -gt 0 + echo "Gitea database restore verification passed: ${tables} public tables, ${core_tables} core tables" + {{- end }} + echo "Gitea restore verification passed" + env: + - name: POSTGRES_HOST + value: {{ .Values.restore.postgresql.host | quote }} + - name: POSTGRES_DB + value: {{ .Values.postgresql.auth.database | quote }} + - name: POSTGRES_USER + value: {{ .Values.postgresql.auth.username | quote }} + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.databaseSecret.name | quote }} + key: {{ .Values.databaseSecret.passwordKey | quote }} + {{- if $restoreFiles }} + volumeMounts: + - name: gitea-data + mountPath: /data + readOnly: true + {{- end }} + {{- else }} + - name: done + image: "{{ .Values.restore.images.busybox.repository }}:{{ .Values.restore.images.busybox.tag }}" + imagePullPolicy: {{ .Values.restore.images.busybox.pullPolicy }} + command: + - /bin/sh + - -ec + - echo "Gitea restore completed" + {{- end }} + volumes: + - name: restore + emptyDir: {} + {{- if $restoreFiles }} + - name: gitea-data + persistentVolumeClaim: + claimName: {{ include "gitea.giteaPvcName" . | quote }} + {{- end }} +{{- end }} diff --git a/.helm/templates/restore-rbac.yaml b/.helm/templates/restore-rbac.yaml new file mode 100644 index 0000000..9f23896 --- /dev/null +++ b/.helm/templates/restore-rbac.yaml @@ -0,0 +1,73 @@ +{{- if and .Values.restore .Values.restore.enabled .Values.restore.scaleGitea.enabled }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ default .Values.restore.name .Values.restore.scaleGitea.serviceAccountName | quote }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "-30" + helm.sh/hook-delete-policy: before-hook-creation +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ default .Values.restore.name .Values.restore.scaleGitea.serviceAccountName | quote }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "-30" + helm.sh/hook-delete-policy: before-hook-creation +rules: + - apiGroups: + - apps + resources: + - deployments + - deployments/scale + verbs: + - get + - list + - watch + - patch + - update + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch + - apiGroups: + - batch + resources: + - jobs + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ default .Values.restore.name .Values.restore.scaleGitea.serviceAccountName | quote }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "-30" + helm.sh/hook-delete-policy: before-hook-creation +subjects: + - kind: ServiceAccount + name: {{ default .Values.restore.name .Values.restore.scaleGitea.serviceAccountName | quote }} + namespace: {{ .Release.Namespace | quote }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ default .Values.restore.name .Values.restore.scaleGitea.serviceAccountName | quote }} +{{- end }} diff --git a/.helm/templates/restore-scale-down-job.yaml b/.helm/templates/restore-scale-down-job.yaml new file mode 100644 index 0000000..13f6956 --- /dev/null +++ b/.helm/templates/restore-scale-down-job.yaml @@ -0,0 +1,38 @@ +{{- if and .Values.restore .Values.restore.enabled .Values.restore.scaleGitea.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ printf "%s-scale-down" .Values.restore.name | quote }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "-10" + helm.sh/hook-delete-policy: before-hook-creation +spec: + backoffLimit: 1 + template: + spec: + restartPolicy: Never + serviceAccountName: {{ default .Values.restore.name .Values.restore.scaleGitea.serviceAccountName | quote }} + containers: + - name: scale-down-gitea + image: "{{ .Values.restore.images.kubectl.repository }}:{{ .Values.restore.images.kubectl.tag }}" + imagePullPolicy: {{ .Values.restore.images.kubectl.pullPolicy }} + command: + - /bin/sh + - -ec + - | + kubectl -n "${K8S_NAMESPACE}" scale deployment "${GITEA_DEPLOYMENT_NAME}" --replicas=0 + kubectl -n "${K8S_NAMESPACE}" wait --for=delete pod -l app=gitea --timeout="${WAIT_TIMEOUT_SECONDS}s" || true + env: + - name: K8S_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: GITEA_DEPLOYMENT_NAME + value: {{ .Values.restore.scaleGitea.deploymentName | quote }} + - name: WAIT_TIMEOUT_SECONDS + value: {{ .Values.restore.scaleGitea.waitTimeoutSeconds | quote }} +{{- end }} diff --git a/.helm/templates/restore-scale-up-job.yaml b/.helm/templates/restore-scale-up-job.yaml new file mode 100644 index 0000000..6c4cd24 --- /dev/null +++ b/.helm/templates/restore-scale-up-job.yaml @@ -0,0 +1,42 @@ +{{- if and .Values.restore .Values.restore.enabled .Values.restore.scaleGitea.enabled .Values.restore.scaleGitea.scaleUp.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ printf "%s-scale-up" .Values.restore.name | quote }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "10" + helm.sh/hook-delete-policy: before-hook-creation +spec: + backoffLimit: 1 + template: + spec: + restartPolicy: Never + serviceAccountName: {{ default .Values.restore.name .Values.restore.scaleGitea.serviceAccountName | quote }} + containers: + - name: scale-up-gitea + image: "{{ .Values.restore.images.kubectl.repository }}:{{ .Values.restore.images.kubectl.tag }}" + imagePullPolicy: {{ .Values.restore.images.kubectl.pullPolicy }} + command: + - /bin/sh + - -ec + - | + kubectl -n "${K8S_NAMESPACE}" wait --for=condition=complete "job/${RESTORE_JOB_NAME}" --timeout="${RESTORE_WAIT_TIMEOUT_SECONDS}s" + kubectl -n "${K8S_NAMESPACE}" scale deployment "${GITEA_DEPLOYMENT_NAME}" --replicas="${REPLICAS_AFTER_RESTORE}" + env: + - name: K8S_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: RESTORE_JOB_NAME + value: {{ .Values.restore.name | quote }} + - name: GITEA_DEPLOYMENT_NAME + value: {{ .Values.restore.scaleGitea.deploymentName | quote }} + - name: REPLICAS_AFTER_RESTORE + value: {{ .Values.restore.scaleGitea.scaleUp.replicas | quote }} + - name: RESTORE_WAIT_TIMEOUT_SECONDS + value: {{ .Values.restore.scaleGitea.scaleUp.waitForRestoreTimeoutSeconds | quote }} +{{- end }} diff --git a/.helm/templates/runner-configmap.yaml b/.helm/templates/runner-configmap.yaml new file mode 100644 index 0000000..af111d1 --- /dev/null +++ b/.helm/templates/runner-configmap.yaml @@ -0,0 +1,43 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: gitea-runner-config + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} +data: + config.yaml: |- +{{ toYaml .Values.runner.config | nindent 4 }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: gitea-runner-entrypoint + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} +data: + runner-entrypoint.sh: |- + #!/bin/sh + set -eu + + case "${GITEA_RUNNER_REGISTRATION_TOKEN:-}" in + ""|GENERATED_*) + echo "GITEA_RUNNER_REGISTRATION_TOKEN is not configured in Kubernetes secret" + echo "Patch secret gitea-secret key runner-registration-token and restart this pod" + exec tail -f /dev/null + ;; + esac + + if [ ! -f /data/.runner ]; then + echo "Registering Gitea runner ${GITEA_RUNNER_NAME} ..." + act_runner register --no-interactive \ + --instance "${GITEA_INSTANCE_URL}" \ + --token "${GITEA_RUNNER_REGISTRATION_TOKEN}" \ + --name "${GITEA_RUNNER_NAME}" \ + --labels "${GITEA_RUNNER_LABELS}" \ + --config /config.yaml + fi + + echo "Starting Gitea runner daemon" + exec act_runner daemon --config /config.yaml diff --git a/.helm/templates/secret.yaml b/.helm/templates/secret.yaml new file mode 100644 index 0000000..764515f --- /dev/null +++ b/.helm/templates/secret.yaml @@ -0,0 +1,31 @@ +{{- if .Values.giteaSecret.create }} +{{- $secretName := include "gitea.secretName" . }} +{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace $secretName }} +{{- if not $existingSecret }} +{{- $runnerToken := printf "GENERATED_%s" (randAlphaNum 48) }} +{{- $awsAccessKeyId := printf "GENERATED_%s" (randAlphaNum 32) }} +{{- $awsSecretAccessKey := printf "GENERATED_%s" (randAlphaNum 64) }} +{{- $kubeconfig := "apiVersion: v1\nclusters: []\ncontexts: []\ncurrent-context: \"\"\nkind: Config\npreferences: {}\nusers: []" }} +{{- $dockerConfigJson := "{\"auths\":{}}" }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $secretName }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} + {{- if .Values.giteaSecret.keep }} + annotations: + helm.sh/resource-policy: keep + {{- end }} +type: Opaque +stringData: + runner-registration-token: {{ $runnerToken | quote }} + aws-access-key-id: {{ $awsAccessKeyId | quote }} + aws-secret-access-key: {{ $awsSecretAccessKey | quote }} + kubeconfig: |- +{{ $kubeconfig | nindent 4 }} + docker-config.json: |- +{{ $dockerConfigJson | nindent 4 }} +{{- end }} +{{- end }} diff --git a/.helm/templates/ssh-service.yaml b/.helm/templates/ssh-service.yaml new file mode 100644 index 0000000..6ea2bbd --- /dev/null +++ b/.helm/templates/ssh-service.yaml @@ -0,0 +1,24 @@ +{{- if .Values.sshService.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.sshService.name | quote }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "gitea.labels" . | nindent 4 }} +spec: + type: {{ .Values.sshService.type }} + {{- if and .Values.sshService.externalTrafficPolicy (or (eq .Values.sshService.type "NodePort") (eq .Values.sshService.type "LoadBalancer")) }} + externalTrafficPolicy: {{ .Values.sshService.externalTrafficPolicy }} + {{- end }} + selector: + app: gitea + ports: + - name: ssh + port: {{ .Values.sshService.port }} + targetPort: {{ .Values.sshService.targetPort }} + protocol: TCP + {{- if and (eq .Values.sshService.type "NodePort") .Values.sshService.nodePort }} + nodePort: {{ .Values.sshService.nodePort }} + {{- end }} +{{- end }} diff --git a/.helm/values.yaml b/.helm/values.yaml new file mode 100644 index 0000000..4c5cca9 --- /dev/null +++ b/.helm/values.yaml @@ -0,0 +1,521 @@ +global: + nameOverride: "" + fullnameOverride: "" + imagePullSecrets: [] + labels: {} + +giteaSecret: + create: true + name: gitea-secret + keep: true + +databaseSecret: + name: postgresql-secret + adminPasswordKey: admin-password + passwordKey: user-password + +persistence: + gitea: + create: true + name: gitea-data + existingClaim: "" + accessModes: + - ReadWriteOnce + storageClass: "" + size: 50Gi + annotations: {} + runner: + create: true + name: gitea-runner-data + existingClaim: "" + accessModes: + - ReadWriteOnce + storageClass: "" + size: 10Gi + annotations: {} + +postgresql: + enabled: true + fullnameOverride: postgresql + global: + imagePullSecrets: [] + security: + allowInsecureImages: true + postgresql: + auth: + username: gitea + database: gitea + existingSecret: postgresql-secret + secretKeys: + adminPasswordKey: admin-password + userPasswordKey: user-password + replicationPasswordKey: replication-password + auth: + username: gitea + database: gitea + existingSecret: postgresql-secret + secretKeys: + adminPasswordKey: admin-password + userPasswordKey: user-password + replicationPasswordKey: replication-password + image: + repository: contour/postgresql + pullSecrets: [] + primary: + podAntiAffinityPreset: "" + networkPolicy: + enabled: false + podSecurityContext: + enabled: false + containerSecurityContext: + enabled: false + persistence: + storageClass: "" + size: 20Gi + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 1000m + memory: 1Gi + metrics: + enabled: false + image: + pullSecrets: [] + serviceMonitor: + enabled: false + prometheusRule: + enabled: false + volumePermissions: + image: + pullSecrets: [] + +sshService: + enabled: true + name: gitea-ssh + type: NodePort + port: 22 + targetPort: 22 + nodePort: 30222 + externalTrafficPolicy: Cluster + +runner: + config: + log: + level: info + runner: + file: /data/.runner + capacity: 2 + insecure: false + timeout: 3h + cache: + enabled: true + dir: /data/cache + container: + network: "" + privileged: false + +gitea: + replicaCount: 1 + image: + repository: gitea/gitea + tag: "1.22.6" + pullPolicy: IfNotPresent + service: + type: ClusterIP + port: 3000 + targetPort: 3000 + uid: "1000" + gid: "1000" + domain: 158-160-253-227.nip.io + sshDomain: 158-160-253-227.nip.io + rootUrl: https://158-160-253-227.nip.io/ + httpPort: "3000" + sshPort: "30222" + sshListenPort: "22" + timezone: Europe/Moscow + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 2000m + memory: 2Gi + probes: + startup: + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 2 + failureThreshold: 60 + readiness: + initialDelaySeconds: 10 + periodSeconds: 10 + liveness: + initialDelaySeconds: 30 + periodSeconds: 30 + +backup: + enabled: true + timeZone: Europe/Moscow + s3: + bucket: gitops-gitea + region: ru-central1 + endpointUrl: https://storage.yandexcloud.net + prefix: gitops-backups + giteaFiles: + enabled: true + mode: sidecar + schedule: "30 2 * * *" + time: "02:30" + runOnStart: false + archiveImage: + repository: busybox + tag: "1.36" + pullPolicy: IfNotPresent + uploadImage: + repository: amazon/aws-cli + tag: "2.15.57" + pullPolicy: IfNotPresent + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + ttlSecondsAfterFinished: 86400 + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 1000m + memory: 1Gi + postgresql: + enabled: true + schedule: "45 2 * * *" + host: postgresql + dumpImage: + repository: postgres + tag: "17" + pullPolicy: IfNotPresent + uploadImage: + repository: amazon/aws-cli + tag: "2.15.57" + pullPolicy: IfNotPresent + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + ttlSecondsAfterFinished: 86400 + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 1000m + memory: 1Gi + +universal-chart: + global: + env: _default + + services: + gitea: + enabled: false + deployment: + enabled: true + name: + _default: gitea + replicaCount: + _default: 1 + port: + _default: 3000 + revisionHistoryLimit: + _default: 10 + resources: + requests: + cpu: + _default: 200m + memory: + _default: 512Mi + limits: + cpu: + _default: 2000m + memory: + _default: 2Gi + probes: + startup: + enabled: + _default: true + type: + _default: tcpSocket + tcpSocket: + port: + _default: 3000 + initialDelaySeconds: + _default: 10 + periodSeconds: + _default: 10 + timeoutSeconds: + _default: 2 + failureThreshold: + _default: 60 + liveness: + enabled: + _default: true + type: + _default: tcpSocket + tcpSocket: + port: + _default: 3000 + initialDelaySeconds: + _default: 30 + periodSeconds: + _default: 30 + readiness: + enabled: + _default: true + type: + _default: tcpSocket + tcpSocket: + port: + _default: 3000 + initialDelaySeconds: + _default: 10 + periodSeconds: + _default: 10 + image: + name: + _default: gitea/gitea:1.22.6 + pullPolicy: + _default: IfNotPresent + imagePullSecrets: + enabled: + _default: false + name: + _default: dockerhub + service: + enabled: true + name: + _default: gitea + type: + _default: ClusterIP + portName: + _default: http + port: + _default: 3000 + targetPort: + _default: http + envs: + - name: USER_UID + value: + _default: "1000" + - name: USER_GID + value: + _default: "1000" + - name: GITEA__database__DB_TYPE + value: + _default: postgres + - name: GITEA__database__HOST + value: + _default: postgresql:5432 + - name: GITEA__database__NAME + value: + _default: gitea + - name: GITEA__database__USER + value: + _default: gitea + - name: GITEA__server__DOMAIN + value: + _default: 158-160-253-227.nip.io + - name: GITEA__server__SSH_DOMAIN + value: + _default: 158-160-253-227.nip.io + - name: GITEA__server__ROOT_URL + value: + _default: https://158-160-253-227.nip.io/ + - name: GITEA__server__HTTP_PORT + value: + _default: "3000" + - name: GITEA__server__SSH_PORT + value: + _default: "30222" + - name: GITEA__server__SSH_LISTEN_PORT + value: + _default: "22" + - name: GITEA__security__INSTALL_LOCK + value: + _default: "true" + - name: GITEA__actions__ENABLED + value: + _default: "true" + - name: TZ + value: + _default: Europe/Moscow + secretEnvs: + - name: GITEA__database__PASSWD + secretName: + _default: postgresql-secret + secretKey: + _default: user-password + volumes: + _default: + - name: gitea-data + mountPath: /data + persistentVolumeClaim: + claimName: + _default: gitea-data + commitSha: "" + gitlabUri: "" + gitlabJobUrl: "" + owner: platform + + gitea-ci-worker: + enabled: true + deployment: + enabled: true + name: + _default: gitea-ci-worker + replicaCount: + _default: 1 + port: + _default: 8088 + command: + _default: + - /bin/sh + - /runner-entrypoint.sh + revisionHistoryLimit: + _default: 10 + resources: + requests: + cpu: + _default: 200m + memory: + _default: 256Mi + limits: + cpu: + _default: 2000m + memory: + _default: 2Gi + probes: + liveness: + enabled: false + readiness: + enabled: false + image: + name: + _default: gitea/act_runner:0.2.11 + pullPolicy: + _default: IfNotPresent + imagePullSecrets: + enabled: + _default: false + name: + _default: dockerhub + service: + enabled: false + name: + _default: gitea-ci-worker + type: + _default: ClusterIP + portName: + _default: http + port: + _default: 8088 + targetPort: + _default: http + envs: + - name: GITEA_INSTANCE_URL + value: + _default: http://gitea:3000/ + - name: GITEA_RUNNER_NAME + value: + _default: registry01-runner + - name: GITEA_RUNNER_LABELS + value: + _default: linux-amd64:docker://node:20-bookworm,linux-shell:host + - name: DOCKER_HOST + value: + _default: unix:///var/run/docker.sock + - name: KUBECONFIG + value: + _default: /data/.kube/config + - name: KUBE_CONTEXT + value: + _default: yc-k8s-test + - name: AWS_DEFAULT_REGION + value: + _default: ru-central1 + - name: AWS_ENDPOINT_URL + value: + _default: https://storage.yandexcloud.net + - name: S3_BUCKET + value: + _default: gitops-gitea + - name: S3_PREFIX + value: + _default: gitops-backups + secretEnvs: + - name: GITEA_RUNNER_REGISTRATION_TOKEN + secretName: + _default: gitea-secret + secretKey: + _default: runner-registration-token + - name: AWS_ACCESS_KEY_ID + secretName: + _default: gitea-secret + secretKey: + _default: aws-access-key-id + - name: AWS_SECRET_ACCESS_KEY + secretName: + _default: gitea-secret + secretKey: + _default: aws-secret-access-key + volumes: + _default: + - name: runner-data + mountPath: /data + persistentVolumeClaim: + claimName: + _default: gitea-runner-data + - name: runner-config + mountPath: /config.yaml + subPath: config.yaml + readOnly: true + configMap: + name: gitea-runner-config + items: + - key: config.yaml + path: config.yaml + - name: runner-entrypoint + mountPath: /runner-entrypoint.sh + subPath: runner-entrypoint.sh + readOnly: true + configMap: + name: gitea-runner-entrypoint + defaultMode: 493 + items: + - key: runner-entrypoint.sh + path: runner-entrypoint.sh + - name: docker-config + mountPath: /root/.docker/config.json + subPath: config.json + readOnly: true + secret: + secretName: gitea-secret + items: + - key: docker-config.json + path: config.json + - name: kubeconfig + mountPath: /data/.kube/config + subPath: config + readOnly: true + secret: + secretName: gitea-secret + items: + - key: kubeconfig + path: config + - name: docker-sock + mountPath: /var/run/docker.sock + hostPath: + path: /var/run/docker.sock + type: Socket + commitSha: "" + gitlabUri: "" + gitlabJobUrl: "" + owner: platform diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd8e570 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# Gitea Helm chart + +Чарт лежит в `.helm`. + +Зависимости: + +- `universal-chart` `0.1.9` для `gitea-ci-worker` +- статический `Deployment` для `gitea`, потому что файловый backup должен жить sidecar-ом в том же pod-е, где смонтирован RWO PVC +- `postgresql-preprod` `16.4.8` из `oci://cr.yandex/crp3ccidau046kdj8g9q/charts` + +HTTP-маршрутизации здесь нет: ни `Ingress`, ни `VirtualService`. VS управляется отдельным чартом. + +## Секреты + +В `values.yaml` нет чувствительных значений. Чарт создает `gitea-secret` через `lookup`: если Secret уже существует, значения сохраняются; если нет, создаются сгенерированные заглушки. С заглушками runner и S3 backup не заработают, пока Secret не будет заполнен реальными данными. + +Перед установкой можно создать Secret отдельно: + +```bash +kubectl create namespace gitea --dry-run=client -o yaml | kubectl apply -f - + +kubectl -n gitea create secret generic gitea-secret \ + --from-literal=runner-registration-token="$GITEA_RUNNER_REGISTRATION_TOKEN" \ + --from-literal=aws-access-key-id="$AWS_ACCESS_KEY_ID" \ + --from-literal=aws-secret-access-key="$AWS_SECRET_ACCESS_KEY" \ + --from-file=kubeconfig=./runner/kubeconfig/config \ + --from-file=docker-config.json=./runner/docker-config/config.json \ + --dry-run=client -o yaml | kubectl apply -f - +``` + +PostgreSQL пароль живет в `postgresql-secret`, который создает PostgreSQL chart и сохраняет через `helm.sh/resource-policy: keep`. Gitea читает ключ `user-password`. + +## Установка + +```bash +helm dependency update .helm +helm upgrade --install gitea .helm -n gitea --create-namespace +``` + +Если Secret создан уже после установки, перезапустить runner: + +```bash +kubectl -n gitea rollout restart deploy/gitea-ci-worker +``` + +## Backup + +Backup включен по умолчанию: + +- `gitea-files-backup`: sidecar внутри pod `gitea`, каждый день в `02:30` архивирует `/data` и кладет в `s3://gitops-gitea/gitops-backups/gitea-files/`. Отдельного pod-а с mount PVC нет, поэтому не возникает second attach/Multi-Attach для `gitea-data`. +- `gitea-postgresql-backup`: каждый день в `02:45`, делает `pg_dump | gzip` и кладет в `s3://gitops-gitea/gitops-backups/postgresql/` + +Оба backup-процесса используют S3-ключи из `gitea-secret`. Если там сгенерированные заглушки, backup завершится с понятной ошибкой и ничего не загрузит. + +## Restore + +Restore не включен в обычную установку. Это разовый Job, который включается отдельным values-файлом. + +1. Взять имена объектов из S3: + +```bash +aws --endpoint-url "$AWS_ENDPOINT_URL" s3 ls s3://gitops-gitea/gitops-backups/gitea-files/ +aws --endpoint-url "$AWS_ENDPOINT_URL" s3 ls s3://gitops-gitea/gitops-backups/postgresql/ +``` + +2. Скопировать `.helm/restore-values.example.yaml`, указать реальные `giteaFilesKey` и/или `postgresqlDumpKey`. + +3. Запустить restore: + +```bash +helm upgrade --install gitea .helm -n gitea -f restore-values.yaml +``` + +Режимы restore: + +- полный restore: `restore.files.enabled=true` и `restore.postgresql.enabled=true` +- только файлы: `restore.files.enabled=true`, `restore.postgresql.enabled=false` +- только база: `restore.files.enabled=false`, `restore.postgresql.enabled=true` + +Restore Job при `restore.scaleGitea.enabled=true` сначала масштабирует deployment `gitea` в `0` и ждёт удаления pod-а. Это нужно, чтобы restore файлов не монтировал RWO PVC одновременно с Gitea. После успешного completion отдельный `gitea-restore-scale-up` Job возвращает replicas обратно.