diff --git a/modules/nih/default.nix b/modules/nih/default.nix new file mode 100644 index 0000000..481f934 --- /dev/null +++ b/modules/nih/default.nix @@ -0,0 +1,93 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.nih; + applyAttrNames = builtins.mapAttrs (name: f: f name); +in +{ + imports = [ + ./sound.nix + ./users.nix + ./networking + ./services + ]; + options.nih = with lib; with lib.types; { + domain = mkOption { + type = str; + default = "${cfg.name}.local"; + }; + enable = mkEnableOption ""; + flakeDir = mkOption { + type = either str path; + }; + handleDomains = mkOption { + type = bool; + default = true; + }; + ip = mkOption { + type = str; + }; + localIp = mkOption { + type = str; + default = cfg.ip; + }; + name = mkOption { + type = str; + default = "nih"; + }; + }; + config = with lib; mkIf cfg.enable { + boot = { + loader.systemd-boot.enable = mkDefault true; + loader.efi.canTouchEfiVariables = mkDefault true; + }; + + systemd.services."nih-setup" = with builtins; { + script = '' + echo ${builtins.toJSON cfg.users} + ''; + wantedBy = [ "multi-user.target" ]; + after = [ "forgejo.service" ]; + serviceConfig = { + Type = "oneshot"; + }; + }; + + # Handle domains configuration + + networking.firewall.allowedTCPPorts = mkIf cfg.handleDomains [ 80 433 ]; + + systemd.services."tailscaled" = mkIf cfg.handleDomains { + serviceConfig = { + Environment = [ "TS_PERMIT_CERT_UID=caddy" ]; + }; + }; + + nih.services = mkIf cfg.handleDomains { + adguard = { + enable = true; + settings.dns.rewrites = (if hasPrefix "*." cfg.domain then { + "${cfg.domain}" = cfg.ip; + } else { + "${cfg.domain}" = cfg.ip; + "${"*." + cfg.domain}" = cfg.ip; + }); + }; + + caddy = + let + nihServices = (filterAttrs (n: v: builtins.isAttrs v && v?domain) cfg.services); + in + mkIf cfg.handleDomains { + enable = true; + virtualHosts = mapAttrs' + (name: value: nameValuePair (value.domain) ({ + extraConfig = '' + reverse_proxy ${cfg.localIp}:${toString value.port} + ''; + })) + nihServices; + }; + }; + }; +} diff --git a/modules/nih/networking/default.nix b/modules/nih/networking/default.nix new file mode 100644 index 0000000..d8a5dd8 --- /dev/null +++ b/modules/nih/networking/default.nix @@ -0,0 +1,66 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.nih.networking; +in +{ + imports = [ + ./tailscale.nix + ]; + options.nih.networking = with lib; with lib.types; { + defaultGateway = mkOption { + type = str; + default = "192.168.1.1"; + }; + hostName = mkOption { + type = str; + default = config.nih.name; + }; + interface = mkOption { + type = nullOr str; + default = null; + }; + localIp = mkOption { + type = str; + default = config.nih.localIp; + }; + nameservers = mkOption { + type = listOf str; + default = [ "1.1.1.1" "8.8.8.8" ]; + }; + networkmanager = mkOption { + type = bool; + default = true; + }; + portForwarding = mkOption { + type = bool; + default = false; + }; + wireless = mkOption { + type = bool; + default = true; + }; + }; + config = with lib; { + boot.kernel.sysctl."net.ipv4.ip_forward" = if cfg.portForwarding then 1 else 0; + boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = if cfg.portForwarding then 1 else 0; + + host.networking.hostName = cfg.hostName; + + networking = { + defaultGateway = cfg.defaultGateway; + dhcpcd.enable = true; + interfaces = mkIf (cfg.interface != null) { + "${cfg.interface}".ipv4.addresses = [{ + address = cfg.localIp; + prefixLength = 28; + }]; + }; + nameservers = [ + (mkIf config.nih.networking.tailscale.enable "100.100.100.100") + ] ++ cfg.nameservers; + networkmanager.enable = cfg.networkmanager; + wireless.enable = cfg.wireless; + }; + }; +} diff --git a/modules/nih/networking/tailscale.nix b/modules/nih/networking/tailscale.nix new file mode 100644 index 0000000..626ba0b --- /dev/null +++ b/modules/nih/networking/tailscale.nix @@ -0,0 +1,41 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.nih.networking.tailscale; +in +{ + imports = [ ]; + options.nih.networking.tailscale = with lib; with lib.types; { + enable = mkEnableOption ""; + exitNode = mkOption { + type = bool; + default = false; + }; + port = mkOption { + type = port; + default = 41641; + }; + routingFeatures = mkOption { + type = enum [ "none" "client" "server" "both" ]; + default = "client"; + }; + upFlags = mkOption { + type = listOf str; + default = [ ]; + }; + }; + config = with lib; { + services.tailscale = { + enable = true; + extraUpFlags = cfg.upFlags ++ [ + (if cfg.exitNode then "--advertise-exit-node" else null) + ]; + port = cfg.port; + useRoutingFeatures = cfg.routingFeatures; + }; + + nih.networking = mkIf cfg.exitNode { + portForwarding = mkDefault true; + }; + }; +} diff --git a/modules/nih/programs/default.nix b/modules/nih/programs/default.nix new file mode 100644 index 0000000..78f8d50 --- /dev/null +++ b/modules/nih/programs/default.nix @@ -0,0 +1,13 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.nih.programs; +in +{ + imports = [ + ./hyprland.nix + ./lf.nix + ]; + options.programs = { }; + config = { }; +} diff --git a/modules/nih/programs/hyprland.nix b/modules/nih/programs/hyprland.nix new file mode 100644 index 0000000..2edf024 --- /dev/null +++ b/modules/nih/programs/hyprland.nix @@ -0,0 +1,313 @@ +{ config, inputs, lib, pkgs, ... }: + +let + cfg = config.programs.hyprland; +in +{ + imports = [ ]; + options.programs.hyprland = with lib; with lib.types; { + enable = mkEnableOption ""; + monitors = mkOption { + default = [ ]; + type = listOf (submodule ({ ... }: { + options = { + id = mkOption { + type = str; + }; + name = mkOption { + type = str; + }; + resolution = mkOption { + type = str; + }; + hz = mkOption { + type = int; + default = 60; + }; + offset = mkOption { + type = str; + default = "0x0"; + }; + scale = mkOption { + type = int; + default = 1; + }; + }; + })); + }; + exec = mkOption { + default = [ ]; + type = listOf str; + }; + env = mkOption { + default = { }; + type = attrsOf str; + }; + windowRules = mkOption { + default = { }; + description = "window = [ \"rule\" ]"; + type = attrsOf (listOf str); + }; + input = { + keyboard.layout = mkOption { + default = "br"; + type = str; + }; + keyboard.variant = mkOption { + default = "abnt2"; + type = str; + }; + mouse.follow = mkOption { + default = true; + type = bool; + }; + mouse.sensitivity = mkOption { + default = 0; + type = number; + }; + }; + general = { + gaps_in = mkOption { + default = 5; + type = number; + }; + gaps_out = mkOption { + default = 10; + type = number; + }; + border.size = mkOption { + default = 0; + type = number; + }; + border.color.active = mkOption { + default = "rgba(ffffff99) rgba(ffffff33) 90deg"; + type = str; + }; + border.color.inactive = mkOption { + default = "rgba(18181800)"; + type = str; + }; + layout = mkOption { + default = "dwindle"; + type = str; + }; + }; + decoration = { + rouding = mkOption { + default = 5; + type = number; + }; + dim.inactive = mkOption { + default = true; + type = bool; + }; + dim.strength = mkOption { + default = 0.2; + type = number; + }; + dim.around = mkOption { + default = 0.4; + type = number; + }; + }; + animations = { + enabled = mkOption { + default = true; + type = bool; + }; + }; + workspaces = mkOption { + default = [ ]; + type = listOf + (submodule ({ ... }: { + options = { + name = mkOption { + type = str; + }; + monitor = mkOption { + type = nullOr str; + default = null; + }; + default = mkOption { + type = bool; + default = false; + }; + extraRules = mkOption { + type = str; + default = ""; + }; + }; + })); + }; + binds = { + mod = mkOption { + default = "SUPER"; + type = str; + }; + keyboard = mkOption { + default = [ ]; + type = listOf str; + }; + mouse = mkOption { + default = [ ]; + type = listOf str; + }; + }; + }; + config = lib.mkIf cfg.enable { + wayland.windowManager.hyprland.enable = true; + wayland.windowManager.hyprland.package = inputs.hyprland.packages."${pkgs.system}".hyprland; + + wayland.windowManager.hyprland.xwayland.enable = true; + wayland.windowManager.hyprland.systemd.enable = true; + + wayland.windowManager.hyprland.settings = lib.mkMerge [ + # Sets monitor variables ("$name" = "id") so it can be used in rules later + (builtins.listToAttrs (map + (m: { + name = "\$${m.name}"; + value = "${m.id}"; + }) + cfg.monitors) + ) + { + # Construct the "name,resolution@hz,offset,scale" strings + monitor = (map + (m: "${m.name},${m.resolution}@${toString m.hz},${m.offset},${toString m.scale}") + cfg.monitors + ); + + exec-once = cfg.exec; + + # "Hack" to transform attributes sets to lists (because I didn't know other way to do it) + # Transform { "envName" = "value" } to [ "envName,value" ] + env = builtins.attrValues + (builtins.mapAttrs (n: v: "${n},${v}") (lib.attrsets.mergeAttrsList [ + { + "XCURSOR_SIZE" = "24"; + "MOZ_ENABLE_WAYLAND" = "1"; + } + cfg.env + ])); + + + windowrulev2 = + let + firefoxPipRules = [ + "float" + # "nofullscreenrequest" + "size 480 270" + "fakefullscreen" + "nodim" + "noblur" + ]; + in + builtins.concatLists + (builtins.attrValues (builtins.mapAttrs + (w: rs: + (map (r: "${r},${w}") rs) + ) + (lib.attrsets.mergeAttrsList [ + { + "title:^(Picture-in-Picture)$,class:^(firefox)$" = firefoxPipRules; + "title:^(Firefox)$,class:^(firefox)$" = firefoxPipRules; + "title:^(Picture-in-Picture)$" = firefoxPipRules; + "class:^(xwaylandvideobridge)$" = [ + "opacity 0.0 override 0.0 override" + "noanim" + "nofocus" + "noinitialfocus" + ]; + } + cfg.windowRules + ]) + )); + + input = { + kb_layout = cfg.input.keyboard.layout; + kb_variant = cfg.input.keyboard.variant; + + follow_mouse = if cfg.input.mouse.follow then "1" else "0"; + + sensitivity = toString cfg.input.mouse.sensitivity; + }; + + general = { + gaps_in = toString cfg.general.gaps_in; + gaps_out = toString cfg.general.gaps_out; + border_size = toString cfg.general.border.size; + "col.active_border" = toString cfg.general.border.color.active; + "col.inactive_border" = toString cfg.general.border.color.inactive; + layout = cfg.general.layout; + }; + + decoration = { + rounding = toString cfg.decoration.rouding; + + dim_inactive = if cfg.decoration.dim.inactive then "true" else "false"; + dim_strength = toString cfg.decoration.dim.strength; + dim_around = toString cfg.decoration.dim.around; + + blur = { + enabled = "false"; + size = "20"; + }; + }; + + animations = { + enabled = if cfg.animations.enabled then "yes" else "no"; + + bezier = "myBezier, 0.05, 0.9, 0.1, 1.05"; + + animation = [ + "windows, 1, 7, myBezier" + "windowsOut, 1, 7, default, popin 80%" + "border, 1, 10, default" + "borderangle, 1, 8, default" + "fade, 1, 7, default" + "workspaces, 1, 6, default" + ]; + }; + + dwindle = { + pseudotile = "yes"; + preserve_split = "yes"; + }; + + master = { + new_is_master = "true"; + }; + + gestures = { + workspace_swipe = "off"; + }; + + workspace = + (map + (w: "${w.name},${ + if w.monitor != null then "monitor:${w.monitor}," else "" + }${ + if w.default then "default:true," else "" + }${w.extraRules} ") + cfg.workspaces + ); + + "$ + mod " = cfg.binds.mod; + + bind = cfg.binds.keyboard; + bindm = cfg.binds.mouse; + + } + ]; + }; +} + + + + + + + + + diff --git a/modules/nih/programs/lf.nix b/modules/nih/programs/lf.nix new file mode 100644 index 0000000..4eb38ab --- /dev/null +++ b/modules/nih/programs/lf.nix @@ -0,0 +1,79 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.programs.lf; +in +{ + imports = [ ]; + options.programs.lf = with lib; with lib.types; { + cmds = { + mkfile = mkOption { + type = bool; + default = true; + }; + editor-open = mkOption { + type = bool; + default = true; + }; + dragon-out = mkOption { + type = bool; + default = true; + }; + }; + extraCfg = mkOption { + type = lines; + default = ""; + }; + filePreviewer = mkOption { + type = bool; + default = true; + }; + }; + config = with lib; mkIf cfg.enable { + programs.lf = { + commands = { + dragon-out = mkIf cfg.cmds.dragon-out ''%${pkgs.xdragon}/bin/xdragon -a -x "$fx"''; + editor-open = mkIf cfg.cmds.editor-open ''$$EDITOR $f''; + mkfile = mkIf cfg.cmds.mkfile ''''${{ + printf "Dirname: " + read DIR + + if [[ $DIR = */ ]]; then + mkdir $DIR + else + touch $DIR + fi + }}''; + }; + + extraConfig = + let + previewer = pkgs.writeShellScriptBin "pv.sh" '' + file=$1 + w=$2 + h=$3 + x=$4 + y=$5 + + if [[ "$(${pkgs.file}/bin/file -Lb --mime-type "$file")" =~ ^image ]]; then + ${pkgs.kitty}/bin/kitty +kitten icat --silent --stdin no --transfer-mode file --place "''${w}x''${h}@''${x}x''${y}" "$file" < /dev/null > /dev/tty + exit 1 + fi + + ${pkgs.pistol}/bin/pistol "$file" + ''; + cleaner = pkgs.writeShellScriptBin "clean.sh" '' + ${pkgs.kitty}/bin/kitty +kitten icat --clear --stdin no --silent --transfer-mode file < /dev/null > /dev/tty + ''; + in + mkDefault '' + ${if cfg.filePreviewer then '' + set cleaner ${cleaner}/bin/clean.sh + set previewer ${previewer}/bin/pv.sh + '' else ""} + + ${cfg.extraCfg} + ''; + }; + }; +} diff --git a/modules/nih/services/adguard.nix b/modules/nih/services/adguard.nix new file mode 100644 index 0000000..d6b0013 --- /dev/null +++ b/modules/nih/services/adguard.nix @@ -0,0 +1,90 @@ +{ config, lib, ... }: + +let + cfg = config.nih.services.adguard; +in +{ + imports = [ ]; + options.nih.services.adguard = with lib; with lib.types; { + enable = mkEnableOption ""; + extraArgs = mkOption { + type = listOf str; + default = [ ]; + }; + domain = mkOption { + type = str; + default = "adguard." + config.nih.domain; + }; + port = mkOption { + type = port; + default = 3010; + }; + settings = { + server.domain = mkOption { + type = str; + default = cfg.domain; + }; + server.port = mkOption { + type = port; + default = cfg.port; + }; + server.address = mkOption { + type = str; + default = "0.0.0.0"; + }; + dns.rewrites = mkOption { + type = attrsOf str; + default = { }; + }; + dns.filters = mkOption { + type = attrsOf (submodule ({ lib, ... }: { + options = { + name = mkOption { + type = nullOr str; + default = null; + }; + url = mkOption { + type = str; + }; + enabled = { + type = bool; + default = true; + }; + }; + })); + default = { }; + }; + }; + }; + config = lib.mkIf cfg.enable { + networking.firewall = { + allowedTCPPorts = [ 53 ]; + allowedUDPPorts = [ 53 51820 ]; + }; + services.adguardhome = with builtins; { + enable = true; + settings = { + bind_port = cfg.settings.server.port; + bind_host = cfg.settings.server.address; + http = { + address = "${cfg.settings.server.address}:${toString cfg.settings.server.port}"; + }; + dns.rewrites = (builtins.attrValues (builtins.mapAttrs + (from: to: { + domain = from; + answer = to; + }) + cfg.settings.dns.rewrites)); + filters = (attrValues (mapAttrs + (id: list: { + name = if isNull list.name then id else list.name; + ID = id; + url = list.url; + enabled = list.enabled; + }) + cfg.settings.dns.filters)); + }; + }; + }; +} + diff --git a/modules/nih/services/caddy.nix b/modules/nih/services/caddy.nix new file mode 100644 index 0000000..4eb3d5a --- /dev/null +++ b/modules/nih/services/caddy.nix @@ -0,0 +1,28 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.nih.services.caddy; +in +{ + imports = [ ]; + options.nih.services.caddy = with lib; with lib.types; { + enable = mkEnableOption ""; + virtualHosts = mkOption { + type = attrsOf (submodule ({ config, lib, ... }: { + options = { + extraConfig = mkOption { + type = lines; + default = ""; + }; + }; + })); + default = { }; + }; + }; + config = with lib; mkIf cfg.enable { + services.caddy = { + enable = true; + virtualHosts = cfg.virtualHosts; + }; + }; +} diff --git a/modules/nih/services/default.nix b/modules/nih/services/default.nix new file mode 100644 index 0000000..45b7382 --- /dev/null +++ b/modules/nih/services/default.nix @@ -0,0 +1,11 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./adguard.nix + ./caddy.nix + ./forgejo.nix + ]; + options.nih.services = { }; + config = { }; +} diff --git a/modules/nih/services/forgejo.nix b/modules/nih/services/forgejo.nix new file mode 100644 index 0000000..d3fe2bc --- /dev/null +++ b/modules/nih/services/forgejo.nix @@ -0,0 +1,301 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.nih.services.forgejo; + users = (builtins.attrValues (builtins.mapAttrs + (username: info: { + name = if isNull info.name then username else info.name; + email = info.email; + password = info.password; + admin = info.admin; + }) + cfg.settings.users)); +in +{ + imports = [ ]; + options.nih.services.forgejo = with lib; with lib.types; { + enable = mkEnableOption ""; + user = mkOption { + type = str; + default = "git"; + }; + package = mkOption { + type = package; + default = pkgs.forgejo; + }; + cliAlias = mkOption { + type = bool; + default = true; + }; + domain = mkOption { + type = str; + default = "forgejo." + config.nih.domain; + }; + port = mkOption { + type = port; + default = 3020; + }; + data = { + root = mkOption { + type = path; + default = config.server.storage + /forgejo; + }; + }; + handleUndeclaredUsers = mkOption { + type = bool; + default = false; + }; + settings = { + users = mkOption { + type = attrsOf (submodule ({ config, lib, ... }: with lib; with lib.types; { + options = { + name = mkOption { + type = nullOr (either str path); + default = null; + }; + password = mkOption { + type = either str path; + }; + email = mkOption { + type = either str path; + }; + admin = mkOption { + type = bool; + default = false; + }; + }; + })); + default = { }; + }; + name = mkOption { + type = str; + default = "Forgejo: Beyond coding. We forge"; + }; + prod = mkOption { + type = bool; + default = true; + }; + repo.defaultUnits = mkOption { + type = listOf (enum [ + "repo.code" + "repo.releases" + "repo.issues" + "repo.pulls" + "repo.wiki" + "repo.projects" + "repo.packages" + "repo.actions" + ]); + default = [ + "repo.code" + "repo.issues" + "repo.pulls" + ]; + }; + repo.disabledUnits = mkOption { + type = listOf (enum [ + "repo.issues" + "repo.ext_issues" + "repo.pulls" + "repo.wiki" + "repo.ext_wiki" + "repo.projects" + "repo.packages" + "repo.actions" + ]); + default = [ ]; + }; + repo.pushCreate = mkOption { + type = bool; + default = false; + }; + cors.enable = mkOption { + type = bool; + default = false; + }; + cors.domains = mkOption { + type = listOf str; + default = [ ]; + }; + cors.methods = mkOption { + type = listOf str; + default = [ ]; + }; + ui.defaultTheme = mkOption { + type = str; + default = "forgejo-auto"; + }; + ui.themes = mkOption { + type = listOf str; + default = [ + "forgejo-auto" + "forgejo-light" + "forgejo-dark" + "auto" + "gitea" + "arc-green" + ]; + }; + server.protocol = mkOption { + type = enum [ "http" "https" "fcgi" "http+unix" "fcgi+unix" ]; + default = "http"; + }; + server.domain = mkOption { + type = str; + default = cfg.domain; + }; + server.port = mkOption { + type = port; + default = cfg.port; + }; + server.address = mkOption { + type = either str path; + default = if hasSuffix "+unix" cfg.settings.server.protocol then "/run/forgejo/forgejo.sock" else "0.0.0.0"; + }; + server.url = mkOption { + type = str; + default = "http://${cfg.settings.server.domain}:${toString cfg.settings.server.port}"; + }; + server.offline = mkOption { + type = bool; + default = false; + }; + server.compression = mkOption { + type = bool; + default = false; + }; + server.landingPage = mkOption { + type = enum [ "home" "explore" "organizations" "login" str ]; + default = "home"; + }; + service.registration = mkOption { + type = bool; + default = false; + }; + security.allowBypassGiteaEnv = mkOption { + type = bool; + default = false; + }; + }; + }; + config = lib.mkIf cfg.enable { + users.users."${cfg.user}" = { + home = cfg.data.root; + useDefaultShell = true; + group = cfg.user; + isSystemUser = true; + initialPassword = "1313"; + }; + users.groups."${cfg.user}" = { }; + + services.forgejo = { + enable = true; + package = cfg.package; + user = cfg.user; + group = cfg.user; + stateDir = toString cfg.data.root; + useWizard = false; + database = { + user = cfg.user; + type = "sqlite3"; + }; + settings = with builtins; { + DEFAULT = { + APP_NAME = cfg.settings.name; + RUN_MODE = if cfg.settings.prod then "prod" else "dev"; + }; + repository = { + DISABLED_REPO_UNITS = concatStringsSep "," cfg.settings.repo.disabledUnits; + DEFAULT_REPO_UNITS = concatStringsSep "," cfg.settings.repo.defaultUnits; + ENABLE_PUSH_CREATE_USER = cfg.settings.repo.pushCreate; + ENABLE_PUSH_CREATE_ORG = cfg.settings.repo.pushCreate; + }; + cors = { + ENABLED = cfg.settings.cors.enable; + ALLOW_DOMAIN = concatStringsSep "," cfg.settings.cors.domains; + METHODS = concatStringsSep "," cfg.settings.cors.methods; + }; + ui = { + DEFAULT_THEME = cfg.settings.ui.defaultTheme; + THEMES = concatStringsSep "," cfg.settings.ui.themes; + }; + server = { + PROTOCOL = cfg.settings.server.protocol; + DOMAIN = cfg.settings.server.domain; + ROOT_URL = cfg.settings.server.url; + HTTP_ADDR = cfg.settings.server.address; + HTTP_PORT = cfg.settings.server.port; + OFFLINE_MODE = cfg.settings.server.offline; + ENABLE_GZIP = cfg.settings.server.compression; + LANDING_PAGE = cfg.settings.server.landingPage; + }; + security = { + ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET = if cfg.settings.security.allowBypassGiteaEnv then false else true; + }; + service = { + DISABLE_REGISTRATION = if cfg.settings.service.registration then false else true; + }; + }; + }; + systemd.services."homelab-forgejo-setup" = with builtins; { + script = '' + + configFile="${toString cfg.data.root}/custom/conf/app.ini"; + touch $configFile + + gum="${pkgs.gum}/bin/gum" + forgejo="${cfg.package}/bin/gitea --config $configFile" + user="$forgejo admin user" + awk="${pkgs.gawk}/bin/awk" + + declaredUsers=(${toString (map (user: "${if isPath user.name then "$(cat ${toString user.name})" else user.name}") users)}); + + $gum log --structured --time timeonly --level info "HANDLING UNDECLARED USERS" + + $user list | $awk '{print $2}' | tail -n +2 | while read username; do + if printf '%s\0' "''${declaredUsers[@]}" | grep -Fxqz -- "$username"; then + $gum log --structured --time timeonly --level warn "Declared user already exists, ignoring" username $username; + else + if [[ "$($user list | tail -n +2 | $awk '{print $2 " " $5}' | grep "$username " | $awk '{print $2}')" == "true" ]]; then + $gum log --structured --time timeonly --level warn "Undeclared user is a ADMIN, ignoring" username $username; + else + ${if cfg.handleUndeclaredUsers then '' + $gum log --structured --time timeonly --level warn "DELETING undeclared user" username $username; + + $user delete -u "$username"; + '' else '' + $gum log --structured --time timeonly --level warn "UNDECLARED user, please declare it in the config so it's reproducible" username "$username"; + ''} + fi + fi + done + + ${toString (map (user: '' + username="${if isPath user.name then "\"$(cat ${toString user.name})\"" else user.name}"; + email="${if isPath user.email then "\"$(cat ${toString user.email})\"" else user.email}"; + password="${if isPath user.password then "\"$(cat ${toString user.password})\"" else user.password}"; + + if [[ "$($user list | grep "$username" | $awk '{print $2}')" ]]; then + $gum log --structured --time timeonly --level warn "User with username already exists" username $username; + + elif [[ "$($user list | grep "$email" | $awk '{print $3}')" ]]; then + $gum log --structured --time timeonly --level warn "User with email already exists" email $email; + + else + $gum log --structured --time timeonly --level info ${if user.admin then "Creating ADMIN user" else "Creating user"} username $username email $email password $password; + $user create --username $username --email $email --password $password ${if user.admin then "--admin" else ""}; + + fi + '') users)} + ''; + wantedBy = [ "multi-user.target" ]; + after = [ "forgejo.service" ]; + serviceConfig = { + Type = "oneshot"; + User = cfg.user; + Group = cfg.user; + }; + }; + }; +} + diff --git a/modules/nih/sound.nix b/modules/nih/sound.nix new file mode 100644 index 0000000..8e8d4ab --- /dev/null +++ b/modules/nih/sound.nix @@ -0,0 +1,33 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.nih.sound; +in +{ + imports = [ ]; + options.nih.sound = with lib; with lib.types; { + enable = mkOption { + type = bool; + default = true; + }; + pipewire.enable = mkOption { + type = bool; + default = true; + }; + pulseaudio.enable = mkOption { + type = bool; + default = !cfg.pipewire.enable; + }; + }; + config = with lib; mkIf cfg.enable { + sound.enable = true; + hardware.pulseaudio.enable = cfg.pulseaudio.enable; + security.rtkit.enable = true; + services.pipewire = mkIf cfg.pipewire.enable { + enable = true; + alsa.enable = mkDefault true; + alsa.support32Bit = mkDefault true; + pulse.enable = mkDefault true; + wireplumber.enable = mkDefault true; + }; + }; +} diff --git a/modules/nih/users.nix b/modules/nih/users.nix new file mode 100644 index 0000000..38873e8 --- /dev/null +++ b/modules/nih/users.nix @@ -0,0 +1,132 @@ +{ config, inputs, lib, pkgs, ... }: + +let + cfg = config.nih; + hmModule = lib.types.submoduleWith { + description = "Home Manager module"; + specialArgs = { + lib = lib; + osConfig = config; + }; + modules = [ + ({ name, ... }: { + config = { + submoduleSupport.enable = true; + submoduleSupport.externalPackageInstall = cfg.useUserPackages; + + home.username = config.users.users.${name}.name; + home.homeDirectory = config.users.users.${name}.home; + + # Make activation script use same version of Nix as system as a whole. + # This avoids problems with Nix not being in PATH. + nix.package = config.nix.package; + }; + }) + ] ++ config.home-manager.sharedModules; + }; +in +{ + imports = [ ]; + options.nih = with lib; with lib.types; { + users = mkOption { + type = attrsOf + (submodule ({ ... }: { + options = { + description = mkOption { + type = nullOr str; + default = null; + }; + extraGroups = mkOption { + type = listOf str; + default = [ "networkmanager" "wheel" ]; + }; + home = mkOption { + type = attrsOf anything; + default = { }; + }; + h = mkOption { + type = attrsOf anything; + default = { }; + }; + normalUser = mkOption { + type = bool; + default = true; + }; + packages = mkOption { + type = listOf package; + default = [ ]; + }; + password = mkOption { + type = nullOr (passwdEntry str); + default = null; + }; + programs = mkOption { + type = attrsOf anything; + default = { }; + }; + services = mkOption { + type = attrsOf anything; + default = { }; + }; + shell = mkOption { + type = package; + default = pkgs.bash; + }; + username = mkOption { + type = passwdEntry str; + apply = x: assert (builtins.stringLength + x < 32 || abort "Username '${x}' is longer than 31 characters"); x; + }; + }; + })); + }; + }; + config = with lib; { + users.users = + (builtins.mapAttrs + (name: value: { + name = value.username; + hashedPassword = value.password; + description = if value.description != null then value.description else value.username; + isNormalUser = value.normalUser; + shell = value.shell; + extraGroups = value.extraGroups ++ [ "wheel" ]; + }) + cfg.users); + + users.mutableUsers = true; + + home-manager.extraSpecialArgs = { inherit inputs; }; + home-manager.users = + (builtins.mapAttrs + (name: value: mkMerge [ + { + imports = [ + inputs.nix-index-database.hmModules.nix-index + inputs.flatpaks.homeManagerModules.nix-flatpak + ./programs + ]; + programs = mkMerge [ + { home-manager.enable = true; } + value.programs + ]; + services = mkMerge [ + { flatpak.enable = mkDefault true; } + value.services + ]; + + home = mkMerge [ + { + username = value.username; + homeDirectory = mkDefault + "/home/${value.username}"; + stateVersion = mkDefault + "23.11"; # Do not change + } + value.home + ]; + } + ]) + cfg.users); + }; +}