fuzzyworld

fuzzyworld-tools / files / server / content / _how.md

_how.md

5.0 KB · history · blame

title: How I moved off GitLab

How I moved off GitLab

Why I left GitLab is short. How took about a day, 89 repositories and 35 web apps, and turned up enough surprises that it seemed worth writing down for anyone thinking about the same move.

What I ended up with

What I gave up: issues, merge requests and search. Only the default branch can be browsed. Cloning over plain HTTP is slower for big repos (a 244 MB repo takes about 50 seconds).

Getting everything out of GitLab

The code is the easy part: git clone --mirror gets every branch and tag. The rest lives in GitLab's settings and is easy to forget:

Traps I fell into

Moving the apps

Every app moved to the same path under a new domain, and they all share one origin, exactly as they did on *.gitlab.io. That means two apps can still clash over localStorage keys, a service worker can claim more than its own folder, and two installed web apps can merge on Android if their manifests look alike. So the deploy script refuses to upload anything whose URLs, manifest or service worker reach outside the app's own path.

Small things broke in surprising places: GitLab-style links (/-/blob/main/..., /-/commit/...) baked into apps, short commit IDs where the new site expected long ones, a vite config whose root was a subfolder, Google sign-in that only works on domains Firebase knows about, and OAuth redirect URIs registered with the old address.

Saying goodbye without breaking links

Each GitLab project got one last commit: a README pointing to the new home, and, for sites, a redirect page in place of the app. The redirect keeps the path, query and #hash, so deep links land on the matching new page. It also retires any old service worker, which would otherwise keep serving the cached old app to returning visitors forever.

Would I do it again?

Yes. Everything is static, it costs nothing extra on hosting I already paid for, and nobody needs an account to read my code. The one real loss is easy contributions: there are no merge requests here yet.

Principles

Things made with AI should be shared freely with the world. The code and apps here were made with AI, so they are MIT-licensed: use them, change them, share them.

My conversations with the AI are different. My words and my way of working are mine. I publish a conversation only when I choose to.

← Why I left GitLab

Source
1title: How I moved off GitLab
2
3<!-- DRAFT 2026-10-27 for code.fuzzyworld.net/how/ (plan Backlog item 3). Pavel edits before it goes live; on hold until the item 2 features. Not installed or linked. -->
4
5# How I moved off GitLab
6
7[Why I left GitLab](/why-i-left-gitlab/) is short. *How* took about a day, 89 repositories and 35 web
8apps, and turned up enough surprises that it seemed worth writing down for anyone thinking about the same
9move.
10
11## What I ended up with
12
13- **Bare git repos on cheap shared hosting** (DreamHost). I push over SSH; nothing else runs there permanently.
14- **code.fuzzyworld.net**: static browse pages, generated by a git hook when I push, plus read-only `git clone https://...`. No app server, no accounts, no rate limits beyond what Apache can serve.
15- **apps.fuzzyworld.net**: every former GitLab Pages site, at the same relative path, built and uploaded from my laptop with one command.
16- **A backup mirror at home** that can read, but not write.
17
18What I gave up: issues, merge requests and search. Only the default branch can be browsed. Cloning over plain HTTP is slower for big repos (a 244 MB repo takes about 50 seconds).
19
20## Getting everything out of GitLab
21
22The code is the easy part: `git clone --mirror` gets every branch and tag. The rest lives in GitLab's settings and is easy to forget:
23
24- **CI/CD variables.** Signing keys, API tokens, deploy keys. Export them before anything else; losing an Android signing key means you can't update that app in the Play Store again.
25- **Release files.** APKs and tarballs attached to releases are often CI *job artifacts*, and those expire. Five of mine already had.
26- **Pipeline schedules**, issues, merge requests and wiki pages. Confidential issues in a public project need separate handling, or the export makes them public.
27- **Merge-request commits** live under `refs/merge-requests/*`, which a normal clone doesn't fetch. I kept them as `refs/gitlab/merge-requests/*`.
28
29## Traps I fell into
30
31- **The rate limit bit me during the export.** The `glab` CLI's login is an OAuth token, which has to be sent as `Authorization: Bearer`. Sent as `PRIVATE-TOKEN`, every call counted as anonymous, and my home IP was blocked within minutes. Web URLs for job artifacts and uploads count as anonymous even with a token; use the API endpoints instead.
32- **My own git hook refused my own history.** A global pre-push hook that rejects lightweight tags (good for new releases) blocked mirroring old ones. Historical tags have to move unchanged, so the migration pushes skip the hook.
33- **Shared hosting kills big git operations.** Cloning a 284 MB repo over SSH failed with "git-pack-objects died": the host's watchdog killed git's packing step. Limiting git's threads and memory (`pack.threads 1`, `pack.windowMemory 64m`) fixed it for good.
34- **The host executes things you might not expect.** DreamHost runs `.php` files and server-side includes by default. A code browser that wrote repository files into the web root with their real names would have run them. Browse pages are pre-generated HTML, raw copies are images only, and every response says `script-src 'none'`.
35- **Node on a hardened kernel.** The host's kernel refuses executable memory, so modern Node crashes as soon as its JIT starts. `--jitless` works; native npm modules don't build, so installs use `--ignore-scripts`.
36
37## Moving the apps
38
39Every app moved to the same path under a new domain, and they all share one origin, exactly as they did on `*.gitlab.io`. That means two apps can still clash over `localStorage` keys, a service worker can claim more than its own folder, and two installed web apps can merge on Android if their manifests look alike. So the deploy script refuses to upload anything whose URLs, manifest or service worker reach outside the app's own path.
40
41Small things broke in surprising places: GitLab-style links (`/-/blob/main/...`, `/-/commit/...`) baked into apps, short commit IDs where the new site expected long ones, a vite config whose root was a subfolder, Google sign-in that only works on domains Firebase knows about, and OAuth redirect URIs registered with the old address.
42
43## Saying goodbye without breaking links
44
45Each GitLab project got one last commit: a README pointing to the new home, and, for sites, a redirect page in place of the app. The redirect keeps the path, query and `#hash`, so deep links land on the matching new page. It also retires any old service worker, which would otherwise keep serving the cached old app to returning visitors forever.
46
47## Would I do it again?
48
49Yes. Everything is static, it costs nothing extra on hosting I already paid for, and nobody needs an account to read my code. The one real loss is easy contributions: there are no merge requests here yet.
50
51## Principles
52
53Things made with AI should be shared freely with the world. The code and apps here were made with AI, so they are MIT-licensed: use them, change them, share them.
54
55My conversations with the AI are different. My words and my way of working are mine. I publish a conversation only when I choose to.
56
57[← Why I left GitLab](/why-i-left-gitlab/)