diff --git a/faucet/default.nix b/faucet/default.nix new file mode 100644 index 00000000..e031681d --- /dev/null +++ b/faucet/default.nix @@ -0,0 +1,7 @@ +{ lib, ... }: { + imports = lib.pipe ./. [ + builtins.readDir + (lib.filterAttrs (n: ty: ty == "directory" && builtins.pathExists ./${n}/default.nix)) + (lib.mapAttrsToList (n: _: ./${n})) + ]; +} diff --git a/home/profile.nix b/home/profile.nix new file mode 100644 index 00000000..de542335 --- /dev/null +++ b/home/profile.nix @@ -0,0 +1,148 @@ +{ pkgs +, lib +, config +, ... }: with lib; let + cfg = config.users; +in { + options.users = { + profiles = mkOption { + type = with types; attrsOf (submodule { + options = { + uid = mkOption { + type = with types; nullOr int; + default = null; + description = "uid passthrough to base user configuration"; + }; + admin = mkOption { + type = with types; bool; + default = false; + description = "add user to privileged groups"; + }; + sshLogin = mkOption { + type = with types; bool; + default = false; + description = "enable ssh authorized keys for user"; + }; + }; + }); + description = "preconfigured users with profile options"; + }; + + adminGroups = mkOption { + type = with types; listOf str; + description = "groups to add privileged users to"; + }; + + homeModules = mkOption { + type = with types; listOf anything; + description = "home manager modules imported into every profile"; + }; + + home = { + size = mkOption { + type = with types; str; + default = "1G"; + description = "default home tmpfs size, mounted to prevent accidentally filling up root"; + }; + persist = { + files = mkOption { + type = with types; listOf (oneOf [ str (attrsOf str) ]); + default = [ ]; + }; + directories = mkOption { + type = with types; listOf (oneOf [ str (attrsOf str) ]); + default = [ ]; + }; + }; + }; + }; + + config = { + users = { + users = mapAttrs (name: opts: { + inherit (opts) uid; + extraGroups = mkIf opts.admin cfg.adminGroups; + openssh.authorizedKeys.keys = mkIf (opts.sshLogin && config.services.openssh.enable) + config.faucet.auth.openssh.publicKeys; + hashedPasswordFile = "/nix/persist/shadow/${name}"; + shell = pkgs.zsh; + isNormalUser = mkIf (name != "root") true; + }) cfg.profiles; + + mutableUsers = false; + + # base groups + adminGroups = [ + "wheel" "dialout" "kvm" + "systemd-journal" + ]; + + # base home modules in current directory + homeModules = pipe ./. [ + builtins.readDir + (filterAttrs (n: ty: ty == "directory" && builtins.pathExists ./${n}/home.nix)) + (mapAttrsToList (n: _: ./${n}/home.nix)) + ] ++ [ { + options.passthrough = mkOption { + type = with types; attrsOf anything; + description = "passthrough values from nixos configuration"; + }; + } ]; + + # basic persistence + home.persist = { + directories = [ + "src" + { directory = ".gnupg"; mode = "0700"; } + { directory = ".ssh"; mode = "0700"; } + { directory = ".local/share/keyrings"; mode = "0700"; } + ]; + }; + }; + + # mount tmpfs on each user's home directory with appropriate ownership + fileSystems = mapAttrs' + (name: opts: nameValuePair + # nixpkgs quirk: accessing user configuration here causes infinite recursion + # this workaround ensures proper home directory path unless overridden elsewhere + (if name != "root" then "/home/${name}" else "/root") { + device = "homefs"; + fsType = "tmpfs"; + options = [ "size=${cfg.home.size}" + "uid=${builtins.toString opts.uid}" + "gid=${builtins.toString cfg.groups.${cfg.users.${name}.group}.gid}" + "mode=700" ]; + + # impermanence sets permissions before filesystems are mounted + # this mounts filesystem in initrd therefore working around that bug + neededForBoot = true; + }) cfg.profiles; + + home-manager.users = mapAttrs (name: opts: { + imports = cfg.homeModules; + home.stateVersion = "23.11"; + }) cfg.profiles; + + # set up standard persistence for users + # this is registered internally for each software's configuration + environment.persistence."/nix/persist" = { + users = mapAttrs (name: _: cfg.home.persist // { + # root workaround, ugly but necessary + # cannot get it properly for the same reason + # mentioned above in fileSystems + home = mkIf (name == "root") "/root"; + }) cfg.profiles; + hideMounts = true; + }; + + # enable passwordless sudo + security.sudo.wheelNeedsPassword = false; + }; + + # this is for home components that need to extend nixos + imports = pipe ./. [ + builtins.readDir + (filterAttrs (n: ty: ty == "directory" && builtins.pathExists ./${n}/nixos.nix)) + (mapAttrsToList (n: _: ./${n}/nixos.nix)) + ]; +} diff --git a/home/user.nix b/home/user.nix new file mode 100644 index 00000000..d53a87b0 --- /dev/null +++ b/home/user.nix @@ -0,0 +1,7 @@ +{ + users.profiles = { + koishi = { uid = 1300; admin = true; sshLogin = true; }; # 0x514 = 1300 :) + staging.uid = 1000; + root.uid = 0; + }; +} diff --git a/spec/channel.nix b/spec/channel.nix new file mode 100644 index 00000000..33b377f3 --- /dev/null +++ b/spec/channel.nix @@ -0,0 +1,15 @@ +{ inputs, ... }: with inputs; with nixpkgs.lib; let + mapInputs = fn: map fn (lists.remove "self" (attrNames inputs)); + channelPath = "/etc/nix/channels"; +in { + nix = { + nixPath = mapInputs (i: "${i}=${channelPath}/${i}"); + registry = listToAttrs + (mapInputs (name: { + inherit name; + value = {flake = inputs.${name};}; + })); + }; + + systemd.tmpfiles.rules = mapInputs (i: "L+ ${channelPath}/${i} - - - - ${inputs.${i}.outPath}"); +} diff --git a/spec/constant.nix b/spec/constant.nix new file mode 100644 index 00000000..f3de324e --- /dev/null +++ b/spec/constant.nix @@ -0,0 +1,7 @@ +{ + i18n.defaultLocale = "en_GB.UTF-8"; + time.timeZone = "Asia/Hong_Kong"; + environment.etc.nixos.source = "/nix/persist/config"; + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + system.stateVersion = "23.11"; +} diff --git a/spec/default.nix b/spec/default.nix new file mode 100644 index 00000000..15dbcba6 --- /dev/null +++ b/spec/default.nix @@ -0,0 +1,29 @@ +{ inputs, ... }: with inputs; with nixpkgs.lib; { + flake.nixosConfigurations = (lists.foldr (name: spec: spec // { + ${name} = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + specialArgs = inputs // { inherit inputs; }; + modules = [ + ../faucet + ../home/profile.nix + ../home/user.nix + ./constant.nix + ./channel.nix + impermanence.nixosModules.impermanence + home-manager.nixosModules.home-manager + { + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + } + lanzaboote.nixosModules.lanzaboote + + ./${name} + { networking.hostName = name; } + ]; + }; + }) { }) (pipe ./. [ + builtins.readDir + (filterAttrs (n: ty: ty == "directory" && builtins.pathExists ./${n}/default.nix)) + (mapAttrsToList (n: _: n)) + ]); +}