From b3ecadf4615a28186c4722c7207ee7ca3092d740 Mon Sep 17 00:00:00 2001 From: arcnmx Date: Tue, 16 Apr 2024 14:04:59 -0700 Subject: [PATCH] feat(extern): ssh home module --- flake.lock | 21 ++++ flake.nix | 4 + modules/extern/home/args.nix | 9 +- modules/extern/home/ssh.nix | 207 ++++++++++++++++++++++++++++++++++ systems/extern-test/nixos.nix | 25 +++- systems/reisen/default.nix | 4 + systems/u7pro/default.nix | 8 ++ 7 files changed, 276 insertions(+), 2 deletions(-) create mode 100644 modules/extern/home/ssh.nix diff --git a/flake.lock b/flake.lock index 0745e2ec..a358a37c 100644 --- a/flake.lock +++ b/flake.lock @@ -153,6 +153,26 @@ "type": "github" } }, + "home-manager": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1713166971, + "narHash": "sha256-t0P/rKlsE5l1O3O2LYtAelLzp7PeoPCSzsIietQ1hSM=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "1c43dcfac48a2d622797f7ab741670fdbcf8f609", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, "nix-std": { "locked": { "lastModified": 1701658249, @@ -209,6 +229,7 @@ "flake-compat": "flake-compat", "flake-utils": "flake-utils", "flakelib": "flakelib", + "home-manager": "home-manager", "nixpkgs": "nixpkgs", "sops-nix": "sops-nix", "std-fl": "std-fl", diff --git a/flake.nix b/flake.nix index 61624398..4f712acc 100644 --- a/flake.nix +++ b/flake.nix @@ -39,6 +39,10 @@ utils.follows = "flake-utils"; }; }; + home-manager = { + url = "github:nix-community/home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; systemd2mqtt = { url = "github:arcnmx/systemd2mqtt"; inputs = { diff --git a/modules/extern/home/args.nix b/modules/extern/home/args.nix index 21ac1032..974d5129 100644 --- a/modules/extern/home/args.nix +++ b/modules/extern/home/args.nix @@ -1,7 +1,14 @@ -{inputs, ...}: {...}: let +{inputs, ...}: {lib, osConfig, ...}: let inherit (inputs.self.lib) meta; + inherit (lib.modules) mkIf; in { imports = [ meta.modules.extern.misc.args ]; + + config = { + lib.gensokyo-zone = mkIf (osConfig ? lib.gensokyo-zone) { + os = osConfig.lib.gensokyo-zone; + }; + }; } diff --git a/modules/extern/home/ssh.nix b/modules/extern/home/ssh.nix new file mode 100644 index 00000000..7807f935 --- /dev/null +++ b/modules/extern/home/ssh.nix @@ -0,0 +1,207 @@ +let + sshHostModule = { + lib, + gensokyo-zone, + osConfig, + homeConfig, + config, + name, + ... + }: let + inherit (gensokyo-zone.lib) unmerged coalesce mkAlmostOptionDefault mapListToAttrs; + inherit (lib.options) mkOption mkEnableOption; + inherit (lib.modules) mkIf mkMerge mkOptionDefault mkDefault; + inherit (lib.lists) length head elem optional filter unique intersectLists; + inherit (lib.attrsets) filterAttrs mapAttrsToList nameValuePair; + inherit (lib.strings) optionalString; + inherit (osConfig.gensokyo-zone) access; + cfg = gensokyo-zone.ssh.cfg; + system = gensokyo-zone.systems.${config.systemName}.config; + in { + options = with lib.types; { + enable = mkEnableOption "ssh client configuration" // { + default = true; + }; + name = mkOption { + type = str; + default = name; + }; + systemName = mkOption { + type = str; + }; + user = mkOption { + type = nullOr str; + default = cfg.user; + }; + networks = mkOption { + type = listOf (nullOr str); + }; + hostName = mkOption { + type = nullOr str; + }; + extraOptions = mkOption { + type = unmerged.types.attrs; + }; + set = { + matchBlocksSettings = mkOption { + type = unmerged.types.attrs; + default = {}; + }; + }; + }; + config = { + hostName = mkOptionDefault system.access.hostName; + extraOptions = mkOptionDefault (unmerged.mergeAttrs cfg.extraOptions); + user = mkIf (config.systemName == "u7pro") (mkAlmostOptionDefault "kittywitch"); + networks = let + enabledNetworks = filterAttrs (_: net: net.enable) system.network.networks; + networkNames = mapAttrsToList (_: net: net.name) enabledNetworks; + networks' = filter (name: name == null || elem name networkNames) cfg.networks; + fallbackNetwork = + if system.network.networks.local.enable or false && access.local.enable then "local" + else if system.access.global.enable then null + else if system.network.networks.int.enable or false then "int" + else if system.network.networks.local.enable or false then "local" + else null; + networks = map (name: coalesce [ name fallbackNetwork ]) networks'; + in mkOptionDefault (unique networks); + set = { + matchBlocksSettings = let + canonNetworkName' = intersectLists config.networks [ null "int" "local" ]; + canonNetworkName = if canonNetworkName' != [ ] then head canonNetworkName' else null; + in mapListToAttrs (network: let + name = config.name + optionalString (network != canonNetworkName) "-${network}"; + inherit (system.exports.services) sshd; + port = head ( + optional (network == null && sshd.ports.global.enable or false) sshd.ports.global.port + ++ optional (sshd.ports.public.enable or false) sshd.ports.public.port + ++ [ sshd.ports.standard.port ] + ); + needsProxy = network == "int" || (network == "local" && !access.local.enable); + in nameValuePair name { + hostname = mkDefault ( + if network == null then system.access.fqdn + else system.network.networks.${network}.fqdn + ); + user = mkIf (config.user != null) (mkDefault config.user); + port = mkIf (port != 22) (mkDefault port); + proxyJump = mkIf needsProxy (assert config.name != cfg.proxyJump; + mkAlmostOptionDefault cfg.proxyJump + ); + identitiesOnly = mkIf (config.systemName == "u7pro") (mkAlmostOptionDefault true); + extraOptions = mkMerge [ + (unmerged.mergeAttrs config.extraOptions) + { + HostKeyAlias = mkIf (config.hostName != null && network != null) (mkOptionDefault system.access.fqdn); + } + ]; + }) config.networks; + }; + }; + }; + sshModule = { + lib, + gensokyo-zone, + osConfig, + homeConfig, + config, + pkgs, + ... + }: let + inherit (lib.options) mkOption mkEnableOption; + inherit (lib.modules) mkIf mkMerge mkOptionDefault; + inherit (lib.attrsets) mapAttrs mapAttrsToList; + inherit (gensokyo-zone.lib) unmerged; + inherit (osConfig.gensokyo-zone) access; + in { + options = with lib.types; { + enable = mkEnableOption "ssh client configuration"; + user = mkOption { + type = nullOr str; + default = null; + }; + hosts = mkOption { + type = attrsOf (submoduleWith { + modules = [ sshHostModule ]; + specialArgs = { + inherit gensokyo-zone osConfig homeConfig pkgs; + }; + }); + }; + networks = mkOption { + type = listOf (nullOr str); + default = [ null ]; + }; + proxyJump = mkOption { + type = str; + }; + extraOptions = mkOption { + type = unmerged.types.attrs; + default = {}; + }; + set = { + matchBlocksSettings = mkOption { + type = unmerged.types.attrs; + default = {}; + }; + }; + }; + config = { + proxyJump = mkOptionDefault ( + if config.hosts.hakurei.enable then config.hosts.hakurei.name + else gensokyo-zone.systems.hakurei.config.access.fqdn + ); + networks = mkOptionDefault [ + (mkIf access.local.enable "local") + (mkIf access.tail.enabled "tail") + ]; + hosts = mapAttrs (name: system: let + enabled = system.config.access.online.enable && system.config.exports.services.sshd.enable; + in mkIf enabled { + systemName = mkOptionDefault name; + }) gensokyo-zone.systems; + set = { + matchBlocksSettings = let + mkMatchBlocksHost = host: mkIf host.enable (unmerged.mergeAttrs host.set.matchBlocksSettings); + in mkMerge ( + mapAttrsToList (_: mkMatchBlocksHost) config.hosts + ); + }; + }; + }; +in { + config, + osConfig, + lib, + gensokyo-zone, + pkgs, + ... +}: let + inherit (lib.options) mkOption; + inherit (lib.modules) mkIf; + inherit (gensokyo-zone.lib) unmerged; + cfg = config.gensokyo-zone.ssh; +in { + options.gensokyo-zone.ssh = mkOption { + type = lib.types.submoduleWith { + modules = [sshModule]; + specialArgs = { + inherit gensokyo-zone pkgs; + inherit osConfig; + homeConfig = config; + }; + }; + default = { }; + }; + + config = { + gensokyo-zone.ssh = { + }; + programs.ssh = mkIf cfg.enable { + matchBlocks = unmerged.mergeAttrs cfg.set.matchBlocksSettings; + }; + lib.gensokyo-zone.ssh = { + inherit cfg sshModule sshHostModule; + }; + }; +} diff --git a/systems/extern-test/nixos.nix b/systems/extern-test/nixos.nix index 20f7bd97..c9187cd4 100644 --- a/systems/extern-test/nixos.nix +++ b/systems/extern-test/nixos.nix @@ -2,11 +2,12 @@ extern'test'inputs, ... }: let - inherit (extern'test'inputs.self) nixosModules; + inherit (extern'test'inputs.self) nixosModules homeModules; in { imports = [ nixosModules.default extern'test'inputs.sops-nix.nixosModules.sops + extern'test'inputs.home-manager.nixosModules.default ]; config = { @@ -42,5 +43,27 @@ in { sops = { age.sshKeyPaths = ["/etc/ssh/ssh_host_ed25519_key"]; }; + + users.users = { + me = { + isNormalUser = true; + }; + }; + home-manager = { + sharedModules = [ + homeModules.default + ]; + users.me = { config, ... }: { + config = { + home.stateVersion = "23.11"; + gensokyo-zone = { + ssh = { + enable = true; + }; + }; + programs.ssh.enable = true; + }; + }; + }; }; } diff --git a/systems/reisen/default.nix b/systems/reisen/default.nix index ae4a8fb3..75ab9653 100644 --- a/systems/reisen/default.nix +++ b/systems/reisen/default.nix @@ -12,6 +12,10 @@ _: { }; exports = { services = { + sshd = { + enable = true; + ports.public.enable = false; + }; proxmox.enable = true; }; }; diff --git a/systems/u7pro/default.nix b/systems/u7pro/default.nix index a3892a03..28247db0 100644 --- a/systems/u7pro/default.nix +++ b/systems/u7pro/default.nix @@ -7,4 +7,12 @@ _: { address6 = null; }; }; + exports = { + services = { + sshd = { + enable = true; + ports.public.enable = false; + }; + }; + }; }