Forgejo Self-Hosted Migration (Codeberg → Hetzner)

# Forgejo Self-Hosted Migration (Codeberg → Hetzner)

> Trigger: Codeberg ToU § 2 (1) 7 (merged 2026-07-22) prohibits projects that "mostly consist of code written by generative AI-tools." To remove platform risk entirely, self-host Forgejo (the same engine Codeberg runs) on the existing Hetzner box. No Docker.
> Created 2026-07-23.

## Context — the Codeberg clause
Exact wording: *"You must not share projects that mostly consist of code written by 'generative AI'-tools (including services such as Claude, OpenAI Codex)."* Rationale: unclear copyright status + lack of safeguards against harmful code.

**Nuance:** ban targets repos that are *mostly* AI-generated (low-effort dumps), not any LLM-assisted project. But enforcement is subjective ("mostly consist of", presidium majority vote) and penalty is immediate removal + warning. Self-hosting eliminates the risk entirely.

---

## Why self-hosted Forgejo (bare binary, no Docker)
- Same software Codeberg runs → zero workflow change, identical UX.
- Single static Go binary, SQLite, systemd-managed → lighter than Docker, easy to debug.
- No ToU over your code; full control of public/private repos.
- Idle footprint well under 200 MB (Forgejo + Caddy).

---

## Migration phases overview
0. Inventory what to move (repos, issues, wiki, CI).
1. Stand up Forgejo binary on Hetzner.
2. Migrate repos (built-in migrator or git mirror).
3. Re-point local remotes.
4. Cutover: update links, badges, deploy keys, DNS.
5. Backups → then retire Codeberg copies.

```mermaid
flowchart LR
  A["0. Inventory"] --> B["1. Forgejo binary<br/>+ systemd"]
  B --> C["2. Migrate repos"]
  C --> D["3. Re-point remotes"]
  D --> E["4. Update links/DNS"]
  E --> F["5. Backups → retire Codeberg"]
```

---

## Phase 1 — Stand up Forgejo (bare binary)

### 1a. Dedicated user + directories
```bash
sudo adduser --system --group --disabled-password --shell /bin/bash --home /var/lib/forgejo git
sudo mkdir -p /var/lib/forgejo/custom /etc/forgejo /var/log/forgejo
sudo chown -R git:git /var/lib/forgejo /etc/forgejo /var/log/forgejo
```

### 1b. Forgejo binary
Download latest `linux-amd64` release from `codeberg.org/forgejo/forgejo/releases`:
```bash
sudo wget -O /usr/local/bin/forgejo <release-url>
sudo chmod +x /usr/local/bin/forgejo
```

### 1c. Config — `/etc/forgejo/app.ini`
```ini
[database]
DB_TYPE  = sqlite3
PATH     = /var/lib/forgejo/data/forgejo.db

[server]
DOMAIN       = git.yourdomain.tld
ROOT_URL     = https://git.yourdomain.tld/
HTTP_ADDR    = 127.0.0.1
HTTP_PORT    = 3000
SSH_DOMAIN   = git.yourdomain.tld
DISABLE_SSH  = false
START_SSH_SERVER = false      # use system sshd
LFS_START_SERVER = true

[security]
INSTALL_LOCK = true           # set after first-run setup

[log]
LEVEL = Info
```
Bind `HTTP_ADDR=127.0.0.1` so Forgejo is only reachable via Caddy.

### 1d. systemd unit — `/etc/systemd/system/forgejo.service`
```ini
[Unit]
Description=Forgejo
After=network.target

[Service]
Type=simple
User=git
Group=git
WorkingDirectory=/var/lib/forgejo
ExecStart=/usr/local/bin/forgejo web --config /etc/forgejo/app.ini
Restart=always
Environment=USER=git HOME=/var/lib/forgejo GITEA_WORK_DIR=/var/lib/forgejo

[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now forgejo
```

### 1e. Caddy for TLS (single binary, no Docker)
Install Caddy (apt repo or binary). `/etc/caddy/Caddyfile`:
```
git.yourdomain.tld {
    reverse_proxy 127.0.0.1:3000
}
```
```bash
sudo systemctl reload caddy
```
**Point DNS A/AAAA at the Hetzner box first** — Caddy won't issue the cert until the name resolves.

### 1f. SSH for git pushes (no extra port)
`START_SSH_SERVER=false` → Forgejo uses the system sshd on port 22. Adding an SSH key in the UI writes to the `git` user's `authorized_keys` with a command wrapper. `git@…` clones work over existing SSH. `--shell /bin/bash` is fine because Forgejo controls the key commands.

### 1g. Firewall
```bash
sudo ufw allow 22/tcp
sudo ufw allow 80,443/tcp
sudo ufw enable
```

### 1h. First-run + verify
1. Visit `https://git.yourdomain.tld` → installer (SQLite pre-filled), create admin; OR seed via CLI:
   ```bash
   sudo -u git /usr/local/bin/forgejo admin user create --config /etc/forgejo/app.ini --admin --username you --email … --password …
   ```
2. Add SSH key in UI, create test repo, push `git@git.yourdomain.tld:you/test.git` → confirms SSH + web.

---

## Phase 2 — Migrate repos

### Primary — built-in migrator (keeps issues/wiki/releases/labels)
Forgejo UI → **New Migration → Codeberg (or Gitea/Forgejo)** → Codeberg URL + access token → select repo → check Issues/Labels/Releases/Wiki. Repeat per repo. History, branches, tags all come over.

### Alternative — git mirror (history only, no issues; fastest)
```bash
git clone --mirror git@codeberg.org:you/repo.git
cd repo.git
git remote set-url --push origin git@git.yourdomain.tld:you/repo.git
git push --mirror
```

---

## Phase 3 — Re-point local remotes
Don't re-clone; retarget in place:
```bash
git remote set-url origin git@git.yourdomain.tld:you/repo.git
# keep Codeberg as backup remote during transition:
git remote add codeberg git@codeberg.org:you/repo.git
```

---

## Phase 4 — Cutover
1. Update README badges, profile links, docs referencing Codeberg URL.
2. Update CI secrets / deploy keys pointing at Codeberg.
3. Confirm poker bot eval harness runs against new remote.
4. Wait ~1 week → delete Codeberg copies.

---

## Phase 5 — Backups (no Docker = simple cron)
```bash
# nightly self-contained zip
sudo -u git /usr/local/bin/forgejo dump --config /etc/forgejo/app.ini --file /var/backups/forgejo/
```
Wrap in cron/systemd-timer, then `borg`/`rclone` the zip to Hetzner Storage Box or off-site. **Test a restore once.**

---

## Poker bot specifics
- Keep the live harness repo **private** (Torn/scraper/ToS exposure — sanitize before any public exposure regardless of host).
- Make the portfolio repo **public** (sim testbed + 3 models + training CLI).
- No platform policy can object on self-host.

---

## Checklist
- [ ] DNS A/AAAA → Hetzner box
- [ ] `git` user + dirs created
- [ ] Forgejo binary installed + executable
- [ ] `/etc/forgejo/app.ini` written
- [ ] systemd unit enabled + running
- [ ] Caddy installed, Caddyfile set, cert issued
- [ ] ufw: 22/80/443 open
- [ ] First admin created
- [ ] Test repo push over SSH verified
- [ ] Repos migrated from Codeberg
- [ ] Local remotes re-pointed
- [ ] Links/badges/deploy keys updated
- [ ] Backup cron + off-site target + restore test
- [ ] (after ~1 week) Codeberg copies deleted

id: 91ddc5785c174087b03fa6ae216a8611
parent_id: cd0501fe1cc24c93bc6bc3e78fb324fd
created_time: 2026-07-23T12:40:17.886Z
updated_time: 2026-07-23T12:40:17.886Z
is_conflict: 0
latitude: 0.00000000
longitude: 0.00000000
altitude: 0.0000
author: 
source_url: 
is_todo: 0
todo_due: 0
todo_completed: 0
source: joplin-desktop
source_application: net.cozic.joplin-desktop
application_data: 
order: 1784810417886
user_created_time: 2026-07-23T12:40:17.886Z
user_updated_time: 2026-07-23T12:40:17.886Z
encryption_cipher_text: 
encryption_applied: 0
markup_language: 1
is_shared: 0
share_id: 
conflict_original_id: 
master_key_id: 
user_data: 
deleted_time: 0
is_locked: 0
extracted_resource_ids: 
type_: 1