Add initial installer
This commit is contained in:
		
							
								
								
									
										31
									
								
								nixos/boxes/colony.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								nixos/boxes/colony.nix
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
{ lib, pkgs, ... }:
 | 
			
		||||
{
 | 
			
		||||
  my = {
 | 
			
		||||
    firewall = {
 | 
			
		||||
      trustedInterfaces = [ "blah" ];
 | 
			
		||||
      nat = {
 | 
			
		||||
        externalInterface = "eth0";
 | 
			
		||||
        forwardPorts = [
 | 
			
		||||
          {
 | 
			
		||||
            proto = "tcp";
 | 
			
		||||
            sourcePort = 2222;
 | 
			
		||||
            destination = "127.0.0.1:22";
 | 
			
		||||
          }
 | 
			
		||||
        ];
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
    server.enable = true;
 | 
			
		||||
 | 
			
		||||
    homeConfig = {};
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  fileSystems = {
 | 
			
		||||
    "/persist" = {
 | 
			
		||||
      device = "/dev/disk/by-label/persist";
 | 
			
		||||
      fsType = "ext4";
 | 
			
		||||
      neededForBoot = true;
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  networking = { };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										118
									
								
								nixos/default.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								nixos/default.nix
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
{ lib, pkgsFlakes, hmFlakes, inputs, pkgs', modules, homeModules }:
 | 
			
		||||
let
 | 
			
		||||
  inherit (builtins) attrValues mapAttrs;
 | 
			
		||||
  inherit (lib) flatten optional optionals mkDefault mkForce;
 | 
			
		||||
  inherit (lib.my) homeStateVersion;
 | 
			
		||||
 | 
			
		||||
  mkSystem =
 | 
			
		||||
    name: {
 | 
			
		||||
      system,
 | 
			
		||||
 | 
			
		||||
      nixpkgs ? "unstable",
 | 
			
		||||
      home-manager ? nixpkgs,
 | 
			
		||||
      hmNixpkgs ? home-manager,
 | 
			
		||||
 | 
			
		||||
      config,
 | 
			
		||||
      # This causes a (very slow) docs rebuild on every change to a module's options it seems
 | 
			
		||||
      docCustom ? true,
 | 
			
		||||
    }:
 | 
			
		||||
    let
 | 
			
		||||
      # The flake contains `nixosSystem`, so we do need it (if we didn't have the TODO hacked version anyway)
 | 
			
		||||
      pkgsFlake = pkgsFlakes.${nixpkgs};
 | 
			
		||||
      # TODO: This is mostly yoinked from nixpkgs/flake.nix master (as of 2022/02/11) since 21.11's version has hacky
 | 
			
		||||
      # vm build stuff that breaks our impl. REMOVE WHEN 22.05 IS OUT!
 | 
			
		||||
      nixosSystem' = args:
 | 
			
		||||
        import "${pkgsFlake}/nixos/lib/eval-config.nix" (args // {
 | 
			
		||||
          modules = args.modules ++ [{
 | 
			
		||||
            system.nixos.versionSuffix =
 | 
			
		||||
              ".${lib.substring 0 8 pkgsFlake.lastModifiedDate}.${pkgsFlake.shortRev}";
 | 
			
		||||
            system.nixos.revision = pkgsFlake.rev;
 | 
			
		||||
          }];
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
      modules' = [
 | 
			
		||||
          # Importing modules from module args causes infinite recursion
 | 
			
		||||
          inputs.impermanence.nixosModule
 | 
			
		||||
          hmFlake.nixosModule
 | 
			
		||||
          inputs.agenix.nixosModules.age
 | 
			
		||||
      ] ++ modules;
 | 
			
		||||
      pkgs = pkgs'.${nixpkgs}.${system};
 | 
			
		||||
      allPkgs = mapAttrs (_: p: p.${system}) pkgs';
 | 
			
		||||
 | 
			
		||||
      hmFlake = hmFlakes.${home-manager};
 | 
			
		||||
    in
 | 
			
		||||
    nixosSystem' {
 | 
			
		||||
      # Gotta override lib here unforunately, eval-config.nix likes to import its own (unextended) lib. We explicitly
 | 
			
		||||
      # don't pass pkgs so that it'll be imported with modularly applied config and overlays.
 | 
			
		||||
      lib = pkgs.lib;
 | 
			
		||||
      # `baseModules` informs the manual which modules to document
 | 
			
		||||
      baseModules =
 | 
			
		||||
        (import "${pkgsFlake}/nixos/modules/module-list.nix") ++ (optionals docCustom modules');
 | 
			
		||||
      modules = (optionals (!docCustom) modules') ++ [
 | 
			
		||||
        (modArgs: {
 | 
			
		||||
          warnings = flatten [
 | 
			
		||||
            (optional (modArgs.config.home-manager.useGlobalPkgs && (nixpkgs != home-manager))
 | 
			
		||||
            ''
 | 
			
		||||
              Using global nixpkgs ${nixpkgs} with home-manager ${home-manager} may cause problems.
 | 
			
		||||
            '')
 | 
			
		||||
          ];
 | 
			
		||||
 | 
			
		||||
          _module.args = {
 | 
			
		||||
            inherit inputs;
 | 
			
		||||
            pkgs' = allPkgs;
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          system.name = name;
 | 
			
		||||
          networking.hostName = mkDefault name;
 | 
			
		||||
          nixpkgs = {
 | 
			
		||||
            inherit system;
 | 
			
		||||
            # Make sure any previously set config / overlays (e.g. lib which will be inherited by home-manager down the
 | 
			
		||||
            # line) are passed on when nixpkgs is imported.
 | 
			
		||||
            inherit (pkgs) config overlays;
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          # Unfortunately it seems there's no way to fully decouple home-manager's lib from NixOS's pkgs.lib. :(
 | 
			
		||||
          # https://github.com/nix-community/home-manager/blob/7c2ae0bdd20ddcaafe41ef669226a1df67f8aa06/nixos/default.nix#L22
 | 
			
		||||
          home-manager = {
 | 
			
		||||
            # Optimise if system and home-manager nixpkgs are the same
 | 
			
		||||
            useGlobalPkgs = mkDefault (nixpkgs == home-manager);
 | 
			
		||||
            sharedModules = homeModules ++ [
 | 
			
		||||
              {
 | 
			
		||||
                warnings = flatten [
 | 
			
		||||
                  (optional (!modArgs.config.home-manager.useGlobalPkgs && (hmNixpkgs != home-manager))
 | 
			
		||||
                  ''
 | 
			
		||||
                    Using per-user nixpkgs ${hmNixpkgs} with home-manager ${home-manager} may cause issues.
 | 
			
		||||
                  '')
 | 
			
		||||
                ];
 | 
			
		||||
 | 
			
		||||
                # pkgsPath is used by home-manager's nixkpgs module to import nixpkgs (i.e. if !useGlobalPkgs)
 | 
			
		||||
                _module.args = {
 | 
			
		||||
                  inherit inputs;
 | 
			
		||||
                  pkgsPath = toString pkgsFlakes.${hmNixpkgs};
 | 
			
		||||
                  pkgs' = allPkgs;
 | 
			
		||||
                };
 | 
			
		||||
              }
 | 
			
		||||
              (homeStateVersion home-manager)
 | 
			
		||||
            ];
 | 
			
		||||
          };
 | 
			
		||||
        })
 | 
			
		||||
        config
 | 
			
		||||
      ];
 | 
			
		||||
    };
 | 
			
		||||
in
 | 
			
		||||
mapAttrs mkSystem {
 | 
			
		||||
  colony = {
 | 
			
		||||
    system = "x86_64-linux";
 | 
			
		||||
    nixpkgs = "stable";
 | 
			
		||||
    home-manager = "unstable";
 | 
			
		||||
    config = boxes/colony.nix;
 | 
			
		||||
    docCustom = false;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  installer = {
 | 
			
		||||
    system = "x86_64-linux";
 | 
			
		||||
    nixpkgs = "unstable";
 | 
			
		||||
    config = ./installer.nix;
 | 
			
		||||
    docCustom = false;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								nixos/installer.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								nixos/installer.nix
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
{ lib, modulesPath, config, ... }:
 | 
			
		||||
let
 | 
			
		||||
  inherit (lib) mkDefault mkForce;
 | 
			
		||||
in
 | 
			
		||||
{
 | 
			
		||||
  imports = [
 | 
			
		||||
    # Lots of kernel modules and firmware
 | 
			
		||||
    "${modulesPath}/profiles/all-hardware.nix"
 | 
			
		||||
    # Useful tools to have
 | 
			
		||||
    "${modulesPath}/profiles/base.nix"
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  # Some of this is yoinked from modules/profiles/installation-device.nix
 | 
			
		||||
  config = {
 | 
			
		||||
    my = {
 | 
			
		||||
      # Whatever installer mechanism is chosen will provied an appropriate `/`
 | 
			
		||||
      tmproot.enable = false;
 | 
			
		||||
      firewall.nat.enable = false;
 | 
			
		||||
      server.enable = true;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    # Good to have docs in the installer!
 | 
			
		||||
    documentation.enable = mkForce true;
 | 
			
		||||
    documentation.nixos.enable = mkForce true;
 | 
			
		||||
 | 
			
		||||
    # Enable wpa_supplicant, but don't start it by default.
 | 
			
		||||
    networking.wireless.enable = mkDefault true;
 | 
			
		||||
    networking.wireless.userControlled.enable = true;
 | 
			
		||||
    systemd.services.wpa_supplicant.wantedBy = mkForce [];
 | 
			
		||||
 | 
			
		||||
    # Tell the Nix evaluator to garbage collect more aggressively.
 | 
			
		||||
    # This is desirable in memory-constrained environments that don't
 | 
			
		||||
    # (yet) have swap set up.
 | 
			
		||||
    environment.variables.GC_INITIAL_HEAP_SIZE = "1M";
 | 
			
		||||
 | 
			
		||||
    # Make the installer more likely to succeed in low memory
 | 
			
		||||
    # environments.  The kernel's overcommit heustistics bite us
 | 
			
		||||
    # fairly often, preventing processes such as nix-worker or
 | 
			
		||||
    # download-using-manifests.pl from forking even if there is
 | 
			
		||||
    # plenty of free memory.
 | 
			
		||||
    boot.kernel.sysctl."vm.overcommit_memory" = "1";
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										82
									
								
								nixos/modules/build.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								nixos/modules/build.nix
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
{ lib, extendModules, modulesPath, baseModules, options, config, ... }:
 | 
			
		||||
let
 | 
			
		||||
  inherit (lib) recursiveUpdate mkOption mkDefault mkIf mkMerge;
 | 
			
		||||
  inherit (lib.my) mkBoolOpt' dummyOption;
 | 
			
		||||
 | 
			
		||||
  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.build.isDevVM = true; }
 | 
			
		||||
    ];
 | 
			
		||||
  };
 | 
			
		||||
  asISO = extendModules {
 | 
			
		||||
    # TODO: see previous
 | 
			
		||||
    specialArgs = { inherit baseModules; };
 | 
			
		||||
    modules = lib.flatten [
 | 
			
		||||
      "${modulesPath}/installer/cd-dvd/iso-image.nix"
 | 
			
		||||
      (lib.optional config.my.build.allHardware { imports = [ "${modulesPath}/profiles/all-hardware.nix" ]; })
 | 
			
		||||
      {
 | 
			
		||||
        isoImage = {
 | 
			
		||||
          makeEfiBootable = true;
 | 
			
		||||
          makeUsbBootable = true;
 | 
			
		||||
          # Not necessarily an installer
 | 
			
		||||
          appendToMenuLabel = mkDefault "";
 | 
			
		||||
 | 
			
		||||
          squashfsCompression = "zstd -Xcompression-level 8";
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    ];
 | 
			
		||||
  };
 | 
			
		||||
in
 | 
			
		||||
{
 | 
			
		||||
  options = with lib.types; {
 | 
			
		||||
    my = {
 | 
			
		||||
      build = {
 | 
			
		||||
        isDevVM = mkBoolOpt' false "Whether the system is a development VM.";
 | 
			
		||||
        allHardware = mkBoolOpt' false
 | 
			
		||||
          ("Whether to enable a lot of firmware and kernel modules for a wide range of hardware." +
 | 
			
		||||
          "Only applies to some build targets.");
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      asDevVM = mkOption {
 | 
			
		||||
        inherit (asDevVM) type;
 | 
			
		||||
        default = { };
 | 
			
		||||
        visible = "shallow";
 | 
			
		||||
        description = "Configuration as a development VM";
 | 
			
		||||
      };
 | 
			
		||||
      asISO = mkOption {
 | 
			
		||||
        inherit (asISO) type;
 | 
			
		||||
        default = { };
 | 
			
		||||
        visible = "shallow";
 | 
			
		||||
        description = "Configuration as a bootable .iso image";
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      buildAs = options.system.build;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    # Forward declare options that won't exist until the VM module is actually imported
 | 
			
		||||
    virtualisation = {
 | 
			
		||||
      diskImage = dummyOption;
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  config = {
 | 
			
		||||
    virtualisation = {
 | 
			
		||||
      diskImage = mkDefault "./.vms/${config.system.name}.qcow2";
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    my = {
 | 
			
		||||
      buildAs = {
 | 
			
		||||
        # The meta.mainProgram should probably be set upstream but oh well...
 | 
			
		||||
        devVM = recursiveUpdate config.my.asDevVM.system.build.vm { meta.mainProgram = "run-${config.system.name}-vm"; };
 | 
			
		||||
        iso = config.my.asISO.system.build.isoImage;
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  meta.buildDocsInSandbox = false;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										144
									
								
								nixos/modules/common.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								nixos/modules/common.nix
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
{ lib, pkgs, pkgs', inputs, options, config, ... }:
 | 
			
		||||
let
 | 
			
		||||
  inherit (builtins) attrValues;
 | 
			
		||||
  inherit (lib) mkIf mkDefault mkMerge mkAliasDefinitions;
 | 
			
		||||
  inherit (lib.my) mkOpt' dummyOption;
 | 
			
		||||
 | 
			
		||||
  defaultUsername = "dev";
 | 
			
		||||
  uname = config.my.user.name;
 | 
			
		||||
in
 | 
			
		||||
{
 | 
			
		||||
  options = with lib.types; {
 | 
			
		||||
    my = {
 | 
			
		||||
      # Pretty hacky but too lazy to figure out if there's a better way to alias the options
 | 
			
		||||
      user = mkOpt' (attrsOf anything) { } "User definition (as `users.users.*`).";
 | 
			
		||||
      homeConfig = mkOpt' anything { } "Home configuration (as `home-manager.users.*`)";
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    # Only present in >=22.05, so forward declare
 | 
			
		||||
    documentation.nixos.options.warningsAreErrors = dummyOption;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  config = mkMerge [
 | 
			
		||||
    {
 | 
			
		||||
      my = {
 | 
			
		||||
        user = {
 | 
			
		||||
          name = mkDefault defaultUsername;
 | 
			
		||||
          isNormalUser = true;
 | 
			
		||||
          uid = mkDefault 1000;
 | 
			
		||||
          extraGroups = mkDefault [ "wheel" ];
 | 
			
		||||
          password = mkDefault "hunter2"; # TODO: secrets...
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      home-manager = {
 | 
			
		||||
        # Installs packages in the system config instead of in the local profile on activation
 | 
			
		||||
        useUserPackages = mkDefault true;
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      users = {
 | 
			
		||||
        mutableUsers = false;
 | 
			
		||||
        users.${uname} = mkAliasDefinitions options.my.user;
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      # NOTE: As the "outermost" module is still being evaluated in NixOS land, special params (e.g. pkgs) won't be
 | 
			
		||||
      # passed to it
 | 
			
		||||
      home-manager.users.${uname} = config.my.homeConfig;
 | 
			
		||||
 | 
			
		||||
      security = {
 | 
			
		||||
        sudo.enable = mkDefault false;
 | 
			
		||||
        doas = {
 | 
			
		||||
          enable = mkDefault true;
 | 
			
		||||
          wheelNeedsPassword = mkDefault false;
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      nix = {
 | 
			
		||||
        extraOptions =
 | 
			
		||||
          ''
 | 
			
		||||
            experimental-features = nix-command flakes ca-derivations
 | 
			
		||||
          '';
 | 
			
		||||
      };
 | 
			
		||||
      nixpkgs = {
 | 
			
		||||
        overlays = [
 | 
			
		||||
          (final: prev: { nix = inputs.nix.defaultPackage.${config.nixpkgs.system}; })
 | 
			
		||||
          # TODO: Wait for https://github.com/NixOS/nixpkgs/pull/159074 to arrive to nixos-unstable
 | 
			
		||||
          (final: prev: { remarshal = pkgs'.master.remarshal; })
 | 
			
		||||
        ];
 | 
			
		||||
        config = {
 | 
			
		||||
          allowUnfree = true;
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      documentation = {
 | 
			
		||||
        nixos = {
 | 
			
		||||
          enable = mkDefault true;
 | 
			
		||||
          options.warningsAreErrors = mkDefault false;
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      time.timeZone = mkDefault "Europe/Dublin";
 | 
			
		||||
 | 
			
		||||
      boot = {
 | 
			
		||||
        # Use latest LTS release by default
 | 
			
		||||
        kernelPackages = mkDefault pkgs.linuxKernel.packages.linux_5_15;
 | 
			
		||||
        loader = {
 | 
			
		||||
          efi = {
 | 
			
		||||
            efiSysMountPoint = mkDefault "/boot";
 | 
			
		||||
            canTouchEfiVariables = mkDefault false;
 | 
			
		||||
          };
 | 
			
		||||
          grub = {
 | 
			
		||||
            memtest86.enable = mkDefault true;
 | 
			
		||||
          };
 | 
			
		||||
          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
 | 
			
		||||
        vim
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      services = {
 | 
			
		||||
        kmscon = {
 | 
			
		||||
          enable = mkDefault true;
 | 
			
		||||
          hwRender = mkDefault true;
 | 
			
		||||
          extraOptions = "--verbose";
 | 
			
		||||
          extraConfig =
 | 
			
		||||
            ''
 | 
			
		||||
              font-name=SauceCodePro Nerd Font Mono
 | 
			
		||||
            '';
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        openssh = {
 | 
			
		||||
          enable = true;
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      system = {
 | 
			
		||||
        stateVersion = "21.11";
 | 
			
		||||
        configurationRevision = with inputs; mkIf (self ? rev) self.rev;
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
    (mkIf config.services.kmscon.enable {
 | 
			
		||||
      fonts.fonts = with pkgs; [
 | 
			
		||||
        (nerdfonts.override {
 | 
			
		||||
          fonts = [ "SourceCodePro" ];
 | 
			
		||||
        })
 | 
			
		||||
      ];
 | 
			
		||||
    })
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  meta.buildDocsInSandbox = false;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								nixos/modules/dynamic-motd.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								nixos/modules/dynamic-motd.nix
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
{ lib, pkgs, config, ... }:
 | 
			
		||||
let
 | 
			
		||||
  inherit (lib) optionalAttrs filterAttrs genAttrs mkIf mkDefault;
 | 
			
		||||
  inherit (lib.my) mkOpt' mkBoolOpt';
 | 
			
		||||
 | 
			
		||||
  cfg = config.my.dynamic-motd;
 | 
			
		||||
 | 
			
		||||
  scriptBin = pkgs.writeShellScript "dynamic-motd-script" cfg.script;
 | 
			
		||||
in
 | 
			
		||||
{
 | 
			
		||||
  options.my.dynamic-motd = with lib.types; {
 | 
			
		||||
    enable = mkBoolOpt' true "Whether to enable the dynamic message of the day PAM module.";
 | 
			
		||||
    services = mkOpt' (listOf str) [ "login" "ssh" ] "PAM services to enable the dynamic message of the day module for.";
 | 
			
		||||
    script = mkOpt' (nullOr lines) null "Script that generates message of the day.";
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  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}
 | 
			
		||||
        '';
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  meta.buildDocsInSandbox = false;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										173
									
								
								nixos/modules/firewall.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								nixos/modules/firewall.nix
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,173 @@
 | 
			
		||||
{ lib, options, config, ... }:
 | 
			
		||||
let
 | 
			
		||||
  inherit (lib) optionalString concatStringsSep concatMapStringsSep optionalAttrs mkIf mkDefault mkMerge mkOverride;
 | 
			
		||||
  inherit (lib.my) parseIPPort mkOpt' mkBoolOpt';
 | 
			
		||||
 | 
			
		||||
  cfg = config.my.firewall;
 | 
			
		||||
in
 | 
			
		||||
{
 | 
			
		||||
  options.my.firewall = with lib.types; {
 | 
			
		||||
    enable = mkBoolOpt' true "Whether to enable the nftables-based firewall.";
 | 
			
		||||
    trustedInterfaces = options.networking.firewall.trustedInterfaces;
 | 
			
		||||
    tcp = {
 | 
			
		||||
      allowed = mkOpt' (listOf (either port str)) [ "ssh" ] "TCP ports to open.";
 | 
			
		||||
    };
 | 
			
		||||
    udp = {
 | 
			
		||||
      allowed = mkOpt' (listOf (either port str)) [ ] "UDP ports to open.";
 | 
			
		||||
    };
 | 
			
		||||
    extraRules = mkOpt' lines "" "Arbitrary additional nftables rules.";
 | 
			
		||||
 | 
			
		||||
    nat = with options.networking.nat; {
 | 
			
		||||
      enable = mkBoolOpt' true "Whether to enable IP forwarding and NAT.";
 | 
			
		||||
      inherit externalInterface forwardPorts;
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  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}
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                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;
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              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";
 | 
			
		||||
        }
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      # 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 {
 | 
			
		||||
              ${optionalString
 | 
			
		||||
                (cfg.nat.externalInterface != null)
 | 
			
		||||
                "iifname ${cfg.nat.externalInterface} jump filter-port-forwards"}
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          table nat {
 | 
			
		||||
            chain port-forward {
 | 
			
		||||
              ${concatMapStringsSep "\n    " makeForward cfg.nat.forwardPorts}
 | 
			
		||||
            }
 | 
			
		||||
            chain prerouting {
 | 
			
		||||
              ${optionalString
 | 
			
		||||
                (cfg.nat.externalInterface != null)
 | 
			
		||||
                "iifname ${cfg.nat.externalInterface} jump port-forward"}
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        '';
 | 
			
		||||
    })
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  meta.buildDocsInSandbox = false;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								nixos/modules/server.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								nixos/modules/server.nix
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
{ config, lib, ... }:
 | 
			
		||||
let
 | 
			
		||||
  inherit (lib) mkIf;
 | 
			
		||||
  inherit (lib.my) mkBoolOpt';
 | 
			
		||||
 | 
			
		||||
  cfg = config.my.server;
 | 
			
		||||
in
 | 
			
		||||
{
 | 
			
		||||
  options.my.server.enable = mkBoolOpt' false "Whether to enable common configuration for servers.";
 | 
			
		||||
  config = mkIf cfg.enable {
 | 
			
		||||
    services = {
 | 
			
		||||
      getty.autologinUser = config.my.user.name;
 | 
			
		||||
      kmscon.autologinUser = config.my.user.name;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    my.homeConfig = {
 | 
			
		||||
      my.gui.enable = false;
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  meta.buildDocsInSandbox = false;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										215
									
								
								nixos/modules/tmproot.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								nixos/modules/tmproot.nix
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,215 @@
 | 
			
		||||
{ lib, pkgs, config, ... }:
 | 
			
		||||
let
 | 
			
		||||
  inherit (lib) optionalString concatStringsSep concatMap concatMapStringsSep mkIf mkDefault mkMerge mkVMOverride;
 | 
			
		||||
  inherit (lib.my) mkOpt' mkBoolOpt' mkVMOverride';
 | 
			
		||||
 | 
			
		||||
  cfg = config.my.tmproot;
 | 
			
		||||
  enablePersistence = cfg.persistDir != null;
 | 
			
		||||
 | 
			
		||||
  showUnsaved =
 | 
			
		||||
    ''
 | 
			
		||||
      #!${pkgs.python310}/bin/python
 | 
			
		||||
      import stat
 | 
			
		||||
      import sys
 | 
			
		||||
      import os
 | 
			
		||||
 | 
			
		||||
      ignored = [
 | 
			
		||||
        ${concatStringsSep ",\n  " (map (p: "'${p}'") cfg.unsaved.ignore)}
 | 
			
		||||
      ]
 | 
			
		||||
 | 
			
		||||
      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:
 | 
			
		||||
            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)
 | 
			
		||||
 | 
			
		||||
      recurse(base)
 | 
			
		||||
    '';
 | 
			
		||||
 | 
			
		||||
  rootDef = {
 | 
			
		||||
    device = "yeet";
 | 
			
		||||
    fsType = "tmpfs";
 | 
			
		||||
    options = [ "size=${cfg.size}" ];
 | 
			
		||||
  };
 | 
			
		||||
in
 | 
			
		||||
{
 | 
			
		||||
  options = with lib.types; {
 | 
			
		||||
    my.tmproot = {
 | 
			
		||||
      enable = mkBoolOpt' true "Whether to enable tmproot.";
 | 
			
		||||
      persistDir = mkOpt' (nullOr str) "/persist" "Path where persisted files are stored.";
 | 
			
		||||
      size = mkOpt' str "2G" "Size of tmpfs root";
 | 
			
		||||
      unsaved = {
 | 
			
		||||
        showMotd = mkBoolOpt' true "Whether to show unsaved files with `dynamic-motd`.";
 | 
			
		||||
        ignore = mkOpt' (listOf str) [ ] "Path prefixes to ignore if unsaved.";
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  config = mkIf cfg.enable (mkMerge [
 | 
			
		||||
    {
 | 
			
		||||
      assertions = [
 | 
			
		||||
        {
 | 
			
		||||
          # 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)
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      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.'
 | 
			
		||||
            ${optionalString enablePersistence ''
 | 
			
		||||
              echo -e '\tAdd these files to `environment.persistence."${cfg.persistDir}"` to keep them!'
 | 
			
		||||
            ''}
 | 
			
		||||
            echo -e "\tIf they don't need to be kept, add them to \`my.tmproot.unsaved.ignore\`."
 | 
			
		||||
            echo
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          tmprootUnsaved
 | 
			
		||||
        '';
 | 
			
		||||
 | 
			
		||||
      fileSystems."/" = rootDef;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    (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.build.isDevVM {
 | 
			
		||||
      my.tmproot.unsaved.ignore = [ "/nix" ];
 | 
			
		||||
 | 
			
		||||
      fileSystems = mkVMOverride {
 | 
			
		||||
        "/" = mkVMOverride' rootDef;
 | 
			
		||||
      };
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    (mkIf enablePersistence (mkMerge [
 | 
			
		||||
      {
 | 
			
		||||
        assertions = [
 | 
			
		||||
          {
 | 
			
		||||
            assertion = config.fileSystems ? "${cfg.persistDir}";
 | 
			
		||||
            message = "The 'fileSystems' option does not specify your persistence file system (${cfg.persistDir}).";
 | 
			
		||||
          }
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        # 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"
 | 
			
		||||
          ];
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        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.my.build.isDevVM {
 | 
			
		||||
        fileSystems = mkVMOverride {
 | 
			
		||||
          # Hijack the "root" device for persistence in the VM
 | 
			
		||||
          "${cfg.persistDir}" = {
 | 
			
		||||
            device = config.virtualisation.bootDevice;
 | 
			
		||||
            neededForBoot = true;
 | 
			
		||||
          };
 | 
			
		||||
        };
 | 
			
		||||
      })
 | 
			
		||||
    ]))
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  meta.buildDocsInSandbox = false;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user