Move the forge from GitLab to a self-hosted Forgejo #489

Open
opened 2026-10-21 10:02:26 +00:00 by root · 24 comments
root commented 2026-10-21 10:02:26 +00:00 (Migrated from gitlab.core-project.space)

Problem Statement

The self-hosted GitLab CE idles at about 4.4 GiB of the 8 GB CI VPS, which leaves a CI job about 2.5 GiB and forces the job memory cap, the low Turbo concurrency and two Vitest workers. ADR 0048 makes a self-hosted Forgejo the forge instead, and ADR 0049 has production and previews pull from its registry. The repository, its CI, its deploys, its issues and the agents' tooling all still speak GitLab.

Solution

Everything Forgejo needs is written and merged on GitLab ahead of time, where it stays inert. Previews and deploys are then turned off on GitLab and every open merge request is closed out. On one switch day, Forgejo is installed beside GitLab on the CI VPS, the project is imported, origin moves, and the first green run on main deploys production. Whatever breaks that day is fixed forward; there is no fallback. GitLab is backed up and stopped the same day and deleted a week later.

User Stories

  1. As the owner, I want Forgejo LTS served at git.core-project.space behind Traefik with HTTPS, so that the forge costs a fraction of GitLab's memory.
  2. As the owner, I want git over HTTPS only, with Forgejo's SSH server off, so that the forge has one door.
  3. As the owner, I want gitlab.core-project.space to redirect to the new host for a while, so that old links keep working.
  4. As the owner, I want one Forgejo runner taking one job at a time through the host's Docker daemon, so that browser jobs and deploys never overlap.
  5. As the owner, I want a pull request's gate (checks, front-test, e2e) to run on Forgejo Actions, so that a red change cannot reach main.
  6. As the owner, I want main to accept only fast-forward merges of pull requests whose required checks passed, and no direct push, so that every commit on main was gated.
  7. As the owner, I want a new push to cancel the run it supersedes, so that the single runner works on the latest commit.
  8. As the owner, I want a push to main to skip the gate when its pull request already passed on that commit, so that production deploys without rerunning the browser suite.
  9. As the owner, I want a docs-only pull request to skip the app jobs and the preview, so that it costs seconds.
  10. As the owner, I want the image built once and pushed to the Forgejo registry with the same tags as today, so that production runs the image the gate tested.
  11. As the owner, I want production deployed when main passes and each same-repository pull request to get a noindex preview, removed on close and by the nightly reap, so that previews and production behave as under ADR 0049.
  12. As a reviewer, I want one bot comment per pull request, updated on each run, holding the preview's screenshots, the failed tests and the engine's coverage, so that I see a run's result without opening logs.
  13. As the owner, I want a chess-bot account with one token per purpose (comments, registry, Renovate), each referenced only by the job that needs it, so that a compromised dependency in another job never sees it.
  14. As the owner, I want the nightly dependency audit, the nightly production backup and the weekly Renovate run (platform: forgejo) as scheduled workflows, so that nothing GitLab scheduled is lost.
  15. As the owner, I want a nightly forgejo dump replacing the GitLab backup, with the same destination and retention, so that the forge can be restored.
  16. As the owner, I want GitHub to keep receiving every branch through a Forgejo push mirror and run nothing, so that the public copy stays current.
  17. As an agent, I want tea, with tea api for the gaps, documented in docs/agents/issue-tracker.md and every skill that calls glab today, so that I can create, read, comment, label and close issues and open and merge pull requests on Forgejo.
  18. As an agent, I want blocking between issues to be Forgejo's native dependencies, with ## Parent kept in the body, so that ordering needs no body-section parsing or link workaround.
  19. As an agent, I want the gitlab-mr plugin ported to a forgejo-pr plugin and scripts/mr-status.sh to scripts/pr-status.sh, so that I am still woken when a run fails, a thread opens or a conflict appears.
  20. As the owner, I want the docs, skills and GLOSSARY.md to say "pull request", with no note mapping it to a merge request, so that the vocabulary matches the forge.
  21. As the owner, I want previews and deploys turned off on GitLab before the switch, every live preview torn down, and production left serving its current image, so that the switch day does not race GitLab's deploys.
  22. As the owner, I want every open merge request merged or closed before the switch, so that the import carries only closed history.
  23. As the owner, I want the repository, issues with their numbers, labels, and closed merge requests (as pull requests) with their comments imported, and nothing CI-related, so that the history survives the move.
  24. As the owner, I want each existing ## Blocked by section converted to native dependencies on switch day, so that open specs keep their order.
  25. As the owner, I want infra/forgejo/ to replace infra/gitlab/, deleted at the switch, with docs/research/*gitlab* kept as history, so that the repo describes only the forge it runs on.
  26. As the owner, I want GitLab backed up once and stopped on switch day, deleted a week later, and the backup kept a month, so that its memory goes back to CI.

Screens

None.

Seams

  • A Forgejo Actions run on a pull request and on main, on switch day: the gate, the build and push, the preview, production and the bot comment run live, fixed forward. Covers 4–13.
  • The existing deploy verifications (deploy/verify*.sh), run by the preview, production and reap jobs as today. Covers 11 and 21.
  • The forgejo-pr plugin's tests, ported from the gitlab-mr band tests, with tea faked as glab is today. Covers 19.
  • A node --test suite for the ## Blocked by converter, on fixture issue bodies. Covers 24.
  • A switch-day checklist in infra/forgejo/, each step ending in a check command (HTTPS answers, runner registered, mirror pushed, branch protection read back, dump written, GitLab stopped). Covers 1–3, 14–18, 20, 22–23, 25–26.

Implementation Decisions

  • ADR 0048 and ADR 0049 govern; ADR 0037 and 0036 are superseded.
  • Forgejo on its LTS line and its runner pinned by tag and digest, in Compose under infra/forgejo/, provisioned on the CI VPS by a wizard modelled on infra/gitlab/vps.
  • Runner capacity: 1, host Docker socket, a mounted volume for the shared Turbo cache, the same job memory cap and /dev/shm as today. Workflow-level concurrency with cancel-in-progress replaces workflow:auto_cancel; capacity: 1 replaces every resource_group.
  • The workflows start from the GitHub Actions set removed in 87f541fd and gain the GitLab-era changes: the shared Turbo volume, reuse-gate, docs-only skipping, the audit, Renovate and backups. GitLab's dotenv job outputs become job outputs.
  • reuse-gate asks Forgejo's API for the pull request holding the commit and its commit status. cancel-merged is dropped: v15 has no API to cancel a run.
  • The JUnit and coverage widgets are dropped; their content moves into the bot comment, written by the job that posts the screenshots today.
  • Secrets live at repository level; each is referenced only in the job that needs it.
  • Everything above merges on GitLab before the switch, in .forgejo/workflows/ and infra/forgejo/, which GitLab ignores.
  • Import through Forgejo's GitLab migrator: issues keep their numbers, merge requests are renumbered past the highest issue number, and !N references are rewritten. Confidential issues and internal notes are not imported.

Testing Decisions

  • Prior art: .claude/skills/gitlab-mr/tests/band.test.ts for the plugin; .claude/hooks/hooks.test.mjs for a node --test suite; deploy/verify*.sh for deploy checks.
  • The converter covers: no ## Blocked by section, "None (can start immediately)", several blockers, a blocker that is not in the project.
  • The plugin covers a tea failure drawn dimmed, as the glab failure is today.

Out of Scope

  • The CI VPS size review after GitLab is gone.
  • Running two jobs at once, or Forgejo's non-LTS line.
  • A fallback to GitLab on switch day.
  • Moving the forge off the CI VPS.

Further Notes

  • Forgejo's workflow-level concurrency is best-effort and it has no job-level concurrency, hence one job at a time.
  • Whether the per-run FORGEJO_TOKEN may comment on a pull request is unverified, hence the bot token for the comment.
## Problem Statement The self-hosted GitLab CE idles at about 4.4 GiB of the 8 GB CI VPS, which leaves a CI job about 2.5 GiB and forces the job memory cap, the low Turbo concurrency and two Vitest workers. ADR 0048 makes a self-hosted Forgejo the forge instead, and ADR 0049 has production and previews pull from its registry. The repository, its CI, its deploys, its issues and the agents' tooling all still speak GitLab. ## Solution Everything Forgejo needs is written and merged on GitLab ahead of time, where it stays inert. Previews and deploys are then turned off on GitLab and every open merge request is closed out. On one switch day, Forgejo is installed beside GitLab on the CI VPS, the project is imported, `origin` moves, and the first green run on `main` deploys production. Whatever breaks that day is fixed forward; there is no fallback. GitLab is backed up and stopped the same day and deleted a week later. ## User Stories 1. As the owner, I want Forgejo LTS served at `git.core-project.space` behind Traefik with HTTPS, so that the forge costs a fraction of GitLab's memory. 2. As the owner, I want git over HTTPS only, with Forgejo's SSH server off, so that the forge has one door. 3. As the owner, I want `gitlab.core-project.space` to redirect to the new host for a while, so that old links keep working. 4. As the owner, I want one Forgejo runner taking one job at a time through the host's Docker daemon, so that browser jobs and deploys never overlap. 5. As the owner, I want a pull request's gate (`checks`, `front-test`, `e2e`) to run on Forgejo Actions, so that a red change cannot reach `main`. 6. As the owner, I want `main` to accept only fast-forward merges of pull requests whose required checks passed, and no direct push, so that every commit on `main` was gated. 7. As the owner, I want a new push to cancel the run it supersedes, so that the single runner works on the latest commit. 8. As the owner, I want a push to `main` to skip the gate when its pull request already passed on that commit, so that production deploys without rerunning the browser suite. 9. As the owner, I want a docs-only pull request to skip the app jobs and the preview, so that it costs seconds. 10. As the owner, I want the image built once and pushed to the Forgejo registry with the same tags as today, so that production runs the image the gate tested. 11. As the owner, I want production deployed when `main` passes and each same-repository pull request to get a `noindex` preview, removed on close and by the nightly reap, so that previews and production behave as under ADR 0049. 12. As a reviewer, I want one bot comment per pull request, updated on each run, holding the preview's screenshots, the failed tests and the engine's coverage, so that I see a run's result without opening logs. 13. As the owner, I want a `chess-bot` account with one token per purpose (comments, registry, Renovate), each referenced only by the job that needs it, so that a compromised dependency in another job never sees it. 14. As the owner, I want the nightly dependency audit, the nightly production backup and the weekly Renovate run (`platform: forgejo`) as scheduled workflows, so that nothing GitLab scheduled is lost. 15. As the owner, I want a nightly `forgejo dump` replacing the GitLab backup, with the same destination and retention, so that the forge can be restored. 16. As the owner, I want GitHub to keep receiving every branch through a Forgejo push mirror and run nothing, so that the public copy stays current. 17. As an agent, I want `tea`, with `tea api` for the gaps, documented in `docs/agents/issue-tracker.md` and every skill that calls `glab` today, so that I can create, read, comment, label and close issues and open and merge pull requests on Forgejo. 18. As an agent, I want blocking between issues to be Forgejo's native dependencies, with `## Parent` kept in the body, so that ordering needs no body-section parsing or link workaround. 19. As an agent, I want the `gitlab-mr` plugin ported to a `forgejo-pr` plugin and `scripts/mr-status.sh` to `scripts/pr-status.sh`, so that I am still woken when a run fails, a thread opens or a conflict appears. 20. As the owner, I want the docs, skills and `GLOSSARY.md` to say "pull request", with no note mapping it to a merge request, so that the vocabulary matches the forge. 21. As the owner, I want previews and deploys turned off on GitLab before the switch, every live preview torn down, and production left serving its current image, so that the switch day does not race GitLab's deploys. 22. As the owner, I want every open merge request merged or closed before the switch, so that the import carries only closed history. 23. As the owner, I want the repository, issues with their numbers, labels, and closed merge requests (as pull requests) with their comments imported, and nothing CI-related, so that the history survives the move. 24. As the owner, I want each existing `## Blocked by` section converted to native dependencies on switch day, so that open specs keep their order. 25. As the owner, I want `infra/forgejo/` to replace `infra/gitlab/`, deleted at the switch, with `docs/research/*gitlab*` kept as history, so that the repo describes only the forge it runs on. 26. As the owner, I want GitLab backed up once and stopped on switch day, deleted a week later, and the backup kept a month, so that its memory goes back to CI. ## Screens None. ## Seams - **A Forgejo Actions run** on a pull request and on `main`, on switch day: the gate, the build and push, the preview, production and the bot comment run live, fixed forward. Covers 4–13. - **The existing deploy verifications** (`deploy/verify*.sh`), run by the preview, production and reap jobs as today. Covers 11 and 21. - **The `forgejo-pr` plugin's tests**, ported from the `gitlab-mr` band tests, with `tea` faked as `glab` is today. Covers 19. - **A `node --test` suite for the `## Blocked by` converter**, on fixture issue bodies. Covers 24. - **A switch-day checklist in `infra/forgejo/`**, each step ending in a check command (HTTPS answers, runner registered, mirror pushed, branch protection read back, dump written, GitLab stopped). Covers 1–3, 14–18, 20, 22–23, 25–26. ## Implementation Decisions - ADR 0048 and ADR 0049 govern; ADR 0037 and 0036 are superseded. - Forgejo on its LTS line and its runner pinned by tag and digest, in Compose under `infra/forgejo/`, provisioned on the CI VPS by a wizard modelled on `infra/gitlab/vps`. - Runner `capacity: 1`, host Docker socket, a mounted volume for the shared Turbo cache, the same job memory cap and `/dev/shm` as today. Workflow-level `concurrency` with `cancel-in-progress` replaces `workflow:auto_cancel`; `capacity: 1` replaces every `resource_group`. - The workflows start from the GitHub Actions set removed in `87f541fd` and gain the GitLab-era changes: the shared Turbo volume, `reuse-gate`, docs-only skipping, the audit, Renovate and backups. GitLab's dotenv job outputs become job `outputs`. - `reuse-gate` asks Forgejo's API for the pull request holding the commit and its commit status. `cancel-merged` is dropped: v15 has no API to cancel a run. - The JUnit and coverage widgets are dropped; their content moves into the bot comment, written by the job that posts the screenshots today. - Secrets live at repository level; each is referenced only in the job that needs it. - Everything above merges on GitLab before the switch, in `.forgejo/workflows/` and `infra/forgejo/`, which GitLab ignores. - Import through Forgejo's GitLab migrator: issues keep their numbers, merge requests are renumbered past the highest issue number, and `!N` references are rewritten. Confidential issues and internal notes are not imported. ## Testing Decisions - Prior art: `.claude/skills/gitlab-mr/tests/band.test.ts` for the plugin; `.claude/hooks/hooks.test.mjs` for a `node --test` suite; `deploy/verify*.sh` for deploy checks. - The converter covers: no `## Blocked by` section, "None (can start immediately)", several blockers, a blocker that is not in the project. - The plugin covers a `tea` failure drawn dimmed, as the `glab` failure is today. ## Out of Scope - The CI VPS size review after GitLab is gone. - Running two jobs at once, or Forgejo's non-LTS line. - A fallback to GitLab on switch day. - Moving the forge off the CI VPS. ## Further Notes - Forgejo's workflow-level concurrency is best-effort and it has no job-level concurrency, hence one job at a time. - Whether the per-run `FORGEJO_TOKEN` may comment on a pull request is unverified, hence the bot token for the comment.
root commented 2026-10-21 10:06:28 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #490

mentioned in issue #490
root commented 2026-10-21 10:06:30 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #491

mentioned in issue #491
root commented 2026-10-21 10:06:33 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #492

mentioned in issue #492
root commented 2026-10-21 10:06:36 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #493

mentioned in issue #493
root commented 2026-10-21 10:06:38 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #494

mentioned in issue #494
root commented 2026-10-21 10:06:57 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #495

mentioned in issue #495
root commented 2026-10-21 10:06:59 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #496

mentioned in issue #496
root commented 2026-10-21 10:07:10 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #497

mentioned in issue #497
root commented 2026-10-21 10:07:19 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #498

mentioned in issue #498
root commented 2026-10-21 10:07:30 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #499

mentioned in issue #499
root commented 2026-10-21 10:07:44 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #500

mentioned in issue #500
root commented 2026-10-21 10:08:00 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #490

marked this issue as related to #490
root commented 2026-10-21 10:08:03 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #491

marked this issue as related to #491
root commented 2026-10-21 10:08:08 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #492

marked this issue as related to #492
root commented 2026-10-21 10:08:12 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #493

marked this issue as related to #493
root commented 2026-10-21 10:08:14 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #494

marked this issue as related to #494
root commented 2026-10-21 10:08:15 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #495

marked this issue as related to #495
root commented 2026-10-21 10:08:17 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #496

marked this issue as related to #496
root commented 2026-10-21 10:08:18 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #497

marked this issue as related to #497
root commented 2026-10-21 10:08:20 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #498

marked this issue as related to #498
root commented 2026-10-21 10:08:23 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #499

marked this issue as related to #499
root commented 2026-10-21 10:08:25 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #500

marked this issue as related to #500
root commented 2026-10-21 10:23:54 +00:00 (Migrated from gitlab.core-project.space)

mentioned in merge request !568

mentioned in merge request !568
root commented 2026-10-21 10:25:57 +00:00 (Migrated from gitlab.core-project.space)

mentioned in commit 0bd1f48ed3

mentioned in commit 0bd1f48ed3aa8437476e1bac9a2f2f7733849115
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amaurycoudr/chess-project#489
No description provided.