Built and proven on Fedora Asahi Remix — Linux on Apple Silicon, the
hardest boot chain there is to support, and the one place where
"reinstall, then restore over it" is the only way back — and run unchanged
on x86_64. One codebase for every distro, root filesystem and boot layout,
that answers the only question that matters once a disk is gone: could I
actually restore this machine? It layers Borg archives, Back In
Time-format rsync snapshots, a local snapshot layer chosen by the root
filesystem (btrfs send/receive or Timeshift), keyslot-tagged LUKS
header backups, and a daily restore-readiness check that asserts, from
what is on the backup drive, that a restore would boot. On restore it
rebuilds the boot chain for whatever it finds — GRUB, systemd-boot,
Unified Kernel Images, an encrypted argon2id /boot — and a restore
test bed turns "it should work" into a verdict by restoring onto a spare
drive, a disk image or free space on the running disk and booting the result.
Nothing is keyed to a distro name and nothing is pinned to a machine: every decision is made from what the machine shows at run time, and the drive, its guard UUID, the keyfile, the schedule and the retention live in one config file. A setup this suite has never seen is a detection gap to close, not a port to write — Contributing says how.
Testing only unless your setup is all green. Unless your distro, root filesystem and boot layout are all ✅ in the Bare-metal restore column of Status, use this for testing, not production. A backup you have never restored is a hope, not a backup. A backup never starts because a drive appeared: plugging the drive in unlocks and mounts it; a backup runs when you start one or a timer fires. Every script takes
--dry-run. Retention never deletes by age.
git clone https://github.com/doug445/linux-backup-system.git
cd linux-backup-system
sudo ./deploy.sh --dry-run # 1. detect distro, fs, boot layout, drive; print the plan; change nothing
sudo ./deploy.sh # 2. install packages, scripts, config, units — and set the drive up if none is mounted
sudo borg-backup.sh --dry-run # 3. see exactly what the first backup would do
sudo borg-backup.sh # 4. the first backup
sudo backup-verify.sh # 5. would a bare-metal restore boot? exit 0 = yesNo backup drive mounted? Step 2 asks you to connect one, lets you pick it, offers to have you encrypt it first, and formats it on a typed confirmation — or installs now and finishes when the drive is there. The drive set-up, sizing, scheduling and retention are in docs/INSTALL.md.
This is the headline feature, and it is tested. A dead disk, a wiped
machine, an upgrade that will not boot: run the Asahi installer from macOS
(it finishes with one step in recoveryOS) to get a fresh Fedora Asahi Remix
stub booting — the only part of the machine that is Apple's to make — then
point this suite's restore at it. Every file comes back; fstab,
crypttab and every kernel command line are rewritten to the new partitions'
ids; the initramfs and the GRUB configuration are rebuilt inside the restored
system; a verification refuses to call it done while anything would boot,
unlock or mount the wrong disk. Reboot, and it is your machine again — users,
keys, Wi-Fi, desktop, the lot. Verified on a MacBook Pro (M1 Pro) in
September 2026: Asahi installer from macOS, restore over the fresh install,
and it boots. No other backup tool documents, let alone tests, this path;
most do not know a Mac cannot be restored bare-metal at all.
Apple Silicon is a first-class target here, not a port. The author's own
machines are an M1 Pro and an M2 Max, and the suite is keyed to the Asahi
boot chain — stub → m1n1 → U-Boot → shim → GRUB on arm64-efi, 16k pages —
which is the same on every M-series generation Asahi runs on. An
Asahi-specific fault on any of them is a bug in this suite and gets fixed:
send the troubleshooting report and it is worked
from there. For the day Apple stops serving the installer's stub image for
your model, the FAQ says what to
protect now.
Two truths, then, and both are in the tables. On a Mac the recovery is the
simple one: reinstall from macOS, restore over it, done — proven. On
x86-64 it is the hard one: bare metal means recreating the boot chain from
files alone, with nothing to reinstall from — GRUB or systemd-boot, UKIs and
their embedded command lines, Secure Boot with shim or your own keys,
LVM-on-LUKS, an encrypted /boot — and that is what the rest of this suite
exists for, seven such restores deep.
An x86 machine boots a file from a FAT partition. A Mac running Linux boots a stub macOS the Asahi installer made, whose boot policy the machine's own Secure Enclave signed; its kernel slot holds m1n1, which loads its second stage and U-Boot from the ESP, which loads shim, which loads GRUB, under a 16k-page kernel, on a 4Kn disk whose first partitions can never be recreated from Linux. Every one of these cost the suite a fix that x86 never needed — which is why it is developed here first:
- Nothing external booted. On the M2 Max, U-Boot enumerated none of the USB drives tried (two bridges, direct and through a hub), so "restore onto a spare drive and boot it" — the whole test-bed method — was impossible there. The test bed grew a disk-image target (a file on the backup drive, booted in a VM) and a same-disk mode (test partitions in free space behind a second Asahi stub, picked at power-on) for this machine.
- 4096-byte sectors. Apple NVMe is 4Kn; a VM that presents the same drive with 512-byte sectors reads its GPT at the wrong offsets. The VM boot now passes the target's logical sector size through.
ID=fedora-asahi-remix,EFI/fedora. The boot rebuild trusted os-release'sIDfor the ESP's vendor directory, so on Asahi it never took the Fedora-shim branch and left shim on the removable path with no GRUB beside it. The test bed caught it before any reboot; the rebuild now finds the directory that actually holds the loaders. Seven x86 bare-metal restores — Fedora, Mint, Manjaro, EndeavourOS — had passed without ever exercising it.- Boot entries that live in a file. U-Boot keeps its EFI variables in
ubootefi.varon the ESP the stub names (/chosen/asahi,efi-system-partition;arch/arm/mach-apple/board.c). Entries shim's fallback writes at boot time persist there; entriesefibootmgrwrites from Linux do not survive a reboot. A restored ESP therefore carries the source machine'sBootOrder, whose first entry is the source machine's own shim — booted from a second stub, it would start the wrong system. The same-disk mode parks that file: with no entries, U-Boot's boot manager tries the default loader on the stub's own ESP before any other partition (efi_bootmgr.c, try EFI system partition), which is the test ESP. - No argon2 in GRUB 2.12 on aarch64. An encrypted
/bootthere needs a GRUB ≥ 2.14 built from source; the verify pass and the rebuild name the floor for whatever KDF the container uses.
A restore that survives that chain leaves the x86 UEFI cases — seven
bare-metal restores in the table: GRUB, systemd-boot, UKIs, Secure Boot with
shim and with sbctl keys, encrypted argon2id /boot, LVM-on-LUKS — as the
easy ones.
Verified on 10 systems, restored and VM-booted on an eleventh — every ✅
below is a real backup, a passing backup-verify.sh and, in the Bare-metal
restore column, a restore onto a blank disk by the suite's own scripts that
booted, logged in and came up on the network, byte-compared against the
archive. The eleventh is the M2 Max: restored onto a disk image and booted in
a VM (PASS-VM, 99.991 % of files identical), the real boot behind a second
Asahi stub still to come. The full tables, row by row,
are docs/STATUS.md; the machines are
docs/TESTED-SYSTEMS.md.
| ✅ verified | ❌ wanted | |
|---|---|---|
| Distros | Fedora, Fedora Asahi Remix (aarch64), Debian / Ubuntu / Linux Mint, Arch / Manjaro / EndeavourOS | openSUSE; Slackware, Gentoo, Alpine, Void, NixOS, Solus (package managers the map does not know) |
| Root filesystems | btrfs (subvolumes, swapfile), ext4, root on LUKS2 (sd-encrypt), LVM-on-LUKS | xfs, f2fs |
| Boot layouts | GRUB (EFI), systemd-boot (Type #1 and UKI), UKIs from dracut / kernel-install / mkinitcpio, Secure Boot with sbctl keys, encrypted argon2id /boot, plain /boot, ESP at /efi + XBOOTLDR, SELinux relabel (enforcing and permissive), Apple Silicon over a fresh Asahi install |
GRUB legacy BIOS, encrypted pbkdf2 /boot, Raspberry Pi firmware boot, Limine, rEFInd; |
A ❌ row turns testbed.sh collect has returned VERDICT: PASS for it — a PASS-VM (a
disk image booted in a VM, never by the machine) is evidence for the restore
and keeps the row at
sudo ./restore.sh # interactive: snapper / btrfs / borg / BIT / combined
sudo ./borg-restore.sh --dry-run /mnt/target /mnt/backup/borg-backup # preview; drop --dry-run to restoreFrom a live USB, or from an installed system onto a second disk — nothing is
written to the disk you are running from. The files come back, then fstab,
crypttab and every kernel command-line carrier are rewritten to the new
disk's ids, then restore-rebuild-boot.sh rebuilds the boot chain inside the
restored system, and a verification refuses to call it done while anything
would boot, unlock or mount the wrong disk. All of it: docs/RESTORE.md.
| Doc | Covers |
|---|---|
docs/STATUS.md |
The tested / untested tables, the restore test bed, what is not yet universal |
docs/TESTED-SYSTEMS.md |
Every tested machine: hardware, layout, what was verified on it and what was found |
docs/INSTALL.md |
deploy.sh, drive set-up and sizing, dependencies, scheduling, retention, the tray, every file |
docs/RESTORE.md |
Restoring from a live USB or an installed system; the boot rebuild per layout |
docs/TROUBLESHOOTING.md |
Logs, dry runs, the troubleshooting report, USB bridge drops; hand-rolling a fix for a new setup |
docs/LAYERS.md |
The layers, why three of them, the 3-2-1 mapping |
docs/FAQ.md |
Distros, encryption, drive size, retention, snapshots, Raspberry Pi, Apple Silicon |
docs/ABOUT.md |
The long-form description: the problem, who it is for, what it deliberately does not do |
CONTRIBUTING.md |
Scope, the setup-report table, adding a Linux setup and getting onto the license |
SECURITY.md |
Supported versions, reporting, what never to send |
backup-system.conf.example |
Every per-host knob, with its default |
.github/rulesets/ |
Branch and tag protection as JSON, applied to this repository |
The goal is one backup system that runs on any Linux — universal in fact,
not just in design — and one person cannot own every distro, filesystem and
boot layout. The project takes setup reports, new Linux setups (a
report, or a patch that passes bash tests/run-all.sh) and serious bugs.
Most wanted: the distro families the package map does not know (Slackware,
Gentoo, Alpine, Void, NixOS, Solus), and a Raspberry Pi. A working patch for
a ❌ row puts your name on the copyright line of LICENSE, in the release
notes, and in the table below; a report alone is still what the fix gets built
from. CONTRIBUTING.md has the terms and the table of what
is still unconfirmed.
| Contributor | Setup added | Release |
|---|---|---|
| (none yet — the first working patch for a ❌ row goes here) |
Reviewed in full on 2026-09-12 by Claude Fable 5.1 (Anthropic) ahead of
the 3.0.0 release: every script, the units, the tray and the documentation,
with each finding reproduced before it was fixed. Found and fixed in 3.0.0:
the timeback shell function was defined but never installed; the drive-attach
unit pointed at /usr/local/bin while the installer put the script in
/usr/local/sbin, so it failed on every host; the udev rule carried the
author's own drive UUID; the tray hardcoded the author's mount path, parsed
values out of a script that no longer held them, and had no Timeshift, LUKS
header, verify or troubleshooting sections; openSUSE was accepted by the
library and refused by the installer. Nothing in this suite has changed hands:
the design decisions are the author's, the audit checked that the code keeps
them.
MIT — see LICENSE.
- Version: 4.1.1
- Author: William MacKinnon (doug445)
- Email: spilled-bowline0j@icloud.com
- Repository: https://github.com/doug445/linux-backup-system
Copyright (c) 2026 William MacKinnon <spilled-bowline0j@icloud.com>