wip: migrate client modules

This commit is contained in:
Leon Schwarzäugl 2026-04-02 19:25:58 +02:00
parent f6d2ff1544
commit 7ce27d5d2f
Signed by: swarsel
GPG key ID: 26A54C31F2A4FD84
245 changed files with 20254 additions and 188 deletions

View file

@ -0,0 +1,25 @@
{ lib, pkgs, config, globals, ... }:
{
options.swarselmodules.boot = lib.mkEnableOption "boot config";
config = lib.mkIf config.swarselmodules.boot {
boot = {
initrd.systemd = {
enable = true;
emergencyAccess = globals.root.hashedPassword;
users.root.shell = "${pkgs.bashInteractive}/bin/bash";
storePaths = [ "${pkgs.bashInteractive}/bin/bash" ];
extraBin = {
ip = "${pkgs.iproute2}/bin/ip";
ping = "${pkgs.iputils}/bin/ping";
cryptsetup = "${pkgs.cryptsetup}/bin/cryptsetup";
};
};
kernelParams = [ "log_buf_len=16M" ];
tmp.useTmpfs = true;
loader.timeout = lib.mkDefault 2;
};
console.earlySetup = true;
};
}

View file

@ -0,0 +1,9 @@
{ lib, ... }:
let
importNames = lib.swarselsystems.readNix "modules-clone/nixos/common";
sharedNames = lib.swarselsystems.readNix "modules-clone/shared";
in
{
imports = lib.swarselsystems.mkImports importNames "modules-clone/nixos/common" ++
lib.swarselsystems.mkImports sharedNames "modules-clone/shared";
}

View file

@ -0,0 +1,290 @@
{ lib, options, ... }:
let
inherit (lib)
mkOption
types
;
firewallOptions = {
allowedTCPPorts = mkOption {
type = types.listOf types.port;
default = [ ];
description = "Convenience option to open specific TCP ports for traffic from the network.";
};
allowedUDPPorts = mkOption {
type = types.listOf types.port;
default = [ ];
description = "Convenience option to open specific UDP ports for traffic from the network.";
};
allowedTCPPortRanges = mkOption {
type = lib.types.listOf (lib.types.attrsOf lib.types.port);
default = [ ];
description = "Convenience option to open specific TCP port ranges for traffic from another node.";
};
allowedUDPPortRanges = mkOption {
type = lib.types.listOf (lib.types.attrsOf lib.types.port);
default = [ ];
description = "Convenience option to open specific UDP port ranges for traffic from another node.";
};
};
networkOptions = netSubmod: {
cidrv4 = mkOption {
type = types.nullOr types.net.cidrv4;
description = "The CIDRv4 of this network";
default = null;
};
subnetMask4 = mkOption {
type = types.nullOr types.net.ipv4;
description = "The dotted decimal form of the subnet mask of this network";
readOnly = true;
default = lib.swarselsystems.cidrToSubnetMask netSubmod.config.cidrv4;
};
cidrv6 = mkOption {
type = types.nullOr types.net.cidrv6;
description = "The CIDRv6 of this network";
default = null;
};
firewallRuleForAll = mkOption {
default = { };
description = ''
If this is a wireguard network: Allows you to set specific firewall rules for traffic originating from any participant in this
wireguard network. A corresponding rule `<network-name>-to-<local-zone-name>` will be created to easily expose
services to the network.
'';
type = types.submodule {
options = firewallOptions;
};
};
hosts = mkOption {
default = { };
type = types.attrsOf (
types.submodule (hostSubmod: {
options = {
id = mkOption {
type = types.int;
description = "The id of this host in the network";
};
mac = mkOption {
type = types.nullOr types.net.mac;
description = "The MAC of the interface on this host that belongs to this network.";
default = null;
};
ipv4 = mkOption {
type = types.nullOr types.net.ipv4;
description = "The IPv4 of this host in this network";
readOnly = true;
default =
if netSubmod.config.cidrv4 == null then
null
else
lib.net.cidr.host hostSubmod.config.id netSubmod.config.cidrv4;
};
ipv6 = mkOption {
type = types.nullOr types.net.ipv6;
description = "The IPv6 of this host in this network";
readOnly = true;
default =
if netSubmod.config.cidrv6 == null then
null
else
lib.net.cidr.host hostSubmod.config.id netSubmod.config.cidrv6;
};
cidrv4 = mkOption {
type = types.nullOr types.str; # FIXME: this is not types.net.cidr because it would zero out the host part
description = "The IPv4 of this host in this network, including CIDR mask";
readOnly = true;
default =
if netSubmod.config.cidrv4 == null then
null
else
lib.net.cidr.hostCidr hostSubmod.config.id netSubmod.config.cidrv4;
};
cidrv6 = mkOption {
type = types.nullOr types.str; # FIXME: this is not types.net.cidr because it would zero out the host part
description = "The IPv6 of this host in this network, including CIDR mask";
readOnly = true;
default =
if netSubmod.config.cidrv6 == null then
null
else
# if we use the /32 wan address as local address directly, do not use the network address in ipv6
lib.net.cidr.hostCidr (if hostSubmod.config.id == 0 then 1 else hostSubmod.config.id) netSubmod.config.cidrv6;
};
firewallRuleForNode = mkOption {
default = { };
description = ''
If this is a wireguard network: Allows you to set specific firewall rules just for traffic originating from another network node.
A corresponding rule `<network-name>-node-<node-name>-to-<local-zone-name>` will be created to easily expose
services to that node.
'';
type = types.attrsOf (
types.submodule {
options = firewallOptions;
}
);
};
};
})
);
};
};
in
{
options = {
globals = mkOption {
default = { };
type = types.submodule {
options = {
root = {
hashedPassword = mkOption {
type = types.str;
};
};
user = {
name = mkOption {
type = types.str;
};
work = mkOption {
type = types.str;
};
};
services = mkOption {
type = types.attrsOf (
types.submodule (serviceSubmod: {
options = {
domain = mkOption {
type = types.str;
};
subDomain = mkOption {
readOnly = true;
type = types.str;
default = lib.swarselsystems.getSubDomain serviceSubmod.config.domain;
};
baseDomain = mkOption {
readOnly = true;
type = types.str;
default = lib.swarselsystems.getBaseDomain serviceSubmod.config.domain;
};
proxyAddress4 = mkOption {
type = types.nullOr types.str;
default = null;
};
proxyAddress6 = mkOption {
type = types.nullOr types.str;
default = null;
};
serviceAddress = mkOption {
type = types.nullOr types.str;
default = null;
};
homeServiceAddress = mkOption {
type = types.nullOr types.str;
default = null;
};
isHome = mkOption {
type = types.bool;
default = false;
};
};
})
);
};
networks = mkOption {
default = { };
type = types.attrsOf (
types.submodule (netSubmod: {
options = networkOptions netSubmod // {
vlans = mkOption {
default = { };
type = types.attrsOf (
types.submodule (vlanNetSubmod: {
options = networkOptions vlanNetSubmod // {
id = mkOption {
type = types.ints.between 1 4094;
description = "The VLAN id";
};
name = mkOption {
description = "The name of this VLAN";
default = vlanNetSubmod.config._module.args.name;
type = types.str;
};
};
})
);
};
};
})
);
};
hosts = mkOption {
type = types.attrsOf (
types.submodule {
options = {
defaultGateway4 = mkOption {
type = types.nullOr types.net.ipv4;
};
defaultGateway6 = mkOption {
type = types.nullOr types.net.ipv6;
};
wanAddress4 = mkOption {
type = types.nullOr types.net.ipv4;
};
wanAddress6 = mkOption {
type = types.nullOr types.net.ipv6;
};
isHome = mkOption {
type = types.bool;
};
};
}
);
};
domains = {
main = mkOption {
type = types.str;
};
externalDns = mkOption {
type = types.listOf types.str;
description = "List of external dns nameservers";
};
};
general = lib.mkOption {
type = types.submodule {
freeformType = types.unspecified;
};
};
};
};
};
_globalsDefs = mkOption {
type = types.unspecified;
default = options.globals.definitions;
readOnly = true;
internal = true;
};
};
}

View file

@ -0,0 +1,42 @@
{ lib, config, globals, withHomeManager, ... }:
let
inherit (config.swarselsystems) mainUser homeDir;
inherit (config.repo.secrets.common.emacs) radicaleUser;
in
{
config = { } // lib.optionalAttrs withHomeManager {
sops =
let
modules = config.home-manager.users.${mainUser}.swarselmodules;
in
{
secrets = (lib.optionalAttrs modules.mail {
address1-token = { owner = mainUser; };
address2-token = { owner = mainUser; };
address3-token = { owner = mainUser; };
address4-token = { owner = mainUser; };
}) // (lib.optionalAttrs modules.waybar {
github-notifications-token = { owner = mainUser; };
}) // (lib.optionalAttrs modules.emacs {
fever-pw = { path = "${homeDir}/.emacs.d/.fever"; owner = mainUser; };
}) // (lib.optionalAttrs modules.emacs {
emacs-radicale-pw = { owner = mainUser; };
github-forge-token = { owner = mainUser; };
}) // (lib.optionalAttrs (modules ? optional-noctalia) {
radicale-token = { owner = mainUser; };
}) // (lib.optionalAttrs modules.anki {
anki-user = { owner = mainUser; };
anki-pw = { owner = mainUser; };
});
templates = {
authinfo = lib.mkIf modules.emacs {
path = "${homeDir}/.emacs.d/.authinfo";
content = ''
machine ${globals.services.radicale.domain} login ${radicaleUser} password ${config.sops.placeholder.emacs-radicale-pw}
'';
owner = mainUser;
};
};
};
};
}

View file

@ -0,0 +1,39 @@
{ self, inputs, config, lib, homeLib, outputs, globals, nodes, minimal, configName, arch, type, withHomeManager, ... }:
{
options.swarselmodules.home-manager = lib.mkEnableOption "home-manager";
config = lib.mkIf config.swarselmodules.home-manager {
home-manager = lib.mkIf withHomeManager {
useGlobalPkgs = true;
useUserPackages = true;
verbose = true;
backupFileExtension = "hm-bak";
overwriteBackup = true;
users.${config.swarselsystems.mainUser}.imports = [
inputs.nix-index-database.homeModules.nix-index
# inputs.sops.homeManagerModules.sops # this is not needed!! we add these secrets in nixos scope
inputs.spicetify-nix.homeManagerModules.default
inputs.swarsel-nix.homeModules.default
{
imports = [
"${self}/profiles/home"
"${self}/modules/home"
{
swarselprofiles = {
minimal = lib.mkIf minimal true;
};
}
];
# node = {
# secretsDir = if (!config.swarselsystems.isNixos) then ../../../hosts/home/${configName}/secrets else ../../../hosts/nixos/${configName}/secrets;
# };
home.stateVersion = lib.mkDefault config.system.stateVersion;
}
];
extraSpecialArgs = {
inherit (inputs) self nixgl;
inherit inputs outputs globals nodes minimal configName arch type;
lib = homeLib;
};
};
};
}

View file

@ -0,0 +1,95 @@
{ config, lib, ... }:
let
mapperTarget = lib.swarselsystems.mkIfElse config.swarselsystems.isCrypted "/dev/mapper/cryptroot" "/dev/disk/by-label/nixos";
inherit (config.swarselsystems) isImpermanence isCrypted isBtrfs;
in
{
options.swarselmodules.impermanence = lib.mkEnableOption "impermanence config";
config = lib.mkIf config.swarselmodules.impermanence {
security.sudo.extraConfig = lib.mkIf isImpermanence ''
# rollback results in sudo lectures after each reboot
Defaults lecture = never
'';
# This script does the actual wipe of the system
# So if it doesn't run, the btrfs system effectively acts like a normal system
# Taken from https://github.com/NotAShelf/nyx/blob/2a8273ed3f11a4b4ca027a68405d9eb35eba567b/modules/core/common/system/impermanence/default.nix
boot.tmp.useTmpfs = lib.mkIf (!isImpermanence) true;
boot.initrd.systemd = lib.mkIf (isImpermanence && isBtrfs) {
enable = true;
services.rollback = {
description = "Rollback BTRFS root subvolume to a pristine state";
wantedBy = [ "initrd.target" ];
# make sure it's done after encryption
# i.e. LUKS/TPM process
after = lib.swarselsystems.mkIfElseList isCrypted [ "systemd-cryptsetup@cryptroot.service" ] [ "dev-disk-by\\x2dlabel-nixos.device" ];
requires = lib.mkIf (!isCrypted) [ "dev-disk-by\\x2dlabel-nixos.device" ];
# mount the root fs before clearing
before = [ "sysroot.mount" ];
unitConfig.DefaultDependencies = "no";
serviceConfig.Type = "oneshot";
script = ''
mkdir -p /mnt
# We first mount the btrfs root to /mnt
# so we can manipulate btrfs subvolumes.
mount -o subvolid=5 -t btrfs ${mapperTarget} /mnt
btrfs subvolume list -o /mnt/root
# While we're tempted to just delete /root and create
# a new snapshot from /root-blank, /root is already
# populated at this point with a number of subvolumes,
# which makes `btrfs subvolume delete` fail.
# So, we remove them first.
#
# /root contains subvolumes:
# - /root/var/lib/portables
# - /root/var/lib/machines
btrfs subvolume list -o /mnt/root |
cut -f9 -d' ' |
while read subvolume; do
echo "deleting /$subvolume subvolume..."
btrfs subvolume delete "/mnt/$subvolume"
done &&
echo "deleting /root subvolume..." &&
btrfs subvolume delete /mnt/root
echo "restoring blank /root subvolume..."
btrfs subvolume snapshot /mnt/root-blank /mnt/root
# Once we're done rolling back to a blank snapshot,
# we can unmount /mnt and continue on the boot process.
umount /mnt
'';
};
};
environment.persistence."/persist" = lib.mkIf isImpermanence {
hideMounts = true;
directories =
[
"/root/.dotfiles"
"/etc/nix"
"/etc/NetworkManager/system-connections"
"/var/lib/nixos"
"/var/tmp"
{
directory = "/var/tmp/nix-import-encrypted"; # Decrypted repo-secrets can be kept
mode = "1777";
}
# "/etc/secureboot"
];
files = [
"/etc/ssh/ssh_host_ed25519_key"
"/etc/ssh/ssh_host_ed25519_key.pub"
"/etc/machine-id"
];
};
};
}

View file

@ -0,0 +1,29 @@
{ lib, pkgs, config, minimal, ... }:
let
inherit (config.swarselsystems) isSecureBoot isImpermanence;
in
{
options.swarselmodules.lanzaboote = lib.mkEnableOption "lanzaboote config";
config = lib.mkIf config.swarselmodules.lanzaboote {
environment.systemPackages = lib.mkIf isSecureBoot [
pkgs.sbctl
];
environment.persistence."/persist" = lib.mkIf (isImpermanence && isSecureBoot) {
directories = [{ directory = "/var/lib/sbctl"; }];
};
boot = {
loader = {
efi.canTouchEfiVariables = true;
systemd-boot.enable = lib.swarselsystems.mkIfElse (minimal || !isSecureBoot) (lib.mkForce true) (lib.mkForce false);
};
lanzaboote = lib.mkIf (!minimal && isSecureBoot) {
enable = true;
pkiBundle = "/var/lib/sbctl";
configurationLimit = 6;
};
};
};
}

View file

@ -0,0 +1,69 @@
# adapted from https://github.com/oddlama/nix-config/blob/main/modules/distributed-config.nix
{ config, lib, nodes, ... }:
let
nodeName = config.node.name;
mkForwardedOption =
path:
lib.mkOption {
type = lib.mkOptionType {
name = "Same type that the receiving option `${lib.concatStringsSep "." path}` normally accepts.";
merge =
_loc: defs:
builtins.filter (x: builtins.isAttrs x -> ((x._type or "") != "__distributed_config_empty")) (
map (x: x.value) defs
);
};
default = {
_type = "__distributed_config_empty";
};
description = ''
Anything specified here will be forwarded to `${lib.concatStringsSep "." path}`
on the given node. Forwarding happens as-is to the raw values,
so validity can only be checked on the receiving node.
'';
};
expandOptions = basePath: optionNames: map (option: basePath ++ [ option ]) optionNames;
splitPath = path: lib.splitString "." path;
forwardedOptions = [
(splitPath "boot.kernel.sysctl")
(splitPath "networking.nftables.chains.postrouting")
(splitPath "services.kanidm.provision.groups")
(splitPath "services.kanidm.provision.systems.oauth2")
(splitPath "sops.secrets")
(splitPath "swarselsystems.server.dns")
(splitPath "topology.self.services")
(splitPath "environment.persistence")
]
++ expandOptions (splitPath "networking.nftables.firewall") [ "zones" "rules" ]
++ expandOptions (splitPath "services.firezone.gateway") [ "enable" "name" "apiUrl" "tokenFile" "package" "logLevel" ]
++ expandOptions (splitPath "services.nginx") [ "upstreams" "virtualHosts" ]
;
attrsForEachOption =
f: lib.foldl' (acc: path: lib.recursiveUpdate acc (lib.setAttrByPath path (f path))) { } forwardedOptions;
in
{
options.nodes = lib.mkOption {
description = "Options forwarded to the given node.";
default = { };
type = lib.types.attrsOf (
lib.types.submodule {
options = attrsForEachOption mkForwardedOption;
}
);
};
config =
let
getConfig =
path: otherNode:
let
cfg = nodes.${otherNode}.config.nodes.${nodeName} or null;
in
lib.optionals (cfg != null) (lib.getAttrFromPath path cfg);
mergeConfigFromOthers = path: lib.mkMerge (lib.concatMap (getConfig path) (lib.attrNames nodes));
in
attrsForEachOption mergeConfigFromOthers;
}

View file

@ -0,0 +1,146 @@
{ self, lib, pkgs, config, outputs, inputs, minimal, globals, withHomeManager, ... }:
let
inherit (config.swarselsystems) mainUser;
inherit (config.repo.secrets.common) atticPublicKey;
settings = if minimal then { } else {
environment.etc."nixos/configuration.nix".source = pkgs.writeText "configuration.nix" ''
assert builtins.trace "This location is not used. The config is found in ${config.swarselsystems.flakePath}!" false;
{ }
'';
nix =
let
flakeInputs = lib.filterAttrs (_: lib.isType "flake") inputs;
in
{
settings = {
connect-timeout = 5;
bash-prompt-prefix = "$SHLVL:\\w ";
bash-prompt = "$(if [[ $? -gt 0 ]]; then printf \"\"; else printf \"\"; fi)λ ";
fallback = true;
min-free = 128000000;
max-free = 1000000000;
flake-registry = "";
auto-optimise-store = true;
warn-dirty = false;
max-jobs = 1;
use-cgroups = lib.mkIf config.swarselsystems.isLinux true;
};
gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 10d";
};
optimise = {
automatic = true;
dates = "weekly";
};
channel.enable = false;
registry = rec {
nixpkgs.flake = inputs.nixpkgs;
# swarsel.flake = inputs.swarsel;
swarsel.flake = self;
n = nixpkgs;
s = swarsel;
};
nixPath = lib.mapAttrsToList (n: _: "${n}=flake:${n}") flakeInputs;
};
services.dbus.implementation = "broker";
systemd.services.nix-daemon = {
environment.TMPDIR = "/var/tmp";
};
};
in
{
options.swarselmodules.general = lib.mkEnableOption "general nix settings";
config = lib.mkIf config.swarselmodules.general
(lib.recursiveUpdate
{
sops.secrets = lib.mkIf (!minimal) {
github-api-token = { owner = mainUser; };
};
nix =
let
nix-version = "2_30";
in
{
package = pkgs.nixVersions."nix_${nix-version}";
settings = {
experimental-features = [
"nix-command"
"flakes"
"ca-derivations"
"cgroups"
"pipe-operators"
];
substituters = [
"https://${globals.services.attic.domain}/${mainUser}"
];
trusted-public-keys = [
atticPublicKey
];
trusted-users = [
"@wheel"
"${config.swarselsystems.mainUser}"
(lib.mkIf config.swarselmodules.server.ssh-builder "builder")
];
};
# extraOptions = ''
# plugin-files = ${pkgs.dev.nix-plugins}/lib/nix/plugins
# extra-builtins-file = ${self + /nix/extra-builtins.nix}
# '' + lib.optionalString (!minimal) ''
# !include ${config.sops.secrets.github-api-token.path}
# '';
# extraOptions = ''
# plugin-files = ${pkgs.nix-plugins.overrideAttrs (o: {
# buildInputs = [config.nix.package pkgs.boost];
# patches = o.patches or [];
# })}/lib/nix/plugins
# extra-builtins-file = ${self + /nix/extra-builtins.nix}
# '';
extraOptions =
let
nix-plugins = pkgs.nix-plugins.override {
nixComponents = pkgs.nixVersions."nixComponents_${nix-version}";
};
in
''
plugin-files = ${nix-plugins}/lib/nix/plugins
extra-builtins-file = ${self + /files/nix/extra-builtins.nix}
'' + lib.optionalString (!minimal) ''
!include ${config.sops.secrets.github-api-token.path}
'';
};
system.stateVersion = lib.mkDefault "23.05";
nixpkgs = {
overlays = [
outputs.overlays.default
outputs.overlays.stables
outputs.overlays.modifications
] ++ lib.optionals withHomeManager [
(final: prev:
let
additions = final: _: import "${self}/pkgs/config" {
inherit self config lib;
pkgs = final;
homeConfig = config.home-manager.users.${config.swarselsystems.mainUser} or { };
};
in
additions final prev
)
];
config = lib.mkIf (!config.swarselsystems.isMicroVM) {
allowUnfree = true;
};
};
}
settings);
}

View file

@ -0,0 +1,25 @@
{ lib, config, ... }:
{
options.swarselmodules.time = lib.mkEnableOption "time config";
config = lib.mkIf config.swarselmodules.time {
time = {
timeZone = "Europe/Vienna";
# hardwareClockInLocalTime = true;
};
i18n = {
defaultLocale = "en_US.UTF-8";
extraLocaleSettings = {
LC_ADDRESS = "de_AT.UTF-8";
LC_IDENTIFICATION = "de_AT.UTF-8";
LC_MEASUREMENT = "de_AT.UTF-8";
LC_MONETARY = "de_AT.UTF-8";
LC_NAME = "de_AT.UTF-8";
LC_NUMERIC = "de_AT.UTF-8";
LC_PAPER = "de_AT.UTF-8";
LC_TELEPHONE = "de_AT.UTF-8";
LC_TIME = "de_AT.UTF-8";
};
};
};
}

View file

@ -0,0 +1,14 @@
{ lib, config, ... }:
{
options.swarselsystems.info = lib.mkOption {
type = lib.types.str;
default = "";
};
config.topology = {
id = config.node.name;
self = {
hardware.info = config.swarselsystems.info;
icon = lib.mkIf config.swarselsystems.isLaptop "devices.laptop";
};
};
}

View file

@ -0,0 +1,12 @@
{ lib, config, ... }:
{
options.swarselmodules.xserver = lib.mkEnableOption "xserver keymap";
config = lib.mkIf config.swarselmodules.packages {
services.xserver = {
xkb = {
layout = "us";
variant = "altgr-intl";
};
};
};
}