Move to Ubuntu 26.04 LTS: backup, setup and restore scripts

Replaces the Bazzite/BlueBuild image with plain Ubuntu. apt covers the system
and dev tools, Flathub the desktop apps, and three scripts do the rest:
backup.sh archives this machine's home to server-marvin and reads it back to
verify it; setup.sh builds a fresh 26.04 install (NVIDIA 595-open, Docker with
GPU access, Steam/Lutris, 27 Flatpaks, VS Code, Claude desktop, the Data drive
and marvin mounts); restore.sh unpacks the backup without the Fedora/KDE parts
that would fight Ubuntu.

The app list comes from a scan of the running machine. The 15 Flatpaks left
behind sit commented at the bottom of flatpaks.txt, and their data still rides
along in the backup. No Node or .NET on the host by choice — LudosData and
landingPage build in Docker.

Drops the BlueBuild recipe, the cosign key and the old dev-setup script. The
Bazzite setup stays in this history.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-23 15:48:47 -04:00
co-authored by Claude Opus 5
parent cf3fc8bb79
commit 1ddc56800a
10 changed files with 638 additions and 370 deletions
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env bash
# Run on the Bazzite box BEFORE wiping it. Archives your home folder to server-marvin as
# tar.zst files, then reads every archive back to prove it's intact.
#
# scripts/backup.sh # -> /var/mnt/server-marvin-personal/desktop-backup-<date>
# scripts/backup.sh /some/other/dir # any directory NOT on the disk you're about to wipe
#
# Re-running skips archives that already finished (FORCE=1 redoes them).
# Close Steam, Firefox, Discord and Claude first — files that change mid-read get a warning.
set -euo pipefail
SHARE=/var/mnt/server-marvin-personal
DEST="${1:-$SHARE/desktop-backup-$(date +%F)}"
step() { printf '\n\033[1;34m== %s ==\033[0m\n' "$*"; }
warn() { printf '\033[1;33mwarning:\033[0m %s\n' "$*" >&2; }
die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; }
# -- Make sure the backup lands somewhere that survives the reinstall ------------------
if [[ $DEST == "$SHARE"/* ]]; then
ls "$SHARE" >/dev/null 2>&1 || true # poke the automount
mountpoint -q "$SHARE" || die "$SHARE isn't mounted — is server-marvin up?"
fi
mkdir -p "$DEST"
disk_of() { findmnt -n -o SOURCE -T "$1" | sed 's/\[.*//'; }
[[ $(disk_of "$DEST") != "$(disk_of "$HOME")" ]] \
|| die "$DEST is on the same disk as your home — that's the disk Ubuntu will wipe"
echo "Backing up $HOME -> $DEST"
df -h "$DEST" | tail -1
# -- Archive helper ---------------------------------------------------------------------
archive() { # archive <name> <tar args + members...>
local name=$1 out="$DEST/$1.tar.zst"; shift
step "$name"
if [[ -s $out && -z ${FORCE:-} ]]; then echo "already done, skipping"; return; fi
local rc=0
tar --create --file="$out.partial" --use-compress-program='zstd -T0 -3' \
--directory="$HOME" \
--checkpoint=100000 --checkpoint-action='ttyout= %{%H:%M:%S}t %T%*\r' \
"$@" || rc=$?
echo
# GNU tar exits 1 when a file changed while it was being read (an app was still open).
# Everything else still made it in, so keep the archive; anything above 1 is fatal.
(( rc <= 1 )) || die "tar failed on $name (exit $rc)"
(( rc == 0 )) || warn "$name: some files changed while being read — close apps and FORCE=1 to redo if it matters"
mv "$out.partial" "$out"
ls -lh "$out" | awk '{print " " $5}'
}
# Big, self-contained folders get their own archive so they can be restored (or skipped)
# separately. Everything else goes in home.tar.zst.
archive home \
--anchored \
--exclude=./.cache \
--exclude=./.local/share/Trash \
--exclude=./.local/share/Steam \
--exclude=./.steam \
--exclude=./.local/share/containers \
--exclude=./.local/share/flatpak \
--exclude=./.local/share/baloo \
--exclude='./.var/app/*/cache' \
--exclude='./.claude.json.tmp.*' \
--exclude=./.xlcore \
--exclude=./ComfyUI \
--no-anchored \
--exclude=node_modules \
--exclude=__pycache__ \
.
# FFXIV game install (~124 GB) — Square's patch servers make a redownload take hours.
[[ -d $HOME/.xlcore ]] && archive xlcore ./.xlcore
# ComfyUI minus its venv (rebuilt on Ubuntu); this is ~110 GB of models.
[[ -d $HOME/ComfyUI ]] && archive comfyui \
--anchored --exclude=./ComfyUI/venv --exclude=./ComfyUI/.venv \
--no-anchored --exclude=__pycache__ \
./ComfyUI
# Steam: only saves and settings. Games get redownloaded; the ones on the Data drive stay put.
archive steam-saves \
./.local/share/Steam/userdata \
./.local/share/Steam/steamapps/compatdata
# -- Reference info for the rebuild -------------------------------------------------------
step "manifest"
m="$DEST/manifest"; mkdir -p "$m"
flatpak list --app --columns=application,origin,installation > "$m/flatpaks.txt" 2>/dev/null || true
code --list-extensions > "$m/vscode-extensions.txt" 2>/dev/null || true
brew leaves > "$m/brew-leaves.txt" 2>/dev/null || true
rpm-ostree status > "$m/rpm-ostree-status.txt" 2>/dev/null || true
cp /etc/fstab "$m/fstab"
cp -r "$HOME/.local/share/flatpak/overrides" "$m/flatpak-overrides-user" 2>/dev/null || true
cp -r /var/lib/flatpak/overrides "$m/flatpak-overrides-system" 2>/dev/null || true
lsblk -f > "$m/disks.txt"
ls "$HOME/.local/share/Steam/steamapps/common" > "$m/steam-games-home.txt" 2>/dev/null || true
ls /run/media/"$USER"/Data/SteamLibrary/steamapps/common > "$m/steam-games-data-drive.txt" 2>/dev/null || true
ls "$m"
# -- Prove every archive reads back cleanly ----------------------------------------------
if [[ -z ${SKIP_VERIFY:-} ]]; then
for f in "$DEST"/*.tar.zst; do
step "verify $(basename "$f")"
tar --use-compress-program=zstd --list --file="$f" > /dev/null || die "$f is corrupt — rerun with FORCE=1"
echo "ok"
done
fi
step "done"
du -sh "$DEST"/*.tar.zst
echo "Backup is in: $DEST"
echo "Safe to wipe the 990 PRO once you've eyeballed that list."