# Forgejo deploy runbook — nullstone Self-host plan: replace GH Actions free-tier (quota-bound) with Forgejo + forgejo-runner running on nullstone. Same `build-iso.yml` workflow, no GH dependency. ## Pre-flight - nullstone reachable at 192.168.0.100 (LAN) and via tailscale (mesh) - Traefik running, `proxy` docker network exists - Gandi API token configured in traefik env (LiveDNS scope, s8n.ru only) → letsencrypt resolver works for new hostnames automatically - DNS for `git.s8n.ru` must point at nullstone's public IP (Gandi manual web UI; API can't add new records per memory reference_gandi_api.md) ## Step 1 — DNS Add A record `git.s8n.ru → ` via Gandi web UI. Wait ~2min for propagation. Verify: ```bash dig +short git.s8n.ru @1.1.1.1 ``` ## Step 2 — copy compose files to nullstone ```bash scp /home/admin/ai-lab/nullstone-server/forgejo/docker-compose.yml \ nullstone:/tmp/forgejo-compose.yml scp /home/admin/ai-lab/nullstone-server/forgejo/runner-compose.yml \ nullstone:/tmp/forgejo-runner-compose.yml ssh nullstone bash <<'EOF' sudo mkdir -p /opt/docker/forgejo/{data,config} sudo mkdir -p /opt/docker/forgejo-runner/{data,cache} sudo chown -R 1000:1000 /opt/docker/forgejo sudo mv /tmp/forgejo-compose.yml /opt/docker/forgejo/docker-compose.yml sudo mv /tmp/forgejo-runner-compose.yml /opt/docker/forgejo-runner/docker-compose.yml EOF ``` ## Step 3 — first-start Forgejo ```bash ssh nullstone 'cd /opt/docker/forgejo && docker compose up -d' ssh nullstone 'docker logs -f forgejo' & # watch first-start ``` When you see `Listen: http://0.0.0.0:3000`, Forgejo is up. Hit in your browser. Traefik gets the LE cert automatically. ## Step 4 — initial admin user The first-time wizard at `/install` is *disabled* by env (we set `FORGEJO__service__DISABLE_REGISTRATION=true`). Create the admin via CLI inside the container: ```bash ssh nullstone 'docker exec -u 1000 forgejo \ forgejo admin user create \ --admin \ --username admin \ --email \ --random-password \ --must-change-password=false' ``` The random password gets printed once — save it somewhere safe. Login at git.s8n.ru with `admin` + that password, change it via the web UI's user settings. ## Step 5 — generate runner registration token ```bash ssh nullstone 'docker exec -u 1000 forgejo \ forgejo actions generate-runner-token' ``` Output is a single line — copy it into `.env` next to the runner compose: ```bash echo "RUNNER_TOKEN=" | ssh nullstone 'sudo tee /opt/docker/forgejo-runner/.env' ssh nullstone 'sudo chmod 600 /opt/docker/forgejo-runner/.env' ``` ## Step 6 — start runner ```bash ssh nullstone 'cd /opt/docker/forgejo-runner && docker compose up -d' ssh nullstone 'docker logs -f forgejo-runner' ``` Look for `Runner registered successfully`. Verify in Forgejo web UI: Site Administration → Actions → Runners — should list `nullstone`. ## Step 7 — mirror veilor-os repo In the Forgejo web UI: 1. Create org `veilor-org` (matches GH org name). 2. Click + → Migrate Repository. 3. Type: GitHub. URL: `https://github.com/veilor-org/veilor-os`. 4. Mirror = ON. Description: "self-hosted mirror; primary dev here". 5. Click Migrate. Forgejo pulls the repo + all branches + tags + actions config. Once done, push from local will go to BOTH (set as second remote): ```bash cd ~/ai-lab/_github/veilor-os git remote add nullstone https://git.s8n.ru/veilor-org/veilor-os git push nullstone main v0.7-bluebuild-spike ``` ## Step 8 — flip workflow to nullstone runner Change `build-iso.yml`: ```yaml runs-on: ubuntu-24.04 # before runs-on: nullstone # after — picks up our forgejo runner ``` Push to nullstone remote. Watch Forgejo Actions tab. Same workflow, runs on our hardware, no GH minutes. ## Step 9 — close the loop Mirror Forgejo → GitHub for public visibility. Forgejo settings on the repo → Mirror → Push mirror → `https://github.com/veilor-org/veilor-os` with a GH PAT that has write access. Forgejo pushes on every commit. End state: - `git push origin` → GH (public mirror) - `git push nullstone` → Forgejo (primary; runs CI) - Forgejo auto-pushes to GH for visibility - ISO builds run unlimited on nullstone hardware - 0 GH Actions minutes consumed ## Disk needs - Forgejo data: ~1GB initial, grows ~100MB/yr per repo - Runner workspace: ~80GB free recommended for ISO builds (squashfs + downloaded RPMs + xorriso staging) - Runner cache: ~20GB for `actions/cache`-style hits across builds Confirm with `df -h /` on nullstone before kickoff. ## Resource cost - Forgejo: ~200MB RAM idle, ~500MB during build queues - Runner: idle 50MB, ~4GB during ISO build (depsolve + squashfs) - Network: ~2GB/build (Fedora package download) Should fit alongside existing nullstone services without contention. ## Rollback If anything breaks: ```bash ssh nullstone 'cd /opt/docker/forgejo && docker compose down' ssh nullstone 'cd /opt/docker/forgejo-runner && docker compose down' ``` Local repo `origin` still points at GH; nothing on the dev side changes. ISO builds fall back to GH Actions until quota cycles. ## See also - veilor-os roadmap: `_github/veilor-os/docs/ROADMAP.md` - nullstone service inventory: `~/ai-lab/SYSTEM.md` - Existing service patterns: `/opt/docker/headscale/`, `/opt/docker/authentik/`