From 239f5e37dfc8fcc2dbce55b35a963b85318c3c99 Mon Sep 17 00:00:00 2001 From: oxmc7769 Date: Thu, 5 Mar 2026 03:50:33 -0800 Subject: [PATCH] Immutable/snapshot system? --- stage1/01-sys-tweaks/files/fstab-amd64 | 11 ++-- stage1/01-sys-tweaks/files/fstab-rpi | 9 ++-- stage1/01-sys-tweaks/files/system-readonly | 56 +++++++++++--------- stage2/01-sys-tweaks/00-packages | 2 + stage2/01-sys-tweaks/02-run.sh | 17 ++++++ stage2/01-sys-tweaks/files/apt-snapper-hook | 2 + stage2/01-sys-tweaks/files/snapper-root.conf | 25 +++++++++ stage3/02-setup-system/00-packages-amd64 | 1 + stage3/02-setup-system/00-run.sh | 6 ++- 9 files changed, 95 insertions(+), 34 deletions(-) create mode 100644 stage2/01-sys-tweaks/02-run.sh create mode 100644 stage2/01-sys-tweaks/files/apt-snapper-hook create mode 100644 stage2/01-sys-tweaks/files/snapper-root.conf create mode 100644 stage3/02-setup-system/00-packages-amd64 diff --git a/stage1/01-sys-tweaks/files/fstab-amd64 b/stage1/01-sys-tweaks/files/fstab-amd64 index 19b08f7..993b270 100644 --- a/stage1/01-sys-tweaks/files/fstab-amd64 +++ b/stage1/01-sys-tweaks/files/fstab-amd64 @@ -1,4 +1,7 @@ -proc /proc proc defaults 0 0 -BOOTDEV /boot vfat defaults 0 2 -EFIDEV /boot/efi vfat defaults 0 2 -ROOTDEV / ext4 defaults,noatime 0 1 +proc /proc proc defaults 0 0 +BOOTDEV /boot vfat defaults 0 2 +EFIDEV /boot/efi vfat defaults 0 2 +ROOTDEV / btrfs defaults,noatime,compress=zstd,subvol=@ 0 1 +ROOTDEV /home btrfs defaults,noatime,compress=zstd,subvol=@home 0 2 +ROOTDEV /var btrfs defaults,noatime,compress=zstd,subvol=@var 0 2 +ROOTDEV /.snapshots btrfs defaults,noatime,compress=zstd,subvol=@snapshots 0 2 diff --git a/stage1/01-sys-tweaks/files/fstab-rpi b/stage1/01-sys-tweaks/files/fstab-rpi index 96b926d..0b5da98 100644 --- a/stage1/01-sys-tweaks/files/fstab-rpi +++ b/stage1/01-sys-tweaks/files/fstab-rpi @@ -1,3 +1,6 @@ -proc /proc proc defaults 0 0 -BOOTDEV /boot/firmware vfat defaults 0 2 -ROOTDEV / ext4 defaults,noatime 0 1 \ No newline at end of file +proc /proc proc defaults 0 0 +BOOTDEV /boot/firmware vfat defaults 0 2 +ROOTDEV / btrfs defaults,noatime,compress=zstd,subvol=@ 0 1 +ROOTDEV /home btrfs defaults,noatime,compress=zstd,subvol=@home 0 2 +ROOTDEV /var btrfs defaults,noatime,compress=zstd,subvol=@var 0 2 +ROOTDEV /.snapshots btrfs defaults,noatime,compress=zstd,subvol=@snapshots 0 2 \ No newline at end of file diff --git a/stage1/01-sys-tweaks/files/system-readonly b/stage1/01-sys-tweaks/files/system-readonly index d5d2fac..2916359 100644 --- a/stage1/01-sys-tweaks/files/system-readonly +++ b/stage1/01-sys-tweaks/files/system-readonly @@ -1,30 +1,36 @@ #!/bin/bash -FSTAB_FILE="/etc/fstab" +set -e -if [ "$1" == "enable" ]; then - echo "Enabling read-only mode..." +SUBVOL="/" - # Modify fstab to set root filesystem as read-only - sudo sed -i 's|\(PARTUUID=[^ ]* / .* ext4 \)defaults,noatime|\1ro,noatime|' "$FSTAB_FILE" - - # Remount root as read-only - sudo mount -o remount,ro / - - echo "System is now read-only. Reboot for changes to persist." - -elif [ "$1" == "disable" ]; then - echo "Disabling read-only mode..." - - # Modify fstab to set root filesystem as writable - sudo sed -i 's|\(PARTUUID=[^ ]* / .* ext4 \)ro,noatime|\1defaults,noatime|' "$FSTAB_FILE" - - # Remount root as writable - sudo mount -o remount,rw / - - echo "System is now writable. Reboot for changes to persist." - -else - echo "Usage: system-readonly enable|disable" +usage() { + echo "Usage: system-readonly enable|disable|status" exit 1 -fi +} + +case "$1" in + enable) + echo "Enabling read-only mode..." + btrfs property set -ts "${SUBVOL}" ro true + mount -o remount,ro "${SUBVOL}" + echo "Root is read-only. Full effect on next reboot." + ;; + disable) + echo "Disabling read-only mode..." + btrfs property set -ts "${SUBVOL}" ro false + mount -o remount,rw "${SUBVOL}" + echo "Root is writable." + ;; + status) + RO=$(btrfs property get -ts "${SUBVOL}" ro 2>/dev/null | awk -F= '{print $2}') + if [ "${RO}" = "true" ]; then + echo "Read-only: enabled" + else + echo "Read-only: disabled" + fi + ;; + *) + usage + ;; +esac diff --git a/stage2/01-sys-tweaks/00-packages b/stage2/01-sys-tweaks/00-packages index 16e0226..fcfc816 100644 --- a/stage2/01-sys-tweaks/00-packages +++ b/stage2/01-sys-tweaks/00-packages @@ -1,6 +1,8 @@ ssh less fbset sudo psmisc strace ed ncdu console-setup keyboard-configuration debconf-utils parted build-essential manpages-dev bash-completion gdb pkg-config +btrfs-progs +snapper python-is-python3 v4l-utils avahi-daemon diff --git a/stage2/01-sys-tweaks/02-run.sh b/stage2/01-sys-tweaks/02-run.sh new file mode 100644 index 0000000..988fd0f --- /dev/null +++ b/stage2/01-sys-tweaks/02-run.sh @@ -0,0 +1,17 @@ +#!/bin/bash -e + +# Install snapper config for root subvolume +mkdir -p "${ROOTFS_DIR}/etc/snapper/configs" +install -m 644 files/snapper-root.conf "${ROOTFS_DIR}/etc/snapper/configs/root" + +# Register root config with snapper +echo 'SNAPPER_CONFIGS="root"' > "${ROOTFS_DIR}/etc/default/snapper" + +# Install apt pre/post snapshot hook +install -m 644 files/apt-snapper-hook "${ROOTFS_DIR}/etc/apt/apt.conf.d/80-snapper" + +# Enable snapper timeline timer +on_chroot << EOF +systemctl enable snapper-timeline.timer +systemctl enable snapper-cleanup.timer +EOF diff --git a/stage2/01-sys-tweaks/files/apt-snapper-hook b/stage2/01-sys-tweaks/files/apt-snapper-hook new file mode 100644 index 0000000..96e0760 --- /dev/null +++ b/stage2/01-sys-tweaks/files/apt-snapper-hook @@ -0,0 +1,2 @@ +DPkg::Pre-Invoke { "if [ -x /usr/bin/snapper ] && snapper -c root list >/dev/null 2>&1; then snapper -c root create --type pre --cleanup-algorithm number --print-number --description 'apt'; fi"; }; +DPkg::Post-Invoke { "if [ -x /usr/bin/snapper ] && snapper -c root list >/dev/null 2>&1; then snapper -c root create --type post --cleanup-algorithm number --print-number --description 'apt'; fi"; }; diff --git a/stage2/01-sys-tweaks/files/snapper-root.conf b/stage2/01-sys-tweaks/files/snapper-root.conf new file mode 100644 index 0000000..11ccf48 --- /dev/null +++ b/stage2/01-sys-tweaks/files/snapper-root.conf @@ -0,0 +1,25 @@ +SUBVOLUME="/" +FSTYPE="btrfs" + +# Snapshot type +ALLOW_GROUPS="wheel sudo" +SYNC_ACL="yes" + +# Timeline snapshots (automatic periodic) +TIMELINE_CREATE="yes" +TIMELINE_CLEANUP="yes" +TIMELINE_MIN_AGE="1800" +TIMELINE_LIMIT_HOURLY="5" +TIMELINE_LIMIT_DAILY="7" +TIMELINE_LIMIT_WEEKLY="0" +TIMELINE_LIMIT_MONTHLY="0" +TIMELINE_LIMIT_YEARLY="0" + +# Number snapshots cleanup +NUMBER_CLEANUP="yes" +NUMBER_MIN_AGE="1800" +NUMBER_LIMIT="50" +NUMBER_LIMIT_IMPORTANT="10" + +# Exclude /var and /home from root snapshots (they have their own subvolumes) +BACKGROUND_COMPARISON="yes" diff --git a/stage3/02-setup-system/00-packages-amd64 b/stage3/02-setup-system/00-packages-amd64 new file mode 100644 index 0000000..64f141d --- /dev/null +++ b/stage3/02-setup-system/00-packages-amd64 @@ -0,0 +1 @@ +grub-btrfs diff --git a/stage3/02-setup-system/00-run.sh b/stage3/02-setup-system/00-run.sh index b890818..eb4c88e 100755 --- a/stage3/02-setup-system/00-run.sh +++ b/stage3/02-setup-system/00-run.sh @@ -14,8 +14,10 @@ sed -i 's/#WaylandEnable=false/WaylandEnable=false/' /etc/gdm3/daemon.conf touch /var/lib/gdm/run-initial-setup EOF -# polkit fixes -install -m 644 files/polkit-admin.conf "${ROOTFS_DIR}/etc/polkit-1/localauthority.conf.d/51-admin.conf" +# polkit fixes (seems to be rpi only thing) +if [[ "${ARCH}" == "arm64" || "${ARCH}" == "armhf" ]]; then + install -m 644 files/polkit-admin.conf "${ROOTFS_DIR}/etc/polkit-1/localauthority.conf.d/51-admin.conf" +fi # Include custom vendor file for gnome-inital-setup mkdir -p "${ROOTFS_DIR}/usr/share/gnome-initial-setup/"