- Go Template 75.2%
- Shell 24.8%
| charts/forgejo-17.0.1/forgejo | ||
| k8up-backup | ||
| resources | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| .sops.yaml | ||
| kustomization.yaml | ||
| kustomize-build.sh | ||
| README.md | ||
| renovate.json | ||
| values.yaml | ||
Forgejo (git.xarif.de)
Forgejo v15.0.1 (Chart 17.0.1) — primary Git platform. Helm + Kustomize + ArgoCD. Auth: OIDC via Authelia. SSH: port 22 (LoadBalancer, rootless 22→2222 remap).
Global Mirror Hook
WHY: Every push to a public repo mirrors to gitlab.com/xarif/<org>/<repo> for disaster recovery.
HOW: Symlink in each repo's hooks/post-receive.d/mirror-git-repo → ConfigMap-mounted script.
New repos: Git's init.templateDir ([git.config] in app.ini) propagates the symlink automatically at git init --bare time. No cron, no manual intervention.
Existing repos: postStart lifecycle hook backfills missing symlinks on every pod start.
Helm chart limitation: Chart v17 ignores lifecycle values key — applied via Kustomize strategic merge patch (resources/patch-lifecycle.yaml).
Mirror State Sync
WHY: Mirror hook fires only on git push, so archive/unarchive and description edits do not propagate. Forgejo has no archive webhook event (verified in gitea/modules/webhook/type.go — repository event covers only created/deleted/transferred). Polling reconcile is the only option.
HOW: Daily CronJob (0 3 * * *) iterates /api/v1/repos/search, compares archived flag and description against gitlab.com/xarif/<org>/<repo>, patches gitlab.com when state diverges. Forgejo is source of truth; orphan gitlab.com repos are never modified.
Skips: private Forgejo repos (consistent with the hook), and 404 on gitlab.com (will be created on next push).
Idempotent: re-run with no drift reports synced=0.
Manual run: ArgoCD prunes Jobs created via kubectl create job --from=cronjob/... (no ownerReference → unmanaged). Use a kubectl run Pod instead — Pods without app.kubernetes.io/instance: forgejo are not pruned:
kubectl run -n forgejo mirror-state-sync-test --rm -i --restart=Never --image=alpine:3.21 \
--overrides='{"apiVersion":"v1","spec":{"containers":[{"name":"mirror-state-sync-test","image":"alpine:3.21","command":["/bin/sh","-c"],"args":["set -eu; apk add --no-cache --quiet bash curl jq > /dev/null; exec /scripts/sync-mirror-state.sh"],"stdin":true,"stdinOnce":true,"tty":false,"volumeMounts":[{"name":"script","mountPath":"/scripts","readOnly":true},{"name":"mirror-secrets","mountPath":"/mirror-secrets","readOnly":true}]}],"volumes":[{"name":"script","configMap":{"name":"forgejo-mirror-state-sync","defaultMode":493}},{"name":"mirror-secrets","secret":{"secretName":"forgejo-mirror-secrets"}}],"restartPolicy":"Never"}}'
Default Merge Style
New repositories default to Create squash commit via [repository.pull-request] DEFAULT_MERGE_STYLE = squash.
Existing repositories are not changed automatically. To backfill them, run:
bash resources/backfill-default-merge-style.sh --dry-run
bash resources/backfill-default-merge-style.sh
The helper reads admin credentials from Kubernetes Secret forgejo-admin-user in namespace forgejo and updates only repositories whose default_merge_style is not already squash.
Key files
values.yaml— Helm values: server config, OIDC, extraVolumes (hook script + secrets),[git.config] init.templateDirresources/mirror-git-repo.sh— Post-receive hook: checks visibility via Forgejo API, creates gitlab.com project if missing, force-pushesresources/sync-mirror-state.sh— Daily reconcile script: paginated walk through Forgejo repos, archives/unarchives gitlab.com viaPOST /projects/:id/(un)archive, updates description viaPUT /projects/:idresources/cronjob-mirror-state-sync.yaml— CronJob (alpine:3.21, daily 03:00); mountsforgejo-mirror-secretsand sync-script ConfigMapresources/patch-lifecycle.yaml— Kustomize patch: postStart waits for ConfigMap mount, creates template dir, backfills symlinksresources/secret-mirror.yaml— SOPS:forgejo-token(read:repository),remote-gitlab-token(api),remote-gitlab-push-token(write_repository)resources/secret-admin-user.yaml— SOPS: bootstrap admin (xarif-admin)resources/secret-oidc.yaml— SOPS: Authelia OIDC client
Path mapping
Forgejo <org>/<repo> → gitlab.com xarif/<org>/<repo> (1:1, no transformation).
On first mirror (new repo), the hook creates the gitlab.com project, pushes, and protects main with allow_force_push=true.
Description on gitlab.com always reads "This project is a mirror of https://git.xarif.de/<org>/<repo>" — set on creation by the hook, kept in sync by the reconcile CronJob.