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:
Pathfinder
2026-10-27 12:37:18 +01:00
parent 47991727ff
commit 3776dcb5a4
9 changed files with 324 additions and 16 deletions
+35
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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