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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.