Add migration plan for Forgejo brew-to-source transition (#140)
Document the plan to transition Forgejo from Homebrew to a source-built binary with LaunchAgent, following the established zot/caddy/alloy pattern. Introduces a new "plans" how-to subdirectory for future migration plans. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
41dfae1f80
commit
2b3bfc669d
5 changed files with 321 additions and 0 deletions
1
docs/changelog.d/doc-forgejo-brew-migration-plan.doc.md
Normal file
1
docs/changelog.d/doc-forgejo-brew-migration-plan.doc.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Add migration plan for Forgejo brew-to-source transition
|
||||
|
|
@ -43,3 +43,12 @@ Task-oriented instructions for common BlumeOps operations. These guides assume y
|
|||
| [[restart-indri]] | Safely shut down and restart indri |
|
||||
| [[manage-flyio-proxy]] | Deploy, shutoff, and troubleshoot the public proxy |
|
||||
| [[troubleshooting]] | Diagnose and fix common issues |
|
||||
|
||||
## Plans
|
||||
|
||||
Migration and transition plans for upcoming infrastructure changes.
|
||||
|
||||
| Plan | Description |
|
||||
|------|-------------|
|
||||
| [[plans]] | Index of all plans |
|
||||
| [[migrate-forgejo-from-brew]] | Transition Forgejo from Homebrew to source-built binary |
|
||||
|
|
|
|||
292
docs/how-to/plans/migrate-forgejo-from-brew.md
Normal file
292
docs/how-to/plans/migrate-forgejo-from-brew.md
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
---
|
||||
title: "Plan: Migrate Forgejo from Brew to Source Build"
|
||||
tags:
|
||||
- how-to
|
||||
- plans
|
||||
- forgejo
|
||||
---
|
||||
|
||||
# Plan: Migrate Forgejo from Brew to Source Build
|
||||
|
||||
> **Status:** Planned (not yet executed)
|
||||
> **Audience:** AI agent, Owner
|
||||
|
||||
## Background
|
||||
|
||||
Forgejo was force-upgraded from v13 to v14 by `brew upgrade`, breaking version control. To prevent uncontrolled upgrades and align with the established pattern for other native services (zot, caddy, alloy), we are transitioning Forgejo from Homebrew to a source-built binary managed by a LaunchAgent.
|
||||
|
||||
### Why Source Build?
|
||||
|
||||
- **Version pinning** — upgrade on our schedule by checking out specific tags
|
||||
- **Consistency** — matches [[zot]], caddy, and alloy deployment patterns
|
||||
- **Control** — build flags, patches, and dependencies are explicit
|
||||
|
||||
## Source Remote
|
||||
|
||||
Use **Codeberg upstream** as the primary clone source to avoid a circular dependency (Forgejo hosting its own source):
|
||||
|
||||
```
|
||||
https://codeberg.org/forgejo/forgejo.git
|
||||
```
|
||||
|
||||
Add the forge mirror as a secondary remote for convenience and backup:
|
||||
|
||||
```
|
||||
http://localhost:3001/eblume/forgejo.git
|
||||
```
|
||||
|
||||
> **Note:** Use `localhost:3001` because hairpinning through Caddy doesn't work from indri itself.
|
||||
|
||||
## One-Time Migration Steps
|
||||
|
||||
These steps are performed manually on indri **before** running Ansible.
|
||||
|
||||
### 1. Clone Forgejo from Codeberg
|
||||
|
||||
```fish
|
||||
ssh indri 'git clone https://codeberg.org/forgejo/forgejo.git ~/code/3rd/forgejo'
|
||||
```
|
||||
|
||||
### 2. Add Forge Mirror as Secondary Remote
|
||||
|
||||
```fish
|
||||
ssh indri 'cd ~/code/3rd/forgejo && git remote add forge http://localhost:3001/eblume/forgejo.git'
|
||||
```
|
||||
|
||||
### 3. Check Out the Desired Version Tag
|
||||
|
||||
```fish
|
||||
ssh indri 'cd ~/code/3rd/forgejo && git checkout v14.0.1'
|
||||
```
|
||||
|
||||
### 4. Set Up Build Dependencies via Mise
|
||||
|
||||
Forgejo requires Go 1.24+ and Node 20+:
|
||||
|
||||
```fish
|
||||
ssh indri 'cd ~/code/3rd/forgejo && mise use go@1.24 node@20'
|
||||
```
|
||||
|
||||
### 5. Build the Binary
|
||||
|
||||
```fish
|
||||
ssh indri 'cd ~/code/3rd/forgejo && TAGS="bindata timedzdata sqlite sqlite_unlock_notify" mise x -- make build'
|
||||
```
|
||||
|
||||
This produces `./forgejo` in the repo root.
|
||||
|
||||
### 6. Stop Brew Forgejo
|
||||
|
||||
```fish
|
||||
ssh indri 'brew services stop forgejo'
|
||||
```
|
||||
|
||||
### 7. Copy Data to New Location
|
||||
|
||||
```fish
|
||||
ssh indri 'sudo cp -a /opt/homebrew/var/forgejo ~/forgejo'
|
||||
```
|
||||
|
||||
### 8. Fix Ownership
|
||||
|
||||
```fish
|
||||
ssh indri 'sudo chown -R erichblume:staff ~/forgejo'
|
||||
```
|
||||
|
||||
### 9. Run Ansible to Deploy New Config + LaunchAgent
|
||||
|
||||
```fish
|
||||
mise run provision-indri -- --tags forgejo
|
||||
```
|
||||
|
||||
### 10. Verify Service Health
|
||||
|
||||
See the verification checklist below.
|
||||
|
||||
### 11. Uninstall Brew Forgejo
|
||||
|
||||
Only after verifying everything works:
|
||||
|
||||
```fish
|
||||
ssh indri 'brew uninstall forgejo'
|
||||
```
|
||||
|
||||
## Ansible Role Changes
|
||||
|
||||
The following changes to `ansible/roles/forgejo/` should be made in the execution session.
|
||||
|
||||
### `defaults/main.yml`
|
||||
|
||||
Update paths and add new variables to match the zot pattern (`ansible/roles/zot/defaults/main.yml`):
|
||||
|
||||
```yaml
|
||||
# Source build paths
|
||||
forgejo_repo_dir: /Users/erichblume/code/3rd/forgejo
|
||||
forgejo_binary: "{{ forgejo_repo_dir }}/forgejo"
|
||||
|
||||
# Data paths (migrated from brew)
|
||||
forgejo_work_path: /Users/erichblume/forgejo
|
||||
forgejo_config_path: "{{ forgejo_work_path }}/custom/conf/app.ini"
|
||||
forgejo_data_path: "{{ forgejo_work_path }}/data"
|
||||
forgejo_log_path: "{{ forgejo_work_path }}/log"
|
||||
forgejo_log_dir: /Users/erichblume/Library/Logs
|
||||
|
||||
# RUN_USER changes from 'forgejo' to 'erichblume' (LaunchAgent user)
|
||||
forgejo_run_user: erichblume
|
||||
```
|
||||
|
||||
### `tasks/main.yml`
|
||||
|
||||
Replace brew install/start with binary-check + LaunchAgent pattern (matching `ansible/roles/zot/tasks/main.yml`):
|
||||
|
||||
```yaml
|
||||
---
|
||||
# Forgejo role — source-built binary with LaunchAgent
|
||||
#
|
||||
# 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 http://localhost:3001/eblume/forgejo.git'
|
||||
#
|
||||
# 3. Set up Go and Node via mise:
|
||||
# ssh indri 'cd ~/code/3rd/forgejo && mise use go@1.24 node@20'
|
||||
#
|
||||
# 4. Build:
|
||||
# ssh indri 'cd ~/code/3rd/forgejo && TAGS="bindata timedzdata sqlite sqlite_unlock_notify" mise x -- make build'
|
||||
#
|
||||
# 5. Run ansible to deploy config and LaunchAgent
|
||||
|
||||
- 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 && TAGS="bindata timedzdata sqlite sqlite_unlock_notify" mise x -- make build'
|
||||
when: not forgejo_binary_stat.stat.exists
|
||||
|
||||
- name: Ensure forgejo config directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ forgejo_work_path }}/custom/conf"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Deploy forgejo config
|
||||
ansible.builtin.template:
|
||||
src: app.ini.j2
|
||||
dest: "{{ forgejo_config_path }}"
|
||||
mode: '0600'
|
||||
notify: Restart forgejo
|
||||
|
||||
- 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
|
||||
```
|
||||
|
||||
### `handlers/main.yml`
|
||||
|
||||
Replace `brew services restart` with `launchctl unload/load` (matching `ansible/roles/zot/handlers/main.yml`):
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: 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
|
||||
```
|
||||
|
||||
### New Template: `forgejo.plist.j2`
|
||||
|
||||
LaunchAgent plist (matching `ansible/roles/zot/templates/zot.plist.j2`):
|
||||
|
||||
```xml
|
||||
<?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>
|
||||
```
|
||||
|
||||
### `app.ini.j2`
|
||||
|
||||
No changes needed — paths already flow through variables in `defaults/main.yml`. The only change is that `RUN_USER` will pick up `erichblume` from the updated default.
|
||||
|
||||
## What Stays the Same
|
||||
|
||||
- **1Password secret fetching** — playbook `pre_tasks` are unchanged
|
||||
- **`forgejo_actions_secrets` role** — API-based secret sync is unaffected
|
||||
- **SSH clone URLs** — `BUILTIN_SSH_SERVER_USER` stays `forgejo` (this is the git SSH user, not the OS user)
|
||||
- **Caddy routing** — still proxies to `localhost:3001`
|
||||
- **SQLite database** — copied as-is to new location
|
||||
- **All `app.ini` settings** — template is unchanged, just re-rendered with new paths
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
After running the migration and Ansible:
|
||||
|
||||
- [ ] `ssh indri 'launchctl list mcquack.eblume.forgejo'` — shows running
|
||||
- [ ] `curl https://forge.ops.eblu.me/api/v1/version` — returns JSON with version
|
||||
- [ ] Git clone over SSH: `git clone ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git /tmp/test-clone`
|
||||
- [ ] Git push works on an existing clone
|
||||
- [ ] Ansible dry-run is clean: `mise run provision-indri -- --tags forgejo --check --diff`
|
||||
- [ ] `mise run services-check` — all green
|
||||
- [ ] Forgejo Actions runners reconnect and jobs succeed
|
||||
|
||||
## Future Considerations
|
||||
|
||||
- **CI-built binaries** — build on gilbert or in Forgejo Actions, deploy as artifact
|
||||
- **Artifact release system** — tag-triggered binary builds, similar to container releases (`mise run container-release`)
|
||||
- **Automated upgrades** — Renovate or similar watching Codeberg tags, opening PRs with version bumps
|
||||
|
||||
## Reference Pattern Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `ansible/roles/zot/tasks/main.yml` | Primary pattern for source-built binary tasks |
|
||||
| `ansible/roles/zot/defaults/main.yml` | Variable naming conventions |
|
||||
| `ansible/roles/zot/templates/zot.plist.j2` | LaunchAgent plist template |
|
||||
| `ansible/roles/zot/handlers/main.yml` | Handler pattern (launchctl unload/load) |
|
||||
16
docs/how-to/plans/plans.md
Normal file
16
docs/how-to/plans/plans.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: Plans
|
||||
tags:
|
||||
- how-to
|
||||
- plans
|
||||
---
|
||||
|
||||
# Plans
|
||||
|
||||
Migration and transition plans for upcoming infrastructure changes. Each plan is a how-to document that captures the full context, steps, and verification criteria for a future execution session.
|
||||
|
||||
Plans differ from regular how-to guides in that they describe work that has been designed but not yet executed. Once a plan is completed, it may be archived or converted into a standard how-to guide.
|
||||
|
||||
| Plan | Status | Description |
|
||||
|------|--------|-------------|
|
||||
| [[migrate-forgejo-from-brew]] | Planned | Transition Forgejo from Homebrew to source-built binary with LaunchAgent |
|
||||
|
|
@ -20,6 +20,7 @@ The docs follow the [Diataxis](https://diataxis.fr/) framework:
|
|||
| **[[tutorials|Tutorials]]** | Learning-oriented | "I'm new and want to understand" |
|
||||
| **[[reference|Reference]]** | Information-oriented | "I need specific technical details" |
|
||||
| **[[how-to|How-to]]** | Task-oriented | "I need to do X" |
|
||||
| **[[plans|Plans]]** | Future work | "What's planned next?" |
|
||||
| **[[explanation|Explanation]]** | Understanding-oriented | "I want to understand why" |
|
||||
|
||||
## Quick Paths by Audience
|
||||
|
|
@ -28,6 +29,7 @@ The docs follow the [Diataxis](https://diataxis.fr/) framework:
|
|||
|
||||
You probably want quick access to operational details:
|
||||
- [[how-to]] guides for common operations (deploy, troubleshoot, update ACLs)
|
||||
- [[plans]] captures migration and transition plans for future execution
|
||||
- [[reference]] has service URLs, commands, and config locations
|
||||
- [[ai-assistance-guide]] explains how to work effectively with Claude
|
||||
- Run `mise run zk-docs` to prime AI context with key documentation
|
||||
|
|
@ -36,6 +38,7 @@ You probably want quick access to operational details:
|
|||
|
||||
Context for effective assistance:
|
||||
- Read [[ai-assistance-guide]] for operational conventions
|
||||
- [[plans]] has migration plans designed for AI-executed sessions
|
||||
- [[reference]] has the technical specifics you'll need
|
||||
- The repo's `CLAUDE.md` has critical rules (especially the kubectl context requirement)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue