From 7537cf4205ef100b9ce1d49c9bc7af5db4cc4124 Mon Sep 17 00:00:00 2001 From: Jack O'Sullivan Date: Sat, 19 Feb 2022 22:55:53 +0000 Subject: [PATCH] Functioning installation --- authorized_keys | 1 + flake.nix | 22 +++- home-manager/modules/common.nix | 23 ++-- install.nix | 205 ++++++++++++++++++++++++++++++++ lib.nix | 50 +++++++- nixos/boxes/colony.nix | 21 +++- nixos/installer.nix | 72 ++++++++--- nixos/modules/common.nix | 71 +++++++---- nixos/modules/deploy-rs.nix | 66 ++++++++++ nixos/modules/server.nix | 9 +- nixos/modules/tmproot.nix | 7 +- 11 files changed, 487 insertions(+), 60 deletions(-) create mode 100644 authorized_keys create mode 100644 install.nix create mode 100644 nixos/modules/deploy-rs.nix diff --git a/authorized_keys b/authorized_keys new file mode 100644 index 0000000..8a3003e --- /dev/null +++ b/authorized_keys @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+kCHXqtznkT9IBN5WxZHmXI97k3BumT+N4lyHWMo0pykpACCOcGw52EXxQveNqgcwcRUgamL9A2JTE//WRf3O4nBofeTRNKcRxTjRoUVIt/F0xbf09yWBqJOXZ8rqLkXhRvSpr1TCUZtYVp5iLtpERp622OMIqHSwa6HlxBqsCFkBeq1bRyNtYK/IaQAuBPW9MNeFriGqA0Vq078ccXp+JINxJbr+ZJybVg6PVqnMD+PgGMZQLkoWjwjH3vcJZZt584UPtrXKpNZuKy6dcMCb2U+O9NOaO66168sBVuK0kZHh51nJ7ZH38VLGiBipRgIQ1fzic3Ncn6GC9ko3/OwT jackos1998@gmail.com diff --git a/flake.nix b/flake.nix index 940ad84..b484a26 100644 --- a/flake.nix +++ b/flake.nix @@ -40,9 +40,9 @@ }: let inherit (builtins) mapAttrs attrValues; - inherit (lib) recurseIntoAttrs; + inherit (lib) recurseIntoAttrs filterAttrs; inherit (lib.flake) flattenTree eachDefaultSystem; - inherit (lib.my) attrsToList inlineModules mkDefaultSystemsPkgs flakePackageOverlay; + inherit (lib.my) attrsToNVList inlineModules mkDefaultSystemsPkgs flakePackageOverlay; # Extend a lib with extras that _must not_ internally reference private nixpkgs. flake-utils doesn't, but many # other flakes (e.g. home-manager) probably do internally. @@ -97,6 +97,7 @@ tmproot = "tmproot.nix"; firewall = "firewall.nix"; server = "server.nix"; + deploy-rs = "deploy-rs.nix"; }; homeModules = mapAttrs (_: f: ./. + "/home-manager/modules/${f}") { common = "common.nix"; @@ -111,6 +112,7 @@ nixosModules = inlineModules modules; homeModules = inlineModules homeModules; + # TODO: Cleanup and possibly even turn into modules? nixosConfigurations = import ./nixos { inherit lib pkgsFlakes hmFlakes inputs; pkgs' = configPkgs'; @@ -126,7 +128,15 @@ pkgs' = configPkgs'; modules = attrValues homeModules; }; - homes = mapAttrs(_: home: home.activationPackage) self.homeConfigurations; + homes = mapAttrs (_: home: home.activationPackage) self.homeConfigurations; + + deploy = { + nodes = filterAttrs (_: n: n != null) + (mapAttrs (_: system: system.config.my.deploy.rendered) self.nixosConfigurations); + + autoRollback = true; + magicRollback = true; + }; } // (eachDefaultSystem (system: let @@ -139,10 +149,14 @@ { checks = flattenTree { homeConfigurations = recurseIntoAttrs self.homes; + deploy = recurseIntoAttrs (pkgs.deploy-rs.lib.deployChecks self.deploy); }; + # TODO: Move shell to a separate file? devShell = pkgs.devshell.mkShell { - env = attrsToList { + imports = [ ./install.nix ]; + + env = attrsToNVList { # starship will show this name = "devshell"; diff --git a/home-manager/modules/common.nix b/home-manager/modules/common.nix index 82666bb..9dd81b2 100644 --- a/home-manager/modules/common.nix +++ b/home-manager/modules/common.nix @@ -1,7 +1,7 @@ { lib, pkgs, pkgs', inputs, options, config, ... }@args: let - inherit (builtins) mapAttrs; - inherit (lib) concatStringsSep optionalAttrs versionAtLeast mkMerge mkIf mkDefault mkOption; + inherit (builtins) mapAttrs readFile; + inherit (lib) concatMapStrings concatStringsSep optionalAttrs versionAtLeast mkMerge mkIf mkDefault mkOption; inherit (lib.hm) dag; inherit (lib.my) mkOpt' dummyOption; in @@ -17,6 +17,7 @@ in ssh = { authKeys = { literal = mkOpt' (listOf singleLineStr) [ ] "List of OpenSSH keys to allow"; + files = mkOpt' (listOf str) [ ] "List of OpenSSH key files to allow"; }; matchBlocks = mkOpt' (attrsOf anything) { } "SSH match blocks"; }; @@ -42,9 +43,6 @@ in isStandalone = !(args ? osConfig); ssh = { - authKeys.literal = [ - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+kCHXqtznkT9IBN5WxZHmXI97k3BumT+N4lyHWMo0pykpACCOcGw52EXxQveNqgcwcRUgamL9A2JTE//WRf3O4nBofeTRNKcRxTjRoUVIt/F0xbf09yWBqJOXZ8rqLkXhRvSpr1TCUZtYVp5iLtpERp622OMIqHSwa6HlxBqsCFkBeq1bRyNtYK/IaQAuBPW9MNeFriGqA0Vq078ccXp+JINxJbr+ZJybVg6PVqnMD+PgGMZQLkoWjwjH3vcJZZt584UPtrXKpNZuKy6dcMCb2U+O9NOaO66168sBVuK0kZHh51nJ7ZH38VLGiBipRgIQ1fzic3Ncn6GC9ko3/OwT jackos1998@gmail.com" - ]; matchBlocks = { nix-dev-vm = { user = "dev"; @@ -74,10 +72,13 @@ in }; }; - home.file.".ssh/authorized_keys".text = mkIf config.programs.ssh.enable - '' - ${concatStringsSep "\n" config.my.ssh.authKeys.literal} - ''; + home.file.".ssh/authorized_keys" = with config.my.ssh.authKeys; + mkIf (config.programs.ssh.enable && (literal != [ ] || files != [ ])) { + text = '' + ${concatStringsSep "\n" literal} + ${concatMapStrings (f: readFile f + "\n") files} + ''; + }; programs = { # Even when enabled this will only be actually installed in standalone mode @@ -179,6 +180,10 @@ in }; }) (mkIf config.my.isStandalone { + my = { + ssh.authKeys.files = [ lib.my.authorizedKeys ]; + }; + fonts.fontconfig.enable = true; home = { diff --git a/install.nix b/install.nix new file mode 100644 index 0000000..adbc758 --- /dev/null +++ b/install.nix @@ -0,0 +1,205 @@ +{ lib, pkgs, config, ... }: +let + inherit (lib) mapAttrsToList concatMapStringsSep; + inherit (lib.my) mkOpt' attrsToNVList; + + parseArgs = opts: + '' + POSITIONAL_ARGS=() + + while [ $# -gt 0 ]; do + # shellcheck disable=SC2221,SC2222 + case $1 in + ${opts} + -*|--*) + die "Unknown option $1" + ;; + *) + POSITIONAL_ARGS+=("$1") # save positional arg + shift # past argument + ;; + esac + done + + set -- "''${POSITIONAL_ARGS[@]}" # restore positional parameters + ''; + installCommon = pkgs.writeShellApplication { + name = "install-common.sh"; + runtimeInputs = with pkgs; [ + coreutils + gnugrep + openssh + nixVersions.stable + jq + ]; + text = + '' + log() { + echo -e "$@" >&2 + } + debug() { + [ -n "$DEBUG" ] || return 0 + log "[\e[32;1mdebug\e[0m]: \e[32m$*\e[0m" + } + info() { + log "[\e[36;1minfo\e[0m]: \e[36m$*\e[0m" + } + warn() { + log "[\e[33;1minfo\e[0m]: \e[33m$*\e[0m" + } + error() { + log "[\e[31;1minfo\e[0m]: \e[31m$*\e[0m" + } + die() { + error "$@" + exit 1 + } + + askYN() { + local question="$1" + local options default + + if [ "$2" = y ]; then + options="Y/n" + default="y" + else + options="y/N" + default="n" + fi + + local input + read -p "$question [$options] " -n 1 -s -r input + : "''${input:=$default}" + echo "$input" + [[ "$input" =~ ^[yY]$ ]] + } + + # : is a builtin that does nothing... + : "''${DEBUG:=}" + : "''${INSTALLER:=}" + : "''${INSTALLER_SSH_OPTS:=}" + : "''${INSTALLER_SSH_PORT:=22}" + + [ -z "$INSTALLER" ] && die "\$INSTALLER is not set" + + KNOWN_HOSTS="$(mktemp --tmpdir known_hosts.XXXXXX)" + cleanup() { + rm -f "$KNOWN_HOSTS" + } + trap cleanup EXIT + + IFS=" " read -ra SSH_OPTS <<< "$INSTALLER_SSH_OPTS" + SSH_OPTS+=(-o StrictHostKeyChecking=ask -o UserKnownHostsFile="$KNOWN_HOSTS" -p "$INSTALLER_SSH_PORT") + debug "ssh params: ''${SSH_OPTS[*]}" + + execInstaller() { + debug "[root@$INSTALLER -p $INSTALLER_SSH_PORT] $*" + ssh "''${SSH_OPTS[@]}" "root@$INSTALLER" -- "$@" 2> >(grep -v "Permanently added" 1>&2) + } + ''; + }; + installerCommandOpts = with lib.types; { + options = { + help = mkOpt' str null "Help message."; + script = mkOpt' lines "" "Script contents."; + packages = mkOpt' (listOf package) [ ] "Packages to make available to the script."; + }; + }; +in +{ + options.my.installerCommands = with lib.types; + mkOpt' (attrsOf (submodule installerCommandOpts)) { } "Installer commands."; + + config = { + my.installerCommands = { + installer-shell = { + help = "Get a shell into the installer"; + script = + '' + execInstaller "$@" + ''; + }; + + # TODO: Add new command to generate a template with the output of nixos-generate-config included + + do-install = { + help = "Install a system configuration into a prepared installer that can be reached at $INSTALLER"; + script = + '' + noBootloader= + noSubstitute= + ${parseArgs + '' + --no-bootloader) + noBootloader=true + shift + ;; + --no-substitute) + noSubstitute=true + shift + ;; + ''} + system="''${1:-}" + [ -z "$system" ] && die "usage: $0 [--no-bootloader] [--no-substitute] " + + : "''${INSTALLER_BUILD_OPTS:=}" + IFS=" " read -ra BUILD_OPTS <<< "$INSTALLER_BUILD_OPTS" + + INSTALL_ROOT="$(execInstaller echo \$INSTALL_ROOT)" + info "Installing configuration for $system to $INSTALLER:$INSTALL_ROOT" + askYN "Continue?" n || exit 1 + + params=() + [ -z "$noSubstitute" ] && params+=(--substitute-on-destination) + + flakeAttr="$PRJ_ROOT#nixosConfigurations.$system.config.system.build.toplevel" + info "Building $flakeAttr..." + storePath="$(nix build "''${BUILD_OPTS[@]}" --no-link --json "$flakeAttr" | jq -r .[0].outputs.out)" + + info "Copying closure of configuration $storePath to target..." + NIX_SSHOPTS="''${SSH_OPTS[*]}" nix copy "''${params[@]}" \ + --to "ssh://root@$INSTALLER?remote-store=$INSTALL_ROOT" "$storePath" + + profile=/nix/var/nix/profiles/system + info "Setting $profile on target to point to copied configuration..." + # Use `nix-env` since `nix profile` uses a non-backwards compatible manifest format + execInstaller nix-env --store "$INSTALL_ROOT" -p "$INSTALL_ROOT$profile" --set "$storePath" + + # Make switch-to-configuration recognise this as a NixOS system + execInstaller "mkdir -m 0755 -p \"$INSTALL_ROOT/etc\" && touch \"$INSTALL_ROOT/etc/NIXOS\"" + + if [ -z "$noBootloader" ]; then + info "Activating configuration and installing bootloader..." + # Grub needs an mtab. + execInstaller ln -sfn /proc/mounts "$INSTALL_ROOT/etc/mtab" + execInstaller "export NIXOS_INSTALL_BOOTLOADER=1 && \ + nixos-enter --root \"$INSTALL_ROOT\" -- /run/current-system/bin/switch-to-configuration boot" + else + info "Activating configuation..." + execInstaller \ + nixos-enter --root "$INSTALL_ROOT" -- /run/current-system/bin/switch-to-configuration boot + fi + + info "Success!" + ''; + }; + }; + + commands = mapAttrsToList (name: cmd: { + inherit name; + inherit (cmd) help; + category = "installation"; + package = pkgs.writeShellApplication { + inherit name; + runtimeInputs = cmd.packages; + text = + '' + # shellcheck disable=SC1091 + source "${installCommon}/bin/install-common.sh" + + ${cmd.script} + ''; + }; + }) config.my.installerCommands; + }; +} diff --git a/lib.nix b/lib.nix index 500c7d0..39c7b02 100644 --- a/lib.nix +++ b/lib.nix @@ -1,7 +1,9 @@ { lib }: let inherit (builtins) replaceStrings elemAt mapAttrs; - inherit (lib) genAttrs mapAttrs' mapAttrsToList nameValuePair types mkOption mkOverride mkForce; + inherit (lib) + genAttrs mapAttrs' mapAttrsToList filterAttrsRecursive nameValuePair types + mkOption mkOverride mkForce; inherit (lib.flake) defaultSystems; in rec { @@ -20,7 +22,7 @@ rec { ip = checked (elemAt m 0); ports = checked (replaceStrings ["-"] [":"] (elemAt m 1)); }; - attrsToList = mapAttrsToList nameValuePair; + attrsToNVList = mapAttrsToList nameValuePair; mkDefaultSystemsPkgs = path: args': genAttrs defaultSystems (system: import path ((args' system) // { inherit system; })); mkApp = program: { type = "app"; inherit program; }; @@ -46,6 +48,9 @@ rec { }); flakePackageOverlay = flake: flakePackageOverlay' flake null; + # Merge together modules which are defined as functions with others that aren't + naiveModule = with types; (coercedTo (attrsOf anything) (conf: { ... }: conf) (functionTo (attrsOf anything))); + mkOpt = type: default: mkOption { inherit type default; }; mkOpt' = type: default: description: mkOption { inherit type default description; }; mkBoolOpt = default: mkOption { @@ -58,12 +63,53 @@ rec { type = types.bool; example = true; }; + nullOrOpt' = type: description: mkOpt' (types.nullOr type) null description; dummyOption = mkOption { }; + # Slightly higher precedence than mkDefault + mkDefault' = mkOverride 900; mkVMOverride' = mkOverride 9; homeStateVersion = hmBranch: { # The flake passes a default setting, but we don't care about that home.stateVersion = mkForce (if hmBranch == "unstable" then "22.05" else "21.11"); }; + + deploy-rs = + with types; + let + globalOpts = { + sshUser = nullOrOpt' str "Username deploy-rs will deploy with."; + user = nullOrOpt' str "Username deploy-rs will deploy with."; + sudo = nullOrOpt' str "Command to elevate privileges with (used if the deployment user != profile user)."; + sshOpts = nullOrOpt' (listOf str) + "Options deploy-rs will pass to ssh. Note: overriding at a lower level _merges_ options."; + fastConnection = nullOrOpt' bool "Whether to copy the whole closure instead of using substitution."; + autoRollback = nullOrOpt' bool "Whether to roll back the profile if activation fails."; + magicRollback = nullOrOpt' bool "Whether to roll back the profile if connectivity to the deployer is lost."; + confirmTimeout = nullOrOpt' ints.u16 "Timeout for confirming activation succeeded."; + tempPath = nullOrOpt' str "Path that deploy-rs will use for temporary files."; + }; + + profileOpts = { + path = mkOpt' package "" "Derivation to build (should include activation script)."; + profilePath = nullOrOpt' str "Path to profile location"; + } // globalOpts; + profile = submodule { options = profileOpts; }; + + nodeOpts = { + hostname = mkOpt' str "" "Hostname deploy-rs will connect to."; + profilesOrder = nullOrOpt' (listOf str) + "Order to deploy profiles in (remainder will be deployed in arbitrary order)."; + profiles = mkOpt' (attrsOf profile) { } "Profiles to deploy."; + } // globalOpts; + in + rec { + inherit profile; + node = submodule { options = nodeOpts; }; + + filterOpts = filterAttrsRecursive (_: v: v != null); + }; + + authorizedKeys = toString ./authorized_keys; } diff --git a/nixos/boxes/colony.nix b/nixos/boxes/colony.nix index d75e462..0682b5d 100644 --- a/nixos/boxes/colony.nix +++ b/nixos/boxes/colony.nix @@ -1,5 +1,7 @@ -{ lib, pkgs, ... }: +{ lib, pkgs, modulesPath, ... }: { + imports = [ "${modulesPath}/profiles/qemu-guest.nix" ]; + my = { firewall = { trustedInterfaces = [ "blah" ]; @@ -15,11 +17,20 @@ }; }; server.enable = true; - - homeConfig = {}; + tmproot.unsaved.ignore = [ + "/var/db/dhcpcd/enp1s0.lease" + ]; }; fileSystems = { + "/boot" = { + device = "/dev/disk/by-label/ESP"; + fsType = "vfat"; + }; + "/nix" = { + device = "/dev/disk/by-label/nix"; + fsType = "ext4"; + }; "/persist" = { device = "/dev/disk/by-label/persist"; fsType = "ext4"; @@ -27,5 +38,7 @@ }; }; - networking = { }; + networking = { + interfaces.enp1s0.useDHCP = true; + }; } diff --git a/nixos/installer.nix b/nixos/installer.nix index 515d437..e299c00 100644 --- a/nixos/installer.nix +++ b/nixos/installer.nix @@ -1,6 +1,8 @@ { lib, pkgs, modulesPath, config, ... }: let - inherit (lib) mkDefault mkForce; + inherit (lib) mkDefault mkForce mkImageMediaOverride; + + installRoot = "/mnt"; in { imports = [ @@ -10,18 +12,69 @@ in "${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; + deploy.enable = false; + user.enable = false; }; + environment.sessionVariables = { + INSTALL_ROOT = installRoot; + }; + users.users.root.openssh.authorizedKeys.keyFiles = [ lib.my.authorizedKeys ]; + home-manager.users.root = { + programs = { + starship.settings = { + hostname.ssh_only = false; + }; + }; + + home.shellAliases = { + show-hw-config = "nixos-generate-config --show-hardware-config --root $INSTALL_ROOT"; + }; + }; + + services = { + openssh = { + permitRootLogin = mkImageMediaOverride "prohibit-password"; + }; + }; + + # Will be set dynamically + networking.hostName = ""; + + # This should be overridden by whatever boot mechanism is used + fileSystems."/" = mkDefault { + device = "none"; + fsType = "tmpfs"; + }; + + systemd.tmpfiles.rules = [ + "d ${installRoot} 0755 root root" + ]; + boot.postBootCommands = + '' + ${pkgs.nettools}/bin/hostname "installer-$(${pkgs.coreutils}/bin/head -c4 /dev/urandom | \ + ${pkgs.coreutils}/bin/od -A none -t x4 | \ + ${pkgs.gawk}/bin/awk '{ print $1 }')" + ''; + + environment.systemPackages = with pkgs; [ + # We disable networking.useDHCP, so bring these in for the user + # dhcpcd probably has more features, but dhclient actually seems a bit more simple + (pkgs.writeShellScriptBin "dhclient" ''exec ${pkgs.dhcp}/bin/dhclient -v "$@"'') + dhcpcd + ]; + + # Much of this onwards is yoinked from modules/profiles/installation-device.nix # Good to have docs in the installer! - documentation.enable = mkForce true; - documentation.nixos.enable = mkForce true; + # TODO: docs rebuilding every time? + documentation.enable = mkForce false; + documentation.nixos.enable = mkForce false; # Enable wpa_supplicant, but don't start it by default. networking.wireless.enable = mkDefault true; @@ -39,16 +92,5 @@ in # download-using-manifests.pl from forking even if there is # plenty of free memory. boot.kernel.sysctl."vm.overcommit_memory" = "1"; - - # This should be overridden by whatever boot mechanism is used - fileSystems."/" = mkDefault { - device = "none"; - fsType = "tmpfs"; - }; - - environment.systemPackages = with pkgs; [ - # We disable networking.useDHCP, so bring this in for the user - dhcpcd - ]; }; } diff --git a/nixos/modules/common.nix b/nixos/modules/common.nix index f61dab8..01f321c 100644 --- a/nixos/modules/common.nix +++ b/nixos/modules/common.nix @@ -1,23 +1,31 @@ { lib, pkgs, pkgs', inputs, options, config, ... }: let inherit (builtins) attrValues; - inherit (lib) flatten optional mkIf mkDefault mkMerge mkAliasDefinitions; - inherit (lib.my) mkOpt' mkBoolOpt' dummyOption; - - defaultUsername = "dev"; - uname = config.my.user.name; + inherit (lib) flatten optional mkIf mkDefault mkMerge mkOption mkAliasDefinitions; + inherit (lib.my) mkOpt' mkBoolOpt' dummyOption mkDefault'; 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.*`)"; + # TODO: Move to separate module + user = { + enable = mkBoolOpt' true "Whether to create a primary user."; + config = mkOption { + type = options.users.users.type.nestedTypes.elemType; + default = { }; + description = "User definition (as `users.users.*`)."; + }; + homeConfig = mkOption { + type = options.home-manager.users.type.nestedTypes.elemType; + default = { }; + # Prevent docs traversing into all of home-manager + visible = "shallow"; + description = "Home configuration (as `home-manager.users.*`)"; + }; + }; ssh = { - # If enabled, we can't set `authorized_keys` from home-manager because SSH won't like the file being owned by - # root. - strictModes = mkBoolOpt' false + strictModes = mkBoolOpt' true ("Specifies whether sshd(8) should check file modes and ownership of the user's files and home directory "+ "before accepting login."); }; @@ -28,17 +36,37 @@ in }; config = mkMerge [ + (let + cfg = config.my.user; + user' = cfg.config; + in mkIf cfg.enable { my = { user = { - name = mkDefault defaultUsername; - isNormalUser = true; - uid = mkDefault 1000; - extraGroups = mkDefault [ "wheel" ]; - password = mkDefault "hunter2"; # TODO: secrets... + config = { + name = mkDefault' "dev"; + isNormalUser = true; + uid = mkDefault 1000; + extraGroups = mkDefault [ "wheel" ]; + password = mkDefault "hunter2"; # TODO: secrets... + openssh.authorizedKeys.keyFiles = [ lib.my.authorizedKeys ]; + }; + # In order for this option to evaluate on its own, home-manager expects the `name` (which is derived from the + # parent attr name) to be the users name, aka `home-manager.users.` + homeConfig = { _module.args.name = lib.mkForce user'.name; }; }; + + deploy.authorizedKeys = mkDefault user'.openssh.authorizedKeys; }; + # mkAliasDefinitions will copy the unmerged defintions to allow the upstream submodule to deal with + users.users.${user'.name} = mkAliasDefinitions options.my.user.config; + + # 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.${user'.name} = mkAliasDefinitions options.my.user.homeConfig; + }) + { home-manager = { # Installs packages in the system config instead of in the local profile on activation useUserPackages = mkDefault true; @@ -46,13 +74,8 @@ in 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 = { @@ -63,6 +86,8 @@ in nix = { package = pkgs'.unstable.nixVersions.stable; + # TODO: This has been renamed to nix.settings.trusted-users in 22.05 + trustedUsers = [ "@wheel" ]; extraOptions = '' experimental-features = nix-command flakes ca-derivations @@ -70,6 +95,7 @@ in }; nixpkgs = { overlays = [ + inputs.deploy-rs.overlay # TODO: Wait for https://github.com/NixOS/nixpkgs/pull/159074 to arrive to nixos-unstable (final: prev: { remarshal = pkgs'.master.remarshal; }) ]; @@ -109,6 +135,7 @@ in }; networking = { + domain = mkDefault "int.nul.ie"; useDHCP = mkDefault false; enableIPv6 = mkDefault true; }; @@ -137,6 +164,8 @@ in openssh = { enable = mkDefault true; extraConfig = ''StrictModes ${if config.my.ssh.strictModes then "yes" else "no"}''; + permitRootLogin = mkDefault "no"; + passwordAuthentication = mkDefault false; }; }; diff --git a/nixos/modules/deploy-rs.nix b/nixos/modules/deploy-rs.nix new file mode 100644 index 0000000..0976a19 --- /dev/null +++ b/nixos/modules/deploy-rs.nix @@ -0,0 +1,66 @@ +{ lib, extendModules, pkgs, options, config, baseModules, ... }: +let + inherit (builtins) head; + inherit (lib) mkOption mkMerge mkIf mkDefault; + inherit (lib.my) mkOpt' mkBoolOpt'; + + cfg = config.my.deploy; +in +{ + options.my.deploy = with lib.types; rec { + authorizedKeys = { + keys = mkOpt' (listOf singleLineStr) [ ] "SSH public keys to add to the default deployment user."; + keyFiles = mkOpt' (listOf str) [ ] "SSH public key files to add to the default deployment user."; + }; + + enable = mkBoolOpt' true "Whether to expose deploy-rs configuration for this system."; + node = mkOpt' lib.my.deploy-rs.node { } "deploy-rs node configuration."; + + generate = { + system.enable = mkBoolOpt' true "Whether to generate a deploy-rs profile for this system's config."; + }; + rendered = mkOption { + type = nullOr (attrsOf anything); + default = null; + internal = true; + description = "Rendered deploy-rs node configuration."; + }; + }; + + config = mkMerge [ + { + my.deploy = { + enable = mkIf config.my.build.isDevVM false; + + node = { + hostname = mkDefault config.networking.fqdn; + profiles = { + system = mkIf cfg.generate.system.enable { + path = pkgs.deploy-rs.lib.activate.nixos { inherit config; }; + + user = "root"; + }; + }; + + sshUser = "deploy"; + user = mkDefault "root"; + sudo = mkDefault (if config.security.doas.enable then "doas -u" else "sudo -u"); + sshOpts = mkDefault [ "-p" (toString (head config.services.openssh.ports)) ]; + }; + rendered = mkIf cfg.enable (lib.my.deploy-rs.filterOpts cfg.node); + }; + } + (mkIf cfg.enable { + users = { + users."${cfg.node.sshUser}" = { + isSystemUser = true; + group = cfg.node.sshUser; + extraGroups = mkDefault [ "wheel" ]; + shell = pkgs.bash; + openssh.authorizedKeys = cfg.authorizedKeys; + }; + groups."${cfg.node.sshUser}" = {}; + }; + }) + ]; +} diff --git a/nixos/modules/server.nix b/nixos/modules/server.nix index 14777d4..4c32973 100644 --- a/nixos/modules/server.nix +++ b/nixos/modules/server.nix @@ -1,19 +1,20 @@ { config, lib, ... }: let - inherit (lib) mkIf; + inherit (lib) mkIf mkDefault; inherit (lib.my) mkBoolOpt'; cfg = config.my.server; + uname = if config.my.user.enable then config.my.user.config.name else "root"; 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; + getty.autologinUser = mkDefault uname; + kmscon.autologinUser = mkDefault uname; }; - my.homeConfig = { + my.user.homeConfig = { my.gui.enable = false; }; }; diff --git a/nixos/modules/tmproot.nix b/nixos/modules/tmproot.nix index d32e52f..de643a7 100644 --- a/nixos/modules/tmproot.nix +++ b/nixos/modules/tmproot.nix @@ -50,7 +50,8 @@ let rootDef = { device = "yeet"; fsType = "tmpfs"; - options = [ "size=${cfg.size}" ]; + # The default mode for tmpfs is 777 + options = [ "size=${cfg.size}" "mode=755" ]; }; in { @@ -98,6 +99,10 @@ in # Specifies obsolete files that should be deleted on activation - we'll never have those! "/etc/.clean" + + # These are set in environment.etc by the sshd module, but because their mode needs to be changed, + # setup-etc will copy them instead of symlinking + "/etc/ssh/authorized_keys.d" ]; environment.systemPackages = [