Migrate Forgejo from Homebrew to source build (#316)

## Summary

- Migrate Forgejo from Homebrew to source-built binary with mcquack LaunchAgent
- Matches the established pattern used by zot, caddy, and alloy
- Upgrades to v14.0.3 (7 security fixes: PKCE bypass, OAuth scope bypass, open redirect, and more)

## Changes

- **Ansible role**: Replace brew install/services with binary stat check + LaunchAgent
- **Paths**: `/opt/homebrew/var/forgejo` → `~/forgejo`, binary at `~/code/3rd/forgejo/forgejo`
- **Run user**: `forgejo` → `erichblume` (LaunchAgent user; SSH git user stays `forgejo`)
- **Docs**: Updated Forgejo reference card, restart-indri guide
- **Service review**: Stamped frigate-notify, cloudnative-pg, blumeops-pg as current

## One-time migration steps (manual, on indri)

1. Clone from Codeberg, add forge mirror remote
2. Check out v14.0.3, build with `make build && make forgejo`
3. Stop brew, `cp -a` data to `~/forgejo`, fix ownership
4. Run `provision-indri --tags forgejo`
5. Verify, then `brew uninstall forgejo`

## Data safety

- `cp -a` preserves everything (repos, SQLite DB, LFS, sessions, OAuth config)
- Brew version stays installed as rollback until verification passes
- No schema changes between 14.0.2 → 14.0.3

Reviewed-on: #316
This commit is contained in:
Erich Blume 2026-03-28 08:19:23 -07:00
commit 3017f759a7
8 changed files with 124 additions and 26 deletions

View file

@ -4,16 +4,21 @@
forgejo_app_name: Forgejo forgejo_app_name: Forgejo
forgejo_app_slogan: "Beyond coding. We Forge." forgejo_app_slogan: "Beyond coding. We Forge."
forgejo_run_user: forgejo forgejo_run_user: erichblume
forgejo_run_mode: prod forgejo_run_mode: prod
# Paths (brew-managed for now, will change to mcquack per migrate-forgejo-from-brew) # Source build paths
forgejo_work_path: /opt/homebrew/var/forgejo forgejo_repo_dir: /Users/erichblume/code/3rd/forgejo
forgejo_binary: "{{ forgejo_repo_dir }}/forgejo"
# Data paths (migrated from brew to ~/forgejo)
forgejo_work_path: /Users/erichblume/forgejo
forgejo_config_path: "{{ forgejo_work_path }}/custom/conf/app.ini" forgejo_config_path: "{{ forgejo_work_path }}/custom/conf/app.ini"
forgejo_data_path: "{{ forgejo_work_path }}/data" forgejo_data_path: "{{ forgejo_work_path }}/data"
forgejo_repo_root: "{{ forgejo_data_path }}/forgejo-repositories" forgejo_repo_root: "{{ forgejo_data_path }}/forgejo-repositories"
forgejo_lfs_path: "{{ forgejo_data_path }}/lfs" forgejo_lfs_path: "{{ forgejo_data_path }}/lfs"
forgejo_log_path: "{{ forgejo_work_path }}/log" forgejo_log_path: "{{ forgejo_work_path }}/log"
forgejo_log_dir: /Users/erichblume/Library/Logs
# Server settings # Server settings
forgejo_http_addr: 0.0.0.0 forgejo_http_addr: 0.0.0.0

View file

@ -1,4 +1,6 @@
--- ---
- name: Restart forgejo - name: Restart forgejo
ansible.builtin.command: brew services restart forgejo ansible.builtin.shell: |
launchctl unload ~/Library/LaunchAgents/mcquack.eblume.forgejo.plist 2>/dev/null || true
launchctl load ~/Library/LaunchAgents/mcquack.eblume.forgejo.plist
changed_when: true changed_when: true

View file

@ -1,16 +1,34 @@
--- ---
# Forgejo role # Forgejo role — source-built binary with LaunchAgent
# #
# Currently uses brew-managed forgejo. Phase 3 of ci-cd-bootstrap will # ONE-TIME SETUP (before running ansible):
# transition to mcquack LaunchAgent with CI-built binary. #
# 1. Clone forgejo from codeberg (avoid circular dependency):
# ssh indri 'git clone https://codeberg.org/forgejo/forgejo.git ~/code/3rd/forgejo'
#
# 2. Add forge mirror as secondary remote:
# ssh indri 'cd ~/code/3rd/forgejo && git remote add forge https://forge.eblu.me/mirrors/forgejo.git'
#
# 3. Build (mise.toml handles Go/Node versions and build tags):
# ssh indri 'cd ~/code/3rd/forgejo && mise run build'
#
# 4. Run ansible to deploy config and LaunchAgent
# #
# Secrets (lfs_jwt_secret, internal_token, oauth2_jwt_secret) are fetched # Secrets (lfs_jwt_secret, internal_token, oauth2_jwt_secret) are fetched
# from 1Password in the playbook pre_tasks. # from 1Password in the playbook pre_tasks.
- name: Install forgejo via homebrew - name: Verify forgejo binary exists
community.general.homebrew: ansible.builtin.stat:
name: forgejo path: "{{ forgejo_binary }}"
state: present register: forgejo_binary_stat
- name: Fail if forgejo binary not found
ansible.builtin.fail:
msg: |
Forgejo binary not found at {{ forgejo_binary }}.
Please build from source first:
ssh indri 'cd ~/code/3rd/forgejo && mise run build'
when: not forgejo_binary_stat.stat.exists
- name: Ensure forgejo config directory exists - name: Ensure forgejo config directory exists
ansible.builtin.file: ansible.builtin.file:
@ -25,8 +43,21 @@
mode: '0600' mode: '0600'
notify: Restart forgejo notify: Restart forgejo
- name: Ensure forgejo service is started - name: Deploy forgejo LaunchAgent plist
ansible.builtin.command: brew services start forgejo ansible.builtin.template:
register: forgejo_brew_start src: forgejo.plist.j2
changed_when: "'Successfully started' in forgejo_brew_start.stdout" dest: ~/Library/LaunchAgents/mcquack.eblume.forgejo.plist
mode: '0644'
notify: Restart forgejo
- name: Check if forgejo LaunchAgent is loaded
ansible.builtin.command: launchctl list mcquack.eblume.forgejo
register: forgejo_launchctl_check
changed_when: false
failed_when: false
- name: Load forgejo LaunchAgent if not loaded
ansible.builtin.command: launchctl load ~/Library/LaunchAgents/mcquack.eblume.forgejo.plist
when: forgejo_launchctl_check.rc != 0
changed_when: true
failed_when: false failed_when: false

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- {{ ansible_managed }} -->
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>mcquack.eblume.forgejo</string>
<key>ProgramArguments</key>
<array>
<string>{{ forgejo_binary }}</string>
<string>-w</string>
<string>{{ forgejo_work_path }}</string>
<string>-c</string>
<string>{{ forgejo_config_path }}</string>
<string>web</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>{{ forgejo_log_dir }}/mcquack.forgejo.out.log</string>
<key>StandardErrorPath</key>
<string>{{ forgejo_log_dir }}/mcquack.forgejo.err.log</string>
</dict>
</plist>

View file

@ -0,0 +1 @@
Migrate Forgejo from Homebrew to source build with mcquack LaunchAgent, matching the pattern used by zot, caddy, and alloy. Upgrades to v14.0.3 (7 security fixes including PKCE bypass and OAuth scope bypass).

View file

@ -37,10 +37,8 @@ ssh indri 'minikube status'
Native services managed by launchd will stop automatically during macOS shutdown. However, if you want to stop them explicitly first: Native services managed by launchd will stop automatically during macOS shutdown. However, if you want to stop them explicitly first:
```bash ```bash
# Forgejo (managed by brew services)
ssh indri 'brew services stop forgejo'
# LaunchAgent services # LaunchAgent services
ssh indri 'launchctl unload ~/Library/LaunchAgents/mcquack.eblume.forgejo.plist'
ssh indri 'launchctl unload ~/Library/LaunchAgents/mcquack.eblume.caddy.plist' ssh indri 'launchctl unload ~/Library/LaunchAgents/mcquack.eblume.caddy.plist'
ssh indri 'launchctl unload ~/Library/LaunchAgents/mcquack.eblume.zot.plist' ssh indri 'launchctl unload ~/Library/LaunchAgents/mcquack.eblume.zot.plist'
ssh indri 'launchctl unload ~/Library/LaunchAgents/mcquack.jellyfin.plist' ssh indri 'launchctl unload ~/Library/LaunchAgents/mcquack.jellyfin.plist'
@ -68,7 +66,7 @@ Or if you're at the console, use the Apple menu.
After indri boots, most services recover automatically. Only a few things need manual attention. After indri boots, most services recover automatically. Only a few things need manual attention.
**What autostarts:** Docker Desktop, brew services (Forgejo), and all mcquack LaunchAgent services (Caddy, Zot, Jellyfin, Alloy, Borgmatic, metrics collectors). **What autostarts:** Docker Desktop and all mcquack LaunchAgent services (Forgejo, Caddy, Zot, Jellyfin, Alloy, Borgmatic, metrics collectors).
**What needs manual action:** Amphetamine, AutoMounter, and minikube (including its Tailscale serve port). **What needs manual action:** Amphetamine, AutoMounter, and minikube (including its Tailscale serve port).

View file

@ -1,6 +1,6 @@
--- ---
title: Forgejo title: Forgejo
modified: 2026-03-03 modified: 2026-03-28
tags: tags:
- service - service
- git - git
@ -11,6 +11,8 @@ tags:
Git forge and CI/CD platform. **Primary source of truth for blumeops** (mirrored to GitHub). Git forge and CI/CD platform. **Primary source of truth for blumeops** (mirrored to GitHub).
Built from source on indri, managed via Ansible + mcquack LaunchAgent. Source cloned from Codeberg with a forge mirror as secondary remote.
## Quick Reference ## Quick Reference
| Property | Value | | Property | Value |
@ -20,6 +22,39 @@ Git forge and CI/CD platform. **Primary source of truth for blumeops** (mirrored
| **SSH** | `ssh://forgejo@forge.ops.eblu.me:2222` | | **SSH** | `ssh://forgejo@forge.ops.eblu.me:2222` |
| **Local Ports** | 3001 (HTTP), 2200 (SSH) | | **Local Ports** | 3001 (HTTP), 2200 (SSH) |
| **Config** | `ansible/roles/forgejo/templates/app.ini.j2` | | **Config** | `ansible/roles/forgejo/templates/app.ini.j2` |
| **Binary** | `~/code/3rd/forgejo/forgejo` (source-built) |
| **Data** | `~/forgejo` |
| **LaunchAgent** | `mcquack.eblume.forgejo` |
| **Source** | `~/code/3rd/forgejo` (cloned from Codeberg) |
## Building from Source
Forgejo is built from source on indri, matching the pattern used by [[zot]], [[caddy]], and [[alloy]].
**One-time setup:**
```fish
# Clone from Codeberg (avoids circular dependency with forge)
ssh indri 'git clone https://codeberg.org/forgejo/forgejo.git ~/code/3rd/forgejo'
# Add forge mirror as secondary remote
ssh indri 'cd ~/code/3rd/forgejo && git remote add forge https://forge.eblu.me/mirrors/forgejo.git'
```
**Building a specific version:**
```fish
ssh indri 'cd ~/code/3rd/forgejo && git fetch --tags && git checkout v14.0.3'
ssh indri 'cd ~/code/3rd/forgejo && mise run build'
```
The `build` mise task (defined in the repo's `mise.toml`) runs `make build` with the correct tags and creates the `./forgejo` hardlink. It uses `go@1.25.8` and `node@24` as configured by `mise use`.
**WARNING:** Do NOT use `make forgejo` directly — it rebuilds with empty TAGS, stripping SQLite support. Always use `mise run build` or pass TAGS explicitly to `make build` and `ln -f gitea forgejo` afterwards.
Build tags: `bindata` (embed assets), `timetzdata` (embed timezone data), `sqlite sqlite_unlock_notify` (SQLite support).
After building, run `mise run provision-indri -- --tags forgejo` to deploy the config and restart the service.
## Repositories ## Repositories

View file

@ -59,7 +59,7 @@ services:
- name: frigate-notify - name: frigate-notify
type: argocd type: argocd
last-reviewed: 2026-02-22 last-reviewed: 2026-03-28
current-version: "v0.5.4" current-version: "v0.5.4"
upstream-source: https://github.com/0x2142/frigate-notify/releases upstream-source: https://github.com/0x2142/frigate-notify/releases
@ -112,7 +112,7 @@ services:
- name: cloudnative-pg - name: cloudnative-pg
type: argocd type: argocd
last-reviewed: 2026-02-24 last-reviewed: 2026-03-28
current-version: "v1.28.1" current-version: "v1.28.1"
upstream-source: https://github.com/cloudnative-pg/cloudnative-pg/releases upstream-source: https://github.com/cloudnative-pg/cloudnative-pg/releases
notes: Deployed via Helm chart (chart v0.27.1 from forge mirror) notes: Deployed via Helm chart (chart v0.27.1 from forge mirror)
@ -147,7 +147,7 @@ services:
- name: blumeops-pg - name: blumeops-pg
type: argocd type: argocd
last-reviewed: 2026-02-27 last-reviewed: 2026-03-28
current-version: "18.3" current-version: "18.3"
upstream-source: https://github.com/cloudnative-pg/cloudnative-pg/releases upstream-source: https://github.com/cloudnative-pg/cloudnative-pg/releases
notes: CloudNativePG Cluster resource; pinned to PG minor version notes: CloudNativePG Cluster resource; pinned to PG minor version
@ -287,10 +287,10 @@ services:
- name: forgejo - name: forgejo
type: ansible type: ansible
last-reviewed: 2026-02-22 last-reviewed: 2026-03-28
current-version: "14.0.2" current-version: "14.0.3"
upstream-source: https://codeberg.org/forgejo/forgejo/releases upstream-source: https://codeberg.org/forgejo/forgejo/releases
notes: Installed via Homebrew on indri; plan to migrate to source build notes: Built from source on indri (~/code/3rd/forgejo)
- name: alloy - name: alloy
type: ansible type: ansible