Normalize Snapper snapshot setup

This commit is contained in:
Ryan Hughes
2026-06-21 02:25:42 -04:00
parent 34ddf5d54e
commit c048b2c745
9 changed files with 203 additions and 1 deletions
+1
View File
@@ -78,6 +78,7 @@ run_logged "$OMARCHY_INSTALL/config/increase-lockout-limit.sh"
run_logged "$OMARCHY_INSTALL/config/lockscreen-pam.sh"
run_logged "$OMARCHY_INSTALL/config/fix-powerprofilesctl-shebang.sh"
run_logged "$OMARCHY_INSTALL/config/docker.sh"
run_logged "$OMARCHY_INSTALL/config/snapper.sh"
run_logged "$OMARCHY_INSTALL/config/enable-services.sh"
run_logged "$OMARCHY_INSTALL/config/firewall.sh"
+13
View File
@@ -456,6 +456,18 @@ normalize_limine_config() {
fi
}
configure_snapper_policy() {
local snapper_config_script=/usr/share/omarchy/install/config/snapper.sh
if ! as_root test -f "$snapper_config_script"; then
warn "$snapper_config_script is unavailable; Snapper snapshot retention was not normalized."
return 0
fi
log "Configuring Omarchy snapshot retention"
as_root env OMARCHY_PATH=/usr/share/omarchy bash -euo pipefail "$snapper_config_script"
}
remove_conflicting_legacy_packages() {
# Remove legacy packages that are known to conflict with Omarchy 4 defaults
# before the main package transaction. Broader retired-package cleanup runs
@@ -2226,6 +2238,7 @@ remove_legacy_limine_configs
remove_conflicting_legacy_packages
install_omarchy_4_packages
normalize_limine_config
configure_snapper_policy
migrate_1password_beta_package
cleanup_legacy_user_paths
apply_system_transition
+2
View File
@@ -2,6 +2,8 @@
SUBVOLUME="/"
FSTYPE="btrfs"
NUMBER_CLEANUP="yes"
NUMBER_MIN_AGE="0"
NUMBER_LIMIT="5"
NUMBER_LIMIT_IMPORTANT="5"
+1
View File
@@ -3,5 +3,6 @@ run_logged "$OMARCHY_INSTALL/config/increase-lockout-limit.sh"
run_logged "$OMARCHY_INSTALL/config/lockscreen-pam.sh"
run_logged "$OMARCHY_INSTALL/config/fix-powerprofilesctl-shebang.sh"
run_logged "$OMARCHY_INSTALL/config/docker.sh"
run_logged "$OMARCHY_INSTALL/config/snapper.sh"
run_logged "$OMARCHY_INSTALL/config/enable-services.sh"
run_logged "$OMARCHY_INSTALL/config/firewall.sh"
+24
View File
@@ -0,0 +1,24 @@
SNAPPER_CONFIG_PATH="${OMARCHY_SNAPPER_CONFIG_PATH:-/etc/snapper/configs/root}"
SNAPPER_CONF_PATH="${OMARCHY_SNAPPER_CONF_PATH:-/etc/conf.d/snapper}"
template="${OMARCHY_SNAPPER_TEMPLATE:-${OMARCHY_PATH:-/usr/share/omarchy}/default/snapper/root}"
echo "Configuring Omarchy Snapper snapshot retention"
if [[ ! -f $SNAPPER_CONFIG_PATH ]]; then
mkdir -p "$(dirname "$SNAPPER_CONFIG_PATH")"
if [[ ${OMARCHY_SNAPPER_CONFIGURE_TEST:-0} == "1" ]]; then
: >"$SNAPPER_CONFIG_PATH"
else
snapper --no-dbus -c root create-config / >/dev/null 2>&1 || snapper -c root create-config / >/dev/null
fi
fi
install -m 0644 "$template" "$SNAPPER_CONFIG_PATH"
mkdir -p "$(dirname "$SNAPPER_CONF_PATH")"
printf '%s\n' 'SNAPPER_CONFIGS="root"' >"$SNAPPER_CONF_PATH"
chmod 0644 "$SNAPPER_CONF_PATH"
systemctl disable --now snapper-timeline.timer >/dev/null 2>&1 || true
systemctl enable --now snapper-cleanup.timer limine-snapper-sync.service >/dev/null 2>&1 || true
+39
View File
@@ -0,0 +1,39 @@
echo "Normalize Snapper snapshot services"
OMARCHY_PATH="${OMARCHY_PATH:-/usr/share/omarchy}"
snapper_config_script=/usr/share/omarchy/install/config/snapper.sh
if [[ ! -f $snapper_config_script ]]; then
snapper_config_script="$OMARCHY_PATH/install/config/snapper.sh"
fi
as_root() {
if (( EUID == 0 )); then
"$@"
else
sudo "$@"
fi
}
unit_enabled() {
systemctl is-enabled --quiet "$1" >/dev/null 2>&1
}
unit_active() {
systemctl is-active --quiet "$1" >/dev/null 2>&1
}
needs_repair=0
[[ -f /etc/snapper/configs/root ]] || needs_repair=1
if ! unit_enabled snapper-cleanup.timer || ! unit_active snapper-cleanup.timer; then
needs_repair=1
fi
if ! unit_enabled limine-snapper-sync.service || ! unit_active limine-snapper-sync.service; then
needs_repair=1
fi
(( needs_repair )) || exit 0
as_root env OMARCHY_PATH="$OMARCHY_PATH" bash -euo pipefail "$snapper_config_script"
+7 -1
View File
@@ -94,7 +94,12 @@ import sys
from pathlib import Path
root = Path(os.environ["ROOT"])
pkgs_root = root.parent / "omarchy-pkgs/pkgbuilds"
pkgs_candidates = [
root.parent / "omarchy-pkgs/pkgbuilds",
root.parent / "omarchy/omarchy-pkgs/pkgbuilds",
root.parent.parent / "omarchy-pkgs/pkgbuilds",
]
pkgs_root = next((path for path in pkgs_candidates if path.exists()), pkgs_candidates[0])
settings_pkgbuild_path = pkgs_root / "omarchy-settings/PKGBUILD"
omarchy_pkgbuild_path = pkgs_root / "omarchy/PKGBUILD"
if not settings_pkgbuild_path.exists():
@@ -118,6 +123,7 @@ package_defaults = [
("default/systemd/user/omarchy-update-user-notify.service", "/usr/lib/systemd/user/omarchy-update-user-notify.service", "systemd/user/omarchy-update-user-notify.service"),
("default/systemd/user/omarchy-update-user-notify.path", "/usr/lib/systemd/user/omarchy-update-user-notify.path", "systemd/user/omarchy-update-user-notify.path"),
("default/fonts/omarchy/omarchy.ttf", "/usr/share/fonts/omarchy/omarchy.ttf", "omarchy.ttf"),
("default/snapper/root", "/etc/snapper/config-templates/omarchy", "snapper/root"),
]
for source, destination, legacy in package_defaults:
+111
View File
@@ -0,0 +1,111 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
template="$ROOT/default/snapper/root"
grep -Fx 'NUMBER_CLEANUP="yes"' "$template" >/dev/null
grep -Fx 'NUMBER_LIMIT="5"' "$template" >/dev/null
grep -Fx 'TIMELINE_CREATE="no"' "$template" >/dev/null
! grep -Eq '^TIMELINE_(CLEANUP|LIMIT_)' "$template" || fail "Snapper template keeps timeline cleanup details out of the default config"
pass "Snapper template keeps update snapshots and disables timeline snapshots"
test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT
fake_bin="$test_tmp/bin"
mkdir -p "$fake_bin"
cat >"$fake_bin/snapper" <<'STUB'
#!/bin/bash
printf 'snapper %s\n' "$*" >>"$TEST_LOG"
STUB
chmod +x "$fake_bin/snapper"
cat >"$fake_bin/systemctl" <<'STUB'
#!/bin/bash
printf 'systemctl %s\n' "$*" >>"$TEST_LOG"
STUB
chmod +x "$fake_bin/systemctl"
TEST_LOG="$test_tmp/calls.log" \
PATH="$fake_bin:$PATH" \
OMARCHY_SNAPPER_CONFIGURE_TEST=1 \
OMARCHY_PATH="$ROOT" \
OMARCHY_SNAPPER_CONFIG_PATH="$test_tmp/etc/snapper/configs/root" \
OMARCHY_SNAPPER_CONF_PATH="$test_tmp/etc/conf.d/snapper" \
bash -euo pipefail "$ROOT/install/config/snapper.sh" >/dev/null
cmp -s "$template" "$test_tmp/etc/snapper/configs/root" || fail "snapshot configure installs the Omarchy Snapper template"
grep -Fx 'SNAPPER_CONFIGS="root"' "$test_tmp/etc/conf.d/snapper" >/dev/null || fail "snapshot configure writes /etc/conf.d/snapper"
grep -Fx 'systemctl disable --now snapper-timeline.timer' "$test_tmp/calls.log" >/dev/null || fail "snapshot configure disables timeline snapshots"
grep -Fx 'systemctl enable --now snapper-cleanup.timer limine-snapper-sync.service' "$test_tmp/calls.log" >/dev/null || fail "snapshot configure enables cleanup and Limine snapshot sync"
pass "snapshot configure normalizes Snapper policy and services"
setup_system="$ROOT/bin/omarchy-setup-system"
grep -F 'config/snapper.sh' "$setup_system" >/dev/null
pass "system setup normalizes Snapper during fresh installs"
migration=$(grep -rl 'Normalize Snapper snapshot services' "$ROOT/migrations" | head -n 1 || true)
[[ -n $migration ]] || fail "Snapper service migration exists"
grep -F 'unit_active snapper-cleanup.timer' "$migration" >/dev/null
grep -F 'unit_active limine-snapper-sync.service' "$migration" >/dev/null
grep -F 'sudo "$@"' "$migration" >/dev/null
grep -F 'as_root env OMARCHY_PATH="$OMARCHY_PATH" bash -euo pipefail "$snapper_config_script"' "$migration" >/dev/null
! grep -F 'NUMBER_LIMIT="5"' "$migration" >/dev/null || fail "Snapper service migration does not overwrite working custom retention"
pass "Snapper service migration only repairs broken services idempotently"
find_omarchy_pks_root() {
local candidate
for candidate in \
"$ROOT/../omarchy-pkgs/pkgbuilds" \
"$ROOT/../omarchy/omarchy-pkgs/pkgbuilds" \
"$ROOT/../../omarchy-pkgs/pkgbuilds"; do
if [[ -d $candidate ]]; then
cd "$candidate" && pwd
return 0
fi
done
return 1
}
pkgs_root=$(find_omarchy_pks_root) || fail "omarchy-pkgs checkout is available for packaging coverage"
settings_pkgbuild="$pkgs_root/omarchy-settings-dev/PKGBUILD"
omarchy_pkgbuild="$pkgs_root/omarchy-dev/PKGBUILD"
grep -F 'cp -a default/. "$pkgdir/usr/share/omarchy/default/"' "$settings_pkgbuild" >/dev/null || fail "omarchy-settings package bundles default/"
grep -F 'install -Dm644 default/snapper/root \' "$settings_pkgbuild" >/dev/null || fail "omarchy-settings package installs Snapper template source"
grep -F '"$pkgdir/etc/snapper/config-templates/omarchy"' "$settings_pkgbuild" >/dev/null || fail "omarchy-settings package installs Snapper template destination"
grep -F "'snapper'" "$omarchy_pkgbuild" >/dev/null || fail "omarchy package depends on snapper"
grep -F "'limine-snapper-sync'" "$omarchy_pkgbuild" >/dev/null || fail "omarchy package depends on limine-snapper-sync"
grep -F 'cp -a install "$pkgdir/usr/share/omarchy/"' "$omarchy_pkgbuild" >/dev/null || fail "omarchy package bundles install scripts"
grep -F 'cp -a migrations "$pkgdir/usr/share/omarchy/"' "$omarchy_pkgbuild" >/dev/null || fail "omarchy package bundles migrations"
pass "omarchy-pkgs packages Snapper template, setup, and migration coverage"
find_omarchy_iso_root() {
local candidate
for candidate in \
"$ROOT/../omarchy-iso" \
"$ROOT/../omarchy/omarchy-iso" \
"$ROOT/../../omarchy-iso"; do
if [[ -d $candidate ]]; then
cd "$candidate" && pwd
return 0
fi
done
return 1
}
iso_root=$(find_omarchy_iso_root) || fail "omarchy-iso checkout is available for installer coverage"
configurator="$iso_root/configs/airootfs/root/configurator"
phases="$iso_root/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py"
manifest="$iso_root/manifests/fresh-4-semantic.json"
! grep -F 'snapshot_config' "$configurator" >/dev/null || fail "ISO does not ask archinstall to create Snapper timeline config"
! grep -F '_configure_snapper_root' "$phases" >/dev/null || fail "ISO does not duplicate Omarchy Snapper setup"
grep -F 'run_system_finalizer' "$phases" >/dev/null || fail "ISO runs packaged system setup"
grep -F '/etc/systemd/system/timers.target.wants/snapper-cleanup.timer' "$manifest" >/dev/null || fail "fresh ISO manifest has snapper-cleanup timer enabled"
! grep -F '/etc/systemd/system/timers.target.wants/snapper-timeline.timer' "$manifest" >/dev/null || fail "fresh ISO manifest does not enable snapper timeline timer"
pass "omarchy-iso delegates Snapper setup to packaged system setup"
+5
View File
@@ -25,6 +25,11 @@ grep -F 'skip-first-run-update-notification' "$first_run_wifi" >/dev/null
grep -F '(( skip_update_notification )) && return 0' "$first_run_wifi" >/dev/null
pass "Omarchy 4 upgrade suppresses the fresh-install update toast"
grep -F 'configure_snapper_policy' "$upgrade_to_4" >/dev/null
grep -F '/usr/share/omarchy/install/config/snapper.sh' "$upgrade_to_4" >/dev/null
grep -F 'bash -euo pipefail "$snapper_config_script"' "$upgrade_to_4" >/dev/null
pass "Omarchy 4 upgrade normalizes Snapper retention"
grep -F 'OMARCHY_UPGRADE_TO_4_LIVE=1' "$upgrade_to_4" >/dev/null
grep -F 'systemd-networkd.service' "$upgrade_to_4" >/dev/null
grep -F 'systemd-networkd.socket' "$upgrade_to_4" >/dev/null