diff --git a/.github/ci/values-ci.yaml b/.github/ci/values-ci.yaml new file mode 100644 index 0000000..ed3b034 --- /dev/null +++ b/.github/ci/values-ci.yaml @@ -0,0 +1,59 @@ +image: + registry: ghcr.io + repository: raylabshq/gitea-mirror + tag: "" + +service: + type: ClusterIP + port: 8080 + +ingress: + enabled: true + className: "nginx" + hosts: + - host: ci.example.com + +route: + enabled: true + forceHTTPS: true + domain: ["ci.example.com"] + gateway: "dummy-gw" + gatewayNamespace: "default" + http: + gatewaySection: "http" + https: + gatewaySection: "https" + +gitea-mirror: + nodeEnv: production + core: + databaseUrl: "file:data/gitea-mirror.db" + betterAuthSecret: "dummy" + betterAuthUrl: "http://localhost:4321" + betterAuthTrustedOrigins: "http://localhost:4321" + github: + username: "ci-user" + token: "not-used-in-template" + type: "personal" + privateRepositories: true + skipForks: false + skipStarredIssues: false + gitea: + url: "https://gitea.example.com" + token: "not-used-in-template" + username: "ci-user" + organization: "github-mirrors" + visibility: "public" + mirror: + releases: true + wiki: true + metadata: true + issues: true + pullRequests: true + starred: false + automation: + schedule_enabled: true + schedule_interval: "3600" + cleanup: + enabled: true + interval: "2592000" diff --git a/.github/workflows/README.md b/.github/workflows/README.md index aba0aa2..e0af5b2 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -85,3 +85,10 @@ If a workflow fails: - Security vulnerabilities For persistent issues, consider opening an issue in the repository. + + +### Helm Test (`helm-test.yml`) + +This workflow run on the main branch and pull requests. it: +- Run yamllint to keep the formating unified +- Run helm template with different value files diff --git a/.github/workflows/helm-test.yml b/.github/workflows/helm-test.yml new file mode 100644 index 0000000..2aa11aa --- /dev/null +++ b/.github/workflows/helm-test.yml @@ -0,0 +1,58 @@ +name: Helm Chart CI + +on: + pull_request: + paths: + - 'helm-charts/gitea-mirror/**' + - '.github/workflows/helm-test.yml' + - '.github/ci/values-ci.yaml' + push: + branches: [ main ] + paths: + - 'helm-charts/gitea-mirror/**' + - '.github/workflows/helm-test.yml' + - '.github/ci/values-ci.yaml' + workflow_dispatch: + +jobs: + yamllint: + name: Lint YAML + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Install yamllint + run: pip install --disable-pip-version-check yamllint + - name: Run yamllint + run: | + yamllint -c helm/gitea-mirror/.yamllint helm/gitea-mirror + + helm-template: + name: Helm lint & template + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Helm + uses: azure/setup-helm@v4 + with: + version: v3.19.0 + - name: Helm lint + run: | + helm lint ./helm/gitea-mirror + - name: Template with defaults + run: | + helm template test ./helm/gitea-mirror > /tmp/render-defaults.yaml + test -s /tmp/render-defaults.yaml + - name: Template with CI values + run: | + helm template test ./helm/gitea-mirror -f .github/ci/values-ci.yaml > /tmp/render-ci.yaml + test -s /tmp/render-ci.yaml + - name: Show a summary + run: | + echo "Rendered with defaults:" + awk 'NR<=50{print} NR==51{print "..."; exit}' /tmp/render-defaults.yaml + echo "" + echo "Rendered with CI values:" + awk 'NR<=50{print} NR==51{print "..."; exit}' /tmp/render-ci.yaml diff --git a/helm/gitea-mirror/.yamllint b/helm/gitea-mirror/.yamllint new file mode 100644 index 0000000..111146a --- /dev/null +++ b/helm/gitea-mirror/.yamllint @@ -0,0 +1,21 @@ +--- +extends: default + +ignore: | + .yamllint + node_modules + templates + unittests/bash + +rules: + truthy: + allowed-values: ['true', 'false'] + check-keys: False + level: error + line-length: disable + document-start: disable + comments: + min-spaces-from-content: 1 + braces: + max-spaces-inside: 2 + diff --git a/helm/gitea-mirror/Chart.yaml b/helm/gitea-mirror/Chart.yaml new file mode 100644 index 0000000..042a837 --- /dev/null +++ b/helm/gitea-mirror/Chart.yaml @@ -0,0 +1,12 @@ +apiVersion: v2 +name: gitea-mirror +description: Kubernetes helm chart for gitea-mirror +type: application +version: 0.0.1 +appVersion: 3.7.2 +icon: https://github.com/RayLabsHQ/gitea-mirror/blob/main/.github/assets/logo.png +keywords: + - git + - gitea +sources: + - https://github.com/RayLabsHQ/gitea-mirror diff --git a/helm/gitea-mirror/README.md b/helm/gitea-mirror/README.md new file mode 100644 index 0000000..f4d663c --- /dev/null +++ b/helm/gitea-mirror/README.md @@ -0,0 +1,307 @@ +# gitea-mirror (Helm Chart) + +Deploy **gitea-mirror** to Kubernetes using Helm. The chart packages a Deployment, Service, optional Ingress or Gateway API HTTPRoutes, ConfigMap and Secret, a PVC (optional), and an optional ServiceAccount. + +- **Chart name:** `gitea-mirror` +- **Type:** `application` +- **App version:** `3.7.2` (default image tag, can be overridden) + +--- + +## Prerequisites + +- Kubernetes 1.23+ +- Helm 3.8+ +- (Optional) Gateway API (v1) if you plan to use `route.*` HTTPRoutes, see https://github.com/kubernetes-sigs/gateway-api/ +- (Optional) An Ingress controller if you plan to use `ingress.*` + +--- + +## Quick start + +From the repo root (chart path: `helm/gitea-mirror`): + +```bash +# Create a namespace (optional) +kubectl create namespace gitea-mirror + +# Install with minimal required secrets/values +helm upgrade --install gitea-mirror ./helm/gitea-mirror --namespace gitea-mirror --set "gitea-mirror.github.username=" --set "gitea-mirror.github.token=" --set "gitea-mirror.gitea.url=https://gitea.example.com" --set "gitea-mirror.gitea.token=" +``` + +The default Service is `ClusterIP` on port `8080`. You can expose it via Ingress or Gateway API; see below. + +--- + +## Upgrading + +Standard Helm upgrade: + +```bash +helm upgrade gitea-mirror ./helm/gitea-mirror -n gitea-mirror +``` + +If you change persistence settings or storage class, a rollout may require PVC recreation. + +--- + +## Uninstalling + +```bash +helm uninstall gitea-mirror -n gitea-mirror +``` + +If you enabled persistence with a PVC the data may persist; delete the PVC manually if you want a clean slate. + +--- + +## Configuration + +### Global image & pod settings + +| Key | Type | Default | Description | +| --- | --- | --- | --- | +| `image.registry` | string | `ghcr.io` | Container registry. | +| `image.repository` | string | `raylabshq/gitea-mirror` | Image repository. | +| `image.tag` | string | `""` | Image tag; when empty, uses the chart `appVersion` (`3.7.2`). | +| `image.pullPolicy` | string | `IfNotPresent` | K8s image pull policy. | +| `imagePullSecrets` | list | `[]` | Image pull secrets. | +| `podSecurityContext.runAsUser` | int | `1001` | UID. | +| `podSecurityContext.runAsGroup` | int | `1001` | GID. | +| `podSecurityContext.fsGroup` | int | `1001` | FS group. | +| `podSecurityContext.fsGroupChangePolicy` | string | `OnRootMismatch` | FS group change policy. | +| `nodeSelector` / `tolerations` / `affinity` / `topologySpreadConstraints` | — | — | Standard scheduling knobs. | +| `extraVolumes` / `extraVolumeMounts` | list | `[]` | Append custom volumes/mounts. | +| `priorityClassName` | string | `""` | Optional Pod priority class. | + +### Deployment + +| Key | Type | Default | Description | +| --- | --- | --- | --- | +| `deployment.port` | int | `8080` | Container port & named `http` port. | +| `deployment.strategy.type` | string | `Recreate` | Update strategy (`Recreate` or `RollingUpdate`). | +| `deployment.strategy.rollingUpdate.maxUnavailable/maxSurge` | string/int | — | Used when `type=RollingUpdate`. | +| `deployment.env` | list | `[]` | Extra environment variables. | +| `deployment.resources` | map | `{}` | CPU/memory requests & limits. | +| `deployment.terminationGracePeriodSeconds` | int | `60` | Grace period. | +| `livenessProbe.*` | — | enabled, `/api/health` | Liveness probe (HTTP GET to `/api/health`). | +| `readinessProbe.*` | — | enabled, `/api/health` | Readiness probe. | +| `startupProbe.*` | — | enabled, `/api/health` | Startup probe. | + +> The Pod mounts a volume at `/app/data` (PVC or `emptyDir` depending on `persistence.enabled`). + +### Service + +| Key | Type | Default | Description | +| --- | --- | --- | --- | +| `service.type` | string | `ClusterIP` | Service type. | +| `service.port` | int | `8080` | Service port. | +| `service.clusterIP` | string | `None` | ClusterIP (only when `type=ClusterIP`). | +| `service.externalTrafficPolicy` | string | `""` | External traffic policy (LB). | +| `service.loadBalancerIP` | string | `""` | LoadBalancer IP. | +| `service.loadBalancerClass` | string | `""` | LoadBalancer class. | +| `service.annotations` / `service.labels` | map | `{}` | Extra metadata. | + +### Ingress (optional) + +| Key | Type | Default | Description | +| --- | --- | --- | --- | +| `ingress.enabled` | bool | `false` | Enable Ingress. | +| `ingress.className` | string | `""` | IngressClass name. | +| `ingress.hosts[0].host` | string | `mirror.example.com` | Hostname. | +| `ingress.tls` | list | `[]` | TLS blocks (secret name etc.). | +| `ingress.annotations` | map | `{}` | Controller-specific annotations. | + +> The Ingress exposes `/` to the chart’s Service. + +### Gateway API HTTPRoutes (optional) + +| Key | Type | Default | Description | +| --- | --- | --- | --- | +| `route.enabled` | bool | `false` | Enable Gateway API HTTPRoutes. | +| `route.forceHTTPS` | bool | `true` | If true, create an HTTP route that redirects to HTTPS (301). | +| `route.domain` | list | `["mirror.example.com"]` | Hostnames. | +| `route.gateway` | string | `""` | Gateway name. | +| `route.gatewayNamespace` | string | `""` | Gateway namespace. | +| `route.http.gatewaySection` | string | `""` | SectionName for HTTP listener. | +| `route.https.gatewaySection` | string | `""` | SectionName for HTTPS listener. | +| `route.http.filters` / `route.https.filters` | list | `[]` | Additional filters. (Defaults add HSTS header on HTTPS.) | + +### Persistence + +| Key | Type | Default | Description | +| --- | --- | --- | --- | +| `persistence.enabled` | bool | `true` | Enable persistent storage. | +| `persistence.create` | bool | `true` | Create a PVC from the chart. | +| `persistence.claimName` | string | `gitea-mirror-storage` | PVC name. | +| `persistence.storageClass` | string | `""` | StorageClass to use. | +| `persistence.accessModes` | list | `["ReadWriteOnce"]` | Access modes. | +| `persistence.size` | string | `1Gi` | Requested size. | +| `persistence.volumeName` | string | `""` | Bind to existing PV by name (optional). | +| `persistence.annotations` | map | `{}` | PVC annotations. | + +### ServiceAccount (optional) + +| Key | Type | Default | Description | +| --- | --- | --- | --- | +| `serviceAccount.create` | bool | `false` | Create a ServiceAccount. | +| `serviceAccount.name` | string | `""` | SA name (defaults to release fullname). | +| `serviceAccount.automountServiceAccountToken` | bool | `false` | Automount token. | +| `serviceAccount.annotations` / `labels` | map | `{}` | Extra metadata. | + +--- + +## Application configuration (`gitea-mirror.*`) + +These values populate a **ConfigMap** (non-secret) and a **Secret** (for tokens and sensitive fields). Environment variables from both are consumed by the container. + +### Core + +| Key | Default | Mapped env | +| --- | --- | --- | +| `gitea-mirror.nodeEnv` | `production` | `NODE_ENV` | +| `gitea-mirror.core.databaseUrl` | `file:data/gitea-mirror.db` | `DATABASE_URL` | +| `gitea-mirror.core.encryptionSecret` | `""` | `ENCRYPTION_SECRET` (Secret) | +| `gitea-mirror.core.betterAuthSecret` | `""` | `BETTER_AUTH_SECRET` | +| `gitea-mirror.core.betterAuthUrl` | `http://localhost:4321` | `BETTER_AUTH_URL` | +| `gitea-mirror.core.betterAuthTrustedOrigins` | `http://localhost:4321` | `BETTER_AUTH_TRUSTED_ORIGINS` | + +### GitHub + +| Key | Default | Mapped env | +| --- | --- | --- | +| `gitea-mirror.github.username` | `""` | `GITHUB_USERNAME` | +| `gitea-mirror.github.token` | `""` | `GITHUB_TOKEN` (Secret) | +| `gitea-mirror.github.type` | `personal` | `GITHUB_TYPE` | +| `gitea-mirror.github.privateRepositories` | `true` | `PRIVATE_REPOSITORIES` | +| `gitea-mirror.github.skipForks` | `false` | `SKIP_FORKS` | +| `gitea-mirror.github.skipStarredIssues` | `false` | `SKIP_STARRED_ISSUES` | +| `gitea-mirror.github.mirrorStarred` | `false` | `MIRROR_STARRED` | + +### Gitea + +| Key | Default | Mapped env | +| --- | --- | --- | +| `gitea-mirror.gitea.url` | `""` | `GITEA_URL` | +| `gitea-mirror.gitea.token` | `""` | `GITEA_TOKEN` (Secret) | +| `gitea-mirror.gitea.username` | `""` | `GITEA_USERNAME` | +| `gitea-mirror.gitea.organization` | `github-mirrors` | `GITEA_ORGANIZATION` | +| `gitea-mirror.gitea.visibility` | `public` | `GITEA_ORG_VISIBILITY` | + +### Mirror options + +| Key | Default | Mapped env | +| --- | --- | --- | +| `gitea-mirror.mirror.releases` | `true` | `MIRROR_RELEASES` | +| `gitea-mirror.mirror.wiki` | `true` | `MIRROR_WIKI` | +| `gitea-mirror.mirror.metadata` | `true` | `MIRROR_METADATA` | +| `gitea-mirror.mirror.issues` | `true` | `MIRROR_ISSUES` | +| `gitea-mirror.mirror.pullRequests` | `true` | `MIRROR_PULL_REQUESTS` | +| `gitea-mirror.mirror.starred` | _(see note above)_ | `MIRROR_STARRED` | + +### Automation & cleanup + +| Key | Default | Mapped env | +| --- | --- | --- | +| `gitea-mirror.automation.schedule_enabled` | `true` | `SCHEDULE_ENABLED` | +| `gitea-mirror.automation.schedule_interval` | `3600` | `SCHEDULE_INTERVAL` (seconds) | +| `gitea-mirror.cleanup.enabled` | `true` | `CLEANUP_ENABLED` | +| `gitea-mirror.cleanup.retentionDays` | `30` | `CLEANUP_RETENTION_DAYS` | + +> **Secrets:** If you set `gitea-mirror.existingSecret` (name of an existing Secret), the chart will **not** create its own Secret and will reference yours instead. Otherwise it creates a Secret with `GITHUB_TOKEN`, `GITEA_TOKEN`, `ENCRYPTION_SECRET`. + +--- + +## Exposing the service + +### Using Ingress + +```yaml +ingress: + enabled: true + className: "nginx" + hosts: + - host: mirror.example.com + tls: + - secretName: mirror-tls + hosts: + - mirror.example.com +``` + +This creates an Ingress routing `/` to the service on port `8080`. + +### Using Gateway API (HTTPRoute) + +```yaml +route: + enabled: true + domain: ["mirror.example.com"] + gateway: "my-gateway" + gatewayNamespace: "gateway-system" + http: + gatewaySection: "http" + https: + gatewaySection: "https" + # Example extra filter already included by default: add HSTS header +``` + +If `forceHTTPS: true`, the chart emits an HTTP route that redirects to HTTPS with 301. An HTTPS route is always created when `route.enabled=true`. + +--- + +## Persistence & data + +By default, the chart provisions a PVC named `gitea-mirror-storage` with `1Gi` and mounts it at `/app/data`. To use an existing PV or tune storage, adjust `persistence.*` in `values.yaml`. If you disable persistence, an `emptyDir` will be used instead. + +--- + +## Environment & health endpoints + +The container listens on `PORT` (defaults to `deployment.port` = `8080`) and exposes `GET /api/health` for liveness/readiness/startup probes. + +--- + +## Examples + +### Minimal (tokens via chart-managed Secret) + +```yaml +gitea-mirror: + github: + username: "gitea-mirror" + token: "" + gitea: + url: "https://gitea.company.tld" + token: "" +``` + +### Bring your own Secret + +```yaml +gitea-mirror: + existingSecret: "gitea-mirror-secrets" + github: + username: "gitea-mirror" + gitea: + url: "https://gitea.company.tld" +``` + +Where `gitea-mirror-secrets` contains keys `GITHUB_TOKEN`, `GITEA_TOKEN`, `ENCRYPTION_SECRET`. + +--- + +## Development + +Lint the chart: + +```bash +yamllint -c helm/gitea-mirror/.yamllint helm/gitea-mirror +``` + +Tweak probes, resources, and scheduling as needed; see `values.yaml`. + +--- + +## License + +This chart is part of the `RayLabsHQ/gitea-mirror` repository. See the repository for licensing details. diff --git a/helm/gitea-mirror/templates/_helpers.tpl b/helm/gitea-mirror/templates/_helpers.tpl new file mode 100644 index 0000000..e01bff9 --- /dev/null +++ b/helm/gitea-mirror/templates/_helpers.tpl @@ -0,0 +1,59 @@ +{{/* +Expand the name of the chart. +*/}} + +{{- define "gitea-mirror.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "gitea-mirror.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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "gitea-mirror.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "gitea-mirror.labels" -}} +helm.sh/chart: {{ include "gitea-mirror.chart" . }} +app: {{ include "gitea-mirror.name" . }} +{{ include "gitea-mirror.selectorLabels" . }} +app.kubernetes.io/version: {{ .Values.image.tag | default .Chart.AppVersion | quote }} +version: {{ .Values.image.tag | default .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels +*/}} +{{- define "gitea-mirror.selectorLabels" -}} +app.kubernetes.io/name: {{ include "gitea-mirror.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{/* +ServiceAccount name +*/}} +{{- define "gitea-mirror.serviceAccountName" -}} +{{ .Values.serviceAccount.name | default (include "gitea-mirror.fullname" .) }} +{{- end -}} diff --git a/helm/gitea-mirror/templates/configmap.yaml b/helm/gitea-mirror/templates/configmap.yaml new file mode 100644 index 0000000..c9acfe7 --- /dev/null +++ b/helm/gitea-mirror/templates/configmap.yaml @@ -0,0 +1,38 @@ +{{- $gm := index .Values "gitea-mirror" -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "gitea-mirror.fullname" . }} + labels: + {{- include "gitea-mirror.labels" . | nindent 4 }} +data: + NODE_ENV: {{ $gm.nodeEnv | quote }} + # Core configuration + DATABASE_URL: {{ $gm.core.databaseUrl | quote }} + BETTER_AUTH_SECRET: {{ $gm.core.betterAuthSecret | quote }} + BETTER_AUTH_URL: {{ $gm.core.betterAuthUrl | quote }} + BETTER_AUTH_TRUSTED_ORIGINS: {{ $gm.core.betterAuthTrustedOrigins | quote }} + # GitHub Config + GITHUB_USERNAME: {{ $gm.github.username | quote }} + GITHUB_TYPE: {{ $gm.github.type | quote }} + PRIVATE_REPOSITORIES: {{ $gm.github.privateRepositories | quote }} + MIRROR_STARRED: {{ $gm.github.mirrorStarred | quote }} + SKIP_FORKS: {{ $gm.github.skipForks | quote }} + SKIP_STARRED_ISSUES: {{ $gm.github.skipStarredIssues | quote }} + # Gitea Config + GITEA_URL: {{ $gm.gitea.url | quote }} + GITEA_USERNAME: {{ $gm.gitea.username | quote }} + GITEA_ORGANIZATION: {{ $gm.gitea.organization | quote }} + GITEA_ORG_VISIBILITY: {{ $gm.gitea.visibility | quote }} + # Mirror Options + MIRROR_RELEASES: {{ $gm.mirror.releases | quote }} + MIRROR_WIKI: {{ $gm.mirror.wiki | quote }} + MIRROR_METADATA: {{ $gm.mirror.metadata | quote }} + MIRROR_ISSUES: {{ $gm.mirror.issues | quote }} + MIRROR_PULL_REQUESTS: {{ $gm.mirror.pullRequests | quote }} + # Automation + SCHEDULE_ENABLED: {{ $gm.automation.schedule_enabled| quote }} + SCHEDULE_INTERVAL: {{ $gm.automation.schedule_interval | quote }} + # Cleanup + CLEANUP_ENABLED: {{ $gm.cleanup.enabled | quote }} + CLEANUP_RETENTION_DAYS: {{ $gm.cleanup.retentionDays | quote }} diff --git a/helm/gitea-mirror/templates/deployment.yaml b/helm/gitea-mirror/templates/deployment.yaml new file mode 100644 index 0000000..34e02a2 --- /dev/null +++ b/helm/gitea-mirror/templates/deployment.yaml @@ -0,0 +1,143 @@ +{{- $gm := index .Values "gitea-mirror" -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "gitea-mirror.fullname" . }} + {{- with .Values.deployment.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "gitea-mirror.labels" . | nindent 4 }} + {{- if .Values.deployment.labels }} + {{- toYaml .Values.deployment.labels | nindent 4 }} + {{- end }} +spec: + replicas: 1 + strategy: + type: {{ .Values.deployment.strategy.type }} + {{- if eq .Values.deployment.strategy.type "RollingUpdate" }} + rollingUpdate: + maxUnavailable: {{ .Values.deployment.strategy.rollingUpdate.maxUnavailable }} + maxSurge: {{ .Values.deployment.strategy.rollingUpdate.maxSurge }} + {{- end }} + selector: + matchLabels: + {{- include "gitea-mirror.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "gitea-mirror.labels" . | nindent 8 }} + {{- if .Values.deployment.labels }} + {{- toYaml .Values.deployment.labels | nindent 8 }} + {{- end }} + spec: + {{- if (or .Values.serviceAccount.create .Values.serviceAccount.name) }} + serviceAccountName: {{ include "gitea-mirror.serviceAccountName" . }} + {{- end }} + {{- if .Values.priorityClassName }} + priorityClassName: "{{ .Values.priorityClassName }}" + {{- end }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.deployment.terminationGracePeriodSeconds }} + containers: + - name: gitea-mirror + image: {{ .Values.image.registry }}/{{ .Values.image.repository }}:v{{ .Values.image.tag | default .Chart.AppVersion | toString }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + envFrom: + - configMapRef: + name: {{ include "gitea-mirror.fullname" . }} + {{- if $gm.existingSecret }} + - secretRef: + name: {{ $gm.existingSecret }} + {{- else }} + - secretRef: + name: {{ include "gitea-mirror.fullname" . }} + {{- end }} + env: + - name: PORT + value: "{{ .Values.deployment.port }}" + {{- if .Values.deployment.env }} + {{- toYaml .Values.deployment.env | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: {{ .Values.deployment.port }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: /api/health + port: "http" + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + successThreshold: {{ .Values.livenessProbe.successThreshold }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /api/health + port: "http" + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + {{- end }} + {{- if .Values.startupProbe.enabled }} + startupProbe: + httpGet: + path: /api/health + port: "http" + initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.startupProbe.periodSeconds }} + timeoutSeconds: {{ .Values.startupProbe.timeoutSeconds }} + failureThreshold: {{ .Values.startupProbe.failureThreshold }} + successThreshold: {{ .Values.startupProbe.successThreshold }} + {{- end }} + volumeMounts: + - name: data + mountPath: /app/data + {{- if .Values.extraVolumeMounts }} + {{- toYaml .Values.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- with .Values.deployment.resources }} + resources: + {{- toYaml .Values.deployment.resources | nindent 12 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + {{- if .Values.persistence.enabled }} + - name: data + persistentVolumeClaim: + claimName: {{ .Values.persistence.claimName }} + {{- else if not .Values.persistence.enabled }} + - name: data + emptyDir: {} + {{- end }} + {{- if .Values.extraVolumes }} + {{- toYaml .Values.extraVolumes | nindent 8 }} + {{- end }} diff --git a/helm/gitea-mirror/templates/httproute.yaml b/helm/gitea-mirror/templates/httproute.yaml new file mode 100644 index 0000000..d9a8bc1 --- /dev/null +++ b/helm/gitea-mirror/templates/httproute.yaml @@ -0,0 +1,77 @@ +{{- if .Values.route.enabled }} +{{- if .Values.route.forceHTTPS }} +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "gitea-mirror.fullname" . }}-http + labels: + {{- include "gitea-mirror.labels" . | nindent 4 }} +spec: + parentRefs: + - name: {{ .Values.route.gateway }} + sectionName: {{ .Values.route.http.gatewaySection }} + namespace: {{ .Values.route.gatewayNamespace }} + hostnames: {{ .Values.route.domain }} + rules: + - filters: + - type: RequestRedirect + requestRedirect: + scheme: https + statusCode: 301 + {{- with .Values.route.http.filters }} + {{ toYaml . | nindent 4 }} + {{- end }} +{{- else }} +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "gitea-mirror.fullname" . }}-http + labels: + {{- include "gitea-mirror.labels" . | nindent 4 }} +spec: + parentRefs: + - name: {{ .Values.route.gateway }} + sectionName: {{ .Values.route.http.gatewaySection }} + namespace: {{ .Values.route.gatewayNamespace }} + hostnames: {{ .Values.route.domain }} + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: {{ include "gitea-mirror.fullname" . }} + port: {{ .Values.service.port }} + {{- with .Values.route.http.filters }} + filters: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "gitea-mirror.fullname" . }}-https + labels: + {{- include "gitea-mirror.labels" . | nindent 4 }} +spec: + parentRefs: + - name: {{ .Values.route.gateway }} + sectionName: {{ .Values.route.https.gatewaySection }} + namespace: {{ .Values.route.gatewayNamespace }} + hostnames: {{ .Values.route.domain }} + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: {{ include "gitea-mirror.fullname" . }} + port: {{ .Values.service.port }} + {{- with .Values.route.https.filters }} + filters: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/helm/gitea-mirror/templates/ingress.yaml b/helm/gitea-mirror/templates/ingress.yaml new file mode 100644 index 0000000..6458752 --- /dev/null +++ b/helm/gitea-mirror/templates/ingress.yaml @@ -0,0 +1,40 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "gitea-mirror.fullname" . }} + labels: + {{- include "gitea-mirror.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: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ tpl . $ | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ tpl .host $ | quote }} + http: + paths: + - path: {{ .path | default "/" }} + pathType: {{ .pathType | default "Prefix" }} + backend: + service: + name: {{ include "gitea-mirror.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} +{{- end }} + diff --git a/helm/gitea-mirror/templates/pvc.yaml b/helm/gitea-mirror/templates/pvc.yaml new file mode 100644 index 0000000..81f9ce9 --- /dev/null +++ b/helm/gitea-mirror/templates/pvc.yaml @@ -0,0 +1,26 @@ +{{- if and .Values.persistence.enabled .Values.persistence.create }} +{{- $gm := index .Values "gitea-mirror" -}} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ .Values.persistence.claimName }} + labels: + {{- include "gitea-mirror.labels" . | nindent 4 }} + {{- with .Values.persistence.annotations }} + annotations: + {{ . | toYaml | indent 4}} + {{- end }} +spec: + accessModes: + {{- toYaml .Values.persistence.accessModes | nindent 4 }} + {{- with .Values.persistence.storageClass }} + storageClassName: {{ . }} + {{- end }} + volumeMode: Filesystem + {{- with .Values.persistence.volumeName }} + volumeName: {{ . }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size }} +{{- end }} diff --git a/helm/gitea-mirror/templates/secret.yaml b/helm/gitea-mirror/templates/secret.yaml new file mode 100644 index 0000000..0cae2e2 --- /dev/null +++ b/helm/gitea-mirror/templates/secret.yaml @@ -0,0 +1,14 @@ +{{- $gm := index .Values "gitea-mirror" -}} +{{- if (empty $gm.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "gitea-mirror.fullname" . }} + labels: + {{- include "gitea-mirror.labels" . | nindent 4 }} +type: Opaque +stringData: + GITHUB_TOKEN: {{ $gm.github.token | quote }} + GITEA_TOKEN: {{ $gm.gitea.token | quote }} + ENCRYPTION_SECRET: {{ $gm.core.encryptionSecret | quote }} +{{- end }} diff --git a/helm/gitea-mirror/templates/service.yaml b/helm/gitea-mirror/templates/service.yaml new file mode 100644 index 0000000..00c4e73 --- /dev/null +++ b/helm/gitea-mirror/templates/service.yaml @@ -0,0 +1,34 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "gitea-mirror.fullname" . }} + labels: + {{- include "gitea-mirror.labels" . | nindent 4 }} + {{- if .Values.service.labels }} + {{- toYaml .Values.service.labels | nindent 4 }} + {{- end }} + annotations: + {{- toYaml .Values.service.annotations | nindent 4 }} +spec: + type: {{ .Values.service.type }} + {{- if eq .Values.service.type "LoadBalancer" }} + {{- if .Values.service.loadBalancerClass }} + loadBalancerClass: {{ .Values.service.loadBalancerClass }} + {{- end }} + {{- if and .Values.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{- end }} + {{- end }} + {{- if .Values.service.externalTrafficPolicy }} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} + {{- end }} + {{- if and .Values.service.clusterIP (eq .Values.service.type "ClusterIP") }} + clusterIP: {{ .Values.service.clusterIP }} + {{- end }} + ports: + - name: http + port: {{ .Values.service.port }} + protocol: TCP + targetPort: http + selector: + {{- include "gitea-mirror.selectorLabels" . | nindent 4 }} diff --git a/helm/gitea-mirror/templates/serviceaccount.yaml b/helm/gitea-mirror/templates/serviceaccount.yaml new file mode 100644 index 0000000..34249c9 --- /dev/null +++ b/helm/gitea-mirror/templates/serviceaccount.yaml @@ -0,0 +1,17 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "gitea-mirror.serviceAccountName" . }} + labels: + {{- include "gitea-mirror.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.labels }} + {{- . | toYaml | nindent 4 }} + {{- end }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- . | toYaml | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} +{{- end }} + diff --git a/helm/gitea-mirror/values.yaml b/helm/gitea-mirror/values.yaml new file mode 100644 index 0000000..41121c8 --- /dev/null +++ b/helm/gitea-mirror/values.yaml @@ -0,0 +1,151 @@ +image: + registry: ghcr.io + repository: raylabshq/gitea-mirror + # Leave blank to use the Appversion tag + tag: "" + pullPolicy: IfNotPresent + +imagePullSecrets: [] +podSecurityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + fsGroupChangePolicy: OnRootMismatch + +ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: mirror.example.com + paths: + - path: / + pathType: Prefix + tls: [] + # - secretName: chart-example-tls + # hosts: + # - mirror.example.com + +route: + enabled: false + forceHTTPS: true + domain: ["mirror.example.com"] + gateway: "" + gatewayNamespace: "" + http: + gatewaySection: "" + filters: [] + https: + gatewaySection: "" + filters: + - type: ResponseHeaderModifier + responseHeaderModifier: + add: + - name: Strict-Transport-Security + value: "max-age=31536000; includeSubDomains; preload" + +service: + type: ClusterIP + port: 8080 + clusterIP: None + annotations: {} + externalTrafficPolicy: + labels: {} + loadBalancerIP: + loadBalancerClass: + +deployment: + port: 8080 + strategy: + type: Recreate + env: [] + terminationGracePeriodSeconds: 60 + labels: {} + annotations: {} + resources: {} + +livenessProbe: + enabled: true + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 +readinessProbe: + enabled: true + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 +startupProbe: + enabled: true + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +persistence: + enabled: true + create: true + claimName: gitea-mirror-storage + storageClass: "" + accessModes: + - ReadWriteOnce + size: 1Gi + +affinity: {} +nodeSelector: {} +tolerations: [] +topologySpreadConstraints: [] +extraVolumes: [] +extraVolumeMounts: [] + +serviceAccount: + create: false + name: "" + annotations: {} + labels: {} + automountServiceAccountToken: false + +gitea-mirror: + existingSecret: "" + nodeEnv: production + core: + databaseUrl: file:data/gitea-mirror.db + encryptionSecret: "" + betterAuthSecret: "" + betterAuthUrl: "http://localhost:4321" + betterAuthTrustedOrigins: "http://localhost:4321" + + github: + username: "" + token: "" + type: personal + privateRepositories: true + mirrorStarred: false + skipForks: false + skipStarredIssues: false + + gitea: + url: "" + token: "" + username: "" + organization: "github-mirrors" + visibility: "public" + + mirror: + releases: true + wiki: true + metadata: true + issues: true + pullRequests: true + + automation: + schedule_enabled: true + schedule_interval: 3600 + + cleanup: + enabled: true + retentionDays: 30