From 0116ecf47fe911c84ce37a7cf5459321399ee608 Mon Sep 17 00:00:00 2001 From: arcnmx Date: Mon, 19 Feb 2024 11:29:46 -0800 Subject: [PATCH] feat: extern nixosModules --- .github/workflows/nodes.yml | 51 +++++++ ci/fmt.nix | 1 + ci/nodes.nix | 6 +- lib.nix | 12 +- modules/extern/home/args.nix | 7 + modules/extern/misc/args.nix | 27 ++++ modules/extern/nixos/args.nix | 7 + modules/extern/nixos/kyuuto.nix | 161 ++++++++++++++++++++++ modules/extern/nixos/nix.nix | 138 +++++++++++++++++++ modules/extern/nixos/users.nix | 230 ++++++++++++++++++++++++++++++++ outputs.nix | 4 + systems/extern-test/default.nix | 15 +++ systems/extern-test/nixos.nix | 28 ++++ tree.nix | 5 + 14 files changed, 690 insertions(+), 2 deletions(-) create mode 100644 modules/extern/home/args.nix create mode 100644 modules/extern/misc/args.nix create mode 100644 modules/extern/nixos/args.nix create mode 100644 modules/extern/nixos/kyuuto.nix create mode 100644 modules/extern/nixos/nix.nix create mode 100644 modules/extern/nixos/users.nix create mode 100644 systems/extern-test/default.nix create mode 100644 systems/extern-test/nixos.nix diff --git a/.github/workflows/nodes.yml b/.github/workflows/nodes.yml index 0138d89f..92486cdd 100644 --- a/.github/workflows/nodes.yml +++ b/.github/workflows/nodes.yml @@ -130,6 +130,57 @@ jobs: command: ci-build-cache quiet: false stdin: ${{ runner.temp }}/ci.build.cache + extern: + name: nodes-extern + runs-on: ubuntu-latest + steps: + - id: checkout + name: git clone + uses: actions/checkout@v4 + with: + submodules: false + - id: nix-install + name: nix install + uses: arcnmx/ci/actions/nix/install@v0.7 + - id: ci-dirty + name: nix test dirty + uses: arcnmx/ci/actions/nix/run@v0.7 + with: + attrs: ci.job.extern.run.test + command: ci-build-dirty + quiet: false + stdout: ${{ runner.temp }}/ci.build.dirty + - id: ci-test + name: nix test build + uses: arcnmx/ci/actions/nix/run@v0.7 + with: + attrs: ci.job.extern.run.test + command: ci-build-realise + ignore-exit-code: true + quiet: false + stdin: ${{ runner.temp }}/ci.build.dirty + - env: + CI_EXIT_CODE: ${{ steps.ci-test.outputs.exit-code }} + id: ci-summary + name: nix test results + uses: arcnmx/ci/actions/nix/run@v0.7 + with: + attrs: ci.job.extern.run.test + command: ci-build-summarise + quiet: false + stdin: ${{ runner.temp }}/ci.build.dirty + stdout: ${{ runner.temp }}/ci.build.cache + - env: + CACHIX_SIGNING_KEY: ${{ secrets.CACHIX_SIGNING_KEY }} + id: ci-cache + if: always() + name: nix test cache + uses: arcnmx/ci/actions/nix/run@v0.7 + with: + attrs: ci.job.extern.run.test + command: ci-build-cache + quiet: false + stdin: ${{ runner.temp }}/ci.build.cache hakurei: name: nodes-hakurei runs-on: ubuntu-latest diff --git a/ci/fmt.nix b/ci/fmt.nix index 0d1fd8fa..4d9591c1 100644 --- a/ci/fmt.nix +++ b/ci/fmt.nix @@ -10,6 +10,7 @@ "tree.nix" ]; whitelistDirs = [ + "modules/extern" "modules/system" "systems" ]; diff --git a/ci/nodes.nix b/ci/nodes.nix index 38240ec1..f3337011 100644 --- a/ci/nodes.nix +++ b/ci/nodes.nix @@ -29,7 +29,11 @@ with lib; { in mapAttrs' (k: nameValuePair "${k}") (genAttrs nixosSystems (host: { tasks.${host}.inputs = channels.nixfiles.nixosConfigurations.${host}.config.system.build.toplevel; - })); + })) // { + extern = { + tasks.test.inputs = channels.nixfiles.nixosConfigurations.extern-test.config.system.build.toplevel; + }; + }; ci.gh-actions.checkoutOptions.submodules = false; cache.cachix.arc = { diff --git a/lib.nix b/lib.nix index 9e046a25..73930a61 100644 --- a/lib.nix +++ b/lib.nix @@ -35,6 +35,16 @@ mkWinPath = replaceStrings ["/"] ["\\"]; mkBaseDn = domain: concatMapStringsSep "," (part: "dc=${part}") (splitString "." domain); + treeToModulesOutput = modules: + { + ${ + if modules ? __functor + then "default" + else null + } = + modules.__functor modules; + } + // builtins.removeAttrs modules ["__functor"]; in { inherit tree nixlib inputs systems; meta = tree.impure; @@ -42,7 +52,7 @@ in { Std = inputs.std-fl.lib; lib = { domain = "gensokyo.zone"; - inherit mkWinPath mkBaseDn userIs eui64 toHexStringLower hexCharToInt; + inherit treeToModulesOutput mkWinPath mkBaseDn userIs eui64 toHexStringLower hexCharToInt; inherit (inputs.arcexprs.lib) unmerged json; }; generate = import ./generate.nix {inherit inputs tree;}; diff --git a/modules/extern/home/args.nix b/modules/extern/home/args.nix new file mode 100644 index 00000000..21ac1032 --- /dev/null +++ b/modules/extern/home/args.nix @@ -0,0 +1,7 @@ +{inputs, ...}: {...}: let + inherit (inputs.self.lib) meta; +in { + imports = [ + meta.modules.extern.misc.args + ]; +} diff --git a/modules/extern/misc/args.nix b/modules/extern/misc/args.nix new file mode 100644 index 00000000..9f92deb5 --- /dev/null +++ b/modules/extern/misc/args.nix @@ -0,0 +1,27 @@ +{inputs, ...}: { + config, + options, + ... +}: let + hasConfigLib = options ? lib; + gensokyo-zone = { + inherit inputs; + inherit (inputs.self.lib) tree meta lib; + }; +in { + config = { + ${ + if hasConfigLib + then "lib" + else null + } = { + inherit gensokyo-zone; + }; + _module.args = { + gensokyo-zone = + if hasConfigLib + then config.lib.gensokyo-zone + else gensokyo-zone; + }; + }; +} diff --git a/modules/extern/nixos/args.nix b/modules/extern/nixos/args.nix new file mode 100644 index 00000000..21ac1032 --- /dev/null +++ b/modules/extern/nixos/args.nix @@ -0,0 +1,7 @@ +{inputs, ...}: {...}: let + inherit (inputs.self.lib) meta; +in { + imports = [ + meta.modules.extern.misc.args + ]; +} diff --git a/modules/extern/nixos/kyuuto.nix b/modules/extern/nixos/kyuuto.nix new file mode 100644 index 00000000..8ce90320 --- /dev/null +++ b/modules/extern/nixos/kyuuto.nix @@ -0,0 +1,161 @@ +{ + config, + lib, + gensokyo-zone, + ... +}: let + inherit (lib.options) mkOption mkEnableOption; + inherit (lib.modules) mkIf mkMerge mkDefault mkOptionDefault; + inherit (gensokyo-zone.lib) unmerged; + cfg = config.gensokyo-zone.kyuuto; + kyuutoModule = { + gensokyo-zone, + nixosConfig, + config, + ... + }: let + inherit (gensokyo-zone.lib) unmerged domain; + setFilesystemOptions = mkMerge [ + (mkIf config.nfs.enable config.nfs.fstabOptions) + (mkIf config.smb.enable config.smb.fstabOptions) + (mkIf config.automount.enable config.automount.fstabOptions) + ]; + in { + options = with lib.types; { + enable = mkEnableOption "kyuuto"; + media.enable = + mkEnableOption "/mnt/kyuuto-media" + // { + default = true; + }; + transfer.enable = + mkEnableOption "/mnt/kyuuto-transfer" + // { + default = true; + }; + shared.enable = mkEnableOption "/mnt/kyuuto-shared"; + domain = mkOption { + type = str; + }; + local.enable = mkEnableOption "LAN"; + automount = { + enable = + mkEnableOption "systemd automount" + // { + default = true; + }; + fstabOptions = mkOption { + type = listOf str; + }; + }; + nfs = { + enable = + mkEnableOption "NFS mounts" + // { + default = true; + }; + fstabOptions = mkOption { + type = listOf str; + }; + }; + smb = { + enable = mkEnableOption "SMB mounts"; + user = mkOption { + type = nullOr null; + default = null; + }; + fstabOptions = mkOption { + type = listOf str; + }; + }; + setFilesystems = mkOption { + type = unmerged.types.attrs; + }; + }; + config = { + domain = mkMerge [ + (mkOptionDefault ( + if config.local.enable + then "local.${domain}" + else domain + )) + (mkIf nixosConfig.services.tailscale.enable ( + mkDefault + "tail.${domain}" + )) + ]; + nfs.fstabOptions = [ + "noauto" + "nfsvers=4" + "soft" + "retrans=2" + "timeo=60" + ]; + smb.fstabOptions = [ + "noauto" + (mkIf (config.smb.user != null) "user=${config.smb.user}") + ]; + automount.fstabOptions = [ + "x-systemd.automount" + "x-systemd.mount-timeout=2m" + "x-systemd.idle-timeout=10m" + ]; + setFilesystems = { + "/mnt/kyuuto-media" = mkIf config.media.enable { + device = mkMerge [ + (mkIf config.nfs.enable "nfs.${config.domain}:/mnt/kyuuto-media") + (mkIf config.smb.enable ( + if config.smb.user != null && config.local.enable + then ''\\smb.${config.domain}\kyuuto-media'' + else if config.smb.user != null + then ''\\smb.${config.domain}\kyuuto-media-global'' + else ''\\smb.${config.domain}\kyuuto-library-access'' + )) + ]; + fsType = mkMerge [ + (mkIf config.nfs.enable "nfs4") + (mkIf config.smb.enable "smb3") + ]; + options = setFilesystemOptions; + }; + "/mnt/kyuuto-transfer" = mkIf config.transfer.enable { + device = mkMerge [ + (mkIf config.nfs.enable "nfs.${config.domain}:/mnt/kyuuto-media/transfer") + (mkIf (config.smb.enable && config.local.enable) ''\\smb.${config.domain}\kyuuto-transfer'') + ]; + fsType = mkMerge [ + (mkIf config.nfs.enable "nfs4") + (mkIf config.smb.enable "smb3") + ]; + options = setFilesystemOptions; + }; + "/mnt/kyuuto-shared" = mkIf (config.shared.enable && config.smb.enable) { + device = mkIf (config.smb.user != null) ''\\smb.${config.domain}\shared''; + fsType = "smb3"; + options = setFilesystemOptions; + }; + }; + }; + }; +in { + options.gensokyo-zone.kyuuto = mkOption { + type = lib.types.submoduleWith { + modules = [kyuutoModule]; + specialArgs = { + inherit gensokyo-zone; + inherit (gensokyo-zone) inputs; + nixosConfig = config; + }; + }; + default = { }; + }; + + config = { + fileSystems = mkIf cfg.enable ( + unmerged.mergeAttrs cfg.setFilesystems + ); + lib.gensokyo-zone.kyuuto = { + inherit cfg kyuutoModule; + }; + }; +} diff --git a/modules/extern/nixos/nix.nix b/modules/extern/nixos/nix.nix new file mode 100644 index 00000000..b99f4ee9 --- /dev/null +++ b/modules/extern/nixos/nix.nix @@ -0,0 +1,138 @@ +{ + config, + lib, + gensokyo-zone, + ... +}: let + inherit (lib.options) mkOption mkEnableOption; + inherit (lib.modules) mkIf mkMerge mkDefault; + inherit (gensokyo-zone.lib) unmerged; + cfg = config.gensokyo-zone.nix; + nixModule = { + gensokyo-zone, + nixosConfig, + config, + ... + }: let + inherit (gensokyo-zone.lib) unmerged domain; + in { + options = with lib.types; { + enable = mkEnableOption "nix settings"; + cache = { + arc.enable = mkEnableOption "arc cache"; + infrastructure.enable = + mkEnableOption "gensokyo-infrastructure cache" + // { + default = true; + }; + }; + builder = { + enable = mkEnableOption "aya nixbld remote builder"; + domain = mkOption { + type = str; + default = "nixbld.${domain}"; + }; + protocol = mkOption { + type = enum ["ssh" "ssh-ng"]; + default = "ssh"; + }; + ssh = { + user = mkOption { + type = str; + default = "nixbld"; + }; + key = mkOption { + type = nullOr path; + default = null; + }; + }; + jobs = mkOption { + type = int; + default = 16; + }; + systems = mkOption { + type = listOf str; + default = ["x86_64-linux"]; + }; + features = mkOption { + type = listOf str; + default = ["nixos-test" "benchmark" "big-parallel" "kvm"]; + }; + setBuildMachine = mkOption { + type = unmerged.types.attrs; + default = {}; + }; + }; + setNixSettings = mkOption { + type = unmerged.types.attrs; + default = {}; + }; + setNixBuildMachines = mkOption { + type = unmerged.type; + default = []; + }; + }; + config = { + setNixSettings = mkMerge [ + (mkIf config.cache.arc.enable { + extra-substituters = [ + "https://arc.cachix.org" + ]; + extra-trusted-public-keys = [ + "arc.cachix.org-1:DZmhclLkB6UO0rc0rBzNpwFbbaeLfyn+fYccuAy7YVY=" + ]; + }) + (mkIf config.cache.infrastructure.enable { + extra-substituters = [ + "https://gensokyo-infrastructure.cachix.org" + ]; + extra-trusted-public-keys = [ + "gensokyo-infrastructure.cachix.org-1:CY6ChfQ8KTUdwWoMbo8ZWr2QCLMXUQspHAxywnS2FyI=" + ]; + }) + ]; + builder = { + domain = mkIf nixosConfig.services.tailscale.enable ( + mkDefault + "nixbld.tail.${domain}" + ); + setBuildMachine = { + hostName = config.builder.domain; + protocol = config.builder.protocol; + sshUser = config.builder.ssh.user; + sshKey = config.builder.ssh.key; + maxJobs = config.builder.jobs; + systems = config.builder.systems; + supportedFeatures = config.builder.features; + }; + }; + setNixBuildMachines = mkIf config.builder.enable [ + ( + unmerged.mergeAttrs config.builder.setBuildMachine + ) + ]; + }; + }; +in { + options.gensokyo-zone.nix = mkOption { + type = lib.types.submoduleWith { + modules = [nixModule]; + specialArgs = { + inherit gensokyo-zone; + inherit (gensokyo-zone) inputs; + nixosConfig = config; + }; + }; + default = { }; + }; + + config = { + nix = mkIf cfg.enable { + settings = unmerged.mergeAttrs cfg.setNixSettings; + buildMachines = unmerged.merge cfg.setNixBuildMachines; + }; + lib.gensokyo-zone.nix = { + inherit cfg nixModule; + }; + }; +} diff --git a/modules/extern/nixos/users.nix b/modules/extern/nixos/users.nix new file mode 100644 index 00000000..008fd332 --- /dev/null +++ b/modules/extern/nixos/users.nix @@ -0,0 +1,230 @@ +{ + config, + lib, + gensokyo-zone, + ... +}: let + inherit (lib.options) mkOption mkEnableOption; + inherit (lib.modules) mkIf mkDefault mkOptionDefault mkOverride; + inherit (lib.lists) filter elem; + inherit (lib.attrsets) nameValuePair attrValues; + inherit (gensokyo-zone.lib) unmerged; + inherit (gensokyo-zone) meta; + cfg = config.gensokyo-zone.users; + userModule = { + gensokyo-zone, + nixosConfig, + config, + name, + ... + }: let + inherit (gensokyo-zone.lib) json unmerged; + cfg = nixosConfig.gensokyo-zone.users; + isValidGroup = group: ! elem group cfg.excludeGroups && cfg.groups.${group}.enable; + mapGroupToSystem = group: cfg.groups.${group}.systemName; + in { + freeformType = json.types.attrs; + options = with lib.types; { + enable = + mkEnableOption "user" + // { + default = true; + }; + name = mkOption { + type = str; + default = name; + }; + systemName = mkOption { + type = str; + default = config.name; + }; + systemUser = mkOption { + type = unspecified; + readOnly = true; + }; + uid = mkOption { + type = int; + }; + group = mkOption { + type = str; + }; + extraGroups = mkOption { + type = listOf str; + default = []; + }; + systemGroup = mkOption { + type = str; + }; + systemGroups = mkOption { + type = listOf str; + }; + setUser = mkOption { + type = unmerged.type; + }; + }; + config = { + systemUser = nixosConfig.users.users.${config.systemName}; + systemGroup = mkOptionDefault (mapGroupToSystem config.group); + systemGroups = mkOptionDefault (map mapGroupToSystem ( + filter isValidGroup config.extraGroups + )); + setUser = { + uid = mkDefault config.uid; + name = mkDefault config.systemName; + autoSubUidGidRange = mkDefault false; + group = mkIf (isValidGroup config.group) ( + mkDefault (mapGroupToSystem config.group) + ); + isSystemUser = mkOverride 1250 (!config.systemUser.isNormalUser); + extraGroups = config.systemGroups; + openssh.authorizedKeys = mkIf (config.systemUser.isNormalUser && config.openssh.authorizedKeys or {} != {}) ( + config.openssh.authorizedKeys + ); + }; + }; + }; + groupModule = { + gensokyo-zone, + nixosConfig, + config, + name, + ... + }: let + inherit (gensokyo-zone.lib) json unmerged; + cfg = nixosConfig.gensokyo-zone.users; + isValidUser = user: ! elem user cfg.excludeUsers && cfg.users.${user}.enable; + mapUserToSystem = user: cfg.users.${user}.systemName; + in { + freeformType = json.types.attrs; + options = with lib.types; { + enable = + mkEnableOption "group" + // { + default = true; + }; + name = mkOption { + type = str; + default = name; + }; + systemName = mkOption { + type = str; + default = config.name; + }; + systemGroup = mkOption { + type = unspecified; + readOnly = true; + }; + gid = mkOption { + type = int; + }; + members = mkOption { + type = listOf str; + }; + systemMembers = mkOption { + type = listOf str; + }; + setGroup = mkOption { + type = unmerged.type; + }; + }; + config = { + systemGroup = nixosConfig.users.groups.${config.systemName}; + systemMembers = mkOptionDefault (map mapUserToSystem ( + filter isValidUser config.members + )); + setGroup = { + gid = mkDefault config.gid; + name = mkDefault config.systemName; + members = config.systemMembers; + openssh.authorizedKeys = mkIf (config.systemUser.isNormalUser && config.openssh.authorizedKeys or {} != {}) ( + config.openssh.authorizedKeys + ); + }; + }; + }; + usersModule = { + gensokyo-zone, + nixosConfig, + config, + ... + }: let + inherit (gensokyo-zone.lib) unmerged; + specialArgs = { + inherit gensokyo-zone nixosConfig; + }; + enabledUsers = filter (user: user.enable) (attrValues config.users); + enabledGroups = filter (group: group.enable) (attrValues config.groups); + in { + options = with lib.types; { + enable = mkEnableOption "gensokyo-zone users"; + users = mkOption { + type = attrsOf (submoduleWith { + modules = [userModule]; + inherit specialArgs; + }); + default = { }; + }; + excludeUsers = mkOption { + type = listOf str; + }; + groups = mkOption { + type = attrsOf (submoduleWith { + modules = [groupModule]; + inherit specialArgs; + }); + default = { }; + }; + excludeGroups = mkOption { + type = listOf str; + }; + setUsers = mkOption { + type = unmerged.types.attrs; + internal = true; + }; + }; + config = { + excludeUsers = []; + excludeGroups = [ + "users" + "wheel" + ]; + setUsers = { + users = map (user: + nameValuePair user.systemName ( + unmerged.merge user.setUser + )) + enabledUsers; + groups = map (group: + nameValuePair group.systemName ( + unmerged.merge group.setGroup + )) + enabledGroups; + }; + }; + }; +in { + options.gensokyo-zone.users = mkOption { + type = lib.types.submoduleWith { + modules = [usersModule]; + specialArgs = { + inherit gensokyo-zone; + inherit (gensokyo-zone) inputs; + nixosConfig = config; + }; + }; + }; + + config = { + gensokyo-zone.users = {...}: { + imports = [ + meta.nixos.users + ]; + }; + users = mkIf cfg.enable ( + unmerged.mergeAttrs cfg.setUsers + ); + lib.gensokyo-zone.users = { + inherit cfg usersModule userModule groupModule; + }; + }; +} diff --git a/outputs.nix b/outputs.nix index ffa12197..5922606b 100644 --- a/outputs.nix +++ b/outputs.nix @@ -46,9 +46,13 @@ }; checks = legacyPackages.deploy-rs.deployChecks inputs.self.deploy; }); + inherit (inputs.self.lib.lib) treeToModulesOutput; in { inherit (outputs) devShells legacyPackages packages checks; inherit (systems) deploy nixosConfigurations; + nixosModules = treeToModulesOutput tree.impure.modules.extern.nixos; + homeModules = treeToModulesOutput tree.impure.modules.extern.home; + miscModules = treeToModulesOutput tree.impure.modules.extern.misc; lib = import ./lib.nix { inherit tree inputs; inherit (systems) systems; diff --git a/systems/extern-test/default.nix b/systems/extern-test/default.nix new file mode 100644 index 00000000..0b15cd1c --- /dev/null +++ b/systems/extern-test/default.nix @@ -0,0 +1,15 @@ +{ inputs, lib, ... }: let + inherit (lib.modules) mkForce; +in { + arch = "x86_64"; + type = "NixOS"; + modules = mkForce [ + ./nixos.nix + ]; + builder = mkForce ({ modules, system, specialArgs, ... }: inputs.nixpkgs.lib.nixosSystem { + inherit modules system; + specialArgs = { + extern'test'inputs = specialArgs.inputs; + }; + }); +} diff --git a/systems/extern-test/nixos.nix b/systems/extern-test/nixos.nix new file mode 100644 index 00000000..526d8017 --- /dev/null +++ b/systems/extern-test/nixos.nix @@ -0,0 +1,28 @@ +{ + extern'test'inputs, + ... +}: let + inherit (extern'test'inputs.self) nixosModules; +in { + imports = [ + nixosModules.default + ]; + + config = { + gensokyo-zone = { + nix = { + enable = true; + builder.enable = true; + }; + kyuuto = { + enable = true; + shared.enable = true; + }; + # TODO: users? + }; + + # this isn't a real machine... + boot.isContainer = true; + system.stateVersion = "23.11"; + }; +} diff --git a/tree.nix b/tree.nix index 30300ad3..aba9e810 100644 --- a/tree.nix +++ b/tree.nix @@ -64,6 +64,11 @@ "modules/system/extern".functor.enable = true; "modules/home".functor.enable = true; "modules/type".functor.enable = true; + "modules/extern/home".functor.enable = true; + "modules/extern/home/args".evaluate = true; + "modules/extern/nixos".functor.enable = true; + "modules/extern/nixos/args".evaluate = true; + "modules/extern/misc/args".evaluate = true; "nixos/*".functor = { enable = true; };