dotfiles

My PC Dotfiles & Website

modules/system/filesystem.nix (10554B)


  1## File System Configuration ##
  2{
  3  config,
  4  inputs,
  5  ...
  6}: let
  7  inherit (config.flake) files;
  8in {
  9  flake.modules = {
 10    nixos.filesystem = {
 11      config,
 12      lib,
 13      pkgs,
 14      ...
 15    }: let
 16      inherit
 17        (lib)
 18        filterAttrs
 19        hasPrefix
 20        mkAliasOptionModule
 21        mkDefault
 22        mkIf
 23        mkMerge
 24        mkOption
 25        nameValuePair
 26        optional
 27        types
 28        ;
 29
 30      cfg = config.system.fs;
 31    in {
 32      imports = [
 33        inputs.disko.nixosModules.disko
 34        (
 35          mkAliasOptionModule
 36          ["system" "fs" "persist"]
 37          [
 38            "environment"
 39            "persistence"
 40            files.path.data
 41          ]
 42        )
 43      ];
 44
 45      options.system.fs = {
 46        scheme = mkOption {
 47          description = "Disk Filesystem Scheme";
 48          type = with types;
 49            nullOr (enum [
 50              "simple"
 51              "advanced"
 52            ]);
 53          default = null;
 54        };
 55
 56        disk = mkOption {
 57          description = "Target Disk for Installation";
 58          type = types.str;
 59          default = "/dev/disk/by-id/disko-placeholder";
 60          example = "/dev/disk/by-id/nvme-eui.0123456789abcdef";
 61        };
 62
 63        swap = mkOption {
 64          description = "Size of SWAP Partition";
 65          type = types.str;
 66          default = "8G";
 67          example = "16G";
 68        };
 69      };
 70
 71      config = mkMerge [
 72        {
 73          warnings = optional (cfg.scheme == null) "Disk Filesystem Scheme is unset";
 74          system.fs.persist.enable = mkDefault false;
 75
 76          boot = {
 77            supportedFilesystems = {
 78              ntfs = true;
 79              vfat = true;
 80              zfs = true;
 81            };
 82            zfs.forceImportRoot = false;
 83          };
 84
 85          ## Disk Layout
 86          disko.devices = mkMerge [
 87            (mkIf (cfg.scheme != null) {
 88              disk.main = {
 89                type = "disk";
 90                device = cfg.disk;
 91                content = {
 92                  type = "gpt";
 93                  partitions = {
 94                    # EFI System Partition
 95                    ESP = {
 96                      label = "ESP";
 97                      type = "EF00";
 98                      size = "1024M";
 99                      content = {
100                        type = "filesystem";
101                        format = "vfat";
102                        mountpoint = "/boot";
103                        mountOptions = ["umask=0077"];
104                      };
105                    };
106
107                    # SWAP Partition
108                    swap = {
109                      label = "swap";
110                      size = cfg.swap;
111                      content = {
112                        type = "swap";
113                        resumeDevice = true;
114                      };
115                    };
116
117                    # System Partition
118                    System = {
119                      label = "System";
120                      size = "100%";
121                      content =
122                        if cfg.scheme == "advanced"
123                        then {
124                          type = "zfs";
125                          pool = "fspool";
126                        }
127                        else {
128                          type = "filesystem";
129                          format = "ext4";
130                          mountpoint = "/";
131                        };
132                    };
133                  };
134                };
135              };
136            })
137
138            (mkIf (cfg.scheme == "advanced") {
139              zpool.fspool = {
140                type = "zpool";
141                options = {
142                  ashift = "12";
143                  cachefile = "none";
144                };
145                rootFsOptions = {
146                  atime = "off";
147                  compression = "zstd";
148                  dnodesize = "auto";
149                  encryption = "on";
150                  keyformat = "passphrase";
151                  keylocation = "prompt";
152                  mountpoint = "none";
153                  xattr = "sa";
154                  acltype = "posixacl";
155                };
156
157                datasets = {
158                  # ROOT Dataset
159                  "system/root" = {
160                    type = "zfs_fs";
161                    mountpoint = "/";
162                    options.mountpoint = "legacy";
163                    postCreateHook = "zfs snapshot fspool/system/root@blank";
164                  };
165
166                  # NIX Store Dataset
167                  "system/nix" = {
168                    type = "zfs_fs";
169                    mountpoint = "/nix";
170                    options = {
171                      mountpoint = "legacy";
172                      recordsize = "1M";
173                    };
174                  };
175
176                  # Persisted Dataset
177                  "data" = {
178                    type = "zfs_fs";
179                    mountpoint = files.path.data;
180                    options = {
181                      mountpoint = "legacy";
182                      "com.sun:auto-snapshot" = "true";
183                    };
184                  };
185
186                  # Reserved space
187                  # ? # Free with 'zfs set refreservation=none fspool/reserve'
188                  "reserve" = {
189                    type = "zfs_fs";
190                    options = {
191                      canmount = "off";
192                      mountpoint = "none";
193                      refreservation = "1G";
194                    };
195                  };
196                };
197              };
198            })
199          ];
200        }
201
202        (mkIf (cfg.scheme != null) {
203          programs.fuse.userAllowOther = true;
204          boot.kernel.sysctl."vm.swappiness" = 1; # SWAP Behaviour
205
206          system.activationScripts.dotfiles = with files.path; let
207            path =
208              if cfg.scheme == "advanced"
209              then "${persist}/"
210              else "";
211            dir = "${path}${system}";
212          in ''
213            chown root:keys ${dir}
214            chmod -R u=rwX,g=rwX,o= ${dir}
215            chmod g+s ${dir}
216          '';
217        })
218
219        ## Advanced File System Configuration using ZFS ##
220        (mkIf (cfg.scheme == "advanced") (
221          let
222            rollback = ''
223              zfs rollback -r fspool/system/root@blank && echo "Rollback Complete!"
224            '';
225          in {
226            fileSystems =
227              {
228                "${files.path.data}".neededForBoot = true;
229              }
230              // filterAttrs (name: _: hasPrefix "/etc" name) (
231                builtins.listToAttrs (
232                  map (
233                    item:
234                      nameValuePair item.directory {
235                        device = "${files.path.persist}${item.directory}";
236                        fsType = "none";
237                        options = ["bind"];
238                        neededForBoot = true;
239                      }
240                  )
241                  config.environment.persist.directories
242                )
243              );
244
245            # Early Boot Requirements
246            boot = {
247              zfs = {
248                forceImportAll = false;
249                devNodes = "/dev/disk/by-partlabel/System";
250              };
251
252              initrd.systemd = {
253                enable = true;
254                services.rollback = {
255                  description = "Wipe ZFS partition for Opt-In State Configuration";
256                  wantedBy = ["initrd.target"];
257                  after = ["zfs-import-fspool.service"];
258                  before = ["sysroot.mount"];
259                  path = [pkgs.zfs];
260                  unitConfig.DefaultDependencies = "no";
261                  serviceConfig.Type = "oneshot";
262                  script = rollback;
263                };
264              };
265            };
266
267            # Maintainence
268            services.zfs = {
269              trim = {
270                enable = true;
271                interval = "weekly";
272              };
273
274              autoScrub = {
275                enable = true;
276                interval = "monthly";
277              };
278
279              autoSnapshot = {
280                enable = true;
281                hourly = 12;
282                daily = 7;
283                weekly = 3;
284                monthly = 1;
285              };
286            };
287
288            # Persisted Files
289            system.fs.persist.enable = true;
290            environment.persistence."${files.path.persist}" = {
291              enable = true;
292              files = ["/etc/machine-id"];
293              directories = [
294                files.path.system
295                "/var/log"
296                "/var/lib/AccountsService"
297                "/var/lib/nixos"
298                "/var/lib/systemd"
299              ];
300            };
301          }
302        ))
303      ];
304    };
305
306    homeManager.filesystem = {
307      lib,
308      pkgs,
309      osConfig ? null,
310      ...
311    }: let
312      advanced = osConfig != null && (osConfig.system.fs.scheme or null) == "advanced";
313    in {
314      imports = [(lib.mkAliasOptionModule ["home" "persist"] ["home" "persistence" files.path.data])];
315
316      home.persistence."${files.path.data}" = {
317        enable = advanced;
318        allowTrash = true;
319        hideMounts = true;
320        directories = [
321          "Desktop"
322          "Documents"
323          "Downloads"
324          "Music"
325          "Pictures"
326          "Public"
327          "Videos"
328          {
329            directory = ".local/share/keyrings";
330            mode = "0700";
331          }
332        ];
333      };
334
335      # Persist Trash Folder
336      systemd.user.services.persist-trash = lib.mkIf advanced {
337        Unit.Description = "Persist Trash Folder";
338        Install.WantedBy = ["default.target"];
339        Service = let
340          run = script:
341            lib.getExe (
342              pkgs.writeShellApplication {
343                name = "script";
344                runtimeInputs = [pkgs.coreutils];
345                text = ''
346                  LOCAL="$HOME/.local/share/Trash"
347                  PERSIST="${files.path.data}/home/$USER/Trash"
348                  mkdir -p "$LOCAL"
349                  ${script}
350                '';
351              }
352            );
353        in {
354          Type = "oneshot";
355          RemainAfterExit = true;
356          StandardOutput = "journal";
357          ExecStart = run ''
358            if [ -d "$PERSIST" ]
359            then
360              cp -r "$PERSIST"/. "$LOCAL"
361              rm -rf "$PERSIST"
362            fi
363          '';
364          ExecStop = run ''
365            mkdir -p "$PERSIST"
366            cp -r "$LOCAL"/. "$PERSIST"
367          '';
368        };
369      };
370    };
371  };
372}