feat: Impermanence (system)

I had to disable initrd's systemd stuff.  I just /couldn't/ get a rollback
service working; believe me, not for a lack of effort!  I've been working on
this on-and-off for a month or two now.
This commit is contained in:
Madeleine Sydney
2025-01-01 15:45:10 -07:00
parent dfa5d6625b
commit 9516c35c7f
7 changed files with 109 additions and 381 deletions

View File

@@ -1,43 +0,0 @@
#!/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))

View File

@@ -0,0 +1,59 @@
{ config, lib, pkgs, ... }:
with lib;
let cfg = config.impermanence.rollback;
in {
options = {
impermanence.rollback = {
enable = mkEnableOption "rollback of the root filesystem";
device = mkOption {
description = "Device to be wiped";
type = types.str;
};
subvolume = mkOption {
description = ''
The subvolume to be rolled back. Usually, this should be where your
root filesystem resides.
'';
type = types.str;
};
};
};
config = mkIf cfg.enable {
boot.initrd.postDeviceCommands = ''
# Mount the btrfs filesystem.
mkdir -p /btrfs-tmp
mount -t btrfs "${cfg.device}" /btrfs-tmp
# If the moribound subvolume exists, send it do 'death row' (old-roots),
# where live for about three days before its eventual deletion.
if [[ -e "/btrfs-tmp/${cfg.subvolume}" ]]; then
mkdir -p /btrfs-tmp/old-roots
timestamp=$(date --date="@$(stat -c %Y "/btrfs-tmp/${cfg.subvolume}")" "+%Y-%m-%-d_%H:%M:%S")
mv "/btrfs-tmp/${cfg.subvolume}" "/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"
}
# Delete previous roots older than three days.
# TODO: I would prefer archiving the last N previous roots, rather than
# time.
for i in $(find /btrfs-tmp/old-roots/ -maxdepth 1 -mtime +3); do
delete_subvolume_recursively "$i"
done
btrfs subvolume create "/btrfs-tmp/${cfg.subvolume}"
umount /btrfs-tmp
'';
};
}