feat: "nix host" module start

This commit is contained in:
Gustavo "Guz" L. de Mello
2024-04-04 16:56:03 -03:00
parent 33b7d3b2c2
commit 7698ba668f
12 changed files with 1200 additions and 0 deletions

93
modules/nih/default.nix Normal file
View File

@@ -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;
};
};
};
}

View File

@@ -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;
};
};
}

View File

@@ -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;
};
};
}

View File

@@ -0,0 +1,13 @@
{ config, lib, pkgs, ... }:
let
cfg = config.nih.programs;
in
{
imports = [
./hyprland.nix
./lf.nix
];
options.programs = { };
config = { };
}

View File

@@ -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;
}
];
};
}

View File

@@ -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}
'';
};
};
}

View File

@@ -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));
};
};
};
}

View File

@@ -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;
};
};
}

View File

@@ -0,0 +1,11 @@
{ config, lib, pkgs, ... }:
{
imports = [
./adguard.nix
./caddy.nix
./forgejo.nix
];
options.nih.services = { };
config = { };
}

View File

@@ -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;
};
};
};
}

33
modules/nih/sound.nix Normal file
View File

@@ -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;
};
};
}

132
modules/nih/users.nix Normal file
View File

@@ -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);
};
}