blob: 7d2a94b7e1555db5df8fc8ad94d30dc988cfc708 [file] [log] [blame]
Skyler Grey40ae7a02024-06-06 21:22:25 +00001# SPDX-FileCopyrightText: 2024 Clicks Codes
2# SPDX-FileCopyrightText: 2020 Nix Community Projects
3#
4# SPDX-License-Identifier: MIT
5# postDeviceCommands based of code from https://github.com/nix-community/impermanence/tree/d5f1ed7141fa407880ff5956ded2c88a307ca940?tab=readme-ov-file#btrfs-subvolumes
6
7{ lib, config, ... }:
8let
9 cfg = config.clicks.storage.impermanence;
10in
11{
12 options.clicks.storage.impermanence = {
13 enable = lib.mkEnableOption "Enable impermanent rootfs with btrfs subvolumes";
Skyler Greyd3377402024-06-06 22:01:26 +000014 devices = {
15 root = lib.mkOption {
16 type = lib.types.str;
17 description = "Rootfs device path";
18 };
19 persist = lib.mkOption {
20 type = lib.types.str;
21 description = "Persistent data device path";
22 };
Skyler Grey40ae7a02024-06-06 21:22:25 +000023 };
24 volumes = {
25 mount = lib.mkOption {
26 type = lib.types.str;
Skyler Greyd3377402024-06-06 22:01:26 +000027 description = "Path on rootfs device to the mounting subvolume, everything on here will be deleted";
Skyler Grey40ae7a02024-06-06 21:22:25 +000028 default = "@";
29 };
30 old_roots = lib.mkOption {
31 type = lib.types.str;
Skyler Greyd3377402024-06-06 22:01:26 +000032 description = "Path on rootfs device to store old roots on";
Skyler Grey40ae7a02024-06-06 21:22:25 +000033 default = "old_roots";
34 };
Skyler Greyd3377402024-06-06 22:01:26 +000035 persistent_data = lib.mkOption {
36 type = lib.types.str;
37 description = "Path on persist device to store persistent data on";
38 default = "data";
39 };
Skyler Grey40ae7a02024-06-06 21:22:25 +000040 };
41 delete_days = lib.mkOption {
42 type = lib.types.int;
Skyler Greyd3377402024-06-06 22:01:26 +000043 description = "How many days to wait before deleting an old root from `cfg.volumes.old_roots`";
Skyler Grey40ae7a02024-06-06 21:22:25 +000044 default = 7;
45 };
Skyler Greyd3377402024-06-06 22:01:26 +000046 persist = {
47 directories = lib.mkOption {
Skyler Grey0a0912a2024-07-04 00:13:40 +000048 type = lib.types.listOf
49 (lib.types.oneOf [
50 lib.types.str
51 (lib.types.attrsOf (lib.types.oneOf [
52 lib.types.str
53 (lib.types.attrsOf lib.types.str)
54 ]))
55 ]);
Skyler Greyd3377402024-06-06 22:01:26 +000056 description = "List of directories to store between boots";
57 default = [ ];
58 };
59 files = lib.mkOption {
60 type = lib.types.listOf lib.types.str;
61 description = "List of files to store between boots";
62 default = [ ];
63 };
64 };
Skyler Grey40ae7a02024-06-06 21:22:25 +000065 };
66
Skyler Greyec13fbd2024-08-03 08:11:04 +000067 config = lib.mkIf cfg.enable ({
Skyler Grey40ae7a02024-06-06 21:22:25 +000068 boot.initrd.postDeviceCommands = lib.mkAfter ''
69 mkdir /impermanent_fs
Skyler Greyd3377402024-06-06 22:01:26 +000070 mount ${cfg.devices.root} /impermanent_fs
Skyler Grey40ae7a02024-06-06 21:22:25 +000071
72 if [[ -e /impermanent_fs/${cfg.volumes.mount} ]]; then
73 mkdir -p /impermanent_fs/${cfg.volumes.old_roots}
74 timestamp=$(date --date="@$(stat -c %Y /impermanent_fs/${cfg.volumes.mount})" "+%Y-%m-%-d_%H:%M:%S")
75 mv /impermanent_fs/${cfg.volumes.mount} "/impermanent_fs/${cfg.volumes.old_roots}/$timestamp"
76 fi
77
78 delete_subvolume_recursively() {
79 IFS=$'\n'
80 for i in $(btrfs subvolume list -o "$1" | cut -f 9- -d ' '); do
81 delete_subvolume_recursively "/impermanent_fs/$i"
82 done
83 btrfs subvolume delete "$1"
84 }
85
86 for i in $(find /impermanent_fs/${cfg.volumes.old_roots}/ -maxdepth 1 -mtime +${builtins.toString cfg.delete_days}); do
87 delete_subvolume_recursively "$i"
88 done
89
90 btrfs subvolume create /impermanent_fs/${cfg.volumes.mount}
91 umount /impermanent_fs
92 '';
93
94 fileSystems."/" = {
Skyler Greyd3377402024-06-06 22:01:26 +000095 device = cfg.devices.root;
Skyler Grey40ae7a02024-06-06 21:22:25 +000096 fsType = "btrfs";
97 options = [ "subvol=${cfg.volumes.mount}" ];
98 };
Skyler Greyd3377402024-06-06 22:01:26 +000099
100 fileSystems."/persist" = {
101 device = cfg.devices.persist;
102 neededForBoot = true;
103 fsType = "btrfs";
104 };
Skyler Greyec13fbd2024-08-03 08:11:04 +0000105 } // {
106 environment = lib.optionalAttrs cfg.enable {
107 persistence."/persist/${cfg.volumes.persistent_data}" = {
108 directories = [
109 "/var/lib/nixos" # https://github.com/nix-community/impermanence/issues/178
110 ] ++ cfg.persist.directories;
111 files = [
112 "/etc/machine-id"
113 "/etc/ssh/ssh_host_ed25519_key"
114 "/etc/ssh/ssh_host_ed25519_key.pub"
115 "/etc/ssh/ssh_host_rsa_key"
116 "/etc/ssh/ssh_host_rsa_key.pub"
117 ] ++ cfg.persist.files;
118 };
Skyler Greyd3377402024-06-06 22:01:26 +0000119 };
Skyler Greyec13fbd2024-08-03 08:11:04 +0000120 });
Skyler Grey40ae7a02024-06-06 21:22:25 +0000121}