Apply nixpkgs-fmt
This commit is contained in:
		@@ -1,32 +1,33 @@
 | 
			
		||||
{ lib, extendModules, modulesPath, baseModules, options, config, ... }:
 | 
			
		||||
  let
 | 
			
		||||
    inherit (lib) mkOption;
 | 
			
		||||
    inherit (lib.my) mkBoolOpt;
 | 
			
		||||
let
 | 
			
		||||
  inherit (lib) mkOption;
 | 
			
		||||
  inherit (lib.my) mkBoolOpt;
 | 
			
		||||
 | 
			
		||||
    cfg = config.my.build;
 | 
			
		||||
  cfg = config.my.build;
 | 
			
		||||
 | 
			
		||||
    asDevVM = extendModules {
 | 
			
		||||
      # TODO: Hack because this is kinda broken on 21.11 (https://github.com/NixOS/nixpkgs/issues/148343)
 | 
			
		||||
      specialArgs = { inherit baseModules; };
 | 
			
		||||
      modules = [
 | 
			
		||||
        "${modulesPath}/virtualisation/qemu-vm.nix"
 | 
			
		||||
        ({ ... }: {
 | 
			
		||||
          my.boot.isDevVM = true;
 | 
			
		||||
        })
 | 
			
		||||
      ];
 | 
			
		||||
    };
 | 
			
		||||
  in {
 | 
			
		||||
    options.my = with lib.types; {
 | 
			
		||||
      boot.isDevVM = mkBoolOpt false;
 | 
			
		||||
      build = options.system.build;
 | 
			
		||||
      asDevVM = mkOption {
 | 
			
		||||
        inherit (asDevVM) type;
 | 
			
		||||
        default = {};
 | 
			
		||||
        visible = "shallow";
 | 
			
		||||
      };
 | 
			
		||||
  asDevVM = extendModules {
 | 
			
		||||
    # TODO: Hack because this is kinda broken on 21.11 (https://github.com/NixOS/nixpkgs/issues/148343)
 | 
			
		||||
    specialArgs = { inherit baseModules; };
 | 
			
		||||
    modules = [
 | 
			
		||||
      "${modulesPath}/virtualisation/qemu-vm.nix"
 | 
			
		||||
      ({ ... }: {
 | 
			
		||||
        my.boot.isDevVM = true;
 | 
			
		||||
      })
 | 
			
		||||
    ];
 | 
			
		||||
  };
 | 
			
		||||
in
 | 
			
		||||
{
 | 
			
		||||
  options.my = with lib.types; {
 | 
			
		||||
    boot.isDevVM = mkBoolOpt false;
 | 
			
		||||
    build = options.system.build;
 | 
			
		||||
    asDevVM = mkOption {
 | 
			
		||||
      inherit (asDevVM) type;
 | 
			
		||||
      default = { };
 | 
			
		||||
      visible = "shallow";
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
    config.my.build = {
 | 
			
		||||
      devVM = config.my.asDevVM.system.build.vm;
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  config.my.build = {
 | 
			
		||||
    devVM = config.my.asDevVM.system.build.vm;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,93 +1,95 @@
 | 
			
		||||
{ lib, pkgs, inputs, system, config, options, ... }:
 | 
			
		||||
  let
 | 
			
		||||
    inherit (lib) mkIf mkDefault mkAliasDefinitions;
 | 
			
		||||
    inherit (lib.my) mkOpt;
 | 
			
		||||
  in {
 | 
			
		||||
    options.my = with lib.types; {
 | 
			
		||||
      user = mkOpt (attrsOf anything) {};
 | 
			
		||||
    };
 | 
			
		||||
let
 | 
			
		||||
  inherit (lib) mkIf mkDefault mkAliasDefinitions;
 | 
			
		||||
  inherit (lib.my) mkOpt;
 | 
			
		||||
in
 | 
			
		||||
{
 | 
			
		||||
  options.my = with lib.types; {
 | 
			
		||||
    user = mkOpt (attrsOf anything) { };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
    config =
 | 
			
		||||
      let
 | 
			
		||||
        defaultUsername = "dev";
 | 
			
		||||
        uname = config.my.user.name;
 | 
			
		||||
      in {
 | 
			
		||||
        my = {
 | 
			
		||||
          user = {
 | 
			
		||||
            name = mkDefault defaultUsername;
 | 
			
		||||
            isNormalUser = true;
 | 
			
		||||
            uid = mkDefault 1000;
 | 
			
		||||
            extraGroups = mkDefault [ "wheel" ];
 | 
			
		||||
            password = mkDefault "hunter2"; # TODO: secrets...
 | 
			
		||||
          };
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        time.timeZone = mkDefault "Europe/Dublin";
 | 
			
		||||
 | 
			
		||||
        users = {
 | 
			
		||||
          mutableUsers = false;
 | 
			
		||||
          users.${uname} = mkAliasDefinitions options.my.user;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        security = {
 | 
			
		||||
          sudo.enable = mkDefault false;
 | 
			
		||||
          doas = {
 | 
			
		||||
            enable = mkDefault true;
 | 
			
		||||
            wheelNeedsPassword = mkDefault false;
 | 
			
		||||
          };
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        nix = {
 | 
			
		||||
          package = inputs.nix.defaultPackage.${system};
 | 
			
		||||
          extraOptions =
 | 
			
		||||
            ''
 | 
			
		||||
              experimental-features = nix-command flakes ca-derivations
 | 
			
		||||
            '';
 | 
			
		||||
        };
 | 
			
		||||
        nixpkgs = {
 | 
			
		||||
          config = {
 | 
			
		||||
            allowUnfree = true;
 | 
			
		||||
          };
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        boot = {
 | 
			
		||||
          # Use latest LTS release by default
 | 
			
		||||
          kernelPackages = mkDefault pkgs.linuxKernel.packages.linux_5_15;
 | 
			
		||||
          loader = {
 | 
			
		||||
            efi = {
 | 
			
		||||
              efiSysMountPoint = mkDefault "/boot";
 | 
			
		||||
              canTouchEfiVariables = mkDefault false;
 | 
			
		||||
            };
 | 
			
		||||
            systemd-boot = {
 | 
			
		||||
              enable = mkDefault true;
 | 
			
		||||
              editor = mkDefault true;
 | 
			
		||||
              consoleMode = mkDefault "max";
 | 
			
		||||
              configurationLimit = mkDefault 10;
 | 
			
		||||
              memtest86.enable = mkDefault true;
 | 
			
		||||
            };
 | 
			
		||||
          };
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        networking = {
 | 
			
		||||
          useDHCP = mkDefault false;
 | 
			
		||||
          enableIPv6 = mkDefault true;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        environment.systemPackages = with pkgs; [
 | 
			
		||||
          bash-completion
 | 
			
		||||
          tree
 | 
			
		||||
          vim
 | 
			
		||||
          htop
 | 
			
		||||
          iperf3
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        services.openssh = {
 | 
			
		||||
          enable = true;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        system = {
 | 
			
		||||
          stateVersion = "21.11";
 | 
			
		||||
          configurationRevision = with inputs; mkIf (self ? rev) self.rev;
 | 
			
		||||
  config =
 | 
			
		||||
    let
 | 
			
		||||
      defaultUsername = "dev";
 | 
			
		||||
      uname = config.my.user.name;
 | 
			
		||||
    in
 | 
			
		||||
    {
 | 
			
		||||
      my = {
 | 
			
		||||
        user = {
 | 
			
		||||
          name = mkDefault defaultUsername;
 | 
			
		||||
          isNormalUser = true;
 | 
			
		||||
          uid = mkDefault 1000;
 | 
			
		||||
          extraGroups = mkDefault [ "wheel" ];
 | 
			
		||||
          password = mkDefault "hunter2"; # TODO: secrets...
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
      time.timeZone = mkDefault "Europe/Dublin";
 | 
			
		||||
 | 
			
		||||
      users = {
 | 
			
		||||
        mutableUsers = false;
 | 
			
		||||
        users.${uname} = mkAliasDefinitions options.my.user;
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      security = {
 | 
			
		||||
        sudo.enable = mkDefault false;
 | 
			
		||||
        doas = {
 | 
			
		||||
          enable = mkDefault true;
 | 
			
		||||
          wheelNeedsPassword = mkDefault false;
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      nix = {
 | 
			
		||||
        package = inputs.nix.defaultPackage.${system};
 | 
			
		||||
        extraOptions =
 | 
			
		||||
          ''
 | 
			
		||||
            experimental-features = nix-command flakes ca-derivations
 | 
			
		||||
          '';
 | 
			
		||||
      };
 | 
			
		||||
      nixpkgs = {
 | 
			
		||||
        config = {
 | 
			
		||||
          allowUnfree = true;
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      boot = {
 | 
			
		||||
        # Use latest LTS release by default
 | 
			
		||||
        kernelPackages = mkDefault pkgs.linuxKernel.packages.linux_5_15;
 | 
			
		||||
        loader = {
 | 
			
		||||
          efi = {
 | 
			
		||||
            efiSysMountPoint = mkDefault "/boot";
 | 
			
		||||
            canTouchEfiVariables = mkDefault false;
 | 
			
		||||
          };
 | 
			
		||||
          systemd-boot = {
 | 
			
		||||
            enable = mkDefault true;
 | 
			
		||||
            editor = mkDefault true;
 | 
			
		||||
            consoleMode = mkDefault "max";
 | 
			
		||||
            configurationLimit = mkDefault 10;
 | 
			
		||||
            memtest86.enable = mkDefault true;
 | 
			
		||||
          };
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      networking = {
 | 
			
		||||
        useDHCP = mkDefault false;
 | 
			
		||||
        enableIPv6 = mkDefault true;
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      environment.systemPackages = with pkgs; [
 | 
			
		||||
        bash-completion
 | 
			
		||||
        tree
 | 
			
		||||
        vim
 | 
			
		||||
        htop
 | 
			
		||||
        iperf3
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      services.openssh = {
 | 
			
		||||
        enable = true;
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      system = {
 | 
			
		||||
        stateVersion = "21.11";
 | 
			
		||||
        configurationRevision = with inputs; mkIf (self ? rev) self.rev;
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,25 @@
 | 
			
		||||
{ lib, pkgs, config, ... }:
 | 
			
		||||
  let
 | 
			
		||||
    inherit (lib) optionalAttrs filterAttrs genAttrs mkIf mkDefault;
 | 
			
		||||
    inherit (lib.my) mkOpt mkBoolOpt;
 | 
			
		||||
let
 | 
			
		||||
  inherit (lib) optionalAttrs filterAttrs genAttrs mkIf mkDefault;
 | 
			
		||||
  inherit (lib.my) mkOpt mkBoolOpt;
 | 
			
		||||
 | 
			
		||||
    cfg = config.my.dynamic-motd;
 | 
			
		||||
  cfg = config.my.dynamic-motd;
 | 
			
		||||
 | 
			
		||||
    scriptBin = pkgs.writeShellScript "dynamic-motd-script" cfg.script;
 | 
			
		||||
  in {
 | 
			
		||||
    options.my.dynamic-motd = with lib.types; {
 | 
			
		||||
      enable = mkBoolOpt true;
 | 
			
		||||
      services = mkOpt (listOf str) [ "login" "ssh" ];
 | 
			
		||||
      script = mkOpt (nullOr lines) null;
 | 
			
		||||
    };
 | 
			
		||||
  scriptBin = pkgs.writeShellScript "dynamic-motd-script" cfg.script;
 | 
			
		||||
in
 | 
			
		||||
{
 | 
			
		||||
  options.my.dynamic-motd = with lib.types; {
 | 
			
		||||
    enable = mkBoolOpt true;
 | 
			
		||||
    services = mkOpt (listOf str) [ "login" "ssh" ];
 | 
			
		||||
    script = mkOpt (nullOr lines) null;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
    config = mkIf (cfg.enable && cfg.script != null) {
 | 
			
		||||
      security.pam.services = genAttrs cfg.services (s: {
 | 
			
		||||
        text = mkDefault
 | 
			
		||||
          ''
 | 
			
		||||
            session optional ${pkgs.pam}/lib/security/pam_exec.so stdout quiet ${scriptBin}
 | 
			
		||||
          '';
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  config = mkIf (cfg.enable && cfg.script != null) {
 | 
			
		||||
    security.pam.services = genAttrs cfg.services (s: {
 | 
			
		||||
      text = mkDefault
 | 
			
		||||
        ''
 | 
			
		||||
          session optional ${pkgs.pam}/lib/security/pam_exec.so stdout quiet ${scriptBin}
 | 
			
		||||
        '';
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,166 +1,167 @@
 | 
			
		||||
{ lib, options, config, ... }:
 | 
			
		||||
  let
 | 
			
		||||
    inherit (lib) optionalString concatStringsSep concatMapStringsSep optionalAttrs mkIf mkDefault mkMerge mkOverride;
 | 
			
		||||
    inherit (lib.my) parseIPPort mkOpt mkBoolOpt dummyOption;
 | 
			
		||||
let
 | 
			
		||||
  inherit (lib) optionalString concatStringsSep concatMapStringsSep optionalAttrs mkIf mkDefault mkMerge mkOverride;
 | 
			
		||||
  inherit (lib.my) parseIPPort mkOpt mkBoolOpt dummyOption;
 | 
			
		||||
 | 
			
		||||
    cfg = config.my.firewall;
 | 
			
		||||
  in {
 | 
			
		||||
    options.my.firewall = with lib.types; {
 | 
			
		||||
      enable = mkBoolOpt true;
 | 
			
		||||
      trustedInterfaces = options.networking.firewall.trustedInterfaces;
 | 
			
		||||
      tcp = {
 | 
			
		||||
        allowed = mkOpt (listOf (either port str)) [ "ssh" ];
 | 
			
		||||
      };
 | 
			
		||||
      udp = {
 | 
			
		||||
        allowed = mkOpt (listOf (either port str)) [];
 | 
			
		||||
      };
 | 
			
		||||
      extraRules = mkOpt lines "";
 | 
			
		||||
 | 
			
		||||
      nat = with options.networking.nat; {
 | 
			
		||||
        enable = mkBoolOpt true;
 | 
			
		||||
        inherit externalInterface forwardPorts;
 | 
			
		||||
      };
 | 
			
		||||
  cfg = config.my.firewall;
 | 
			
		||||
in
 | 
			
		||||
{
 | 
			
		||||
  options.my.firewall = with lib.types; {
 | 
			
		||||
    enable = mkBoolOpt true;
 | 
			
		||||
    trustedInterfaces = options.networking.firewall.trustedInterfaces;
 | 
			
		||||
    tcp = {
 | 
			
		||||
      allowed = mkOpt (listOf (either port str)) [ "ssh" ];
 | 
			
		||||
    };
 | 
			
		||||
    udp = {
 | 
			
		||||
      allowed = mkOpt (listOf (either port str)) [ ];
 | 
			
		||||
    };
 | 
			
		||||
    extraRules = mkOpt lines "";
 | 
			
		||||
 | 
			
		||||
    config = mkIf cfg.enable (mkMerge [
 | 
			
		||||
      {
 | 
			
		||||
        networking = {
 | 
			
		||||
          firewall.enable = false;
 | 
			
		||||
          nftables = {
 | 
			
		||||
            enable = true;
 | 
			
		||||
            ruleset =
 | 
			
		||||
              let
 | 
			
		||||
                trusted' = "{ ${concatStringsSep ", " cfg.trustedInterfaces} }";
 | 
			
		||||
              in
 | 
			
		||||
              ''
 | 
			
		||||
                table inet filter {
 | 
			
		||||
                  chain wan-tcp {
 | 
			
		||||
                    ${concatMapStringsSep "\n    " (p: "tcp dport ${toString p} accept") cfg.tcp.allowed}
 | 
			
		||||
                  }
 | 
			
		||||
                  chain wan-udp {
 | 
			
		||||
                    ${concatMapStringsSep "\n    " (p: "udp dport ${toString p} accept") cfg.udp.allowed}
 | 
			
		||||
                  }
 | 
			
		||||
    nat = with options.networking.nat; {
 | 
			
		||||
      enable = mkBoolOpt true;
 | 
			
		||||
      inherit externalInterface forwardPorts;
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
                  chain wan {
 | 
			
		||||
                    ip6 nexthdr icmpv6 icmpv6 type {
 | 
			
		||||
                      destination-unreachable,
 | 
			
		||||
                      packet-too-big,
 | 
			
		||||
                      time-exceeded,
 | 
			
		||||
                      parameter-problem,
 | 
			
		||||
                      mld-listener-query,
 | 
			
		||||
                      mld-listener-report,
 | 
			
		||||
                      mld-listener-reduction,
 | 
			
		||||
                      nd-router-solicit,
 | 
			
		||||
                      nd-router-advert,
 | 
			
		||||
                      nd-neighbor-solicit,
 | 
			
		||||
                      nd-neighbor-advert,
 | 
			
		||||
                      ind-neighbor-solicit,
 | 
			
		||||
                      ind-neighbor-advert,
 | 
			
		||||
                      mld2-listener-report,
 | 
			
		||||
                      echo-request
 | 
			
		||||
                    } accept
 | 
			
		||||
                    ip protocol icmp icmp type {
 | 
			
		||||
                      destination-unreachable,
 | 
			
		||||
                      router-solicitation,
 | 
			
		||||
                      router-advertisement,
 | 
			
		||||
                      time-exceeded,
 | 
			
		||||
                      parameter-problem,
 | 
			
		||||
                      echo-request
 | 
			
		||||
                    } accept
 | 
			
		||||
                    ip protocol igmp accept
 | 
			
		||||
 | 
			
		||||
                    ip protocol tcp tcp flags & (fin|syn|rst|ack) == syn ct state new jump wan-tcp
 | 
			
		||||
                    ip protocol udp ct state new jump wan-udp
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                  chain input {
 | 
			
		||||
                    type filter hook input priority 0; policy drop;
 | 
			
		||||
 | 
			
		||||
                    ct state established,related accept
 | 
			
		||||
                    ct state invalid drop
 | 
			
		||||
 | 
			
		||||
                    iif lo accept
 | 
			
		||||
                    ${optionalString (cfg.trustedInterfaces != []) "iifname ${trusted'} accept\n"}
 | 
			
		||||
                    jump wan
 | 
			
		||||
                  }
 | 
			
		||||
                  chain forward {
 | 
			
		||||
                    type filter hook forward priority 0; policy drop;
 | 
			
		||||
                    ${optionalString (cfg.trustedInterfaces != []) "\n    iifname ${trusted'} accept\n"}
 | 
			
		||||
                    ct state related,established accept
 | 
			
		||||
                  }
 | 
			
		||||
                  chain output {
 | 
			
		||||
                    type filter hook output priority 0; policy accept;
 | 
			
		||||
                  }
 | 
			
		||||
  config = mkIf cfg.enable (mkMerge [
 | 
			
		||||
    {
 | 
			
		||||
      networking = {
 | 
			
		||||
        firewall.enable = false;
 | 
			
		||||
        nftables = {
 | 
			
		||||
          enable = true;
 | 
			
		||||
          ruleset =
 | 
			
		||||
            let
 | 
			
		||||
              trusted' = "{ ${concatStringsSep ", " cfg.trustedInterfaces} }";
 | 
			
		||||
            in
 | 
			
		||||
            ''
 | 
			
		||||
              table inet filter {
 | 
			
		||||
                chain wan-tcp {
 | 
			
		||||
                  ${concatMapStringsSep "\n    " (p: "tcp dport ${toString p} accept") cfg.tcp.allowed}
 | 
			
		||||
                }
 | 
			
		||||
                chain wan-udp {
 | 
			
		||||
                  ${concatMapStringsSep "\n    " (p: "udp dport ${toString p} accept") cfg.udp.allowed}
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                table nat {
 | 
			
		||||
                  chain prerouting {
 | 
			
		||||
                    type nat hook prerouting priority 0;
 | 
			
		||||
                  }
 | 
			
		||||
                chain wan {
 | 
			
		||||
                  ip6 nexthdr icmpv6 icmpv6 type {
 | 
			
		||||
                    destination-unreachable,
 | 
			
		||||
                    packet-too-big,
 | 
			
		||||
                    time-exceeded,
 | 
			
		||||
                    parameter-problem,
 | 
			
		||||
                    mld-listener-query,
 | 
			
		||||
                    mld-listener-report,
 | 
			
		||||
                    mld-listener-reduction,
 | 
			
		||||
                    nd-router-solicit,
 | 
			
		||||
                    nd-router-advert,
 | 
			
		||||
                    nd-neighbor-solicit,
 | 
			
		||||
                    nd-neighbor-advert,
 | 
			
		||||
                    ind-neighbor-solicit,
 | 
			
		||||
                    ind-neighbor-advert,
 | 
			
		||||
                    mld2-listener-report,
 | 
			
		||||
                    echo-request
 | 
			
		||||
                  } accept
 | 
			
		||||
                  ip protocol icmp icmp type {
 | 
			
		||||
                    destination-unreachable,
 | 
			
		||||
                    router-solicitation,
 | 
			
		||||
                    router-advertisement,
 | 
			
		||||
                    time-exceeded,
 | 
			
		||||
                    parameter-problem,
 | 
			
		||||
                    echo-request
 | 
			
		||||
                  } accept
 | 
			
		||||
                  ip protocol igmp accept
 | 
			
		||||
 | 
			
		||||
                  chain postrouting {
 | 
			
		||||
                    type nat hook postrouting priority 100;
 | 
			
		||||
                  }
 | 
			
		||||
                  ip protocol tcp tcp flags & (fin|syn|rst|ack) == syn ct state new jump wan-tcp
 | 
			
		||||
                  ip protocol udp ct state new jump wan-udp
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                ${cfg.extraRules}
 | 
			
		||||
              '';
 | 
			
		||||
          };
 | 
			
		||||
                chain input {
 | 
			
		||||
                  type filter hook input priority 0; policy drop;
 | 
			
		||||
 | 
			
		||||
                  ct state established,related accept
 | 
			
		||||
                  ct state invalid drop
 | 
			
		||||
 | 
			
		||||
                  iif lo accept
 | 
			
		||||
                  ${optionalString (cfg.trustedInterfaces != []) "iifname ${trusted'} accept\n"}
 | 
			
		||||
                  jump wan
 | 
			
		||||
                }
 | 
			
		||||
                chain forward {
 | 
			
		||||
                  type filter hook forward priority 0; policy drop;
 | 
			
		||||
                  ${optionalString (cfg.trustedInterfaces != []) "\n    iifname ${trusted'} accept\n"}
 | 
			
		||||
                  ct state related,established accept
 | 
			
		||||
                }
 | 
			
		||||
                chain output {
 | 
			
		||||
                  type filter hook output priority 0; policy accept;
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              table nat {
 | 
			
		||||
                chain prerouting {
 | 
			
		||||
                  type nat hook prerouting priority 0;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                chain postrouting {
 | 
			
		||||
                  type nat hook postrouting priority 100;
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              ${cfg.extraRules}
 | 
			
		||||
            '';
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
      (mkIf cfg.nat.enable {
 | 
			
		||||
        assertions = [
 | 
			
		||||
          {
 | 
			
		||||
            assertion = (cfg.nat.forwardPorts != []) -> (cfg.nat.externalInterface != null);
 | 
			
		||||
            message = "my.firewall.nat.forwardPorts requires my.firewall.nat.externalInterface";
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
    (mkIf cfg.nat.enable {
 | 
			
		||||
      assertions = [
 | 
			
		||||
        {
 | 
			
		||||
          assertion = (cfg.nat.forwardPorts != [ ]) -> (cfg.nat.externalInterface != null);
 | 
			
		||||
          message = "my.firewall.nat.forwardPorts requires my.firewall.nat.externalInterface";
 | 
			
		||||
        }
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      # Yoinked from nixpkgs/nixos/modules/services/networking/nat.nix
 | 
			
		||||
      boot = {
 | 
			
		||||
        kernel.sysctl = {
 | 
			
		||||
          "net.ipv4.conf.all.forwarding" = mkOverride 99 true;
 | 
			
		||||
          "net.ipv4.conf.default.forwarding" = mkOverride 99 true;
 | 
			
		||||
        } // optionalAttrs config.networking.enableIPv6 {
 | 
			
		||||
          # Do not prevent IPv6 autoconfiguration.
 | 
			
		||||
          # See <http://strugglers.net/~andy/blog/2011/09/04/linux-ipv6-router-advertisements-and-forwarding/>.
 | 
			
		||||
          "net.ipv6.conf.all.accept_ra" = mkOverride 99 2;
 | 
			
		||||
          "net.ipv6.conf.default.accept_ra" = mkOverride 99 2;
 | 
			
		||||
 | 
			
		||||
          # Forward IPv6 packets.
 | 
			
		||||
          "net.ipv6.conf.all.forwarding" = mkOverride 99 true;
 | 
			
		||||
          "net.ipv6.conf.default.forwarding" = mkOverride 99 true;
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      my.firewall.extraRules =
 | 
			
		||||
        let
 | 
			
		||||
          makeFilter = f:
 | 
			
		||||
            let
 | 
			
		||||
              ipp = parseIPPort f.destination;
 | 
			
		||||
            in
 | 
			
		||||
            "ip${optionalString ipp.v6 "6"} daddr ${ipp.ip} ${f.proto} dport ${toString f.sourcePort} accept";
 | 
			
		||||
          makeForward = f: "${f.proto} dport ${toString f.sourcePort} dnat to ${f.destination}";
 | 
			
		||||
        in
 | 
			
		||||
        ''
 | 
			
		||||
          table inet filter {
 | 
			
		||||
            chain filter-port-forwards {
 | 
			
		||||
              ${concatMapStringsSep "\n    " makeFilter cfg.nat.forwardPorts}
 | 
			
		||||
            }
 | 
			
		||||
            chain forward {
 | 
			
		||||
              iifname ${cfg.nat.externalInterface} jump filter-port-forwards
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        # Yoinked from nixpkgs/nixos/modules/services/networking/nat.nix
 | 
			
		||||
        boot = {
 | 
			
		||||
          kernel.sysctl = {
 | 
			
		||||
            "net.ipv4.conf.all.forwarding" = mkOverride 99 true;
 | 
			
		||||
            "net.ipv4.conf.default.forwarding" = mkOverride 99 true;
 | 
			
		||||
          } // optionalAttrs config.networking.enableIPv6 {
 | 
			
		||||
            # Do not prevent IPv6 autoconfiguration.
 | 
			
		||||
            # See <http://strugglers.net/~andy/blog/2011/09/04/linux-ipv6-router-advertisements-and-forwarding/>.
 | 
			
		||||
            "net.ipv6.conf.all.accept_ra" = mkOverride 99 2;
 | 
			
		||||
            "net.ipv6.conf.default.accept_ra" = mkOverride 99 2;
 | 
			
		||||
 | 
			
		||||
            # Forward IPv6 packets.
 | 
			
		||||
            "net.ipv6.conf.all.forwarding" = mkOverride 99 true;
 | 
			
		||||
            "net.ipv6.conf.default.forwarding" = mkOverride 99 true;
 | 
			
		||||
          };
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        my.firewall.extraRules =
 | 
			
		||||
          let
 | 
			
		||||
            makeFilter = f:
 | 
			
		||||
              let
 | 
			
		||||
                ipp = parseIPPort f.destination;
 | 
			
		||||
              in
 | 
			
		||||
                "ip${optionalString ipp.v6 "6"} daddr ${ipp.ip} ${f.proto} dport ${toString f.sourcePort} accept";
 | 
			
		||||
            makeForward = f: "${f.proto} dport ${toString f.sourcePort} dnat to ${f.destination}";
 | 
			
		||||
          in
 | 
			
		||||
          ''
 | 
			
		||||
            table inet filter {
 | 
			
		||||
              chain filter-port-forwards {
 | 
			
		||||
                ${concatMapStringsSep "\n    " makeFilter cfg.nat.forwardPorts}
 | 
			
		||||
              }
 | 
			
		||||
              chain forward {
 | 
			
		||||
                iifname ${cfg.nat.externalInterface} jump filter-port-forwards
 | 
			
		||||
              }
 | 
			
		||||
          table nat {
 | 
			
		||||
            chain port-forward {
 | 
			
		||||
              ${concatMapStringsSep "\n    " makeForward cfg.nat.forwardPorts}
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            table nat {
 | 
			
		||||
              chain port-forward {
 | 
			
		||||
                ${concatMapStringsSep "\n    " makeForward cfg.nat.forwardPorts}
 | 
			
		||||
              }
 | 
			
		||||
              chain prerouting {
 | 
			
		||||
                iifname ${cfg.nat.externalInterface} jump port-forward
 | 
			
		||||
              }
 | 
			
		||||
            chain prerouting {
 | 
			
		||||
              iifname ${cfg.nat.externalInterface} jump port-forward
 | 
			
		||||
            }
 | 
			
		||||
          '';
 | 
			
		||||
      })
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
          }
 | 
			
		||||
        '';
 | 
			
		||||
    })
 | 
			
		||||
  ]);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,11 @@
 | 
			
		||||
{ config, lib, ... }:
 | 
			
		||||
  let
 | 
			
		||||
    inherit (lib) mkIf;
 | 
			
		||||
    inherit (lib.my) mkBoolOpt;
 | 
			
		||||
  in {
 | 
			
		||||
    options.my.server.enable = mkBoolOpt false;
 | 
			
		||||
    config = mkIf config.my.server.enable {
 | 
			
		||||
      services.getty.autologinUser = config.my.user.name;
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
let
 | 
			
		||||
  inherit (lib) mkIf;
 | 
			
		||||
  inherit (lib.my) mkBoolOpt;
 | 
			
		||||
in
 | 
			
		||||
{
 | 
			
		||||
  options.my.server.enable = mkBoolOpt false;
 | 
			
		||||
  config = mkIf config.my.server.enable {
 | 
			
		||||
    services.getty.autologinUser = config.my.user.name;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,205 +1,206 @@
 | 
			
		||||
{ lib, pkgs, inputs, config, ... }:
 | 
			
		||||
  let
 | 
			
		||||
    inherit (builtins) elem;
 | 
			
		||||
    inherit (lib) concatStringsSep concatMap concatMapStringsSep mkIf mkDefault mkMerge mkForce mkVMOverride;
 | 
			
		||||
    inherit (lib.my) mkOpt mkBoolOpt mkVMOverride' dummyOption;
 | 
			
		||||
let
 | 
			
		||||
  inherit (builtins) elem;
 | 
			
		||||
  inherit (lib) concatStringsSep concatMap concatMapStringsSep mkIf mkDefault mkMerge mkForce mkVMOverride;
 | 
			
		||||
  inherit (lib.my) mkOpt mkBoolOpt mkVMOverride' dummyOption;
 | 
			
		||||
 | 
			
		||||
    cfg = config.my.tmproot;
 | 
			
		||||
  cfg = config.my.tmproot;
 | 
			
		||||
 | 
			
		||||
    showUnsaved =
 | 
			
		||||
      ''
 | 
			
		||||
        #!${pkgs.python310}/bin/python
 | 
			
		||||
        import stat
 | 
			
		||||
        import sys
 | 
			
		||||
        import os
 | 
			
		||||
  showUnsaved =
 | 
			
		||||
    ''
 | 
			
		||||
      #!${pkgs.python310}/bin/python
 | 
			
		||||
      import stat
 | 
			
		||||
      import sys
 | 
			
		||||
      import os
 | 
			
		||||
 | 
			
		||||
        ignored = [
 | 
			
		||||
          ${concatStringsSep ",\n  " (map (p: "'${p}'") cfg.unsaved.ignore)}
 | 
			
		||||
        ]
 | 
			
		||||
      ignored = [
 | 
			
		||||
        ${concatStringsSep ",\n  " (map (p: "'${p}'") cfg.unsaved.ignore)}
 | 
			
		||||
      ]
 | 
			
		||||
 | 
			
		||||
        base = '/'
 | 
			
		||||
        base_dev = os.stat(base).st_dev
 | 
			
		||||
      base = '/'
 | 
			
		||||
      base_dev = os.stat(base).st_dev
 | 
			
		||||
 | 
			
		||||
        def recurse(p, link=None):
 | 
			
		||||
          try:
 | 
			
		||||
            for ignore in ignored:
 | 
			
		||||
              if p.startswith(ignore):
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            st = os.lstat(p)
 | 
			
		||||
            if st.st_dev != base_dev:
 | 
			
		||||
      def recurse(p, link=None):
 | 
			
		||||
        try:
 | 
			
		||||
          for ignore in ignored:
 | 
			
		||||
            if p.startswith(ignore):
 | 
			
		||||
              return
 | 
			
		||||
 | 
			
		||||
            if stat.S_ISLNK(st.st_mode):
 | 
			
		||||
              target = os.path.realpath(p, strict=False)
 | 
			
		||||
              if os.access(target, os.F_OK):
 | 
			
		||||
                recurse(target, link=p)
 | 
			
		||||
                return
 | 
			
		||||
            elif stat.S_ISDIR(st.st_mode):
 | 
			
		||||
              for e in os.listdir(p):
 | 
			
		||||
                recurse(os.path.join(p, e))
 | 
			
		||||
          st = os.lstat(p)
 | 
			
		||||
          if st.st_dev != base_dev:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
          if stat.S_ISLNK(st.st_mode):
 | 
			
		||||
            target = os.path.realpath(p, strict=False)
 | 
			
		||||
            if os.access(target, os.F_OK):
 | 
			
		||||
              recurse(target, link=p)
 | 
			
		||||
              return
 | 
			
		||||
          elif stat.S_ISDIR(st.st_mode):
 | 
			
		||||
            for e in os.listdir(p):
 | 
			
		||||
              recurse(os.path.join(p, e))
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
            print(link or p)
 | 
			
		||||
          except PermissionError as ex:
 | 
			
		||||
            print(f'{p}: {ex.strerror}', file=sys.stderr)
 | 
			
		||||
          print(link or p)
 | 
			
		||||
        except PermissionError as ex:
 | 
			
		||||
          print(f'{p}: {ex.strerror}', file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
        recurse(base)
 | 
			
		||||
      '';
 | 
			
		||||
      recurse(base)
 | 
			
		||||
    '';
 | 
			
		||||
 | 
			
		||||
    rootDef = {
 | 
			
		||||
      device = "yeet";
 | 
			
		||||
      fsType = "tmpfs";
 | 
			
		||||
      options = [ "size=${cfg.size}" ];
 | 
			
		||||
  rootDef = {
 | 
			
		||||
    device = "yeet";
 | 
			
		||||
    fsType = "tmpfs";
 | 
			
		||||
    options = [ "size=${cfg.size}" ];
 | 
			
		||||
  };
 | 
			
		||||
in
 | 
			
		||||
{
 | 
			
		||||
  imports = [ inputs.impermanence.nixosModule ];
 | 
			
		||||
 | 
			
		||||
  options = with lib.types; {
 | 
			
		||||
    my.tmproot = {
 | 
			
		||||
      enable = mkBoolOpt true;
 | 
			
		||||
      persistDir = mkOpt str "/persist";
 | 
			
		||||
      size = mkOpt str "2G";
 | 
			
		||||
      unsaved = {
 | 
			
		||||
        showMotd = mkBoolOpt true;
 | 
			
		||||
        ignore = mkOpt (listOf str) [ ];
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
  in {
 | 
			
		||||
    imports = [ inputs.impermanence.nixosModule ];
 | 
			
		||||
 | 
			
		||||
    options = with lib.types; {
 | 
			
		||||
      my.tmproot = {
 | 
			
		||||
        enable = mkBoolOpt true;
 | 
			
		||||
        persistDir = mkOpt str "/persist";
 | 
			
		||||
        size = mkOpt str "2G";
 | 
			
		||||
        unsaved = {
 | 
			
		||||
          showMotd = mkBoolOpt true;
 | 
			
		||||
          ignore = mkOpt (listOf str) [];
 | 
			
		||||
        };
 | 
			
		||||
    # Forward declare options that won't exist until the VM module is actually imported
 | 
			
		||||
    virtualisation = {
 | 
			
		||||
      diskImage = dummyOption;
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  config = mkIf cfg.enable (mkMerge [
 | 
			
		||||
    {
 | 
			
		||||
      assertions = [
 | 
			
		||||
        {
 | 
			
		||||
          assertion = config.fileSystems ? "${cfg.persistDir}";
 | 
			
		||||
          message = "The 'fileSystems' option does not specify your persistence file system (${cfg.persistDir}).";
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
          # I mean you probably _could_, but if you're doing tmproot... come on
 | 
			
		||||
          assertion = !config.users.mutableUsers;
 | 
			
		||||
          message = "users.mutableUsers is incompatible with tmproot";
 | 
			
		||||
        }
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      my.tmproot.unsaved.ignore = [
 | 
			
		||||
        "/tmp"
 | 
			
		||||
 | 
			
		||||
        # setup-etc.pl will create this for us
 | 
			
		||||
        "/etc/NIXOS"
 | 
			
		||||
 | 
			
		||||
        # Once mutableUsers is disabled, we should be all clear here
 | 
			
		||||
        "/etc/passwd"
 | 
			
		||||
        "/etc/group"
 | 
			
		||||
        "/etc/shadow"
 | 
			
		||||
        "/etc/subuid"
 | 
			
		||||
        "/etc/subgid"
 | 
			
		||||
 | 
			
		||||
        # Lock file for /etc/{passwd,shadow}
 | 
			
		||||
        "/etc/.pwd.lock"
 | 
			
		||||
 | 
			
		||||
        # systemd last updated? I presume they'll get updated on boot...
 | 
			
		||||
        "/etc/.updated"
 | 
			
		||||
        "/var/.updated"
 | 
			
		||||
 | 
			
		||||
        # Specifies obsolete files that should be deleted on activation - we'll never have those!
 | 
			
		||||
        "/etc/.clean"
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      environment.systemPackages = [
 | 
			
		||||
        (pkgs.writeScriptBin "tmproot-unsaved" showUnsaved)
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      # Catch non-existent source directories that are needed for boot (see `pathsNeededForBoot` in
 | 
			
		||||
      # nixos/lib/util.nix). We do this by monkey-patching the `waitDevice` function that would otherwise hang.
 | 
			
		||||
      boot.initrd.postDeviceCommands =
 | 
			
		||||
        ''
 | 
			
		||||
          ensurePersistSource() {
 | 
			
		||||
            [ -e "/mnt-root$1" ] && return
 | 
			
		||||
            echo "Persistent source directory $1 does not exist, creating..."
 | 
			
		||||
            install -dm "$2" "/mnt-root$1" || fail
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          _waitDevice() {
 | 
			
		||||
            local device="$1"
 | 
			
		||||
 | 
			
		||||
            ${concatMapStringsSep " || \\\n  " (d:
 | 
			
		||||
              let
 | 
			
		||||
                sourceDir = "${d.persistentStoragePath}${d.directory}";
 | 
			
		||||
              in
 | 
			
		||||
                ''([ "$device" = "/mnt-root${sourceDir}" ] && ensurePersistSource "${sourceDir}" "${d.mode}")'')
 | 
			
		||||
              config.environment.persistence."${cfg.persistDir}".directories}
 | 
			
		||||
 | 
			
		||||
            waitDevice "$@"
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          type waitDevice > /dev/null || (echo "waitDevice is missing!"; fail)
 | 
			
		||||
          alias waitDevice=_waitDevice
 | 
			
		||||
        '';
 | 
			
		||||
 | 
			
		||||
      environment.persistence."${cfg.persistDir}" = {
 | 
			
		||||
        hideMounts = mkDefault true;
 | 
			
		||||
        directories = [
 | 
			
		||||
          "/var/log"
 | 
			
		||||
          # In theory we'd include only the files needed individually (i.e. the {U,G}ID map files that track deleted
 | 
			
		||||
          # users and groups), but `update-users-groups.pl` actually deletes the original files for "atomic update".
 | 
			
		||||
          # Also the script runs before impermanence does.
 | 
			
		||||
          "/var/lib/nixos"
 | 
			
		||||
          "/var/lib/systemd"
 | 
			
		||||
        ];
 | 
			
		||||
        files = [
 | 
			
		||||
          "/etc/machine-id"
 | 
			
		||||
        ];
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      # Forward declare options that won't exist until the VM module is actually imported
 | 
			
		||||
      my.dynamic-motd.script = mkIf cfg.unsaved.showMotd
 | 
			
		||||
        ''
 | 
			
		||||
          tmprootUnsaved() {
 | 
			
		||||
            local count="$(tmproot-unsaved | wc -l)"
 | 
			
		||||
            [ $count -eq 0 ] && return
 | 
			
		||||
 | 
			
		||||
            echo
 | 
			
		||||
            echo -e "\t\e[31;1;4mWarning:\e[0m $count file(s) on / will be lost on shutdown!"
 | 
			
		||||
            echo -e '\tTo see them, run `tmproot-unsaved` as root.'
 | 
			
		||||
            echo -e '\tAdd these files to `environment.persistence."${cfg.persistDir}"` to keep them!'
 | 
			
		||||
            echo -e '\tOtherwise, they can be ignored by adding to `my.tmproot.unsaved.ignore`.'
 | 
			
		||||
            echo
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          tmprootUnsaved
 | 
			
		||||
        '';
 | 
			
		||||
 | 
			
		||||
      fileSystems."/" = rootDef;
 | 
			
		||||
 | 
			
		||||
      virtualisation = {
 | 
			
		||||
        diskImage = dummyOption;
 | 
			
		||||
        diskImage = "./.vms/${config.system.name}-persist.qcow2";
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
    }
 | 
			
		||||
    (mkIf config.services.openssh.enable {
 | 
			
		||||
      environment.persistence."${cfg.persistDir}".files =
 | 
			
		||||
        concatMap (k: [ k.path "${k.path}.pub" ]) config.services.openssh.hostKeys;
 | 
			
		||||
    })
 | 
			
		||||
    (mkIf config.networking.resolvconf.enable {
 | 
			
		||||
      my.tmproot.unsaved.ignore = [ "/etc/resolv.conf" ];
 | 
			
		||||
    })
 | 
			
		||||
    (mkIf config.security.doas.enable {
 | 
			
		||||
      my.tmproot.unsaved.ignore = [ "/etc/doas.conf" ];
 | 
			
		||||
    })
 | 
			
		||||
    (mkIf config.my.boot.isDevVM {
 | 
			
		||||
      my.tmproot.unsaved.ignore = [ "/nix" ];
 | 
			
		||||
 | 
			
		||||
    config = mkIf cfg.enable (mkMerge [
 | 
			
		||||
      {
 | 
			
		||||
        assertions = [
 | 
			
		||||
          {
 | 
			
		||||
            assertion = config.fileSystems ? "${cfg.persistDir}";
 | 
			
		||||
            message = "The 'fileSystems' option does not specify your persistence file system (${cfg.persistDir}).";
 | 
			
		||||
          }
 | 
			
		||||
          {
 | 
			
		||||
            # I mean you probably _could_, but if you're doing tmproot... come on
 | 
			
		||||
            assertion = !config.users.mutableUsers;
 | 
			
		||||
            message = "users.mutableUsers is incompatible with tmproot";
 | 
			
		||||
          }
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        my.tmproot.unsaved.ignore = [
 | 
			
		||||
          "/tmp"
 | 
			
		||||
 | 
			
		||||
          # setup-etc.pl will create this for us
 | 
			
		||||
          "/etc/NIXOS"
 | 
			
		||||
 | 
			
		||||
          # Once mutableUsers is disabled, we should be all clear here
 | 
			
		||||
          "/etc/passwd"
 | 
			
		||||
          "/etc/group"
 | 
			
		||||
          "/etc/shadow"
 | 
			
		||||
          "/etc/subuid"
 | 
			
		||||
          "/etc/subgid"
 | 
			
		||||
 | 
			
		||||
          # Lock file for /etc/{passwd,shadow}
 | 
			
		||||
          "/etc/.pwd.lock"
 | 
			
		||||
 | 
			
		||||
          # systemd last updated? I presume they'll get updated on boot...
 | 
			
		||||
          "/etc/.updated"
 | 
			
		||||
          "/var/.updated"
 | 
			
		||||
 | 
			
		||||
          # Specifies obsolete files that should be deleted on activation - we'll never have those!
 | 
			
		||||
          "/etc/.clean"
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        environment.systemPackages = [
 | 
			
		||||
          (pkgs.writeScriptBin "tmproot-unsaved" showUnsaved)
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        # Catch non-existent source directories that are needed for boot (see `pathsNeededForBoot` in
 | 
			
		||||
        # nixos/lib/util.nix). We do this by monkey-patching the `waitDevice` function that would otherwise hang.
 | 
			
		||||
        boot.initrd.postDeviceCommands =
 | 
			
		||||
          ''
 | 
			
		||||
            ensurePersistSource() {
 | 
			
		||||
              [ -e "/mnt-root$1" ] && return
 | 
			
		||||
              echo "Persistent source directory $1 does not exist, creating..."
 | 
			
		||||
              install -dm "$2" "/mnt-root$1" || fail
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _waitDevice() {
 | 
			
		||||
              local device="$1"
 | 
			
		||||
 | 
			
		||||
              ${concatMapStringsSep " || \\\n  " (d:
 | 
			
		||||
                let
 | 
			
		||||
                  sourceDir = "${d.persistentStoragePath}${d.directory}";
 | 
			
		||||
                in
 | 
			
		||||
                  ''([ "$device" = "/mnt-root${sourceDir}" ] && ensurePersistSource "${sourceDir}" "${d.mode}")'')
 | 
			
		||||
                config.environment.persistence."${cfg.persistDir}".directories}
 | 
			
		||||
 | 
			
		||||
              waitDevice "$@"
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            type waitDevice > /dev/null || (echo "waitDevice is missing!"; fail)
 | 
			
		||||
            alias waitDevice=_waitDevice
 | 
			
		||||
          '';
 | 
			
		||||
 | 
			
		||||
        environment.persistence."${cfg.persistDir}" = {
 | 
			
		||||
          hideMounts = mkDefault true;
 | 
			
		||||
          directories = [
 | 
			
		||||
            "/var/log"
 | 
			
		||||
            # In theory we'd include only the files needed individually (i.e. the {U,G}ID map files that track deleted
 | 
			
		||||
            # users and groups), but `update-users-groups.pl` actually deletes the original files for "atomic update".
 | 
			
		||||
            # Also the script runs before impermanence does.
 | 
			
		||||
            "/var/lib/nixos"
 | 
			
		||||
            "/var/lib/systemd"
 | 
			
		||||
          ];
 | 
			
		||||
          files = [
 | 
			
		||||
            "/etc/machine-id"
 | 
			
		||||
          ];
 | 
			
		||||
      fileSystems = mkVMOverride {
 | 
			
		||||
        "/" = mkVMOverride' rootDef;
 | 
			
		||||
        # Hijack the "root" device for persistence in the VM
 | 
			
		||||
        "${cfg.persistDir}" = {
 | 
			
		||||
          device = config.virtualisation.bootDevice;
 | 
			
		||||
          neededForBoot = true;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        my.dynamic-motd.script = mkIf cfg.unsaved.showMotd
 | 
			
		||||
          ''
 | 
			
		||||
            tmprootUnsaved() {
 | 
			
		||||
              local count="$(tmproot-unsaved | wc -l)"
 | 
			
		||||
              [ $count -eq 0 ] && return
 | 
			
		||||
 | 
			
		||||
              echo
 | 
			
		||||
              echo -e "\t\e[31;1;4mWarning:\e[0m $count file(s) on / will be lost on shutdown!"
 | 
			
		||||
              echo -e '\tTo see them, run `tmproot-unsaved` as root.'
 | 
			
		||||
              echo -e '\tAdd these files to `environment.persistence."${cfg.persistDir}"` to keep them!'
 | 
			
		||||
              echo -e '\tOtherwise, they can be ignored by adding to `my.tmproot.unsaved.ignore`.'
 | 
			
		||||
              echo
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            tmprootUnsaved
 | 
			
		||||
          '';
 | 
			
		||||
 | 
			
		||||
        fileSystems."/" = rootDef;
 | 
			
		||||
 | 
			
		||||
        virtualisation = {
 | 
			
		||||
          diskImage = "./.vms/${config.system.name}-persist.qcow2";
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
      (mkIf config.services.openssh.enable {
 | 
			
		||||
        environment.persistence."${cfg.persistDir}".files =
 | 
			
		||||
          concatMap (k: [ k.path "${k.path}.pub" ]) config.services.openssh.hostKeys;
 | 
			
		||||
      })
 | 
			
		||||
      (mkIf config.networking.resolvconf.enable {
 | 
			
		||||
        my.tmproot.unsaved.ignore = [ "/etc/resolv.conf" ];
 | 
			
		||||
      })
 | 
			
		||||
      (mkIf config.security.doas.enable {
 | 
			
		||||
        my.tmproot.unsaved.ignore = [ "/etc/doas.conf" ];
 | 
			
		||||
      })
 | 
			
		||||
      (mkIf config.my.boot.isDevVM {
 | 
			
		||||
        my.tmproot.unsaved.ignore = [ "/nix" ];
 | 
			
		||||
 | 
			
		||||
        fileSystems = mkVMOverride {
 | 
			
		||||
          "/" = mkVMOverride' rootDef;
 | 
			
		||||
          # Hijack the "root" device for persistence in the VM
 | 
			
		||||
          "${cfg.persistDir}" = {
 | 
			
		||||
            device = config.virtualisation.bootDevice;
 | 
			
		||||
            neededForBoot = true;
 | 
			
		||||
          };
 | 
			
		||||
        };
 | 
			
		||||
      })
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
      };
 | 
			
		||||
    })
 | 
			
		||||
  ]);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user