refactor: mkHost load-all infrastructure and convert simple features

This commit is contained in:
2026-04-12 21:27:43 +00:00
parent 3ff4583017
commit 6770bc76a2
18 changed files with 455 additions and 210 deletions

View File

@@ -1,10 +1,21 @@
{ {
home = nixos =
{ pkgs, ... }: { lib, ... }:
{ {
home.packages = [ options.features.claude.enable = lib.mkEnableOption "claude";
pkgs.claude-code };
pkgs.mcp-nixos
]; home =
{ pkgs, lib, osConfig, ... }:
let
cfg = osConfig.features.claude;
in
{
config = lib.mkIf cfg.enable {
home.packages = [
pkgs.claude-code
pkgs.mcp-nixos
];
};
}; };
} }

View File

@@ -1,26 +1,40 @@
{ {
nixos = nixos =
{ inputs, ... }: { config, lib, inputs, ... }:
let
cfg = config.features.direnv;
in
{ {
nix.registry.dev = { options.features.direnv.enable = lib.mkEnableOption "direnv";
from = {
type = "indirect"; config = lib.mkIf cfg.enable {
id = "dev"; nix.registry.dev = {
}; from = {
to = { type = "indirect";
type = "path"; id = "dev";
path = inputs.self.outPath; };
to = {
type = "path";
path = inputs.self.outPath;
};
}; };
}; };
}; };
home = _: { home =
programs.direnv = { { lib, osConfig, ... }:
enable = true; let
nix-direnv.enable = true; cfg = osConfig.features.direnv;
config.global.hide_env_diff = true; in
}; {
config = lib.mkIf cfg.enable {
programs.direnv = {
enable = true;
nix-direnv.enable = true;
config.global.hide_env_diff = true;
};
xdg.configFile."direnv/lib/use_dev.sh".source = ./use_dev.sh; xdg.configFile."direnv/lib/use_dev.sh".source = ./use_dev.sh;
}; };
};
} }

View File

@@ -1,12 +1,19 @@
{ {
nixos = nixos =
{ user, ... }: { config, lib, user, ... }:
let
cfg = config.features.docker;
in
{ {
virtualisation.docker = { options.features.docker.enable = lib.mkEnableOption "docker";
enable = true;
logDriver = "json-file";
};
users.users.${user}.extraGroups = [ "docker" ]; config = lib.mkIf cfg.enable {
virtualisation.docker = {
enable = true;
logDriver = "json-file";
};
users.users.${user}.extraGroups = [ "docker" ];
};
}; };
} }

View File

@@ -1,42 +1,56 @@
{ {
nixos = nixos =
{ config, userKeys, ... }: { config, lib, userKeys, ... }:
let
cfg = config.features.filedrop;
in
{ {
sops.secrets.filedrop-authorized-keys = { options.features.filedrop = {
sopsFile = ../secrets/floo.yaml; enable = lib.mkEnableOption "filedrop sftp service";
mode = "0444";
sopsFile = lib.mkOption {
type = lib.types.path;
};
}; };
users.groups.filedrop = { config = lib.mkIf cfg.enable {
members = [ "matej" ]; sops.secrets.filedrop-authorized-keys = {
inherit (cfg) sopsFile;
mode = "0444";
};
users.groups.filedrop = {
members = [ "matej" ];
};
users.users.filedrop = {
isSystemUser = true;
group = "filedrop";
home = "/home/filedrop";
shell = "/run/current-system/sw/bin/nologin";
openssh.authorizedKeys.keys = userKeys.sshAuthorizedKeys;
};
# chroot dir must be root-owned; incoming is writable by filedrop
systemd.tmpfiles.rules = [
"d /home/filedrop 0755 root root -"
"d /home/filedrop/incoming 2775 filedrop filedrop -"
"a+ /home/filedrop/incoming - - - - group:filedrop:rwx"
"a+ /home/filedrop/incoming - - - - default:group:filedrop:rwx"
"a+ /home/filedrop/incoming - - - - default:mask::rwx"
"L /home/matej/filedrop - - - - /home/filedrop/incoming"
];
# relaxed umask so default acl takes full effect
services.openssh.extraConfig = ''
Match User filedrop
ForceCommand internal-sftp -u 0002
ChrootDirectory /home/filedrop
AuthorizedKeysFile /etc/ssh/authorized_keys.d/filedrop %h/.ssh/authorized_keys ${config.sops.secrets.filedrop-authorized-keys.path}
AllowTcpForwarding no
X11Forwarding no
'';
}; };
users.users.filedrop = {
isSystemUser = true;
group = "filedrop";
home = "/home/filedrop";
shell = "/run/current-system/sw/bin/nologin";
openssh.authorizedKeys.keys = userKeys.sshAuthorizedKeys;
};
# chroot dir must be root-owned; incoming is writable by filedrop
systemd.tmpfiles.rules = [
"d /home/filedrop 0755 root root -"
"d /home/filedrop/incoming 2775 filedrop filedrop -"
"a+ /home/filedrop/incoming - - - - group:filedrop:rwx"
"a+ /home/filedrop/incoming - - - - default:group:filedrop:rwx"
"a+ /home/filedrop/incoming - - - - default:mask::rwx"
"L /home/matej/filedrop - - - - /home/filedrop/incoming"
];
# relaxed umask so default acl takes full effect
services.openssh.extraConfig = ''
Match User filedrop
ForceCommand internal-sftp -u 0002
ChrootDirectory /home/filedrop
AuthorizedKeysFile /etc/ssh/authorized_keys.d/filedrop %h/.ssh/authorized_keys ${config.sops.secrets.filedrop-authorized-keys.path}
AllowTcpForwarding no
X11Forwarding no
'';
}; };
} }

View File

@@ -1,14 +1,21 @@
{ {
nixos = nixos =
{ pkgs, ... }: { config, lib, pkgs, ... }:
let
cfg = config.features.gaming;
in
{ {
programs.steam = { options.features.gaming.enable = lib.mkEnableOption "gaming";
enable = true;
remotePlay.openFirewall = true;
dedicatedServer.openFirewall = true;
localNetworkGameTransfers.openFirewall = true;
};
environment.systemPackages = [ pkgs.prismlauncher ]; config = lib.mkIf cfg.enable {
programs.steam = {
enable = true;
remotePlay.openFirewall = true;
dedicatedServer.openFirewall = true;
localNetworkGameTransfers.openFirewall = true;
};
environment.systemPackages = [ pkgs.prismlauncher ];
};
}; };
} }

View File

@@ -1,9 +1,36 @@
{ {
nixos = _: { nixos =
programs.gnupg.agent = { { config, lib, pkgs, ... }:
enable = true; let
enableSSHSupport = true; cfg = config.features.gnupg;
enableExtraSocket = true; in
{
options.features.gnupg = {
enable = lib.mkEnableOption "gnupg";
yubikey.enable = lib.mkOption {
type = lib.types.bool;
default = false;
};
};
config = lib.mkIf cfg.enable (lib.mkMerge [
{
programs.gnupg.agent = {
enable = true;
enableSSHSupport = true;
enableExtraSocket = true;
};
}
(lib.mkIf cfg.yubikey.enable {
environment.systemPackages = with pkgs; [
yubikey-personalization
yubikey-manager
];
services.pcscd.enable = true;
})
]);
}; };
};
} }

View File

@@ -1,12 +1,14 @@
{ {
nixos = nixos =
{ {
pkgs,
config, config,
lib,
pkgs,
inputs, inputs,
... ...
}: }:
let let
cfg = config.features.harmonia;
hosts = [ hosts = [
"fw16" "fw16"
"tower" "tower"
@@ -17,34 +19,38 @@
flakeRef = inputs.self.outPath; flakeRef = inputs.self.outPath;
in in
{ {
services.harmonia.cache = { options.features.harmonia.enable = lib.mkEnableOption "harmonia";
enable = true;
signKeyPaths = [ config.sops.secrets.nix-signing-key.path ];
};
networking.firewall.interfaces."tailscale0".allowedTCPPorts = [ 5000 ]; config = lib.mkIf cfg.enable {
services.harmonia.cache = {
systemd.services.cache-builder = { enable = true;
description = "Build all host closures for binary cache"; signKeyPaths = [ config.sops.secrets.nix-signing-key.path ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.bash}/bin/bash ${./cache-builder.sh}";
}; };
environment = {
FLAKE_REF = flakeRef;
HOSTS = builtins.concatStringsSep " " hosts;
GC_ROOT_DIR = "/nix/var/nix/gcroots/cache-builder";
};
path = [ config.nix.package ];
};
systemd.timers.cache-builder = { networking.firewall.interfaces."tailscale0".allowedTCPPorts = [ 5000 ];
description = "Periodically build all host closures";
wantedBy = [ "timers.target" ]; systemd.services.cache-builder = {
timerConfig = { description = "Build all host closures for binary cache";
OnUnitActiveSec = "15min"; serviceConfig = {
OnBootSec = "5min"; Type = "oneshot";
Persistent = true; ExecStart = "${pkgs.bash}/bin/bash ${./cache-builder.sh}";
};
environment = {
FLAKE_REF = flakeRef;
HOSTS = builtins.concatStringsSep " " hosts;
GC_ROOT_DIR = "/nix/var/nix/gcroots/cache-builder";
};
path = [ config.nix.package ];
};
systemd.timers.cache-builder = {
description = "Periodically build all host closures";
wantedBy = [ "timers.target" ];
timerConfig = {
OnUnitActiveSec = "15min";
OnBootSec = "5min";
Persistent = true;
};
}; };
}; };
}; };

View File

@@ -2,6 +2,7 @@
nixos = nixos =
{ lib, config, ... }: { lib, config, ... }:
let let
cfg = config.features.initrd-ssh;
keyDir = "/etc/secrets/initrd"; keyDir = "/etc/secrets/initrd";
mkIpString = mkIpString =
@@ -15,44 +16,45 @@
"${address}::${gateway}:${netmask}::${interface}:none"; "${address}::${gateway}:${netmask}::${interface}:none";
in in
{ {
options = { options.features.initrd-ssh = {
initrd-ssh = { enable = lib.mkEnableOption "initrd ssh";
ip = {
enable = lib.mkEnableOption "static IP for initrd (otherwise DHCP)";
address = lib.mkOption { ip = {
type = lib.types.str; enable = lib.mkEnableOption "static IP for initrd (otherwise DHCP)";
};
gateway = lib.mkOption { address = lib.mkOption {
type = lib.types.str; type = lib.types.str;
};
netmask = lib.mkOption {
type = lib.types.str;
default = "255.255.255.0";
};
interface = lib.mkOption {
type = lib.types.str;
};
}; };
authorizedKeys = lib.mkOption { gateway = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.str;
default = [ ];
}; };
networkModule = lib.mkOption { netmask = lib.mkOption {
type = lib.types.str;
default = "255.255.255.0";
};
interface = lib.mkOption {
type = lib.types.str; type = lib.types.str;
}; };
}; };
authorizedKeys = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
networkModule = lib.mkOption {
type = lib.types.str;
};
}; };
config = { config = lib.mkIf cfg.enable {
boot.initrd.kernelModules = [ config.initrd-ssh.networkModule ]; boot.initrd.availableKernelModules = [ cfg.networkModule ];
boot.kernelParams = lib.mkIf config.initrd-ssh.ip.enable [ boot.initrd.kernelModules = [ cfg.networkModule ];
"ip=${mkIpString config.initrd-ssh.ip}" boot.kernelParams = lib.mkIf cfg.ip.enable [
"ip=${mkIpString cfg.ip}"
]; ];
boot.initrd.network = { boot.initrd.network = {
@@ -64,7 +66,7 @@
"${keyDir}/ssh_host_rsa_key" "${keyDir}/ssh_host_rsa_key"
"${keyDir}/ssh_host_ed25519_key" "${keyDir}/ssh_host_ed25519_key"
]; ];
inherit (config.initrd-ssh) authorizedKeys; inherit (cfg) authorizedKeys;
}; };
postCommands = '' postCommands = ''
echo 'cryptsetup-askpass' >> /root/.profile echo 'cryptsetup-askpass' >> /root/.profile

View File

@@ -1,25 +1,30 @@
{ {
nixos = nixos =
{ lib, config, ... }: { lib, config, ... }:
let
cfg = config.features.localisation;
in
{ {
options = { options.features.localisation = {
localisation = { enable = lib.mkEnableOption "localisation";
timeZone = lib.mkOption {
type = lib.types.str;
};
defaultLocale = lib.mkOption { timeZone = lib.mkOption {
type = lib.types.str; type = lib.types.str;
}; default = "Europe/Ljubljana";
};
defaultLocale = lib.mkOption {
type = lib.types.str;
default = "en_US.UTF-8";
}; };
}; };
config = { config = lib.mkIf cfg.enable {
time.timeZone = config.localisation.timeZone; time.timeZone = cfg.timeZone;
i18n.defaultLocale = config.localisation.defaultLocale; i18n.defaultLocale = cfg.defaultLocale;
# NOTE:(@janezicmatej) some apps (e.g. java) need TZ env var explicitly # NOTE:(@janezicmatej) some apps (e.g. java) need TZ env var explicitly
environment.variables.TZ = config.localisation.timeZone; environment.variables.TZ = cfg.timeZone;
}; };
}; };
} }

View File

@@ -1,4 +1,17 @@
{ {
nixos =
{ lib, ... }:
{
options.features.neovim = {
enable = lib.mkEnableOption "neovim";
dotfiles = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
};
};
};
home = home =
{ {
config, config,
@@ -6,24 +19,21 @@
lib, lib,
pkgs, pkgs,
inputs, inputs,
osConfig,
... ...
}: }:
let
cfg = osConfig.features.neovim;
in
{ {
options = { config = lib.mkIf cfg.enable (lib.mkMerge [
neovim.dotfiles = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
};
};
config = lib.mkMerge [
(lib.optionalAttrs (options ? stylix) { (lib.optionalAttrs (options ? stylix) {
# disable stylix neovim target when stylix is present (loaded by desktop feature) # disable stylix neovim target when stylix is present
stylix.targets.neovim.enable = false; stylix.targets.neovim.enable = false;
}) })
{ {
xdg.configFile."nvim" = lib.mkIf (config.neovim.dotfiles != null) { xdg.configFile."nvim" = lib.mkIf (cfg.dotfiles != null) {
source = config.neovim.dotfiles; source = cfg.dotfiles;
}; };
programs.neovim = { programs.neovim = {
@@ -64,6 +74,6 @@
]; ];
}; };
} }
]; ]);
}; };
} }

View File

@@ -1,9 +1,18 @@
{ {
nixos = _: { nixos =
networking.networkmanager.enable = true; { config, lib, ... }:
networking.nameservers = [ let
"1.1.1.1" cfg = config.features.networkmanager;
"8.8.8.8" in
]; {
}; options.features.networkmanager.enable = lib.mkEnableOption "networkmanager";
config = lib.mkIf cfg.enable {
networking.networkmanager.enable = true;
networking.nameservers = [
"1.1.1.1"
"8.8.8.8"
];
};
};
} }

View File

@@ -1,5 +1,14 @@
{ {
nixos = _: { nixos =
programs.nix-ld.enable = true; { config, lib, ... }:
}; let
cfg = config.features.nix-ld;
in
{
options.features.nix-ld.enable = lib.mkEnableOption "nix-ld";
config = lib.mkIf cfg.enable {
programs.nix-ld.enable = true;
};
};
} }

71
features/nix-settings.nix Normal file
View File

@@ -0,0 +1,71 @@
{
nixos =
{ config, lib, ... }:
let
cfg = config.features.nix-settings;
in
{
options.features.nix-settings = {
enable = lib.mkEnableOption "nix settings";
towerCache.enable = lib.mkOption {
type = lib.types.bool;
default = true;
};
gc = {
dates = lib.mkOption {
type = lib.types.str;
default = "monthly";
};
olderThan = lib.mkOption {
type = lib.types.str;
default = "30d";
};
};
optimise.dates = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "monthly" ];
};
};
config = lib.mkIf cfg.enable {
nix = {
settings = {
experimental-features = [
"nix-command"
"flakes"
];
download-buffer-size = 2 * 1024 * 1024 * 1024;
warn-dirty = false;
substituters =
[
"https://cache.nixos.org"
"https://nix-community.cachix.org?priority=45"
]
++ lib.optional cfg.towerCache.enable "http://tower:5000?priority=50";
trusted-public-keys =
[
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
]
++ lib.optional cfg.towerCache.enable "matej.nix-1:TdbemLVYblvAxqJcwb3mVKmmr3cfzXbMcZHE5ILnZDE=";
};
gc = {
automatic = true;
dates = cfg.gc.dates;
options = "--delete-older-than ${cfg.gc.olderThan}";
};
optimise = {
automatic = true;
dates = cfg.optimise.dates;
};
};
};
};
}

View File

@@ -1,18 +1,23 @@
{ {
nixos = nixos =
{ lib, config, ... }: { lib, config, ... }:
let
cfg = config.features.openssh;
in
{ {
options = { options.features.openssh = {
openssh.port = lib.mkOption { enable = lib.mkEnableOption "openssh";
port = lib.mkOption {
type = lib.types.port; type = lib.types.port;
default = 22; default = 22;
}; };
}; };
config = { config = lib.mkIf cfg.enable {
services.openssh = { services.openssh = {
enable = true; enable = true;
ports = [ config.openssh.port ]; ports = [ cfg.port ];
settings = { settings = {
PasswordAuthentication = false; PasswordAuthentication = false;
AllowUsers = null; AllowUsers = null;

View File

@@ -1,10 +1,19 @@
{ {
nixos = _: { nixos =
services.printing.enable = true; { config, lib, ... }:
services.avahi = { let
enable = true; cfg = config.features.printing;
nssmdns4 = true; in
openFirewall = true; {
options.features.printing.enable = lib.mkEnableOption "printing";
config = lib.mkIf cfg.enable {
services.printing.enable = true;
services.avahi = {
enable = true;
nssmdns4 = true;
openFirewall = true;
};
};
}; };
};
} }

View File

@@ -1,13 +1,20 @@
{ {
nixos = nixos =
{ config, user, ... }: { config, lib, user, ... }:
let
cfg = config.features.remote-base;
in
{ {
sops.secrets.user-password = { options.features.remote-base.enable = lib.mkEnableOption "remote-base";
sopsFile = ../secrets/common.yaml;
neededForUsers = true;
};
users.mutableUsers = false; config = lib.mkIf cfg.enable {
users.users.${user}.hashedPasswordFile = config.sops.secrets.user-password.path; sops.secrets.user-password = {
sopsFile = ../secrets/common.yaml;
neededForUsers = true;
};
users.mutableUsers = false;
users.users.${user}.hashedPasswordFile = config.sops.secrets.user-password.path;
};
}; };
} }

View File

@@ -1,8 +1,17 @@
{ {
nixos = _: { nixos =
services.tailscale = { { config, lib, ... }:
enable = true; let
useRoutingFeatures = "both"; cfg = config.features.tailscale;
in
{
options.features.tailscale.enable = lib.mkEnableOption "tailscale";
config = lib.mkIf cfg.enable {
services.tailscale = {
enable = true;
useRoutingFeatures = "both";
};
};
}; };
};
} }

View File

@@ -27,7 +27,19 @@ let
hostConfig = ../hosts/${name}/configuration.nix; hostConfig = ../hosts/${name}/configuration.nix;
hostHWConfig = ../hosts/${name}/hardware-configuration.nix; hostHWConfig = ../hosts/${name}/hardware-configuration.nix;
# load feature with path check # auto-discover all features, excluding user-* and default.nix
featureDir = builtins.readDir ../features;
allFeatureNames = lib.pipe featureDir [
(lib.filterAttrs (
n: t:
(t == "regular" && lib.hasSuffix ".nix" n && n != "default.nix" && !lib.hasPrefix "user-" n)
|| (t == "directory" && builtins.pathExists ../features/${n}/default.nix)
))
builtins.attrNames
(map (n: lib.removeSuffix ".nix" n))
];
# load all features unconditionally
loadFeature = loadFeature =
f: f:
assert assert
@@ -35,7 +47,7 @@ let
|| throw "feature '${f}' not found at ${toString (featurePath f)}"; || throw "feature '${f}' not found at ${toString (featurePath f)}";
import (featurePath f); import (featurePath f);
loadedFeatures = map loadFeature features; loadedFeatures = map loadFeature allFeatureNames;
# load user feature with path check # load user feature with path check
userFeature = userFeature =
@@ -55,31 +67,42 @@ let
# collect nixos and home modules from all features # collect nixos and home modules from all features
nixosMods = map (f: f.nixos) (builtins.filter (f: f ? nixos) allFeatures); nixosMods = map (f: f.nixos) (builtins.filter (f: f ? nixos) allFeatures);
homeMods = map (f: f.home) (builtins.filter (f: f ? home) allFeatures); homeMods = map (f: f.home) (builtins.filter (f: f ? home) allFeatures);
# translate features list to enable flags
featureEnableModule =
{ lib, ... }:
{
config.features = lib.genAttrs features (_: {
enable = true;
});
};
in in
nixpkgs.lib.nixosSystem { nixpkgs.lib.nixosSystem {
inherit system; inherit system;
modules = [ modules =
../nix.nix [
inputs.sops-nix.nixosModules.sops inputs.sops-nix.nixosModules.sops
inputs.stylix.nixosModules.stylix
{ nixpkgs.overlays = overlays; } { nixpkgs.overlays = overlays; }
{ nixpkgs.config.allowUnfree = true; } { nixpkgs.config.allowUnfree = true; }
{ networking.hostName = name; } { networking.hostName = name; }
hostConfig featureEnableModule
] hostConfig
++ lib.optional (builtins.pathExists hostHWConfig) hostHWConfig ]
++ nixosMods ++ lib.optional (builtins.pathExists hostHWConfig) hostHWConfig
++ lib.optionals hasUser [ ++ nixosMods
inputs.home-manager.nixosModules.home-manager ++ lib.optionals hasUser [
{ inputs.home-manager.nixosModules.home-manager
home-manager.useGlobalPkgs = true; {
home-manager.useUserPackages = true; home-manager.useGlobalPkgs = true;
home-manager.backupFileExtension = "backup"; home-manager.useUserPackages = true;
home-manager.users.${user}.imports = homeMods; home-manager.backupFileExtension = "backup";
home-manager.extraSpecialArgs = { inherit inputs; }; home-manager.users.${user}.imports = homeMods;
} home-manager.extraSpecialArgs = { inherit inputs; };
]; }
];
specialArgs = { specialArgs = {
inherit inputs userKeys user; inherit inputs userKeys user;
}; };