Snapper: no question on btrfs; vendor the SysGuides scripts
- On btrfs, snapshots + zstd compression now just happen (no prompt); --snapper=no remains the explicit opt-out. - Vendor scripts/snapper-*.sh + config/snapper.actions from SysGuides/sysguides-snapper-fedora (MIT, upstream 0a28eb4) into vendor/snapper/ and install from there — no runtime clone of an external repo, and no dependency on exec bits (fixes the './install.sh: Permission denied' failure seen on the test VM). - grub-btrfs still cloned at runtime from Antynea/grub-btrfs with the Fedora config applied; everything else is local now.
This commit is contained in:
+57
-12
@@ -258,9 +258,14 @@ ensure_btrfs_compression() {
|
||||
info " sudo mount -o remount / && sudo mount -o remount /home"
|
||||
}
|
||||
|
||||
# Full snapshot setup via the SysGuides installer (snapper + grub-btrfs +
|
||||
# DNF5 pre/post snapshot actions). The installer re-validates btrfs itself.
|
||||
# Snapshot setup using the vendored SysGuides scripts (vendor/snapper/).
|
||||
# Original: https://github.com/SysGuides/sysguides-snapper-fedora (MIT).
|
||||
# Vendored so we do not depend on that repo at runtime; upstream changes can
|
||||
# be pulled deliberately, not accidentally.
|
||||
# Exec bits are not relied on: scripts are invoked via `bash`.
|
||||
setup_snapper() {
|
||||
local vendor="$SCRIPT_DIR/vendor/snapper"
|
||||
|
||||
if pkg_installed snapper && [[ -d /.snapshots ]]; then
|
||||
ok "Snapper already configured (config for / exists)."
|
||||
return 0
|
||||
@@ -269,17 +274,57 @@ setup_snapper() {
|
||||
warn "Root filesystem is not btrfs; skipping snapshot setup."
|
||||
return 0
|
||||
fi
|
||||
info "Setting up snapper + grub-btrfs (SysGuides installer)..."
|
||||
if [[ ! -f "$vendor/scripts/snapper-pre.sh" ]]; then
|
||||
err "Vendored snapper scripts missing at $vendor — repo checkout incomplete?"
|
||||
return 1
|
||||
fi
|
||||
|
||||
info "Installing snapper + grub-btrfs packages..."
|
||||
install_packages snapper libdnf5-plugin-actions btrfs-assistant inotify-tools make git
|
||||
|
||||
info "Creating snapper configs (root + home)..."
|
||||
[[ -d /.snapshots ]] || sudo snapper -c root create-config /
|
||||
[[ -d /home/.snapshots ]] || sudo snapper -c home create-config /home
|
||||
sudo restorecon -RF /.snapshots /home/.snapshots 2>/dev/null || true
|
||||
|
||||
local real_user="${SUDO_USER:-$USER}"
|
||||
sudo snapper -c root set-config ALLOW_USERS="$real_user" SYNC_ACL=yes
|
||||
sudo snapper -c home set-config ALLOW_USERS="$real_user" SYNC_ACL=yes
|
||||
# no home timeline: snapshots of your own files on every dnf run are noise
|
||||
sudo snapper -c home set-config TIMELINE_CREATE=no
|
||||
|
||||
info "Excluding .snapshots from updatedb..."
|
||||
if grep -q '^PRUNENAMES' /etc/updatedb.conf 2>/dev/null; then
|
||||
grep -q '\.snapshots' /etc/updatedb.conf || \
|
||||
sudo sed -i 's|^PRUNENAMES *= *"|PRUNENAMES = ".snapshots |' /etc/updatedb.conf
|
||||
else
|
||||
printf 'PRUNENAMES = ".snapshots"\n' | sudo tee -a /etc/updatedb.conf >/dev/null
|
||||
fi
|
||||
|
||||
info "Installing grub-btrfs (from source, Antynea/grub-btrfs)..."
|
||||
local tmp
|
||||
tmp=$(mktemp -d)
|
||||
git clone --depth 1 https://github.com/SysGuides/sysguides-snapper-fedora "$tmp/sysguides" \
|
||||
|| { err "Could not clone the SysGuides installer."; rm -rf "$tmp"; return 1; }
|
||||
( cd "$tmp/sysguides" && ./install.sh )
|
||||
local rc=$?
|
||||
git clone --quiet --depth 1 https://github.com/Antynea/grub-btrfs "$tmp/grub-btrfs"
|
||||
sed -i \
|
||||
-e 's|^#GRUB_BTRFS_SNAPSHOT_KERNEL_PARAMETERS=.*|GRUB_BTRFS_SNAPSHOT_KERNEL_PARAMETERS="rd.live.overlay.overlayfs=1"|' \
|
||||
-e 's|^#GRUB_BTRFS_GRUB_DIRNAME=.*|GRUB_BTRFS_GRUB_DIRNAME="/boot/grub2"|' \
|
||||
-e 's|^#GRUB_BTRFS_MKCONFIG=.*|GRUB_BTRFS_MKCONFIG=/usr/bin/grub2-mkconfig|' \
|
||||
-e 's|^#GRUB_BTRFS_SCRIPT_CHECK=.*|GRUB_BTRFS_SCRIPT_CHECK=grub2-script-check|' \
|
||||
"$tmp/grub-btrfs/config"
|
||||
sudo make -C "$tmp/grub-btrfs" install
|
||||
sudo systemctl enable --now grub-btrfsd.service
|
||||
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
|
||||
rm -rf "$tmp"
|
||||
if (( rc == 0 )); then
|
||||
ok "Snapper + grub-btrfs installed. Every dnf transaction now gets a pre/post snapshot."
|
||||
info "Try it: snapper ls | snapper status 1..2 | sudo snapper undochange 1..2"
|
||||
fi
|
||||
return $rc
|
||||
|
||||
info "Installing DNF5 snapper integration (vendored scripts)..."
|
||||
sudo install -m 755 "$vendor"/scripts/* /usr/local/bin/
|
||||
sudo restorecon -v /usr/local/bin/snapper-*.sh 2>/dev/null || true
|
||||
sudo mkdir -p /etc/dnf/libdnf5-plugins/actions.d/
|
||||
sudo install -m 644 "$vendor/config/snapper.actions" /etc/dnf/libdnf5-plugins/actions.d/
|
||||
|
||||
info "Enabling snapper timers..."
|
||||
sudo systemctl enable --now snapper-timeline.timer snapper-cleanup.timer
|
||||
|
||||
ok "Snapper + grub-btrfs installed. Every dnf transaction now gets a pre/post snapshot."
|
||||
info "Try it: snapper ls | snapper status 1..2 | sudo snapper undochange 1..2"
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ Options:
|
||||
--de=NAME gnome | plasma | xfce | none
|
||||
--twm=NAME chadwm | hyprland | mango | niri | none
|
||||
--selinux=X keep | disabled (asked, default keep)
|
||||
--snapper=X yes | no (asked when / is btrfs, default yes)
|
||||
--snapper=X yes | no (automatic on btrfs; use 'no' to opt out)
|
||||
--dotfiles=X yes | no (clone personal dotfiles, default no)
|
||||
-y, --yes never prompt (requires --de and --twm)
|
||||
-d, --debug pause after each section instead of aborting on error
|
||||
@@ -244,9 +244,8 @@ if [[ -z "$SELINUX_OPT" ]]; then
|
||||
[[ "$choice" == "yes" ]] && SELINUX_DISABLED=true
|
||||
printf "\n"
|
||||
if findmnt -n -o FSTYPE / 2>/dev/null | grep -q btrfs; then
|
||||
ok "Root filesystem is btrfs — snapshot support is available."
|
||||
choice=$(ask_yes_no "Set up snapper + grub-btrfs snapshots and zstd compression?" "yes")
|
||||
[[ "$choice" == "yes" ]] && BTRFS_SNAPPER=true
|
||||
ok "Root filesystem is btrfs — snapshots + zstd compression will be set up."
|
||||
BTRFS_SNAPPER=true
|
||||
else
|
||||
info "Root filesystem is not btrfs; skipping snapshot setup."
|
||||
fi
|
||||
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
# Vendored: SysGuides snapper scripts for Fedora
|
||||
|
||||
Source: https://github.com/SysGuides/sysguides-snapper-fedora (MIT, Madhu Desai / SysGuides)
|
||||
|
||||
Vendored (2026-10-27, upstream commit 0a28eb4) so setup does not depend on an
|
||||
external repo at runtime. `lib/common.sh:setup_snapper` installs:
|
||||
|
||||
- scripts/snapper-*.sh -> /usr/local/bin/ (DNF5 pre/post snapshot actions)
|
||||
- config/snapper.actions -> /etc/dnf/libdnf5-plugins/actions.d/
|
||||
|
||||
To update: re-fetch from upstream, diff, commit here.
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
# snapper.actions
|
||||
# ----------------
|
||||
# Snapper integration with libdnf5 (DNF5 transactions)
|
||||
#
|
||||
# This file defines hooks that:
|
||||
# - Create PRE snapshots before transactions
|
||||
# - Capture GUI package details
|
||||
# - Create POST snapshots after transactions
|
||||
# - Apply WAL checkpoint fix for consistency
|
||||
#
|
||||
# Project: sysguides-snapper-fedora
|
||||
# Author: Madhu Desai (SysGuides)
|
||||
# Website: https://sysguides.com
|
||||
# GitHub: https://github.com/SysGuides/sysguides-snapper-fedora
|
||||
|
||||
# Create PRE snapshot
|
||||
pre_transaction::::/usr/local/bin/snapper-pre.sh ${pid}
|
||||
|
||||
# Capture GUI package info (incoming packages)
|
||||
pre_transaction:*:in::/usr/local/bin/snapper-gui-pkg.sh ${pid} ${pkg.action} ${pkg.name}
|
||||
|
||||
# Capture GUI package info (outgoing packages)
|
||||
pre_transaction:*:out::/usr/local/bin/snapper-gui-pkg.sh ${pid} ${pkg.action} ${pkg.name}
|
||||
|
||||
# Create POST snapshot (with WAL fix)
|
||||
post_transaction::::/usr/local/bin/snapper-post.sh ${pid}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# snapper-desc.sh
|
||||
# ----------------
|
||||
# Determines a human-readable description for the transaction.
|
||||
#
|
||||
# This script inspects the process that triggered the DNF5 transaction.
|
||||
#
|
||||
# Behavior:
|
||||
# - If the transaction originates from GUI tools (PackageKit / dnf5daemon),
|
||||
# it returns "GUI"
|
||||
# - Otherwise, it returns the actual command (e.g., "dnf install vim")
|
||||
#
|
||||
# This description is later used by Snapper for snapshot naming.
|
||||
#
|
||||
# Project: sysguides-snapper-fedora
|
||||
# Author: Madhu Desai (SysGuides)
|
||||
# Website: https://sysguides.com
|
||||
# GitHub: https://github.com/SysGuides/sysguides-snapper-fedora
|
||||
|
||||
PID="$1"
|
||||
|
||||
# Get the full command of the originating process
|
||||
cmd=$(ps -o command --no-headers -p "$PID" 2>/dev/null || echo "Unknown Task")
|
||||
|
||||
case "$cmd" in
|
||||
*/dnf5daemon* | */packagekitd*)
|
||||
# GUI-based transaction
|
||||
echo "GUI"
|
||||
;;
|
||||
*)
|
||||
# CLI or unknown source
|
||||
echo "$cmd"
|
||||
;;
|
||||
esac
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# snapper-gui-pkg.sh
|
||||
# ------------------
|
||||
# Captures the first package involved in a GUI-based transaction.
|
||||
#
|
||||
# This script:
|
||||
# - Runs during DNF5 transaction events (pre_transaction hooks)
|
||||
# - Detects if the transaction originated from a GUI
|
||||
# - Records the first package name and action (install/remove)
|
||||
#
|
||||
# This allows snapshots to have meaningful descriptions like:
|
||||
# "GUI install firefox"
|
||||
#
|
||||
# Only the first package is recorded to keep descriptions concise.
|
||||
#
|
||||
# Project: sysguides-snapper-fedora
|
||||
# Author: Madhu Desai (SysGuides)
|
||||
# Website: https://sysguides.com
|
||||
# GitHub: https://github.com/SysGuides/sysguides-snapper-fedora
|
||||
|
||||
PID="$1"
|
||||
ACTION="$2"
|
||||
NAME="$3"
|
||||
|
||||
STATE_DIR="/run/snapper-actions"
|
||||
DESC_FILE="$STATE_DIR/snapper_desc_${PID}"
|
||||
PKG_FILE="$STATE_DIR/snapper_gui_${PID}"
|
||||
|
||||
# Read previously stored description (set in PRE phase)
|
||||
desc=$(cat "$DESC_FILE" 2>/dev/null || echo "")
|
||||
|
||||
# Only proceed if this is a GUI transaction
|
||||
[[ "$desc" != "GUI" ]] && exit 0
|
||||
|
||||
# Only capture the first package to avoid overwriting
|
||||
[[ -f "$PKG_FILE" ]] && exit 0
|
||||
|
||||
# Determine action type and store description
|
||||
case "$ACTION" in
|
||||
I|U|D|R)
|
||||
# Install / upgrade / downgrade / reinstall
|
||||
echo "GUI install ${NAME}" > "$PKG_FILE"
|
||||
;;
|
||||
E|O)
|
||||
# Erase / obsolete
|
||||
echo "GUI remove ${NAME}" > "$PKG_FILE"
|
||||
;;
|
||||
esac
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# snapper-post.sh
|
||||
# ----------------
|
||||
# Creates a POST snapshot after a DNF5 transaction completes.
|
||||
#
|
||||
# This script:
|
||||
# - Retrieves stored PRE snapshot and description
|
||||
# - Improves description using GUI package info (if available)
|
||||
# - Applies SQLite WAL checkpoint fix for libdnf5
|
||||
# - Creates the POST snapshot linked to the PRE snapshot
|
||||
# - Cleans up temporary state files
|
||||
#
|
||||
# The WAL checkpoint is necessary on Fedora 44+ to reduce
|
||||
# inconsistencies between filesystem state and RPM database.
|
||||
#
|
||||
# Project: sysguides-snapper-fedora
|
||||
# Author: Madhu Desai (SysGuides)
|
||||
# Website: https://sysguides.com
|
||||
# GitHub: https://github.com/SysGuides/sysguides-snapper-fedora
|
||||
|
||||
PID="$1"
|
||||
STATE_DIR="/run/snapper-actions"
|
||||
|
||||
DESC_FILE="$STATE_DIR/snapper_desc_${PID}"
|
||||
PRE_FILE="$STATE_DIR/snapper_pre_${PID}"
|
||||
GUI_FILE="$STATE_DIR/snapper_gui_${PID}"
|
||||
|
||||
# Load stored state (if available)
|
||||
desc=$(cat "$DESC_FILE" 2>/dev/null || echo "")
|
||||
pre=$(cat "$PRE_FILE" 2>/dev/null || echo "")
|
||||
gui_pkg=$(cat "$GUI_FILE" 2>/dev/null || echo "")
|
||||
|
||||
# If no PRE snapshot exists, nothing to do
|
||||
[[ -z "$pre" ]] && exit 0
|
||||
|
||||
# If GUI package info exists, improve snapshot description
|
||||
if [[ -n "$gui_pkg" ]]; then
|
||||
desc="$gui_pkg"
|
||||
snapper -c root modify -d "$desc" "$pre" || true
|
||||
fi
|
||||
|
||||
# Apply WAL checkpoint (best-effort; do not fail if it doesn't succeed)
|
||||
/usr/local/bin/snapper-wal-checkpoint.sh || true
|
||||
|
||||
# Create POST snapshot linked to PRE snapshot
|
||||
snapper -c root create -c number -t post \
|
||||
--pre-number "$pre" -d "$desc"
|
||||
|
||||
# Clean up temporary state files
|
||||
rm -f "$DESC_FILE" "$PRE_FILE" "$GUI_FILE"
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# snapper-pre.sh
|
||||
# ----------------
|
||||
# Creates a PRE snapshot before a DNF5 transaction begins.
|
||||
#
|
||||
# This script:
|
||||
# - Generates a description using snapper-desc.sh
|
||||
# - Stores the description and snapshot number in /run for later use
|
||||
#
|
||||
# The stored data is used by the POST script to:
|
||||
# - Pair snapshots correctly
|
||||
# - Improve snapshot descriptions (especially for GUI installs)
|
||||
#
|
||||
# Project: sysguides-snapper-fedora
|
||||
# Author: Madhu Desai (SysGuides)
|
||||
# Website: https://sysguides.com
|
||||
# GitHub: https://github.com/SysGuides/sysguides-snapper-fedora
|
||||
|
||||
PID="$1"
|
||||
STATE_DIR="/run/snapper-actions"
|
||||
|
||||
# Ensure runtime directory exists with secure permissions
|
||||
mkdir -p "$STATE_DIR"
|
||||
chmod 700 "$STATE_DIR"
|
||||
|
||||
# On a fresh setup, DNF5 may fail with "filesystem error: cannot copy: packages.toml"
|
||||
# if this directory is missing, leaving an orphaned PRE snapshot with no POST.
|
||||
# This block ensures the directory exists before the transaction proceeds.
|
||||
if [[ ! -d /usr/lib/sysimage/libdnf5 ]]; then
|
||||
mkdir -p /usr/lib/sysimage/libdnf5
|
||||
restorecon -q /usr/lib/sysimage/libdnf5 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Get transaction description (GUI or CLI command)
|
||||
desc=$(/usr/local/bin/snapper-desc.sh "$PID")
|
||||
|
||||
# Store description for use in POST phase
|
||||
echo "$desc" > "$STATE_DIR/snapper_desc_${PID}"
|
||||
|
||||
# Create PRE snapshot and store snapshot number
|
||||
pre=$(snapper -c root create -c number -t pre -p -d "$desc") || exit 1
|
||||
|
||||
echo "$pre" > "$STATE_DIR/snapper_pre_${PID}"
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# snapper-wal-checkpoint.sh
|
||||
# --------------------------
|
||||
# Forces an SQLite WAL checkpoint for the RPM database used by libdnf5.
|
||||
#
|
||||
# Fedora 44 introduced DNF5 (libdnf5), which uses SQLite with WAL mode.
|
||||
# This can lead to inconsistencies during snapper undochange, where:
|
||||
# - Files are reverted correctly
|
||||
# - But the RPM database still reports packages as installed
|
||||
#
|
||||
# This script attempts to flush WAL data to the main database file
|
||||
# before the POST snapshot is created.
|
||||
#
|
||||
# The operation is best-effort:
|
||||
# - Retries multiple times if the database is busy
|
||||
# - Fails silently if unable to complete
|
||||
#
|
||||
# Project: sysguides-snapper-fedora
|
||||
# Author: Madhu Desai (SysGuides)
|
||||
# Website: https://sysguides.com
|
||||
# GitHub: https://github.com/SysGuides/sysguides-snapper-fedora
|
||||
|
||||
python3 - <<'EOF'
|
||||
import sqlite3
|
||||
import time
|
||||
import sys
|
||||
|
||||
DB = "/usr/lib/sysimage/rpm/rpmdb.sqlite"
|
||||
|
||||
# Try up to 10 times to checkpoint WAL (single process, no respawn overhead)
|
||||
for i in range(10):
|
||||
try:
|
||||
conn = sqlite3.connect(DB, timeout=3)
|
||||
conn.execute("PRAGMA busy_timeout=3000")
|
||||
result = conn.execute("PRAGMA wal_checkpoint(TRUNCATE)").fetchone()
|
||||
conn.close()
|
||||
# result[1] = number of remaining frames in WAL (0 = fully flushed)
|
||||
if result and result[1] == 0:
|
||||
sys.exit(0)
|
||||
except sqlite3.OperationalError:
|
||||
pass
|
||||
time.sleep(0.5)
|
||||
|
||||
# Best-effort fallback
|
||||
sys.exit(1)
|
||||
EOF
|
||||
Reference in New Issue
Block a user