About freebsd-automerge — An automated upgrade ass istant for FreeBSD
Mario Marietto <[email protected]>
| Newsgroups | gmane.os.freebsd.devel.hackers |
|---|---|
| Message-ID | <CA+1FSigWOkPDsB17TatzPhuJfZrmLc1O0zWvFUaOEaku1_4JFw@mail.gmail.com> |
## Introduction I would like to share a shell script I developed with the assistance of Claude (Anthropic's AI) to automate the most tedious part of FreeBSD upgrades: resolving merge conflicts in configuration files. The script is called **freebsd-automerge.sh** and it handles the complete upgrade workflow from start to finish, including conflict resolution, backup, and the install/reboot cycle. --- ## The problem it solves Anyone who has upgraded FreeBSD using `freebsd-update upgrade` knows the pain: When configuration files have been locally modified, freebsd-update stops and asks you to manually resolve conflicts one by one in an editor. On a system with many customized files, this can mean dozens of interruptions requiring careful manual editing. The standard approach (mergemaster, etcupdate) works well but still requires significant manual intervention. I wanted something fully automated that could handle an upgrade — including a major version jump — with minimal human input. --- ## How it works The core idea is a **3-way merge**: ``` base OLD (e.g. 14.3-RELEASE clean) → your customized file = YOUR changes base OLD (e.g. 14.3-RELEASE clean) → base NEW (14.4-RELEASE) = FreeBSD changes ``` By comparing both sets of changes independently against the original clean base,the script can automatically apply both your customizations and FreeBSD's updates without conflicts — exactly the same mechanism git uses for merging branches. The script operates in two distinct modes: ### EDITOR mode The script registers itself as the `$EDITOR` for `freebsd-update`: ```sh EDITOR=/root/freebsd-automerge.sh freebsd-update -r 14.4-RELEASE upgrade ``` When freebsd-update encounters a file it cannot merge automatically, instead of opening nano it calls our script with the conflicted file path. The script then: 1. Extracts the clean OLD base version from `base.txz` 2. Extracts the clean NEW base version from `base.txz` 3. Uses `merge(1)` (from the base system) for the 3-way merge in-place 4. If residual conflicts remain, fall back to nano for manual resolution 5. Returns control to freebsd-update ### MAIN mode The primary workflow: 1. Interactive menu querying the FreeBSD mirror in real time to show available RELEASE, STABLE, RC, and BETA targets 2. Downloads `base.txz` for both OLD and NEW releases 3. Runs `freebsd-update upgrade` with itself as EDITOR 4. After upgrade, scans `/etc` and `/usr/share` for any remaining `<<<<<<<` markers and resolves them with the same 3-way merge 5. Creates a centralized backup of all conflicted files with SHA256 manifest and generates a `restore.sh` script for rollback 6. Runs `freebsd-update install` and handles the reboot cycle automatically, including the two-pass install required for major version upgrades --- ## Key features - **Zero configuration** — detects OLD and NEW versions automatically from `uname -r` and `/var/db/freebsd-update/tag` - **Live mirror query** — the target selection menu fetches available versions directly from the FreeBSD mirror at runtime - **Kernel/userland selection** — asks whether to upgrade both or userland only,useful for systems running custom kernels (e.g. ARM SBCs with custom DRM drivers) - **Auto-confirm mode** — `--auto-confirm` flag uses `expect(1)` to answer freebsd-update's interactive prompts automatically - **Major upgrade support** — handles the two-install, two-reboot cycle for major version jumps (e.g. 14.x → 15.x) using a temporary `rc.d` service that survives the first reboot - **Safe by design** — full backup before touching anything, restore script generated automatically, SHA256 integrity verification --- ## Current limitations and known bugs I want to be transparent about what still needs work: 1. **STABLE branch detection** — the menu regex for `-STABLE` targets does not always match the mirror directory listing correctly 2. **State recovery after interruption** — if the script is killed mid-upgrade, resuming it does not correctly re-enter the freebsd-update merge phase; it tries to call `freebsd-update install` prematurely 3. **grep -c arithmetic** — a subtle shell arithmetic bug produces `[: 0 0: bad number` in some cases when counting conflict markers 4. **STABLE/CURRENT upgrade type** — `is_major_upgrade()` only compares major version numbers; it does not distinguish STABLE or CURRENT targets which may need different handling 5. **Terminal compatibility** — Unicode box-drawing characters in menus do not render correctly in all FreeBSD terminal configurations; should fall back to plain ASCII --- ## What I would love feedback on This is where I genuinely need input from people who know FreeBSD internals better than I do: **1. Is using `$EDITOR` the right hook into freebsd-update?** Is there a cleaner, more supported way to intercept freebsd-update's merge phase? I am essentially abusing the EDITOR variable — it works, but it feels fragile. Is there an official API or hook mechanism I am missing? **2. The 3-way merge approach — is merge(1) the right tool?** I am using `merge(1)` from the `rcs` package. Are there better alternatives in the FreeBSD base system? `diff3(1)` is available but requires more orchestration. `patch(1)` could work but loses context. Thoughts? **3. Handling STABLE and CURRENT upgrades** Upgrading to a -STABLE or -CURRENT branch is fundamentally different from a RELEASE upgrade — freebsd-update behaves differently, and the base.txz download path is different. Has anyone automated this reliably? **4. The two-reboot major upgrade cycle** I implemented this using a temporary `rc.d` service (`automerge_resume`) that runs `freebsd-update install` after the first reboot and then removes itself. It works, but it feels like a hack. Is there a cleaner pattern? **5. pkg alignment after major upgrades** After a major upgrade (e.g. 14.x → 15.x), all installed packages need to be rebuilt or reinstalled. I currently just tell the user to run `pkg upgrade -f` manually. Should this be integrated into the script? What is the safest sequence? --- ## How to get it The script is a single self-contained shell file with no external dependencies beyond `merge(1)` (from `pkg install rcs`) and optionally `expect(1)` for auto-confirm mode. Usage: ```sh # Show interactive target menu (queries mirror live) sh freebsd-automerge.sh # Filter menu to branch 14 sh freebsd-automerge.sh 14 # Direct upgrade, no menu sh freebsd-automerge.sh 14.4-RELEASE # Fully automated (requires: pkg install expect) sh freebsd-automerge.sh --auto-confirm 14 ``` --- ## Credits The script was conceived and directed by **ZioMario** (forums.freebsd.org user: ZioMario), developed with the assistance of **Claude** (Anthropic AI). The idea, architecture decisions, testing, and debugging were all driven by ZioMario — Claude was used as an implementation tool. All feedback, pull requests, and brutal criticism are welcome. -- Mario.
freebsd-automerge.sh.zip
(application/zip, 7.2 KB) - not displayed