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_slogan: "Beyond coding. We Forge."
forgejo_run_user: forgejo
forgejo_run_user: erichblume
forgejo_run_mode: prod
# Paths (brew-managed for now, will change to mcquack per migrate-forgejo-from-brew)
forgejo_work_path: /opt/homebrew/var/forgejo
# Source build paths
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_data_path: "{{ forgejo_work_path }}/data"
forgejo_repo_root: "{{ forgejo_data_path }}/forgejo-repositories"
forgejo_lfs_path: "{{ forgejo_data_path }}/lfs"
forgejo_log_path: "{{ forgejo_work_path }}/log"
forgejo_log_dir: /Users/erichblume/Library/Logs
# Server settings
forgejo_http_addr: 0.0.0.0

View file

@ -1,4 +1,6 @@
---
- 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

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
# transition to mcquack LaunchAgent with CI-built binary.
# ONE-TIME SETUP (before running ansible):
#
# 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
# from 1Password in the playbook pre_tasks.
- name: Install forgejo via homebrew
community.general.homebrew:
name: forgejo
state: present
- name: Verify forgejo binary exists
ansible.builtin.stat:
path: "{{ forgejo_binary }}"
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
ansible.builtin.file:
@ -25,8 +43,21 @@
mode: '0600'
notify: Restart forgejo
- name: Ensure forgejo service is started
ansible.builtin.command: brew services start forgejo
register: forgejo_brew_start
changed_when: "'Successfully started' in forgejo_brew_start.stdout"
- name: Deploy forgejo LaunchAgent plist
ansible.builtin.template:
src: forgejo.plist.j2
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

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>