From b5746e62c227f363e1775074032d59232026b52d Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Tue, 10 Feb 2026 10:18:53 -0800 Subject: [PATCH] Add migration plan for Forgejo brew-to-source transition (#140) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Add `docs/how-to/plans/migrate-forgejo-from-brew.md` — full Diataxis-style plan covering background, one-time migration steps, Ansible role changes (with exact code), verification checklist, and future considerations - Add `docs/how-to/plans/plans.md` — new plans subdirectory index for upcoming migration/transition plans - Update `docs/how-to/how-to.md` with a Plans section - Update `docs/tutorials/exploring-the-docs.md` to mention plans in the doc structure table and quick-path sections for Owner and AI audiences ## Test plan - [x] `docs-check-links` passes - [x] `docs-check-index` passes - [x] All pre-commit hooks pass Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/140 --- .../doc-forgejo-brew-migration-plan.doc.md | 1 + docs/how-to/how-to.md | 9 + .../how-to/plans/migrate-forgejo-from-brew.md | 298 ++++++++++++++++++ docs/how-to/plans/plans.md | 16 + docs/tutorials/exploring-the-docs.md | 3 + 5 files changed, 327 insertions(+) create mode 100644 docs/changelog.d/doc-forgejo-brew-migration-plan.doc.md create mode 100644 docs/how-to/plans/migrate-forgejo-from-brew.md create mode 100644 docs/how-to/plans/plans.md diff --git a/docs/changelog.d/doc-forgejo-brew-migration-plan.doc.md b/docs/changelog.d/doc-forgejo-brew-migration-plan.doc.md new file mode 100644 index 0000000..d066ceb --- /dev/null +++ b/docs/changelog.d/doc-forgejo-brew-migration-plan.doc.md @@ -0,0 +1 @@ +Add migration plan for Forgejo brew-to-source transition diff --git a/docs/how-to/how-to.md b/docs/how-to/how-to.md index 0d12ec4..8959626 100644 --- a/docs/how-to/how-to.md +++ b/docs/how-to/how-to.md @@ -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 | diff --git a/docs/how-to/plans/migrate-forgejo-from-brew.md b/docs/how-to/plans/migrate-forgejo-from-brew.md new file mode 100644 index 0000000..1f148da --- /dev/null +++ b/docs/how-to/plans/migrate-forgejo-from-brew.md @@ -0,0 +1,298 @@ +--- +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) + +## 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: + +``` +https://forge.ops.eblu.me/eblume/forgejo.git +``` + +## 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 https://forge.ops.eblu.me/eblume/forgejo.git' +``` + +### 3. Check Out the Desired Version Tag + +```fish +ssh indri 'cd ~/code/3rd/forgejo && git checkout v14.0.1' +``` + +### 4. Create a Local Deployment Branch + +Create a local-only `indri-deployment` branch to track the deployed version. Rebase this branch when upgrading to new tags: + +```fish +ssh indri 'cd ~/code/3rd/forgejo && git checkout -b indri-deployment' +``` + +### 5. 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' +``` + +### 6. 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. + +### 7. Stop Brew Forgejo + +```fish +ssh indri 'brew services stop forgejo' +``` + +### 8. Copy Data to New Location + +```fish +ssh indri 'sudo cp -a /opt/homebrew/var/forgejo ~/forgejo' +``` + +### 9. Fix Ownership + +```fish +ssh indri 'sudo chown -R erichblume:staff ~/forgejo' +``` + +### 10. Run Ansible to Deploy New Config + LaunchAgent + +```fish +mise run provision-indri -- --tags forgejo +``` + +### 11. Verify Service Health + +See the verification checklist below. + +### 12. 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 https://forge.ops.eblu.me/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 + + + + + + Label + mcquack.eblume.forgejo + ProgramArguments + + {{ forgejo_binary }} + -w + {{ forgejo_work_path }} + -c + {{ forgejo_config_path }} + web + + RunAtLoad + + KeepAlive + + StandardOutPath + {{ forgejo_log_dir }}/mcquack.forgejo.out.log + StandardErrorPath + {{ forgejo_log_dir }}/mcquack.forgejo.err.log + + +``` + +### `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 +- **Indri user management** — run each service as its own macOS user for isolation (a `forgejo` user exists but LaunchAgent session management under non-login users is tricky on macOS) + +## 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) | diff --git a/docs/how-to/plans/plans.md b/docs/how-to/plans/plans.md new file mode 100644 index 0000000..d7d73ab --- /dev/null +++ b/docs/how-to/plans/plans.md @@ -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 | diff --git a/docs/tutorials/exploring-the-docs.md b/docs/tutorials/exploring-the-docs.md index db9c8a0..afbd255 100644 --- a/docs/tutorials/exploring-the-docs.md +++ b/docs/tutorials/exploring-the-docs.md @@ -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)