From caa175b122b75c37598463ce32dfc3754e74f70b Mon Sep 17 00:00:00 2001 From: ed209 Date: Thu, 1 Jan 2026 08:50:27 -0500 Subject: [PATCH] feat: modulerize and improve impermanence --- hosts/sachiel/configuration.nix | 161 +------------------------ hosts/sachiel/disko-config.nix | 7 +- modules/nixos/default.nix | 1 + modules/nixos/impermanence/default.nix | 135 +++++++++++++++++++++ 4 files changed, 140 insertions(+), 164 deletions(-) create mode 100644 modules/nixos/impermanence/default.nix diff --git a/hosts/sachiel/configuration.nix b/hosts/sachiel/configuration.nix index 17d37a1..8c66f57 100644 --- a/hosts/sachiel/configuration.nix +++ b/hosts/sachiel/configuration.nix @@ -13,6 +13,7 @@ # If you want to use modules your own flake exports (from modules/nixos): outputs.nixosModules.boot outputs.nixosModules.agenix + outputs.nixosModules.impermanence # Or modules from other flakes (such as nixos-hardware): # inputs.hardware.nixosModules.common-cpu-amd @@ -120,166 +121,6 @@ # Load the TPM drivers in the initram to allow for decryption of agenix # secrets. boot.initrd.availableKernelModules = ["tpm_crb" "tpm_tis"]; - boot.initrd.systemd.services.rollback = { - description = "Rollback BTRFS root subvolume to a pristine state"; - wantedBy = ["initrd.target"]; - after = [ - "systemd-cryptsetup@enc.service" - "systemd-cryptsetup@cryptroot.service" - ]; - before = ["sysroot.mount"]; - unitConfig.DefaultDependencies = "no"; - serviceConfig = { - Type = "oneshot"; - UMask = "0077"; - }; - script = '' - set -euo pipefail - - echo "Starting impermanence rollback..." - - LUKS_DEVICE="" - for device in /dev/mapper/enc /dev/mapper/cryptroot; do - if [[ -b "$device" ]]; then - LUKS_DEVICE="$device" - break - fi - done - - if [[ -z "$LUKS_DEVICE" ]]; then - echo "Error: No LUKS device found (tried enc, cryptroot), skipping rollback" - exit 0 - fi - - echo "Found LUKS device: $LUKS_DEVICE" - - mkdir -p /mnt - - if ! mount -o subvol=/ "$LUKS_DEVICE" /mnt; then - echo "Error: Failed to mount root filesystem" - exit 1 - fi - - if [[ ! -d "/mnt/root-blank" ]]; then - echo "Error: /mnt/root-blank snapshot not found, skipping rollback" - umount /mnt || true - exit 0 - fi - - echo "Found root-blank snapshot, proceeding with rollback" - - if [[ -d "/mnt/root" ]]; then - echo "Removing nested subvolumes..." - btrfs subvolume list -o /mnt/root | cut -f9 -d' ' | while read -r subvolume; do - if [[ -n "$subvolume" ]]; then - echo "Deleting /$subvolume subvolume..." - btrfs subvolume delete "/mnt/$subvolume" || echo "Warning: Failed to delete $subvolume" - fi - done - - echo "Deleting /root subvolume..." - if ! btrfs subvolume delete /mnt/root; then - echo "Error: Failed to delete /root subvolume" - umount /mnt || true - exit 1 - fi - fi - - echo "Restoring blank /root subvolume..." - if ! btrfs subvolume snapshot /mnt/root-blank /mnt/root; then - echo "Error: Failed to create snapshot" - umount /mnt || true - exit 1 - fi - - echo "Rollback completed successfully" - umount /mnt || echo "Warning: Failed to unmount /mnt" - ''; - }; - # boot.initrd.postDeviceCommands = lib.mkAfter '' - # set -euo pipefail - # mkdir -p /mnt - # { - # echo "Starting impermanence rollback..." - # - # LUKS_DEVICE="" - # for device in /dev/mapper/enc /dev/mapper/cryptroot; do - # if [[ -b "$device" ]]; then - # LUKS_DEVICE="$device" - # break - # fi - # done - # - # if [[ -z "$LUKS_DEVICE" ]]; then - # echo "Error: No LUKS device found (tried enc, cryptroot), skipping rollback" - # exit 0 - # fi - # - # echo "Found LUKS device: $LUKS_DEVICE" - # if ! mount -o subvol=/ "$LUKS_DEVICE" /mnt; then - # echo "Error: Failed to mount root filesystem" - # exit 1 - # fi - # - # if [[ ! -d "/mnt/root-blank" ]]; then - # echo "Error: /mnt/root-blank snapshot not found, skipping rollback" - # umount /mnt || true - # exit 0 - # fi - # - # echo "Found root-blank snapshot, proceeding with rollback" - # - # if [[ -d "/mnt/root" ]]; then - # echo "Removing nested subvolumes..." - # btrfs subvolume list -o /mnt/root | cut -f9 -d' ' | while read -r subvolume; do - # if [[ -n "$subvolume" ]]; then - # echo "Deleting /$subvolume subvolume..." - # btrfs subvolume delete "/mnt/$subvolume" || echo "Warning: Failed to delete $subvolume" - # fi - # done - # - # echo "Deleting /root subvolume..." - # if ! btrfs subvolume delete /mnt/root; then - # echo "Error: Failed to delete /root subvolume" - # umount /mnt || true - # exit 1 - # fi - # fi - # - # echo "Restoring blank /root subvolume..." - # if ! btrfs subvolume snapshot /mnt/root-blank /mnt/root; then - # echo "Error: Failed to create snapshot" - # umount /mnt || true - # exit 1 - # fi - # - # echo "Rollback completed successfully" - # umount /mnt || echo "Warning: Failed to unmount /mnt" - # } > /mnt/rollback.log - # ''; - # mkdir /btrfs_tmp - # mount /dev/mapper/cryptroot /btrfs_tmp - # if [[ -e /btrfs_tmp/root ]]; then - # mkdir -p /btrfs_tmp/old_roots - # timestamp=$(date --date="@$(stat -c %Y /btrfs_tmp/root)" "+%Y-%m-%-d_%H:%M:%S") - # mv /btrfs_tmp/root "/btrfs_tmp/old_roots/$timestamp" - # fi - # - # delete_subvolume_recursively() { - # IFS=$'\n' - # for i in $(btrfs subvolume list -o "$1" | cut -f 9- -d ' '); do - # delete_subvolume_recursively "/btrfs_tmp/$i" - # done - # btrfs subvolume delete "$1" - # } - # - # for i in $(find /btrfs_tmp/old_roots/ -maxdepth 1 -mtime +30); do - # delete_subvolume_recursively "$i" - # done - # - # btrfs subvolume create /btrfs_tmp/root - # umount /btrfs_tmp - # ''; networking.hostName = "sachiel-vm"; # Define your hostname. # networking.wireless.enable = true; # Enables wireless support via wpa_supplicant. diff --git a/hosts/sachiel/disko-config.nix b/hosts/sachiel/disko-config.nix index 2b34cc8..1332ccd 100644 --- a/hosts/sachiel/disko-config.nix +++ b/hosts/sachiel/disko-config.nix @@ -31,11 +31,11 @@ extraArgs = ["-L" "nixos" "-f"]; subvolumes = { "/root" = { + mountpoint = "/"; mountOptions = [ "compress=zstd" "noatime" ]; - mountpoint = "/"; }; "/home" = { mountOptions = [ @@ -61,8 +61,8 @@ }; postCreateHook = '' mount -t btrfs /dev/disk/by-label/nixos /mnt - btrfs subvolume snapshot -r /mnt/ /mnt/root-blank - cp /home/nixos/age-tpm.txt /mnt/persistent/age-tpm.txt + btrfs subvolume snapshot -r /mnt/root /mnt/root-blank + # cp /home/nixos/age-tpm.txt /mnt/persistent/age-tpm.txt umount /mnt ''; }; @@ -75,5 +75,4 @@ }; fileSystems."/persistent".neededForBoot = true; - fileSystems."/var/log".neededForBoot = true; } diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 8d5ba3b..d5419b4 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -5,4 +5,5 @@ # my-module = import ./my-module.nix; agenix = import ./agenix/default.nix; boot = import ./boot/default.nix; + impermanence = import ./impermanence/default.nix; } diff --git a/modules/nixos/impermanence/default.nix b/modules/nixos/impermanence/default.nix new file mode 100644 index 0000000..85375d6 --- /dev/null +++ b/modules/nixos/impermanence/default.nix @@ -0,0 +1,135 @@ +{ + config, + lib, + ... +}: +with lib; let + cfg = config.system.impermanence; +in { + options.system.impermanence.enable = mkEnableOption "Enable impermanence"; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = config.fileSystems."/persistent".fsType or null == "btrfs"; + message = "Impermanence requires /persistent to be mounted as btrfs"; + } + { + assertion = builtins.any (fs: fs.mountPoint == "/" && fs.fsType == "btrfs") ( + builtins.attrValues config.fileSystems + ); + message = "Impermanence requires root filesystem to be btrfs"; + } + ]; + boot.initrd.systemd.enable = true; + + # This script does the actual wipe of the system + # So if it doesn't run, the btrfs system effectively acts like a normal system + # Taken from https://github.com/NotAShelf/nyx/blob/2a8273ed3f11a4b4ca027a68405d9eb35eba567b/modules/core/common/system/impermanence/default.nix + boot.initrd.systemd.services.rollback = { + description = "Rollback BTRFS root subvolume"; + wantedBy = ["initrd.target"]; + after = [ + "systemd-cryptsetup@enc.service" + "systemd-cryptsetup@cryptroot.service" + ]; + before = ["sysroot.mount"]; + unitConfig.DefaultDependencies = "no"; + serviceConfig = { + Type = "oneshot"; + UMask = "0077"; + }; + script = '' + set -euo pipefail + + echo "Starting impermanence rollback..." + + LUKS_DEVICE="" + for device in /dev/mapper/enc /dev/mapper/cryptroot; do + if [[ -b "$device" ]]; then + LUKS_DEVICE="$device" + break + fi + done + + if [[ -z "$LUKS_DEVICE" ]]; then + echo "Error: No LUKS device found (tried enc, cryptroot), skipping rollback" + exit 0 + fi + + echo "Found LUKS device: $LUKS_DEVICE" + + mkdir -p /mnt + + if ! mount -o subvol=/ "$LUKS_DEVICE" /mnt; then + echo "Error: Failed to mount root filesystem" + exit 1 + fi + + if [[ ! -d "/mnt/root-blank" ]]; then + echo "Error: /mnt/root-blank snapshot not found, skipping rollback" + umount /mnt || true + exit 0 + fi + + echo "Found root-blank snapshot, proceeding with rollback" + + if [[ -d "/mnt/root" ]]; then + echo "Removing nested subvolumes..." + btrfs subvolume list -o /mnt/root | cut -f9 -d' ' | while read -r subvolume; do + if [[ -n "$subvolume" ]]; then + echo "Deleting /$subvolume subvolume..." + btrfs subvolume delete "/mnt/$subvolume" || echo "Warning: Failed to delete $subvolume" + fi + done + + echo "Deleting /root subvolume..." + if ! btrfs subvolume delete /mnt/root; then + echo "Error: Failed to delete /root subvolume" + umount /mnt || true + exit 1 + fi + fi + + echo "Restoring blank /root subvolume..." + if ! btrfs subvolume snapshot /mnt/root-blank /mnt/root; then + echo "Error: Failed to create snapshot" + umount /mnt || true + exit 1 + fi + + echo "Rollback completed successfully" + umount /mnt || echo "Warning: Failed to unmount /mnt" + ''; + }; + + environment.persistence."/persistent" = { + hideMounts = true; + directories = [ + "/etc/nixos" + "/srv" + "/var/spool" + "/.cache/nix/" + "/etc/NetworkManager/system-connections" + "/var/cache/" + "/var/db/sudo/" + "/var/lib/nixos" + "/var/lib/systemd/coredump" + "/var/lib/systemd/timers" + "/var/lib/systemd/timesync" + "/var/lib/bluetooth" + "/var/lib/NetworkManager" + "/var/lib/dbus" + "/root" + ]; + files = [ + "/etc/adjtime" + "/etc/machine-id" + "/etc/ssh/ssh_host_ed25519_key" + "/etc/ssh/ssh_host_ed25519_key.pub" + "/etc/ssh/ssh_host_rsa_key" + "/etc/ssh/ssh_host_rsa_key.pub" + ]; + }; + }; +} -- 2.51.2