From 9a99a6dfb7990cbf9dd08660c6e138f68c1286fd Mon Sep 17 00:00:00 2001 From: Madeleine Sydney Date: Sat, 28 Dec 2024 20:29:43 -0700 Subject: [PATCH] (wip) Setup btrfs impermanence --- hosts/nixos-testbed/configuration.nix | 7 +- modules/nixos/filesystemType.nix | 13 ++ modules/nixos/impermanence.nix | 127 +++++++++++------- modules/nixos/impermanence/erase-darlings.clj | 43 ++++++ 4 files changed, 140 insertions(+), 50 deletions(-) create mode 100644 modules/nixos/filesystemType.nix create mode 100644 modules/nixos/impermanence/erase-darlings.clj diff --git a/hosts/nixos-testbed/configuration.nix b/hosts/nixos-testbed/configuration.nix index 261068a..9d99eb9 100644 --- a/hosts/nixos-testbed/configuration.nix +++ b/hosts/nixos-testbed/configuration.nix @@ -6,6 +6,7 @@ ]; sydnix = { + filesystemType = "btrfs"; impermanence = { enable = false; directories = [ @@ -13,10 +14,12 @@ # This means all users/groups without specified uids/gids will have them # reassigned on reboot." "/var/lib/nixos" + # We don't want to have different ssh keys on reboot, because ssh keys + # are expected to consistently identify machines... I think. I mostly + # just think it's annoying to edit ~/.ssh/known_hosts all the time. "/etc/ssh" ]; - rollbackTo = "blank"; - dataset = "rpool/local/home"; + device = "placeholderrrr"; archiveLimit = 3; }; users.users = [ diff --git a/modules/nixos/filesystemType.nix b/modules/nixos/filesystemType.nix new file mode 100644 index 0000000..4f9121f --- /dev/null +++ b/modules/nixos/filesystemType.nix @@ -0,0 +1,13 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + options = { + sydnix.filesystemType = mkOption { + type = types.nullOr (types.enum [ "btrfs" ]); + description = "The name of the filesystem to be used."; + default = null; + }; + }; +} diff --git a/modules/nixos/impermanence.nix b/modules/nixos/impermanence.nix index 4f31ae3..72cb33f 100644 --- a/modules/nixos/impermanence.nix +++ b/modules/nixos/impermanence.nix @@ -32,30 +32,29 @@ in { default = []; }; - rollbackTo = mkOption { - type = types.str; - }; - archiveTo = mkOption { - type = types.str; - default = "/persist/previous/home"; - }; - dataset = mkOption { + device = mkOption { + description = "Device to be wiped"; type = types.str; }; + archiveLimit = mkOption { type = types.ints.positive; + description = "The number of previous roots to preserve."; default = 3; }; }; }; config = mkIf cfg.enable { + # Create a group called `cfg.persistGroupName` users.groups.${cfg.persistGroupName} = { name = cfg.persistGroupName; }; systemd.tmpfiles.settings = { "10-persist" = { + # Permit members of `cfg.persistGroupName` to read, write, and execute + # /persist. "/persist" = { z = { group = cfg.persistGroupName; @@ -65,68 +64,100 @@ in { }; }; - boot.initrd.systemd.initrdBin = with pkgs; [ - zfs - ]; - # TODO: Move this somewhere else. programs.fuse.userAllowOther = true; + # sudo mount -o ro -t virtiofs mount-dots /persist/dots + boot.initrd.systemd = { + # `pkgs.babashka` calls `pkgs.babashka-unwrapped`, and will explode if it + # cannot find it. + storePaths = [ pkgs.babashka-unwrapped ]; + + initrdBin = with pkgs; [ + babashka + util-linux + ]; + }; + boot.initrd.systemd.services.erase-darlings = let service = { description = "Rollback filesystem to a blank state on boot"; wantedBy = [ "initrd.target" ]; - after = [ - # "zfs-import.service" - "zfs-import-rpool.service" - ]; before = [ "sysroot.mount" ]; - path = [ pkgs.zfs ]; + path = with pkgs; [ util-linux babashka ]; unitConfig.DefaultDependencies = "no"; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; }; - script = /* bash */ '' - zfs rollback -r rpool/local/root@blank \ - && echo ">> >> rollback complete << <<" - ''; + script = + let bb-script = "/sysroot" + ./impermanence/erase-darlings.clj; + in ''${pkgs.babashka}/bin/bb "${bb-script}" -n "${toString cfg.archiveLimit}" --device "${cfg.device}"''; }; in if config.boot.initrd.systemd.enable then service else throw "sydnix.impermanence currently requires config.boot.initrd.systemd.enable'!"; - systemd.services = - let erase-home-darlings = { - description = "Rollback home to a blank state on boot"; - wantedBy = [ - "local-fs-pre.target" - "zfs-mount.service" - ]; - before = [ - "local-fs.target" - "local-fs-pre.target" - "zfs-mount.service" - ]; - path = [ pkgs.zfs pkgs.babashka pkgs.util-linux ]; - unitConfig.DefaultDependencies = "no"; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = - let script = ./erase-home-darlings.clj; - in ''${pkgs.babashka}/bin/bb "${script}" -n "${toString cfg.archiveLimit}" --dataset "${cfg.dataset}" --rollback-to "${cfg.rollbackTo}"''; - }; - stopIfChanged = false; - restartIfChanged = false; - }; - in { - # inherit erase-home-darlings; - }; + # TODO: Remove this. + # boot.initrd.systemd.services.erase-darlings-old = + # let service = { + # description = "Rollback filesystem to a blank state on boot"; + # wantedBy = [ + # "initrd.target" + # ]; + # after = [ + # # "zfs-import.service" + # "zfs-import-rpool.service" + # ]; + # before = [ + # "sysroot.mount" + # ]; + # path = [ pkgs.zfs ]; + # unitConfig.DefaultDependencies = "no"; + # serviceConfig = { + # Type = "oneshot"; + # RemainAfterExit = true; + # }; + # script = /* bash */ '' + # zfs rollback -r rpool/local/root@blank \ + # && echo ">> >> rollback complete << <<" + # ''; + # }; + # in if config.boot.initrd.systemd.enable + # then service + # else throw "sydnix.impermanence currently requires config.boot.initrd.systemd.enable'!"; + + # systemd.services = + # let erase-home-darlings = { + # description = "Rollback home to a blank state on boot"; + # wantedBy = [ + # "local-fs-pre.target" + # "zfs-mount.service" + # ]; + # before = [ + # "local-fs.target" + # "local-fs-pre.target" + # "zfs-mount.service" + # ]; + # path = [ pkgs.zfs pkgs.babashka pkgs.util-linux ]; + # unitConfig.DefaultDependencies = "no"; + # serviceConfig = { + # Type = "oneshot"; + # RemainAfterExit = true; + # ExecStart = + # let script = ./erase-home-darlings.clj; + # in ''${pkgs.babashka}/bin/bb "${script}" -n "${toString cfg.archiveLimit}" --dataset "${cfg.dataset}" --rollback-to "${cfg.rollbackTo}"''; + # }; + # stopIfChanged = false; + # restartIfChanged = false; + # }; + # in { + # # inherit erase-home-darlings; + # }; environment.persistence."/persist/root" = { directories = cfg.directories; diff --git a/modules/nixos/impermanence/erase-darlings.clj b/modules/nixos/impermanence/erase-darlings.clj new file mode 100644 index 0000000..3aad43a --- /dev/null +++ b/modules/nixos/impermanence/erase-darlings.clj @@ -0,0 +1,43 @@ +#!/usr/bin/env bb + +(require '[clojure.core.match :refer [match]] + '[babashka.cli :as cli] + '[clojure.pprint :as pp] + '[clojure.tools.logging :as l] + '[babashka.process :refer [shell check process] :as p]) + +(def cli-spec + {:spec + {:archive-limit {:coerce :int + :alias :n + :validate #(pos? %) + :default 3 + :desc "Number of archives to save at a time."} + :device {:coerce :string + ;; :validate fs/exists? + :require true + :desc "Dataset to be archived and rolled back."} + :previous-roots + {:coerce :string + :default "/persist/previous" + :desc "The path under which previous roots will be stored."} + :error-fn + (fn [{:keys [spec type cause msg option] :as data}] + (when (= :org.babashka/cli type) + (case cause + :require + (println + (format "Missing required argument: %s\n" option)))) + (System/exit 1))}}) + +(defmacro with-echoed-shell-commands [& body] + (let [print-cmd #(println (str "+ " (str/join (:cmd %))))] + `(binding [p/*defaults* {:pre-start-fn ~print-cmd}] + ~@body))) + +(defn -main [opts] + (pp/pprint opts) + (with-echoed-shell-commands + (shell "mount"))) + +(-main (cli/parse-opts *command-line-args* cli-spec))