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:
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