This commit is contained in:
Patrick 2024-07-26 22:12:48 +02:00
parent 857140bbd2
commit 44d265ecdb
Signed by: patrick
GPG key ID: 451F95EFB8BECD0F
144 changed files with 3479 additions and 2930 deletions

View file

@ -3,7 +3,8 @@
lib, lib,
pkgs, pkgs,
... ...
}: { }:
{
boot = lib.mkIf (!config.boot.isContainer) { boot = lib.mkIf (!config.boot.isContainer) {
initrd.systemd = { initrd.systemd = {
enable = true; enable = true;
@ -11,12 +12,23 @@
extraBin.ip = "${pkgs.iproute}/bin/ip"; extraBin.ip = "${pkgs.iproute}/bin/ip";
extraBin.cryptsetup = "${pkgs.cryptsetup}/bin/cryptsetup"; extraBin.cryptsetup = "${pkgs.cryptsetup}/bin/cryptsetup";
users.root.shell = "${pkgs.bashInteractive}/bin/bash"; users.root.shell = "${pkgs.bashInteractive}/bin/bash";
storePaths = ["${pkgs.bashInteractive}/bin/bash"]; storePaths = [ "${pkgs.bashInteractive}/bin/bash" ];
}; };
initrd.availableKernelModules = ["xhci_pci" "nvme" "r8169" "usb_storage" "usbhid" "sd_mod" "rtsx_pci_sdmmc" "ahci" "uas" "tpm_crb"]; initrd.availableKernelModules = [
supportedFilesystems = ["ntfs"]; "xhci_pci"
kernelModules = ["kvm-intel"]; "nvme"
"r8169"
"usb_storage"
"usbhid"
"sd_mod"
"rtsx_pci_sdmmc"
"ahci"
"uas"
"tpm_crb"
];
supportedFilesystems = [ "ntfs" ];
kernelModules = [ "kvm-intel" ];
kernelParams = [ kernelParams = [
"rd.luks.options=timeout=0" "rd.luks.options=timeout=0"
"rootflags=x-systemd.device-timeout=0" "rootflags=x-systemd.device-timeout=0"

View file

@ -1,8 +1,5 @@
{ inputs, lib, ... }:
{ {
inputs,
lib,
...
}: {
imports = [ imports = [
./boot.nix ./boot.nix
./home-manager.nix ./home-manager.nix
@ -37,6 +34,6 @@
inputs.nixos-nftables-firewall.nixosModules.default inputs.nixos-nftables-firewall.nixosModules.default
inputs.nixvim.nixosModules.nixvim inputs.nixvim.nixosModules.nixvim
]; ];
age.identityPaths = ["/state/etc/ssh/ssh_host_ed25519_key"]; age.identityPaths = [ "/state/etc/ssh/ssh_host_ed25519_key" ];
boot.mode = lib.mkDefault "efi"; boot.mode = lib.mkDefault "efi";
} }

View file

@ -4,7 +4,8 @@
pkgs, pkgs,
nodes, nodes,
... ...
}: { }:
{
imports = [ imports = [
../../modules-hm/impermanence.nix ../../modules-hm/impermanence.nix
../../modules-hm/images.nix ../../modules-hm/images.nix
@ -18,9 +19,7 @@
spicePkgs = inputs.spicetify-nix.legacyPackages.${pkgs.system}; spicePkgs = inputs.spicetify-nix.legacyPackages.${pkgs.system};
}; };
sharedModules = [ sharedModules = [
{ { home.stateVersion = stateVersion; }
home.stateVersion = stateVersion;
}
inputs.nix-index-database.hmModules.nix-index inputs.nix-index-database.hmModules.nix-index
inputs.nixos-extra-modules.homeManagerModules.default inputs.nixos-extra-modules.homeManagerModules.default
inputs.nixvim.homeManagerModules.nixvim inputs.nixvim.homeManagerModules.nixvim
@ -38,5 +37,5 @@
# But still link all completions from all packages so they # But still link all completions from all packages so they
# can be found by zsh # can be found by zsh
environment.pathsToLink = ["/share/zsh"]; environment.pathsToLink = [ "/share/zsh" ];
} }

View file

@ -3,35 +3,23 @@
lib, lib,
pkgs, pkgs,
... ...
}: let }:
onlyHost = let
lib.mkIf (!config.boot.isContainer); onlyHost = lib.mkIf (!config.boot.isContainer);
prune = folder: prune =
pkgs.writers.writePython3Bin "impermanence-prune" {} '' folder:
pkgs.writers.writePython3Bin "impermanence-prune" { } ''
import os import os
import sys import sys
mounts = [${ mounts = [${
lib.concatStringsSep ", " lib.concatStringsSep ", " (
((map (x: (map (
"\"" x: "\"" + (if x.home != null then x.home + "/" else "") + x.directory + "\""
+ ( ) config.environment.persistence.${folder}.directories)
if x.home != null ++ (map (
then x.home + "/" x: "\"" + (if x.home != null then x.home + "/" else "") + x.file + "\""
else "" ) config.environment.persistence.${folder}.files)
) )
+ x.directory
+ "\"")
config.environment.persistence.${folder}.directories)
++ (map (x:
"\""
+ (
if x.home != null
then x.home + "/"
else ""
)
+ x.file
+ "\"")
config.environment.persistence.${folder}.files))
}] # noqa: E501 }] # noqa: E501
mounts = [os.path.normpath(x) for x in mounts] mounts = [os.path.normpath(x) for x in mounts]
mounts.sort() mounts.sort()
@ -53,11 +41,10 @@
file=sys.stderr) file=sys.stderr)
print("\n".join(erg)) print("\n".join(erg))
''; '';
in { in
{
# to allow all users to access hm managed persistent folders # to allow all users to access hm managed persistent folders
lib.scripts.impermanence.pruneScripts = lib.scripts.impermanence.pruneScripts = lib.mapAttrs (k: _: prune k) config.environment.persistence;
lib.mapAttrs (k: _: prune k)
config.environment.persistence;
programs.fuse.userAllowOther = true; programs.fuse.userAllowOther = true;
services.openssh.hostKeys = lib.mkForce [ services.openssh.hostKeys = lib.mkForce [
{ {
@ -68,15 +55,10 @@ in {
environment.persistence."/state" = { environment.persistence."/state" = {
hideMounts = true; hideMounts = true;
files = files = [
[ "/etc/ssh/ssh_host_ed25519_key"
"/etc/ssh/ssh_host_ed25519_key" "/etc/ssh/ssh_host_ed25519_key.pub"
"/etc/ssh/ssh_host_ed25519_key.pub" ] ++ lib.lists.optionals (!config.boot.isContainer) [ "/etc/machine-id" ];
]
++ lib.lists.optionals (!config.boot.isContainer)
[
"/etc/machine-id"
];
directories = [ directories = [
"/var/log" "/var/log"
"/var/lib/systemd" "/var/lib/systemd"
@ -93,22 +75,20 @@ in {
}; };
environment.persistence."/persist" = { environment.persistence."/persist" = {
hideMounts = true; hideMounts = true;
directories = []; directories = [ ];
}; };
fileSystems."/persist".neededForBoot = true; fileSystems."/persist".neededForBoot = true;
fileSystems."/state".neededForBoot = true; fileSystems."/state".neededForBoot = true;
# After importing the rpool, rollback the root system to be empty. # After importing the rpool, rollback the root system to be empty.
boot.initrd.systemd.services.impermanence-root = boot.initrd.systemd.services.impermanence-root = onlyHost {
onlyHost wantedBy = [ "initrd.target" ];
{ after = [ "zfs-import-rpool.service" ];
wantedBy = ["initrd.target"]; before = [ "sysroot.mount" ];
after = ["zfs-import-rpool.service"]; unitConfig.DefaultDependencies = "no";
before = ["sysroot.mount"]; serviceConfig = {
unitConfig.DefaultDependencies = "no"; Type = "oneshot";
serviceConfig = { ExecStart = "${pkgs.zfs}/bin/zfs rollback -r rpool/local/root@blank";
Type = "oneshot";
ExecStart = "${pkgs.zfs}/bin/zfs rollback -r rpool/local/root@blank";
};
}; };
};
} }

View file

@ -1,17 +1,13 @@
{ lib, config, ... }:
{ {
lib,
config,
...
}: {
networking = { networking = {
useNetworkd = true; useNetworkd = true;
dhcpcd.enable = false; dhcpcd.enable = false;
useDHCP = false; useDHCP = false;
# allow mdns port # allow mdns port
firewall.allowedUDPPorts = [5353]; firewall.allowedUDPPorts = [ 5353 ];
renameInterfacesByMac = lib.mkIf (!config.boot.isContainer) ( renameInterfacesByMac = lib.mkIf (!config.boot.isContainer) (
lib.mapAttrs (_: v: v.mac) lib.mapAttrs (_: v: v.mac) (config.secrets.secrets.local.networking.interfaces or { })
(config.secrets.secrets.local.networking.interfaces or {})
); );
}; };
systemd.network = { systemd.network = {
@ -19,8 +15,8 @@
wait-online.anyInterface = true; wait-online.anyInterface = true;
}; };
system.nssDatabases.hosts = lib.mkMerge [ system.nssDatabases.hosts = lib.mkMerge [
(lib.mkBefore ["mdns_minimal [NOTFOUND=return]"]) (lib.mkBefore [ "mdns_minimal [NOTFOUND=return]" ])
(lib.mkAfter ["mdns"]) (lib.mkAfter [ "mdns" ])
]; ];
services.resolved = { services.resolved = {
enable = true; enable = true;

View file

@ -1,8 +1,5 @@
{ config, lib, ... }:
{ {
config,
lib,
...
}: {
networking.nftables = { networking.nftables = {
stopRuleset = lib.mkDefault '' stopRuleset = lib.mkDefault ''
table inet filter { table inet filter {
@ -36,20 +33,31 @@
nnf-ssh.enable = true; nnf-ssh.enable = true;
nnf-icmp = { nnf-icmp = {
enable = true; enable = true;
ipv6Types = ["echo-request" "destination-unreachable" "packet-too-big" "time-exceeded" "parameter-problem" "nd-router-advert" "nd-neighbor-solicit" "nd-neighbor-advert"]; ipv6Types = [
ipv4Types = ["echo-request" "destination-unreachable" "router-advertisement" "time-exceeded" "parameter-problem"]; "echo-request"
"destination-unreachable"
"packet-too-big"
"time-exceeded"
"parameter-problem"
"nd-router-advert"
"nd-neighbor-solicit"
"nd-neighbor-advert"
];
ipv4Types = [
"echo-request"
"destination-unreachable"
"router-advertisement"
"time-exceeded"
"parameter-problem"
];
}; };
}; };
rules.untrusted-to-local = { rules.untrusted-to-local = {
from = ["untrusted"]; from = [ "untrusted" ];
to = ["local"]; to = [ "local" ];
inherit inherit (config.networking.firewall) allowedTCPPorts allowedUDPPorts;
(config.networking.firewall)
allowedTCPPorts
allowedUDPPorts
;
}; };
}; };
}; };

View file

@ -1,14 +1,15 @@
{ inputs, stateVersion, ... }:
{ {
inputs,
stateVersion,
...
}: {
nix = { nix = {
settings = { settings = {
auto-optimise-store = true; auto-optimise-store = true;
allowed-users = ["@wheel"]; allowed-users = [ "@wheel" ];
trusted-users = ["root"]; trusted-users = [ "root" ];
system-features = ["recursive-nix" "repl-flake" "big-parallel"]; system-features = [
"recursive-nix"
"repl-flake"
"big-parallel"
];
substituters = [ substituters = [
"https://nix-community.cachix.org" "https://nix-community.cachix.org"
"https://cache.nixos.org" "https://cache.nixos.org"
@ -24,7 +25,7 @@
cores = 0; cores = 0;
max-jobs = "auto"; max-jobs = "auto";
# make agenix rekey find the secrets even without trusted user # make agenix rekey find the secrets even without trusted user
extra-sandbox-paths = ["/var/tmp/agenix-rekey?"]; extra-sandbox-paths = [ "/var/tmp/agenix-rekey?" ];
}; };
daemonCPUSchedPolicy = "batch"; daemonCPUSchedPolicy = "batch";
daemonIOSchedPriority = 5; daemonIOSchedPriority = 5;
@ -34,7 +35,7 @@
experimental-features = nix-command flakes recursive-nix experimental-features = nix-command flakes recursive-nix
flake-registry = /etc/nix/registry.json flake-registry = /etc/nix/registry.json
''; '';
nixPath = ["nixpkgs=/run/current-system/nixpkgs"]; nixPath = [ "nixpkgs=/run/current-system/nixpkgs" ];
optimise.automatic = true; optimise.automatic = true;
gc = { gc = {
automatic = true; automatic = true;

View file

@ -1,4 +1,5 @@
{lib, ...}: { { lib, ... }:
{
# Enable the OpenSSH daemon. # Enable the OpenSSH daemon.
services.openssh = { services.openssh = {
enable = true; enable = true;

View file

@ -5,24 +5,24 @@
pkgs, pkgs,
config, config,
... ...
}: { }:
{
system.stateVersion = stateVersion; system.stateVersion = stateVersion;
age.rekey = { age.rekey = {
inherit inherit (inputs.self.secretsConfig) masterIdentities extraEncryptionPubkeys;
(inputs.self.secretsConfig)
masterIdentities
extraEncryptionPubkeys
;
storageMode = "derivation"; storageMode = "derivation";
forceRekeyOnSystem = builtins.extraBuiltins.unsafeCurrentSystem; forceRekeyOnSystem = builtins.extraBuiltins.unsafeCurrentSystem;
hostPubkey = let hostPubkey =
pubkeyPath = config.node.secretsDir + "/host.pub"; let
in pubkeyPath = config.node.secretsDir + "/host.pub";
lib.mkIf (lib.pathExists pubkeyPath || lib.trace "Missing pubkey for ${config.node.name}: ${toString pubkeyPath} not found, using dummy replacement key for now." false) in
pubkeyPath; lib.mkIf (
lib.pathExists pubkeyPath
|| lib.trace "Missing pubkey for ${config.node.name}: ${toString pubkeyPath} not found, using dummy replacement key for now." false
) pubkeyPath;
generatedSecretsDir = config.node.secretsDir + "/generated/"; generatedSecretsDir = config.node.secretsDir + "/generated/";
cacheDir = "/var/tmp/agenix-rekey/\"$UID\""; cacheDir = "/var/tmp/agenix-rekey/\"$UID\"";
}; };
@ -38,16 +38,16 @@
# to create a link called /run/agenix. Agenix should probably fail in this case, # to create a link called /run/agenix. Agenix should probably fail in this case,
# but doesn't and instead puts the generation link into the existing directory. # but doesn't and instead puts the generation link into the existing directory.
# TODO See https://github.com/ryantm/agenix/pull/187. # TODO See https://github.com/ryantm/agenix/pull/187.
system.activationScripts = lib.mkIf (config.age.secrets != {}) { system.activationScripts = lib.mkIf (config.age.secrets != { }) {
removeAgenixLink.text = "[[ ! -L /run/agenix ]] && [[ -d /run/agenix ]] && rm -rf /run/agenix"; removeAgenixLink.text = "[[ ! -L /run/agenix ]] && [[ -d /run/agenix ]] && rm -rf /run/agenix";
agenixNewGeneration.deps = ["removeAgenixLink"]; agenixNewGeneration.deps = [ "removeAgenixLink" ];
}; };
time.timeZone = lib.mkDefault "Europe/Berlin"; time.timeZone = lib.mkDefault "Europe/Berlin";
i18n.defaultLocale = "C.UTF-8"; i18n.defaultLocale = "C.UTF-8";
console = { console = {
font = "${pkgs.terminus_font}/share/consolefonts/ter-v28n.psf.gz"; font = "${pkgs.terminus_font}/share/consolefonts/ter-v28n.psf.gz";
packages = with pkgs; [terminus_font]; packages = with pkgs; [ terminus_font ];
useXkbConfig = true; # use xkbOptions in tty. useXkbConfig = true; # use xkbOptions in tty.
keyMap = lib.mkDefault "de-latin1-nodeadkeys"; keyMap = lib.mkDefault "de-latin1-nodeadkeys";
}; };
@ -71,11 +71,12 @@
powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; powerManagement.cpuFreqGovernor = lib.mkDefault "powersave";
secrets.secretFiles = let secrets.secretFiles =
local = config.node.secretsDir + "/secrets.nix.age"; let
in local = config.node.secretsDir + "/secrets.nix.age";
in
{ {
global = ../../secrets/secrets.nix.age; global = ../../secrets/secrets.nix.age;
} }
// lib.optionalAttrs (config.node.name != null && lib.pathExists local) {inherit local;}; // lib.optionalAttrs (config.node.name != null && lib.pathExists local) { inherit local; };
} }

View file

@ -1,49 +1,51 @@
{ {
users.mutableUsers = false; users.mutableUsers = false;
users.deterministicIds = let users.deterministicIds =
uidGid = id: { let
uid = id; uidGid = id: {
gid = id; uid = id;
gid = id;
};
in
{
nscd = uidGid 201;
sshd = uidGid 202;
tss = uidGid 203;
rtkit = uidGid 204;
nixseparatedebuginfod = uidGid 205;
wireshark = uidGid 206;
polkituser = uidGid 207;
msr = uidGid 208;
avahi = uidGid 209;
fwupd-refresh = uidGid 210;
podman = uidGid 211;
acme = uidGid 212;
nextcloud = uidGid 213;
redis-nextcloud = uidGid 214;
radicale = uidGid 215;
git = uidGid 215;
vaultwarden = uidGid 215;
redis-paperless = uidGid 216;
microvm = uidGid 217;
maddy = uidGid 218;
tt_rss = uidGid 219;
freshrss = uidGid 220;
mongodb = uidGid 221;
authelia-main = uidGid 222;
kanidm = uidGid 223;
oauth2-proxy = uidGid 224;
influxdb2 = uidGid 225;
firefly-iii = uidGid 226;
paperless = uidGid 315;
systemd-oom = uidGid 300;
systemd-coredump = uidGid 301;
patrick = uidGid 1000;
smb = uidGid 2000;
david = uidGid 2004;
helen = uidGid 2001;
ggr = uidGid 2002;
family = uidGid 2003;
printer = uidGid 2005;
pr-tracker = uidGid 2006;
}; };
in {
nscd = uidGid 201;
sshd = uidGid 202;
tss = uidGid 203;
rtkit = uidGid 204;
nixseparatedebuginfod = uidGid 205;
wireshark = uidGid 206;
polkituser = uidGid 207;
msr = uidGid 208;
avahi = uidGid 209;
fwupd-refresh = uidGid 210;
podman = uidGid 211;
acme = uidGid 212;
nextcloud = uidGid 213;
redis-nextcloud = uidGid 214;
radicale = uidGid 215;
git = uidGid 215;
vaultwarden = uidGid 215;
redis-paperless = uidGid 216;
microvm = uidGid 217;
maddy = uidGid 218;
tt_rss = uidGid 219;
freshrss = uidGid 220;
mongodb = uidGid 221;
authelia-main = uidGid 222;
kanidm = uidGid 223;
oauth2-proxy = uidGid 224;
influxdb2 = uidGid 225;
firefly-iii = uidGid 226;
paperless = uidGid 315;
systemd-oom = uidGid 300;
systemd-coredump = uidGid 301;
patrick = uidGid 1000;
smb = uidGid 2000;
david = uidGid 2004;
helen = uidGid 2001;
ggr = uidGid 2002;
family = uidGid 2003;
printer = uidGid 2005;
pr-tracker = uidGid 2006;
};
} }

View file

@ -1,10 +1,11 @@
{pkgs, ...}: { { pkgs, ... }:
environment.systemPackages = with pkgs; [bluetuith]; {
environment.systemPackages = with pkgs; [ bluetuith ];
hardware.bluetooth = { hardware.bluetooth = {
enable = true; enable = true;
powerOnBoot = false; powerOnBoot = false;
disabledPlugins = ["sap"]; disabledPlugins = [ "sap" ];
settings = { settings = {
General = { General = {
FastConnectable = "true"; FastConnectable = "true";
@ -16,15 +17,13 @@
}; };
hardware.pulseaudio = { hardware.pulseaudio = {
package = pkgs.pulseaudio.override {bluetoothSupport = true;}; package = pkgs.pulseaudio.override { bluetoothSupport = true; };
extraConfig = '' extraConfig = ''
load-module module-bluetooth-discover load-module module-bluetooth-discover
load-module module-bluetooth-policy load-module module-bluetooth-policy
load-module module-switch-on-connect load-module module-switch-on-connect
''; '';
extraModules = with pkgs; [pulseaudio-modules-bt]; extraModules = with pkgs; [ pulseaudio-modules-bt ];
}; };
environment.persistence."/state".directories = [ environment.persistence."/state".directories = [ "/var/lib/bluetooth" ];
"/var/lib/bluetooth"
];
} }

View file

@ -1,3 +1 @@
{ { services.joycond.enable = true; }
services.joycond.enable = true;
}

View file

@ -5,7 +5,7 @@
... ...
}: }:
lib.optionalAttrs (!minimal) { lib.optionalAttrs (!minimal) {
services.xserver.videoDrivers = lib.mkForce ["nvidia"]; services.xserver.videoDrivers = lib.mkForce [ "nvidia" ];
hardware = { hardware = {
graphics = { graphics = {

View file

@ -1,5 +1,6 @@
# Configuration for actual physical machines # Configuration for actual physical machines
{config, ...}: { { config, ... }:
{
hardware = { hardware = {
enableRedistributableFirmware = true; enableRedistributableFirmware = true;
enableAllFirmware = true; enableAllFirmware = true;
@ -8,6 +9,6 @@
services = { services = {
fwupd.enable = true; fwupd.enable = true;
smartd.enable = true; smartd.enable = true;
thermald.enable = builtins.elem config.nixpkgs.hostPlatform.system ["x86_64-linux"]; thermald.enable = builtins.elem config.nixpkgs.hostPlatform.system [ "x86_64-linux" ];
}; };
} }

View file

@ -13,7 +13,10 @@ lib.optionalAttrs (!minimal) {
# packages = pkgs.linuxPackages_6_6_rt; # packages = pkgs.linuxPackages_6_6_rt;
# }; # };
#}; #};
environment.systemPackages = with pkgs; [pulseaudio pulsemixer]; environment.systemPackages = with pkgs; [
pulseaudio
pulsemixer
];
hardware.pulseaudio.enable = lib.mkForce false; hardware.pulseaudio.enable = lib.mkForce false;
security.rtkit.enable = true; security.rtkit.enable = true;

View file

@ -1,4 +1,5 @@
{pkgs, ...}: { { pkgs, ... }:
{
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
yubikey-personalization yubikey-personalization
yubikey-manager yubikey-manager
@ -7,5 +8,8 @@
services.pcscd.enable = true; services.pcscd.enable = true;
services.udev.packages = with pkgs; [yubikey-personalization libu2f-host]; services.udev.packages = with pkgs; [
yubikey-personalization
libu2f-host
];
} }

View file

@ -4,13 +4,11 @@
pkgs, pkgs,
lib, lib,
... ...
}: let }:
inherit let
(lib) inherit (lib) mkOption types;
mkOption in
types {
;
in {
options.hidpi = mkOption { options.hidpi = mkOption {
default = false; default = false;
type = types.bool; type = types.bool;
@ -18,14 +16,10 @@ in {
}; };
# stylix acceses stylix options on import meaning you can only import this module when you're actually setting stylix options # stylix acceses stylix options on import meaning you can only import this module when you're actually setting stylix options
imports = [ imports = [ inputs.stylix.nixosModules.stylix ];
inputs.stylix.nixosModules.stylix
];
config = { config = {
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [ xdg-utils ];
xdg-utils
];
xdg.portal = { xdg.portal = {
xdgOpenUsePortal = true; xdgOpenUsePortal = true;
enable = true; enable = true;
@ -38,13 +32,11 @@ in {
"gtk" "gtk"
"hyprland" "hyprland"
]; ];
sway.default = [ sway.default = [ "wlr" ];
"wlr"
];
}; };
}; };
# needed for gnome pinentry # needed for gnome pinentry
services.dbus.packages = [pkgs.gcr]; services.dbus.packages = [ pkgs.gcr ];
fonts = { fonts = {
enableGhostscriptFonts = false; enableGhostscriptFonts = false;
fontDir.enable = false; fontDir.enable = false;
@ -75,7 +67,7 @@ in {
''; '';
}; };
packages = with pkgs; [ packages = with pkgs; [
(nerdfonts.override {fonts = ["FiraCode"];}) (nerdfonts.override { fonts = [ "FiraCode" ]; })
ibm-plex ibm-plex
dejavu_fonts dejavu_fonts
unifont unifont
@ -160,71 +152,73 @@ in {
}; };
home-manager.sharedModules = [ home-manager.sharedModules = [
({ (
pkgs, {
config, pkgs,
nixosConfig, config,
... nixosConfig,
}: { ...
stylix = { }:
cursor = { {
package = pkgs.openzone-cursors; stylix = {
name = "OpenZone_White_Slim"; cursor = {
size = package = pkgs.openzone-cursors;
if nixosConfig.hidpi name = "OpenZone_White_Slim";
then 48 size = if nixosConfig.hidpi then 48 else 18;
else 18; };
}; inherit (nixosConfig.stylix) polarity;
inherit (nixosConfig.stylix) polarity; targets = {
targets = { gtk.enable = true;
gtk.enable = true; bat.enable = true;
bat.enable = true; dunst.enable = true;
dunst.enable = true; zathura.enable = true;
zathura.enable = true; xresources.enable = true;
xresources.enable = true; };
};
};
xresources.properties = {
"Xft.hinting" = true;
"Xft.antialias" = true;
"Xft.autohint" = false;
"Xft.lcdfilter" = "lcddefault";
"Xft.hintstyle" = "hintfull";
"Xft.rgba" = "rgb";
};
gtk = let
gtk34extraConfig = {
gtk-application-prefer-dark-theme = 1;
gtk-cursor-theme-size = 18;
gtk-enable-animations = true;
gtk-xft-antialias = 1;
gtk-xft-dpi = 96; # XXX: delete for wayland?
gtk-xft-hinting = 1;
gtk-xft-hintstyle = "hintfull";
gtk-xft-rgba = "rgb";
};
in {
enable = true;
iconTheme = {
name = "Vimix-Doder";
package = pkgs.vimix-icon-theme;
}; };
gtk2.extraConfig = "gtk-application-prefer-dark-theme = true"; xresources.properties = {
gtk3.extraConfig = gtk34extraConfig; "Xft.hinting" = true;
gtk4.extraConfig = gtk34extraConfig; "Xft.antialias" = true;
}; "Xft.autohint" = false;
"Xft.lcdfilter" = "lcddefault";
"Xft.hintstyle" = "hintfull";
"Xft.rgba" = "rgb";
};
home.sessionVariables.GTK_THEME = config.gtk.theme.name; gtk =
let
gtk34extraConfig = {
gtk-application-prefer-dark-theme = 1;
gtk-cursor-theme-size = 18;
gtk-enable-animations = true;
gtk-xft-antialias = 1;
gtk-xft-dpi = 96; # XXX: delete for wayland?
gtk-xft-hinting = 1;
gtk-xft-hintstyle = "hintfull";
gtk-xft-rgba = "rgb";
};
in
{
enable = true;
iconTheme = {
name = "Vimix-Doder";
package = pkgs.vimix-icon-theme;
};
qt = { gtk2.extraConfig = "gtk-application-prefer-dark-theme = true";
enable = true; gtk3.extraConfig = gtk34extraConfig;
platformTheme.name = "adwaita"; gtk4.extraConfig = gtk34extraConfig;
style.name = "Adwaita-Dark"; };
};
}) home.sessionVariables.GTK_THEME = config.gtk.theme.name;
qt = {
enable = true;
platformTheme.name = "adwaita";
style.name = "Adwaita-Dark";
};
}
)
]; ];
}; };
} }

View file

@ -1,8 +1,5 @@
{ config, pkgs, ... }:
{ {
config,
pkgs,
...
}: {
age.secrets.initrd_host_ed25519_key.generator.script = "ssh-ed25519"; age.secrets.initrd_host_ed25519_key.generator.script = "ssh-ed25519";
boot.initrd.network.enable = true; boot.initrd.network.enable = true;
@ -14,7 +11,7 @@
# need two activations to change as well as that to enable this # need two activations to change as well as that to enable this
# module you need to set hostKeys to a dummy value and generate # module you need to set hostKeys to a dummy value and generate
# and invalid initrd once # and invalid initrd once
hostKeys = [config.age.secrets.initrd_host_ed25519_key.path]; hostKeys = [ config.age.secrets.initrd_host_ed25519_key.path ];
}; };
# Make sure that there is always a valid initrd hostkey available that can be installed into # Make sure that there is always a valid initrd hostkey available that can be installed into
@ -30,7 +27,10 @@
${pkgs.openssh}/bin/ssh-keygen -t ed25519 -N "" -f "${config.age.secrets.initrd_host_ed25519_key.path}" ${pkgs.openssh}/bin/ssh-keygen -t ed25519 -N "" -f "${config.age.secrets.initrd_host_ed25519_key.path}"
fi fi
''; '';
deps = ["agenixInstall" "users"]; deps = [
"agenixInstall"
"users"
];
}; };
system.activationScripts.agenixChown.deps = ["agenixEnsureInitrdHostkey"]; system.activationScripts.agenixChown.deps = [ "agenixEnsureInitrdHostkey" ];
} }

View file

@ -1,7 +1,11 @@
{pkgs, ...}: { { pkgs, ... }:
{
services.printing = { services.printing = {
enable = true; enable = true;
drivers = [pkgs.hplipWithPlugin pkgs.hplip]; drivers = [
pkgs.hplipWithPlugin
pkgs.hplip
];
}; };
environment.persistence."/state".directories = [ environment.persistence."/state".directories = [
{ {

View file

@ -8,10 +8,7 @@
lib.optionalAttrs (!minimal) { lib.optionalAttrs (!minimal) {
environment.systemPackages = [ environment.systemPackages = [
# For debugging and troubleshooting Secure Boot. # For debugging and troubleshooting Secure Boot.
(pkgs.sbctl.override (pkgs.sbctl.override { databasePath = "/run/secureboot"; })
{
databasePath = "/run/secureboot";
})
]; ];
age.secrets.secureboot.rekeyFile = ../../hosts/${config.node.name}/secrets/secureboot.tar.age; age.secrets.secureboot.rekeyFile = ../../hosts/${config.node.name}/secrets/secureboot.tar.age;
system.activationScripts.securebootuntar = { system.activationScripts.securebootuntar = {
@ -21,7 +18,7 @@ lib.optionalAttrs (!minimal) {
chmod 700 /run/secureboot chmod 700 /run/secureboot
${pkgs.gnutar}/bin/tar xf ${config.age.secrets.secureboot.path} -C /run/secureboot || true ${pkgs.gnutar}/bin/tar xf ${config.age.secrets.secureboot.path} -C /run/secureboot || true
''; '';
deps = ["agenix"]; deps = [ "agenix" ];
}; };
# Lanzaboote currently replaces the systemd-boot module. # Lanzaboote currently replaces the systemd-boot module.

View file

@ -8,8 +8,8 @@ lib.optionalAttrs (!minimal) {
programs.steam = { programs.steam = {
enable = true; enable = true;
package = pkgs.steam.override { package = pkgs.steam.override {
extraPkgs = pkgs: extraPkgs =
with pkgs; [ pkgs: with pkgs; [
# vampir überlebende braucht diese pkgs # vampir überlebende braucht diese pkgs
libgdiplus libgdiplus
cups cups

View file

@ -14,10 +14,10 @@ lib.optionalAttrs (!minimal) {
enable = true; enable = true;
xdgOpenUsePortal = true; xdgOpenUsePortal = true;
config.common = { config.common = {
"org.freedesktop.impl.portal.Secret" = ["gnome-keyring"]; "org.freedesktop.impl.portal.Secret" = [ "gnome-keyring" ];
"org.freedesktop.impl.portal.ScreenCast" = ["hyprland"]; "org.freedesktop.impl.portal.ScreenCast" = [ "hyprland" ];
"org.freedesktop.impl.portal.Screenshot" = ["hyprland"]; "org.freedesktop.impl.portal.Screenshot" = [ "hyprland" ];
"org.freedesktop.portal.FileChooser" = ["xdg-desktop-portal-gtk"]; "org.freedesktop.portal.FileChooser" = [ "xdg-desktop-portal-gtk" ];
}; };
extraPortals = [ extraPortals = [
pkgs.xdg-desktop-portal-hyprland pkgs.xdg-desktop-portal-hyprland

View file

@ -12,7 +12,7 @@ lib.optionalAttrs (!minimal) {
displayManager.startx.enable = true; displayManager.startx.enable = true;
autoRepeatDelay = 235; autoRepeatDelay = 235;
autoRepeatInterval = 60; autoRepeatInterval = 60;
videoDrivers = ["modesetting"]; videoDrivers = [ "modesetting" ];
}; };
services.libinput = { services.libinput = {
enable = true; enable = true;
@ -28,10 +28,9 @@ lib.optionalAttrs (!minimal) {
disableWhileTyping = true; disableWhileTyping = true;
}; };
}; };
services.udev.extraRules = let services.udev.extraRules =
exe = let
pkgs.writeShellScript "set-key-repeat" exe = pkgs.writeShellScript "set-key-repeat" ''
''
if [ -d "/tmp/.X11-unix" ]; then if [ -d "/tmp/.X11-unix" ]; then
for D in /tmp/.X11-unix/*; do for D in /tmp/.X11-unix/*; do
file=$(${pkgs.coreutils}/bin/basename $D) file=$(${pkgs.coreutils}/bin/basename $D)
@ -43,7 +42,8 @@ lib.optionalAttrs (!minimal) {
done done
fi fi
''; '';
in '' in
ACTION=="add", SUBSYSTEM=="input", ATTRS{bInterfaceClass}=="03", RUN+="${exe}" ''
''; ACTION=="add", SUBSYSTEM=="input", ATTRS{bInterfaceClass}=="03", RUN+="${exe}"
'';
} }

View file

@ -3,15 +3,16 @@
config, config,
lib, lib,
... ...
}: { }:
boot.supportedFilesystems = ["zfs"]; {
boot.supportedFilesystems = [ "zfs" ];
boot.kernelPackages = lib.mkDefault config.boot.zfs.package.latestCompatibleLinuxPackages; boot.kernelPackages = lib.mkDefault config.boot.zfs.package.latestCompatibleLinuxPackages;
# The root pool should never be imported forcefully. # The root pool should never be imported forcefully.
# Failure to import is important to notice! # Failure to import is important to notice!
boot.zfs.forceImportRoot = false; boot.zfs.forceImportRoot = false;
environment.systemPackages = with pkgs; [zfs]; environment.systemPackages = with pkgs; [ zfs ];
# Might help with hangs mainly atuin # Might help with hangs mainly atuin
#boot.kernelPatches = [ #boot.kernelPatches = [
@ -40,5 +41,5 @@
}; };
}; };
# TODO remove once this is upstreamed # TODO remove once this is upstreamed
boot.initrd.systemd.services."zfs-import-rpool".after = ["cryptsetup.target"]; boot.initrd.systemd.services."zfs-import-rpool".after = [ "cryptsetup.target" ];
} }

View file

@ -1,16 +1,12 @@
{ {
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [3000]; firewallRuleForNode.elisabeth.allowedTCPPorts = [ 3000 ];
}; };
imports = [../actual.nix]; imports = [ ../actual.nix ];
services.actual = { services.actual = {
enable = true; enable = true;
settings.port = 3000; settings.port = 3000;
}; };
environment.persistence."/persist".directories = [ environment.persistence."/persist".directories = [ { directory = "/var/lib/private/actual"; } ];
{
directory = "/var/lib/private/actual";
}
];
} }

View file

@ -1,11 +1,8 @@
{ config, lib, ... }:
{ {
config,
lib,
...
}: {
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [config.services.adguardhome.port]; firewallRuleForNode.elisabeth.allowedTCPPorts = [ config.services.adguardhome.port ];
}; };
services.adguardhome = { services.adguardhome = {
enable = true; enable = true;
@ -16,8 +13,12 @@
settings = { settings = {
dns = { dns = {
bind_hosts = [ bind_hosts = [
(lib.net.cidr.host config.secrets.secrets.global.net.ips.${config.node.name} config.secrets.secrets.global.net.privateSubnetv4) (lib.net.cidr.host config.secrets.secrets.global.net.ips.${config.node.name}
(lib.net.cidr.host config.secrets.secrets.global.net.ips.${config.node.name} config.secrets.secrets.global.net.privateSubnetv6) config.secrets.secrets.global.net.privateSubnetv4
)
(lib.net.cidr.host config.secrets.secrets.global.net.ips.${config.node.name}
config.secrets.secrets.global.net.privateSubnetv6
)
]; ];
anonymize_client_ip = false; anonymize_client_ip = false;
upstream_dns = [ upstream_dns = [
@ -61,8 +62,8 @@
}; };
}; };
networking.firewall = { networking.firewall = {
allowedTCPPorts = [53]; allowedTCPPorts = [ 53 ];
allowedUDPPorts = [53]; allowedUDPPorts = [ 53 ];
}; };
environment.persistence."/persist".directories = [ environment.persistence."/persist".directories = [
{ {

View file

@ -1,4 +1,5 @@
{config, ...}: { { config, ... }:
{
age.secrets.cloudflare_token_dns = { age.secrets.cloudflare_token_dns = {
rekeyFile = config.node.secretsDir + "/cloudflare_api_token.age"; rekeyFile = config.node.secretsDir + "/cloudflare_api_token.age";
mode = "440"; mode = "440";
@ -13,6 +14,6 @@
usev4 = "webv4, webv4='https://cloudflare.com/cdn-cgi/trace', webv4-skip='ip='"; usev4 = "webv4, webv4='https://cloudflare.com/cdn-cgi/trace', webv4-skip='ip='";
usev6 = ""; usev6 = "";
passwordFile = config.age.secrets.cloudflare_token_dns.path; passwordFile = config.age.secrets.cloudflare_token_dns.path;
domains = [config.secrets.secrets.global.domains.web]; domains = [ config.secrets.secrets.global.domains.web ];
}; };
} }

View file

@ -1,12 +1,9 @@
{ config, nodes, ... }:
{ {
config, i18n.supportedLocales = [ "all" ];
nodes,
...
}: {
i18n.supportedLocales = ["all"];
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [80]; firewallRuleForNode.elisabeth.allowedTCPPorts = [ 80 ];
}; };
age.secrets.appKey = { age.secrets.appKey = {

View file

@ -4,9 +4,11 @@
pkgs, pkgs,
lib, lib,
... ...
}: let }:
let
forgejoDomain = "forge.${config.secrets.secrets.global.domains.web}"; forgejoDomain = "forge.${config.secrets.secrets.global.domains.web}";
in { in
{
age.secrets.resticpasswd = { age.secrets.resticpasswd = {
generator.script = "alnum"; generator.script = "alnum";
}; };
@ -29,7 +31,7 @@ in {
inherit (config.secrets.secrets.global.hetzner.users.forgejo) subUid path; inherit (config.secrets.secrets.global.hetzner.users.forgejo) subUid path;
sshAgeSecret = "forgejoHetznerSsh"; sshAgeSecret = "forgejoHetznerSsh";
}; };
paths = [config.services.forgejo.stateDir]; paths = [ config.services.forgejo.stateDir ];
pruneOpts = [ pruneOpts = [
"--keep-daily 10" "--keep-daily 10"
"--keep-weekly 7" "--keep-weekly 7"
@ -42,7 +44,7 @@ in {
# Recommended by forgejo: https://forgejo.org/docs/latest/admin/recommendations/#git-over-ssh # Recommended by forgejo: https://forgejo.org/docs/latest/admin/recommendations/#git-over-ssh
services.openssh.settings.AcceptEnv = "GIT_PROTOCOL"; services.openssh.settings.AcceptEnv = "GIT_PROTOCOL";
users.groups.git = {}; users.groups.git = { };
users.users.git = { users.users.git = {
isSystemUser = true; isSystemUser = true;
useDefaultShell = true; useDefaultShell = true;
@ -52,9 +54,11 @@ in {
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [config.services.forgejo.settings.server.HTTP_PORT]; firewallRuleForNode.elisabeth.allowedTCPPorts = [
config.services.forgejo.settings.server.HTTP_PORT
];
}; };
networking.firewall.allowedTCPPorts = [config.services.forgejo.settings.server.SSH_PORT]; networking.firewall.allowedTCPPorts = [ config.services.forgejo.settings.server.SSH_PORT ];
environment.persistence."/panzer".directories = [ environment.persistence."/panzer".directories = [
{ {
@ -145,30 +149,31 @@ in {
# see https://github.com/go-gitea/gitea/issues/21376. # see https://github.com/go-gitea/gitea/issues/21376.
systemd.services.forgejo = { systemd.services.forgejo = {
serviceConfig.RestartSec = "60"; # Retry every minute serviceConfig.RestartSec = "60"; # Retry every minute
preStart = let preStart =
exe = lib.getExe config.services.forgejo.package; let
providerName = "kanidm"; exe = lib.getExe config.services.forgejo.package;
clientId = "forgejo"; providerName = "kanidm";
args = lib.escapeShellArgs [ clientId = "forgejo";
"--name" args = lib.escapeShellArgs [
providerName "--name"
"--provider" providerName
"openidConnect" "--provider"
"--key" "openidConnect"
clientId "--key"
"--auto-discover-url" clientId
"https://auth.${config.secrets.secrets.global.domains.web}/oauth2/openid/${clientId}/.well-known/openid-configuration" "--auto-discover-url"
"--scopes" "https://auth.${config.secrets.secrets.global.domains.web}/oauth2/openid/${clientId}/.well-known/openid-configuration"
"email" "--scopes"
"--scopes" "email"
"profile" "--scopes"
"--group-claim-name" "profile"
"groups" "--group-claim-name"
"--admin-group" "groups"
"admin" "--admin-group"
"--skip-local-2fa" "admin"
]; "--skip-local-2fa"
in ];
in
lib.mkAfter '' lib.mkAfter ''
provider_id=$(${exe} admin auth list | ${pkgs.gnugrep}/bin/grep -w '${providerName}' | cut -f1) provider_id=$(${exe} admin auth list | ${pkgs.gnugrep}/bin/grep -w '${providerName}' | cut -f1)
SECRET="$(< ${config.age.secrets.openid-secret.path})" SECRET="$(< ${config.age.secrets.openid-secret.path})"

View file

@ -1,8 +1,8 @@
{ {
imports = [../../modules/homebox.nix]; imports = [ ../../modules/homebox.nix ];
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [3000]; firewallRuleForNode.elisabeth.allowedTCPPorts = [ 3000 ];
}; };
services.homebox = { services.homebox = {
enable = true; enable = true;

View file

@ -4,7 +4,8 @@
nodes, nodes,
config, config,
... ...
}: let }:
let
version = "v1.106.4"; version = "v1.106.4";
immichDomain = "immich.${config.secrets.secrets.global.domains.web}"; immichDomain = "immich.${config.secrets.secrets.global.domains.web}";
@ -136,21 +137,14 @@
serviceConfig = { serviceConfig = {
Restart = "always"; Restart = "always";
}; };
after = [ after = [ "podman-network-immich-default.service" ];
"podman-network-immich-default.service" requires = [ "podman-network-immich-default.service" ];
]; partOf = [ "podman-compose-immich-root.target" ];
requires = [ wantedBy = [ "podman-compose-immich-root.target" ];
"podman-network-immich-default.service"
];
partOf = [
"podman-compose-immich-root.target"
];
wantedBy = [
"podman-compose-immich-root.target"
];
}; };
processedConfigFile = "/run/agenix/immich.config.json"; processedConfigFile = "/run/agenix/immich.config.json";
in { in
{
age.secrets.resticpasswd = { age.secrets.resticpasswd = {
generator.script = "alnum"; generator.script = "alnum";
}; };
@ -206,7 +200,7 @@ in {
system.activationScripts.agenixRooterDerivedSecrets = { system.activationScripts.agenixRooterDerivedSecrets = {
# Run after agenix has generated secrets # Run after agenix has generated secrets
deps = ["agenix"]; deps = [ "agenix" ];
text = '' text = ''
immichClientSecret=$(< ${config.age.secrets.immich-oauth2-client-secret.path}) immichClientSecret=$(< ${config.age.secrets.immich-oauth2-client-secret.path})
${pkgs.jq}/bin/jq --arg immichClientSecret "$immichClientSecret" '.oauth.clientSecret = $immichClientSecret' ${configFile} > ${processedConfigFile} ${pkgs.jq}/bin/jq --arg immichClientSecret "$immichClientSecret" '.oauth.clientSecret = $immichClientSecret' ${configFile} > ${processedConfigFile}
@ -221,11 +215,11 @@ in {
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [3000]; firewallRuleForNode.elisabeth.allowedTCPPorts = [ 3000 ];
}; };
networking.nftables.chains.forward.into-immich-container = { networking.nftables.chains.forward.into-immich-container = {
after = ["conntrack"]; after = [ "conntrack" ];
rules = [ rules = [
"iifname elisabeth ip saddr ${nodes.elisabeth.config.wireguard.elisabeth.ipv4} tcp dport 3001 accept" "iifname elisabeth ip saddr ${nodes.elisabeth.config.wireguard.elisabeth.ipv4} tcp dport 3001 accept"
"iifname podman1 oifname lan accept" "iifname podman1 oifname lan accept"
@ -313,9 +307,7 @@ in {
"${upload_folder}:/usr/src/app/upload:rw" "${upload_folder}:/usr/src/app/upload:rw"
"${environment.DB_PASSWORD_FILE}:${environment.DB_PASSWORD_FILE}:ro" "${environment.DB_PASSWORD_FILE}:${environment.DB_PASSWORD_FILE}:ro"
]; ];
ports = [ ports = [ "3000:3001/tcp" ];
"3000:3001/tcp"
];
dependsOn = [ dependsOn = [
"immich_postgres" "immich_postgres"
"immich_redis" "immich_redis"
@ -327,18 +319,16 @@ in {
"--ip=${ipImmichServer}" "--ip=${ipImmichServer}"
]; ];
}; };
systemd.services."podman-immich_server" = systemd.services."podman-immich_server" = serviceConfig // {
serviceConfig unitConfig.UpheldBy = [
// { "podman-immich_postgres.service"
unitConfig.UpheldBy = [ "podman-immich_redis.service"
"podman-immich_postgres.service" ];
"podman-immich_redis.service" };
];
};
# Networks # Networks
systemd.services."podman-network-immich-default" = { systemd.services."podman-network-immich-default" = {
path = [pkgs.podman]; path = [ pkgs.podman ];
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
RemainAfterExit = true; RemainAfterExit = true;
@ -347,8 +337,8 @@ in {
script = '' script = ''
podman network inspect immich-default || podman network create immich-default --opt isolate=true --disable-dns --subnet=10.89.0.0/24 podman network inspect immich-default || podman network create immich-default --opt isolate=true --disable-dns --subnet=10.89.0.0/24
''; '';
partOf = ["podman-compose-immich-root.target"]; partOf = [ "podman-compose-immich-root.target" ];
wantedBy = ["podman-compose-immich-root.target"]; wantedBy = [ "podman-compose-immich-root.target" ];
}; };
# Root service # Root service
@ -358,6 +348,6 @@ in {
unitConfig = { unitConfig = {
Description = "Root target generated by compose2nix."; Description = "Root target generated by compose2nix.";
}; };
wantedBy = ["multi-user.target"]; wantedBy = [ "multi-user.target" ];
}; };
} }

View file

@ -1,12 +1,14 @@
{config, ...}: let { config, ... }:
let
kanidmdomain = "auth.${config.secrets.secrets.global.domains.web}"; kanidmdomain = "auth.${config.secrets.secrets.global.domains.web}";
in { in
imports = [../../modules/kanidm.nix]; {
imports = [ ../../modules/kanidm.nix ];
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [3000]; firewallRuleForNode.elisabeth.allowedTCPPorts = [ 3000 ];
}; };
disabledModules = ["services/security/kanidm.nix"]; disabledModules = [ "services/security/kanidm.nix" ];
environment.persistence."/persist".directories = [ environment.persistence."/persist".directories = [
{ {
directory = "/var/lib/kanidm"; directory = "/var/lib/kanidm";
@ -74,42 +76,50 @@ in {
inherit (config.secrets.secrets.local.kanidm) persons; inherit (config.secrets.secrets.local.kanidm) persons;
groups."paperless.access" = { groups."paperless.access" = {
members = ["paperless.admins"]; members = [ "paperless.admins" ];
}; };
# currently not usable # currently not usable
groups."paperless.admins" = { groups."paperless.admins" = {
members = ["administrator"]; members = [ "administrator" ];
}; };
systems.oauth2.paperless = { systems.oauth2.paperless = {
displayName = "paperless"; displayName = "paperless";
originUrl = "https://ppl.${config.secrets.secrets.global.domains.web}/"; originUrl = "https://ppl.${config.secrets.secrets.global.domains.web}/";
basicSecretFile = config.age.secrets.oauth2-paperless.path; basicSecretFile = config.age.secrets.oauth2-paperless.path;
scopeMaps."paperless.access" = ["openid" "email" "profile"]; scopeMaps."paperless.access" = [
"openid"
"email"
"profile"
];
preferShortUsername = true; preferShortUsername = true;
}; };
groups."nextcloud.access" = { groups."nextcloud.access" = {
members = ["nextcloud.admins"]; members = [ "nextcloud.admins" ];
}; };
# currently not usable # currently not usable
groups."nextcloud.admins" = { groups."nextcloud.admins" = {
members = ["administrator"]; members = [ "administrator" ];
}; };
systems.oauth2.nextcloud = { systems.oauth2.nextcloud = {
displayName = "nextcloud"; displayName = "nextcloud";
originUrl = "https://nc.${config.secrets.secrets.global.domains.web}/"; originUrl = "https://nc.${config.secrets.secrets.global.domains.web}/";
basicSecretFile = config.age.secrets.oauth2-nextcloud.path; basicSecretFile = config.age.secrets.oauth2-nextcloud.path;
allowInsecureClientDisablePkce = true; allowInsecureClientDisablePkce = true;
scopeMaps."nextcloud.access" = ["openid" "email" "profile"]; scopeMaps."nextcloud.access" = [
"openid"
"email"
"profile"
];
preferShortUsername = true; preferShortUsername = true;
}; };
groups."immich.access" = { groups."immich.access" = {
members = ["immich.admins"]; members = [ "immich.admins" ];
}; };
# currently not usable # currently not usable
groups."immich.admins" = { groups."immich.admins" = {
members = ["administrator"]; members = [ "administrator" ];
}; };
systems.oauth2.immich = { systems.oauth2.immich = {
displayName = "Immich"; displayName = "Immich";
@ -117,57 +127,84 @@ in {
basicSecretFile = config.age.secrets.oauth2-immich.path; basicSecretFile = config.age.secrets.oauth2-immich.path;
allowInsecureClientDisablePkce = true; allowInsecureClientDisablePkce = true;
enableLegacyCrypto = true; enableLegacyCrypto = true;
scopeMaps."immich.access" = ["openid" "email" "profile"]; scopeMaps."immich.access" = [
"openid"
"email"
"profile"
];
preferShortUsername = true; preferShortUsername = true;
}; };
groups."rss.access" = {}; groups."rss.access" = { };
groups."firefly.access" = {}; groups."firefly.access" = { };
groups."ollama.access" = {}; groups."ollama.access" = { };
groups."adguardhome.access" = {}; groups."adguardhome.access" = { };
groups."octoprint.access" = {}; groups."octoprint.access" = { };
systems.oauth2.oauth2-proxy = { systems.oauth2.oauth2-proxy = {
displayName = "Oauth2-Proxy"; displayName = "Oauth2-Proxy";
originUrl = "https://oauth2.${config.secrets.secrets.global.domains.web}/"; originUrl = "https://oauth2.${config.secrets.secrets.global.domains.web}/";
basicSecretFile = config.age.secrets.oauth2-proxy.path; basicSecretFile = config.age.secrets.oauth2-proxy.path;
scopeMaps."adguardhome.access" = ["openid" "email" "profile"]; scopeMaps."adguardhome.access" = [
scopeMaps."rss.access" = ["openid" "email" "profile"]; "openid"
scopeMaps."firefly.access" = ["openid" "email" "profile"]; "email"
scopeMaps."ollama.access" = ["openid" "email" "profile"]; "profile"
scopeMaps."octoprint.access" = ["openid" "email" "profile"]; ];
scopeMaps."rss.access" = [
"openid"
"email"
"profile"
];
scopeMaps."firefly.access" = [
"openid"
"email"
"profile"
];
scopeMaps."ollama.access" = [
"openid"
"email"
"profile"
];
scopeMaps."octoprint.access" = [
"openid"
"email"
"profile"
];
preferShortUsername = true; preferShortUsername = true;
claimMaps.groups = { claimMaps.groups = {
joinType = "array"; joinType = "array";
valuesByGroup."adguardhome.access" = ["adguardhome_access"]; valuesByGroup."adguardhome.access" = [ "adguardhome_access" ];
valuesByGroup."rss.access" = ["ttrss_access"]; valuesByGroup."rss.access" = [ "ttrss_access" ];
valuesByGroup."firefly.access" = ["firefly_access"]; valuesByGroup."firefly.access" = [ "firefly_access" ];
valuesByGroup."ollama.access" = ["ollama_access"]; valuesByGroup."ollama.access" = [ "ollama_access" ];
valuesByGroup."octoprint.access" = ["octoprint_access"]; valuesByGroup."octoprint.access" = [ "octoprint_access" ];
}; };
}; };
groups."forgejo.access" = { groups."forgejo.access" = {
members = ["forgejo.admins"]; members = [ "forgejo.admins" ];
}; };
groups."forgejo.admins" = { groups."forgejo.admins" = {
members = ["administrator"]; members = [ "administrator" ];
}; };
systems.oauth2.forgejo = { systems.oauth2.forgejo = {
displayName = "Forgejo"; displayName = "Forgejo";
originUrl = "https://forge.${config.secrets.secrets.global.domains.web}/"; originUrl = "https://forge.${config.secrets.secrets.global.domains.web}/";
basicSecretFile = config.age.secrets.oauth2-forgejo.path; basicSecretFile = config.age.secrets.oauth2-forgejo.path;
scopeMaps."forgejo.access" = ["openid" "email" "profile"]; scopeMaps."forgejo.access" = [
"openid"
"email"
"profile"
];
allowInsecureClientDisablePkce = true; allowInsecureClientDisablePkce = true;
preferShortUsername = true; preferShortUsername = true;
claimMaps.groups = { claimMaps.groups = {
joinType = "array"; joinType = "array";
valuesByGroup."forgejo.admins" = ["admin"]; valuesByGroup."forgejo.admins" = [ "admin" ];
}; };
}; };
groups."netbird.access" = { groups."netbird.access" = { };
};
systems.oauth2.netbird = { systems.oauth2.netbird = {
public = true; public = true;
displayName = "Netbird"; displayName = "Netbird";
@ -175,7 +212,11 @@ in {
preferShortUsername = true; preferShortUsername = true;
enableLocalhostRedirects = true; enableLocalhostRedirects = true;
enableLegacyCrypto = true; enableLegacyCrypto = true;
scopeMaps."netbird.access" = ["openid" "email" "profile"]; scopeMaps."netbird.access" = [
"openid"
"email"
"profile"
];
}; };
}; };
}; };

View file

@ -5,12 +5,17 @@
pkgs, pkgs,
lib, lib,
... ...
}: let }:
let
priv_domain = config.secrets.secrets.global.domains.mail_private; priv_domain = config.secrets.secrets.global.domains.mail_private;
domain = config.secrets.secrets.global.domains.mail_public; domain = config.secrets.secrets.global.domains.mail_public;
mailDomains = [priv_domain domain]; mailDomains = [
priv_domain
domain
];
maddyBackupDir = "/var/cache/backups/maddy"; maddyBackupDir = "/var/cache/backups/maddy";
in { in
{
systemd.tmpfiles.settings = { systemd.tmpfiles.settings = {
"10-maddy".${maddyBackupDir}.d = { "10-maddy".${maddyBackupDir}.d = {
inherit (config.services.maddy) user group; inherit (config.services.maddy) user group;
@ -40,7 +45,10 @@ in {
inherit (config.secrets.secrets.global.hetzner.users.maddy) subUid path; inherit (config.secrets.secrets.global.hetzner.users.maddy) subUid path;
sshAgeSecret = "maddyHetznerSsh"; sshAgeSecret = "maddyHetznerSsh";
}; };
paths = ["/var/lib/maddy/messages" maddyBackupDir]; paths = [
"/var/lib/maddy/messages"
maddyBackupDir
];
pruneOpts = [ pruneOpts = [
"--keep-daily 10" "--keep-daily 10"
"--keep-weekly 7" "--keep-weekly 7"
@ -49,22 +57,21 @@ in {
]; ];
}; };
}; };
systemd.services.maddy-backup = let systemd.services.maddy-backup =
cfg = config.systemd.services.maddy; let
in { cfg = config.systemd.services.maddy;
description = "Maddy db backup"; in
serviceConfig = {
lib.recursiveUpdate description = "Maddy db backup";
cfg.serviceConfig serviceConfig = lib.recursiveUpdate cfg.serviceConfig {
{
ExecStart = "${pkgs.sqlite}/bin/sqlite3 /var/lib/maddy/imapsql.db \".backup '${maddyBackupDir}/imapsql.sqlite3'\""; ExecStart = "${pkgs.sqlite}/bin/sqlite3 /var/lib/maddy/imapsql.db \".backup '${maddyBackupDir}/imapsql.sqlite3'\"";
Restart = "no"; Restart = "no";
Type = "oneshot"; Type = "oneshot";
}; };
inherit (cfg) environment; inherit (cfg) environment;
requiredBy = ["restic-backups-main.service"]; requiredBy = [ "restic-backups-main.service" ];
before = ["restic-backups-main.service"]; before = [ "restic-backups-main.service" ];
}; };
age.secrets.patrickPasswd = { age.secrets.patrickPasswd = {
generator.script = "alnum"; generator.script = "alnum";
@ -73,7 +80,10 @@ in {
}; };
# Opening ports for additional TLS listeners. This is not yet # Opening ports for additional TLS listeners. This is not yet
# implemented in the module. # implemented in the module.
networking.firewall.allowedTCPPorts = [993 465]; networking.firewall.allowedTCPPorts = [
993
465
];
services.maddy = { services.maddy = {
enable = true; enable = true;
hostname = "mx1." + domain; hostname = "mx1." + domain;
@ -91,9 +101,7 @@ in {
ensureCredentials = { ensureCredentials = {
"patrick@${domain}".passwordFile = config.age.secrets.patrickPasswd.path; "patrick@${domain}".passwordFile = config.age.secrets.patrickPasswd.path;
}; };
ensureAccounts = [ ensureAccounts = [ "patrick@${domain}" ];
"patrick@${domain}"
];
openFirewall = true; openFirewall = true;
config = '' config = ''
## Maddy Mail Server - default configuration file (2022-06-18) ## Maddy Mail Server - default configuration file (2022-06-18)
@ -288,33 +296,31 @@ in {
useACMEWildcardHost = true; useACMEWildcardHost = true;
locations."=/mail/config-v1.1.xml".alias = locations."=/mail/config-v1.1.xml".alias =
pkgs.writeText "autoconfig.${domain}.xml" pkgs.writeText "autoconfig.${domain}.xml"
/* # xml
xml ''
*/ <?xml version="1.0" encoding="UTF-8"?>
'' <clientConfig version="1.1">
<?xml version="1.0" encoding="UTF-8"?> <emailProvider id="${domain}">
<clientConfig version="1.1"> <domain>${domain}</domain>
<emailProvider id="${domain}"> <displayName>%EMAILADDRESS%</displayName>
<domain>${domain}</domain> <displayShortName>%EMAILLOCALPART%</displayShortName>
<displayName>%EMAILADDRESS%</displayName> <incomingServer type="imap">
<displayShortName>%EMAILLOCALPART%</displayShortName> <hostname>mail.${domain}</hostname>
<incomingServer type="imap"> <port>993</port>
<hostname>mail.${domain}</hostname> <socketType>SSL</socketType>
<port>993</port> <authentication>password-cleartext</authentication>
<socketType>SSL</socketType> <username>%EMAILADDRESS%</username>
<authentication>password-cleartext</authentication> </incomingServer>
<username>%EMAILADDRESS%</username> <outgoingServer type="smtp">
</incomingServer> <hostname>mail.${domain}</hostname>
<outgoingServer type="smtp"> <port>465</port>
<hostname>mail.${domain}</hostname> <socketType>SSL</socketType>
<port>465</port> <authentication>password-cleartext</authentication>
<socketType>SSL</socketType> <username>%EMAILADDRESS%</username>
<authentication>password-cleartext</authentication> </outgoingServer>
<username>%EMAILADDRESS%</username> </emailProvider>
</outgoingServer> </clientConfig>
</emailProvider> '';
</clientConfig>
'';
})) }))
]; ];
environment.persistence."/persist".directories = [ environment.persistence."/persist".directories = [

View file

@ -1,5 +1,6 @@
{config, ...}: { { config, ... }:
networking.firewall.allowedUDPPorts = [config.services.teamspeak3.defaultVoicePort]; {
networking.firewall.allowedUDPPorts = [ config.services.teamspeak3.defaultVoicePort ];
services.teamspeak3 = { services.teamspeak3 = {
enable = true; enable = true;
}; };

View file

@ -1,11 +1,12 @@
{ config, lib, ... }:
{ {
config,
lib,
...
}: {
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [80 3000 3001]; firewallRuleForNode.elisabeth.allowedTCPPorts = [
80
3000
3001
];
}; };
age.secrets.coturnPassword = { age.secrets.coturnPassword = {
@ -19,14 +20,20 @@
}; };
age.secrets.dataEnc = { age.secrets.dataEnc = {
generator.script = {pkgs, ...}: '' generator.script =
${lib.getExe pkgs.openssl} rand -base64 32 { pkgs, ... }:
''; ''
${lib.getExe pkgs.openssl} rand -base64 32
'';
group = "netbird"; group = "netbird";
}; };
networking.firewall.allowedTCPPorts = [80 3000 3001]; networking.firewall.allowedTCPPorts = [
networking.firewall.allowedUDPPorts = [3478]; 80
3000
3001
];
networking.firewall.allowedUDPPorts = [ 3478 ];
services.netbird = { services.netbird = {
server = { server = {
enable = true; enable = true;

View file

@ -4,9 +4,11 @@
config, config,
nodes, nodes,
... ...
}: let }:
let
hostName = "nc.${config.secrets.secrets.global.domains.web}"; hostName = "nc.${config.secrets.secrets.global.domains.web}";
in { in
{
age.secrets.maddyPasswd = { age.secrets.maddyPasswd = {
generator.script = "alnum"; generator.script = "alnum";
mode = "440"; mode = "440";
@ -20,7 +22,8 @@ in {
mode = "640"; mode = "640";
}; };
services.maddy.ensureCredentials = { services.maddy.ensureCredentials = {
"nextcloud@${config.secrets.secrets.global.domains.mail_public}".passwordFile = nodes.maddy.config.age.secrets.nextcloudPasswd.path; "nextcloud@${config.secrets.secrets.global.domains.mail_public}".passwordFile =
nodes.maddy.config.age.secrets.nextcloudPasswd.path;
}; };
}; };
environment.persistence."/persist".directories = [ environment.persistence."/persist".directories = [
@ -54,7 +57,15 @@ in {
config.adminpassFile = config.age.secrets.ncpasswd.path; # Kinda ok just remember to instanly change after first setup config.adminpassFile = config.age.secrets.ncpasswd.path; # Kinda ok just remember to instanly change after first setup
config.adminuser = "admin"; config.adminuser = "admin";
extraApps = with config.services.nextcloud.package.packages.apps; { extraApps = with config.services.nextcloud.package.packages.apps; {
inherit contacts calendar tasks notes maps phonetrack user_oidc; inherit
contacts
calendar
tasks
notes
maps
phonetrack
user_oidc
;
}; };
maxUploadSize = "4G"; maxUploadSize = "4G";
extraAppsEnable = true; extraAppsEnable = true;
@ -62,7 +73,7 @@ in {
phpOptions."opcache.interned_strings_buffer" = "32"; phpOptions."opcache.interned_strings_buffer" = "32";
settings = { settings = {
default_phone_region = "DE"; default_phone_region = "DE";
trusted_proxies = [nodes.elisabeth.config.wireguard.elisabeth.ipv4]; trusted_proxies = [ nodes.elisabeth.config.wireguard.elisabeth.ipv4 ];
overwriteprotocol = "https"; overwriteprotocol = "https";
maintenance_window_start = 2; maintenance_window_start = 2;
enabledPreviewProviders = [ enabledPreviewProviders = [
@ -93,20 +104,22 @@ in {
dbtype = "pgsql"; dbtype = "pgsql";
}; };
}; };
systemd.tmpfiles.rules = let systemd.tmpfiles.rules =
mailer-passwd-conf = pkgs.writeText "nextcloud-config.php" '' let
<?php mailer-passwd-conf = pkgs.writeText "nextcloud-config.php" ''
$CONFIG = [ <?php
'mail_smtppassword' => trim(file_get_contents('${config.age.secrets.maddyPasswd.path}')), $CONFIG = [
]; 'mail_smtppassword' => trim(file_get_contents('${config.age.secrets.maddyPasswd.path}')),
''; ];
in [ '';
"L+ ${config.services.nextcloud.datadir}/config/mailer.config.php - - - - ${mailer-passwd-conf}" in
]; [
"L+ ${config.services.nextcloud.datadir}/config/mailer.config.php - - - - ${mailer-passwd-conf}"
];
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [80]; firewallRuleForNode.elisabeth.allowedTCPPorts = [ 80 ];
}; };
networking = { networking = {
# Use systemd-resolved inside the container # Use systemd-resolved inside the container

View file

@ -1,11 +1,8 @@
{ config, nodes, ... }:
{ {
config,
nodes,
...
}: {
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [3000]; firewallRuleForNode.elisabeth.allowedTCPPorts = [ 3000 ];
}; };
age.secrets.oauth2-cookie-secret = { age.secrets.oauth2-cookie-secret = {
@ -46,7 +43,7 @@
redeemURL = "https://auth.${config.secrets.secrets.global.domains.web}/oauth2/token"; redeemURL = "https://auth.${config.secrets.secrets.global.domains.web}/oauth2/token";
validateURL = "https://auth.${config.secrets.secrets.global.domains.web}/oauth2/openid/oauth2-proxy/userinfo"; validateURL = "https://auth.${config.secrets.secrets.global.domains.web}/oauth2/openid/oauth2-proxy/userinfo";
clientID = "oauth2-proxy"; clientID = "oauth2-proxy";
email.domains = ["*"]; email.domains = [ "*" ];
}; };
systemd.services.oauth2-proxy.serviceConfig = { systemd.services.oauth2-proxy.serviceConfig = {
@ -72,18 +69,18 @@
# it includes the newline terminating the file which # it includes the newline terminating the file which
# makes kanidm reject the secret # makes kanidm reject the secret
age.secrets.oauth2-client-secret-env = { age.secrets.oauth2-client-secret-env = {
generator.dependencies = [ generator.dependencies = [ nodes.elisabeth-kanidm.config.age.secrets.oauth2-proxy ];
nodes.elisabeth-kanidm.config.age.secrets.oauth2-proxy generator.script =
]; {
generator.script = { lib,
lib, decrypt,
decrypt, deps,
deps, ...
... }:
}: '' ''
echo -n "OAUTH2_PROXY_CLIENT_SECRET=" echo -n "OAUTH2_PROXY_CLIENT_SECRET="
${decrypt} ${lib.escapeShellArg (lib.head deps).file} ${decrypt} ${lib.escapeShellArg (lib.head deps).file}
''; '';
mode = "440"; mode = "440";
group = "oauth2-proxy"; group = "oauth2-proxy";
}; };

View file

@ -1,12 +1,13 @@
{config, ...}: { { config, ... }:
{
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [config.services.octoprint.port]; firewallRuleForNode.elisabeth.allowedTCPPorts = [ config.services.octoprint.port ];
}; };
services.octoprint = { services.octoprint = {
port = 3000; port = 3000;
enable = true; enable = true;
plugins = ps: with ps; [ender3v2tempfix]; plugins = ps: with ps; [ ender3v2tempfix ];
extraConfig = { extraConfig = {
accessControl = { accessControl = {
addRemoteUser = true; addRemoteUser = true;

View file

@ -1,7 +1,8 @@
{config, ...}: { { config, ... }:
{
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [config.services.open-webui.port]; firewallRuleForNode.elisabeth.allowedTCPPorts = [ config.services.open-webui.port ];
}; };
services.ollama = { services.ollama = {
host = "localhost"; host = "localhost";

View file

@ -4,10 +4,12 @@
config, config,
lib, lib,
... ...
}: let }:
let
paperlessdomain = "ppl.${config.secrets.secrets.global.domains.web}"; paperlessdomain = "ppl.${config.secrets.secrets.global.domains.web}";
paperlessBackupDir = "/var/cache/backups/paperless"; paperlessBackupDir = "/var/cache/backups/paperless";
in { in
{
systemd.tmpfiles.settings = { systemd.tmpfiles.settings = {
"10-paperless".${paperlessBackupDir}.d = { "10-paperless".${paperlessBackupDir}.d = {
inherit (config.services.paperless) user; inherit (config.services.paperless) user;
@ -36,7 +38,7 @@ in {
inherit (config.secrets.secrets.global.hetzner.users.paperless) subUid path; inherit (config.secrets.secrets.global.hetzner.users.paperless) subUid path;
sshAgeSecret = "paperlessHetznerSsh"; sshAgeSecret = "paperlessHetznerSsh";
}; };
paths = [paperlessBackupDir]; paths = [ paperlessBackupDir ];
pruneOpts = [ pruneOpts = [
"--keep-daily 10" "--keep-daily 10"
"--keep-weekly 7" "--keep-weekly 7"
@ -45,27 +47,26 @@ in {
]; ];
}; };
}; };
systemd.services.paperless-backup = let systemd.services.paperless-backup =
cfg = config.systemd.services.paperless-consumer; let
in { cfg = config.systemd.services.paperless-consumer;
description = "Paperless document backup"; in
serviceConfig = {
lib.recursiveUpdate description = "Paperless document backup";
cfg.serviceConfig serviceConfig = lib.recursiveUpdate cfg.serviceConfig {
{
ExecStart = "${config.services.paperless.package}/bin/paperless-ngx document_exporter -na -nt -f -d ${paperlessBackupDir}"; ExecStart = "${config.services.paperless.package}/bin/paperless-ngx document_exporter -na -nt -f -d ${paperlessBackupDir}";
ReadWritePaths = cfg.serviceConfig.ReadWritePaths ++ [paperlessBackupDir]; ReadWritePaths = cfg.serviceConfig.ReadWritePaths ++ [ paperlessBackupDir ];
Restart = "no"; Restart = "no";
Type = "oneshot"; Type = "oneshot";
}; };
inherit (cfg) environment; inherit (cfg) environment;
requiredBy = ["restic-backups-main.service"]; requiredBy = [ "restic-backups-main.service" ];
before = ["restic-backups-main.service"]; before = [ "restic-backups-main.service" ];
}; };
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [config.services.paperless.port]; firewallRuleForNode.elisabeth.allowedTCPPorts = [ config.services.paperless.port ];
}; };
age.secrets.paperless-admin-passwd = { age.secrets.paperless-admin-passwd = {

View file

@ -4,18 +4,20 @@
lib, lib,
pkgs, pkgs,
... ...
}: let }:
let
prestart = pkgs.writeShellScript "pr-tracker-pre" '' prestart = pkgs.writeShellScript "pr-tracker-pre" ''
if [ ! -d ./nixpkgs ]; then if [ ! -d ./nixpkgs ]; then
${lib.getExe pkgs.git} clone https://github.com/NixOS/nixpkgs.git ${lib.getExe pkgs.git} clone https://github.com/NixOS/nixpkgs.git
fi fi
''; '';
in { in
{
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [3000]; firewallRuleForNode.elisabeth.allowedTCPPorts = [ 3000 ];
}; };
networking.firewall.allowedTCPPorts = [3000]; networking.firewall.allowedTCPPorts = [ 3000 ];
environment.persistence."/persist".directories = [ environment.persistence."/persist".directories = [
{ {
directory = "/var/lib/pr-tracker"; directory = "/var/lib/pr-tracker";
@ -43,15 +45,16 @@ in {
mode = "640"; mode = "640";
}; };
services.maddy.ensureCredentials = { services.maddy.ensureCredentials = {
"pr-tracker@${config.secrets.secrets.global.domains.mail_public}".passwordFile = nodes.maddy.config.age.secrets.pr-trackerPasswd.path; "pr-tracker@${config.secrets.secrets.global.domains.mail_public}".passwordFile =
nodes.maddy.config.age.secrets.pr-trackerPasswd.path;
}; };
}; };
systemd.sockets.pr-tracker = { systemd.sockets.pr-tracker = {
listenStreams = ["0.0.0.0:3000"]; listenStreams = [ "0.0.0.0:3000" ];
wantedBy = ["sockets.target"]; wantedBy = [ "sockets.target" ];
}; };
systemd.services.pr-tracker = { systemd.services.pr-tracker = {
path = [pkgs.git]; path = [ pkgs.git ];
serviceConfig = { serviceConfig = {
User = "pr-tracker"; User = "pr-tracker";
Group = "pr-tracker"; Group = "pr-tracker";
@ -104,13 +107,13 @@ in {
}; };
}; };
systemd.timers.pr-tracker-update = { systemd.timers.pr-tracker-update = {
wantedBy = ["timers.target"]; wantedBy = [ "timers.target" ];
timerConfig = { timerConfig = {
OnBootSec = "30m"; OnBootSec = "30m";
OnUnitActiveSec = "30m"; OnUnitActiveSec = "30m";
}; };
}; };
users.groups.pr-tracker = {}; users.groups.pr-tracker = { };
users.users.pr-tracker = { users.users.pr-tracker = {
isSystemUser = true; isSystemUser = true;
group = "pr-tracker"; group = "pr-tracker";

View file

@ -4,14 +4,20 @@
config, config,
pkgs, # not unused needed for the usage of attrs later to contains pkgs pkgs, # not unused needed for the usage of attrs later to contains pkgs
... ...
} @ attrs: let }@attrs:
let
hostName = "radicale.${config.secrets.secrets.global.domains.mail}"; hostName = "radicale.${config.secrets.secrets.global.domains.mail}";
in { in
imports = [./containers.nix ./ddclient.nix ./acme.nix]; {
imports = [
./containers.nix
./ddclient.nix
./acme.nix
];
services.nginx = { services.nginx = {
enable = true; enable = true;
upstreams.radicale = { upstreams.radicale = {
servers."192.168.178.34:8000" = {}; servers."192.168.178.34:8000" = { };
extraConfig = '' extraConfig = ''
zone radicale 64k ; zone radicale 64k ;
@ -32,10 +38,10 @@ in {
config = _: { config = _: {
systemd.network.networks = { systemd.network.networks = {
"lan01" = { "lan01" = {
address = ["192.168.178.34/24"]; address = [ "192.168.178.34/24" ];
gateway = ["192.168.178.1"]; gateway = [ "192.168.178.1" ];
matchConfig.Name = "lan01*"; matchConfig.Name = "lan01*";
dns = ["192.168.178.2"]; dns = [ "192.168.178.2" ];
networkConfig = { networkConfig = {
IPv6PrivacyExtensions = "yes"; IPv6PrivacyExtensions = "yes";
MulticastDNS = true; MulticastDNS = true;
@ -54,7 +60,10 @@ in {
enable = true; enable = true;
setting = { setting = {
server = { server = {
hosts = ["0.0.0.0:8000" "[::]:8000"]; hosts = [
"0.0.0.0:8000"
"[::]:8000"
];
auth = { auth = {
type = "htpasswd"; type = "htpasswd";
htpasswd_filename = "/etc/radicale/users"; htpasswd_filename = "/etc/radicale/users";
@ -89,7 +98,7 @@ in {
networking = { networking = {
firewall = { firewall = {
enable = true; enable = true;
allowedTCPPorts = [8000]; allowedTCPPorts = [ 8000 ];
}; };
# Use systemd-resolved inside the container # Use systemd-resolved inside the container
useHostResolvConf = lib.mkForce false; useHostResolvConf = lib.mkForce false;
@ -106,4 +115,3 @@ in {
#kanidm #kanidm
#remote backups #remote backups
#immich #immich

View file

@ -1,16 +1,13 @@
{ config, lib, ... }:
{ {
config,
lib,
...
}: {
services.samba-wsdd = { services.samba-wsdd = {
enable = true; # make shares visible for windows 10 clients enable = true; # make shares visible for windows 10 clients
openFirewall = true; openFirewall = true;
}; };
disabledModules = ["services/networking/netbird.nix"]; disabledModules = [ "services/networking/netbird.nix" ];
imports = [../../modules/netbird-client.nix]; imports = [ ../../modules/netbird-client.nix ];
services.netbird.tunnels = { services.netbird.tunnels = {
netbird-samba = { netbird-samba = {
environment = { environment = {
@ -43,7 +40,7 @@
inherit (config.secrets.secrets.global.hetzner.users.smb) subUid path; inherit (config.secrets.secrets.global.hetzner.users.smb) subUid path;
sshAgeSecret = "resticHetznerSsh"; sshAgeSecret = "resticHetznerSsh";
}; };
paths = ["/bunker"]; paths = [ "/bunker" ];
pruneOpts = [ pruneOpts = [
"--keep-daily 10" "--keep-daily 10"
"--keep-weekly 7" "--keep-weekly 7"
@ -55,11 +52,17 @@
wireguard.samba-patrick.server = { wireguard.samba-patrick.server = {
host = config.secrets.secrets.global.domains.web; host = config.secrets.secrets.global.domains.web;
port = 51830; port = 51830;
reservedAddresses = ["10.43.0.0/20" "fd00:1765::/112"]; reservedAddresses = [
"10.43.0.0/20"
"fd00:1765::/112"
];
openFirewall = true; openFirewall = true;
}; };
networking.nftables.firewall.zones.untrusted.interfaces = ["samba-patrick" "netbird-samba"]; networking.nftables.firewall.zones.untrusted.interfaces = [
"samba-patrick"
"netbird-samba"
];
services.samba = { services.samba = {
enable = true; enable = true;
@ -106,53 +109,49 @@
"disable spoolss = yes" "disable spoolss = yes"
"show add printer wizard = no" "show add printer wizard = no"
]; ];
shares = let shares =
mkShare = { let
name, mkShare =
user ? "smb",
group ? "smb",
hasBunker ? false,
hasPaperless ? false,
persistRoot ? "/panzer",
}: cfg: let
config =
{ {
"#persistRoot" = persistRoot; name,
"#user" = user; user ? "smb",
"#group" = group; group ? "smb",
"read only" = "no"; hasBunker ? false,
"guest ok" = "no"; hasPaperless ? false,
"create mask" = "0740"; persistRoot ? "/panzer",
"directory mask" = "0750"; }:
"force user" = user; cfg:
"force group" = group; let
"valid users" = "${user} @${group}"; config = {
"force create mode" = "0660"; "#persistRoot" = persistRoot;
"force directory mode" = "0770"; "#user" = user;
# Might be necessary for windows user to be able to open thing in smb "#group" = group;
"acl allow execute always" = "no"; "read only" = "no";
"guest ok" = "no";
"create mask" = "0740";
"directory mask" = "0750";
"force user" = user;
"force group" = group;
"valid users" = "${user} @${group}";
"force create mode" = "0660";
"force directory mode" = "0770";
# Might be necessary for windows user to be able to open thing in smb
"acl allow execute always" = "no";
} // cfg;
in
{
"${name}" = config // {
"path" = "/media/smb/${name}";
};
} }
// cfg; // lib.optionalAttrs hasBunker {
in "${name}-important" = config // {
{
"${name}" =
config
// {"path" = "/media/smb/${name}";};
}
// lib.optionalAttrs hasBunker
{
"${name}-important" =
config
// {
"path" = "/media/smb/${name}-important"; "path" = "/media/smb/${name}-important";
"#persistRoot" = "/bunker"; "#persistRoot" = "/bunker";
}; };
} }
// lib.optionalAttrs hasPaperless // lib.optionalAttrs hasPaperless {
{ "${name}-paperless" = config // {
"${name}-paperless" =
config
// {
"path" = "/media/smb/${name}-paperless"; "path" = "/media/smb/${name}-paperless";
"#paperless" = true; "#paperless" = true;
"force user" = "paperless"; "force user" = "paperless";
@ -160,46 +159,47 @@
# Empty to prevent imperamence setting a persistence folder # Empty to prevent imperamence setting a persistence folder
"#persistRoot" = ""; "#persistRoot" = "";
}; };
}; };
in in
lib.mkMerge [ lib.mkMerge [
(mkShare { (mkShare {
name = "ggr-data"; name = "ggr-data";
user = "ggr"; user = "ggr";
group = "ggr"; group = "ggr";
hasBunker = true; hasBunker = true;
} {}) } { })
(mkShare { (mkShare {
name = "patri"; name = "patri";
user = "patrick"; user = "patrick";
group = "patrick"; group = "patrick";
hasBunker = true; hasBunker = true;
hasPaperless = true; hasPaperless = true;
} {}) } { })
(mkShare { (mkShare {
name = "helen-data"; name = "helen-data";
user = "helen"; user = "helen";
group = "helen"; group = "helen";
hasBunker = true; hasBunker = true;
} {}) } { })
(mkShare { (mkShare {
name = "david"; name = "david";
user = "david"; user = "david";
group = "david"; group = "david";
hasBunker = true; hasBunker = true;
hasPaperless = true; hasPaperless = true;
} {}) } { })
(mkShare { (mkShare {
name = "printer"; name = "printer";
user = "printer"; user = "printer";
group = "printer"; group = "printer";
} {}) } { })
(mkShare { (mkShare {
name = "family-data"; name = "family-data";
user = "family"; user = "family";
group = "family"; group = "family";
} {}) } { })
(mkShare { (mkShare
{
name = "media"; name = "media";
user = "family"; user = "family";
group = "family"; group = "family";
@ -208,7 +208,8 @@
{ {
"read only" = "yes"; "read only" = "yes";
"write list" = "@family"; "write list" = "@family";
}) }
)
]; ];
}; };
# to get this file start a smbd, add users using 'smbpasswd -a <user>' # to get this file start a smbd, add users using 'smbpasswd -a <user>'
@ -216,129 +217,162 @@
age.secrets.smbpassdb = { age.secrets.smbpassdb = {
rekeyFile = config.node.secretsDir + "/smbpassdb.tdb.age"; rekeyFile = config.node.secretsDir + "/smbpassdb.tdb.age";
}; };
users = let users =
users = lib.unique (lib.mapAttrsToList (_: val: val."force user") config.services.samba.shares); let
groups = lib.unique (users ++ (lib.mapAttrsToList (_: val: val."force group") config.services.samba.shares)); users = lib.unique (lib.mapAttrsToList (_: val: val."force user") config.services.samba.shares);
in { groups = lib.unique (
users = lib.mkMerge ((lib.flip map users (user: { users ++ (lib.mapAttrsToList (_: val: val."force group") config.services.samba.shares)
${user} = { );
isNormalUser = true; in
home = "/var/empty"; {
createHome = false; users = lib.mkMerge (
useDefaultShell = false; (lib.flip map users (user: {
autoSubUidGidRange = false; ${user} = {
group = "${user}"; isNormalUser = true;
}; home = "/var/empty";
})) createHome = false;
++ [ useDefaultShell = false;
{paperless.isNormalUser = lib.mkForce false;} autoSubUidGidRange = false;
]); group = "${user}";
groups = lib.mkMerge ((lib.flip map groups (group: { };
${group} = { }))
}; ++ [ { paperless.isNormalUser = lib.mkForce false; } ]
})) );
++ [ groups = lib.mkMerge (
{ (lib.flip map groups (group: {
family.members = ["patrick" "david" "helen" "ggr"]; ${group} = { };
printer.members = ["patrick" "david" "helen" "ggr"]; }))
} ++ [
]);
};
fileSystems = lib.mkMerge (lib.flip lib.mapAttrsToList config.services.samba.shares (_: v:
lib.optionalAttrs ((v ? "#paperless") && v."#paperless") {
"${v.path}/consume" = {
fsType = "none";
options = ["bind"];
device = "/paperless/consume/${v."#user"}";
};
"${v.path}/media/archive" = {
fsType = "none ";
options = ["bind" "ro"];
device = "/paperless/media/documents/archive/${v."#user"}";
};
"${v.path}/media/originals" = {
fsType = "none ";
options = ["bind" "ro"];
device = "/paperless/media/documents/originals/${v."#user"}";
};
}));
systemd.tmpfiles.settings = lib.mkMerge (lib.flip lib.mapAttrsToList config.services.samba.shares (_: v:
lib.optionalAttrs ((v ? "#paperless") && v."#paperless") {
"10-smb-paperless"."/paperless/consume/".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/consume/${v."#user"}".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/media/".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/media/documents/".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/media/documents/archive/".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/media/documents/archive/${v."#user"}".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/media/documents/archive/${v."#user"}/.keep".f = {
user = "paperless";
group = "paperless";
mode = "0660";
};
"10-smb-paperless"."/paperless/media/documents/originals/".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/media/documents/originals/${v."#user"}".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/media/documents/originals/${v."#user"}/.keep".f = {
user = "paperless";
group = "paperless";
mode = "0660";
};
}));
environment.persistence = lib.mkMerge (lib.flatten [
(lib.flip lib.mapAttrsToList config.services.samba.shares (_: v:
lib.optionalAttrs ((v ? "#persistRoot") && (v."#persistRoot" != "")) {
${v."#persistRoot"}.directories = [
{ {
directory = "${v.path}"; family.members = [
user = "${v."force user"}"; "patrick"
group = "${v."force group"}"; "david"
mode = "0770"; "helen"
"ggr"
];
printer.members = [
"patrick"
"david"
"helen"
"ggr"
];
} }
]; ]
})) );
(lib.flip lib.mapAttrsToList config.services.netbird.tunnels ( };
_: v: {
"/state".directories = [ fileSystems = lib.mkMerge (
{ lib.flip lib.mapAttrsToList config.services.samba.shares (
directory = "/var/lib/${v.stateDir}"; _: v:
mode = "0770"; lib.optionalAttrs ((v ? "#paperless") && v."#paperless") {
} "${v.path}/consume" = {
]; fsType = "none";
options = [ "bind" ];
device = "/paperless/consume/${v."#user"}";
};
"${v.path}/media/archive" = {
fsType = "none ";
options = [
"bind"
"ro"
];
device = "/paperless/media/documents/archive/${v."#user"}";
};
"${v.path}/media/originals" = {
fsType = "none ";
options = [
"bind"
"ro"
];
device = "/paperless/media/documents/originals/${v."#user"}";
};
} }
)) )
]); );
systemd.tmpfiles.settings = lib.mkMerge (
lib.flip lib.mapAttrsToList config.services.samba.shares (
_: v:
lib.optionalAttrs ((v ? "#paperless") && v."#paperless") {
"10-smb-paperless"."/paperless/consume/".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/consume/${v."#user"}".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/media/".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/media/documents/".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/media/documents/archive/".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/media/documents/archive/${v."#user"}".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/media/documents/archive/${v."#user"}/.keep".f = {
user = "paperless";
group = "paperless";
mode = "0660";
};
"10-smb-paperless"."/paperless/media/documents/originals/".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/media/documents/originals/${v."#user"}".d = {
user = "paperless";
group = "paperless";
mode = "0770";
};
"10-smb-paperless"."/paperless/media/documents/originals/${v."#user"}/.keep".f = {
user = "paperless";
group = "paperless";
mode = "0660";
};
}
)
);
environment.persistence = lib.mkMerge (
lib.flatten [
(lib.flip lib.mapAttrsToList config.services.samba.shares (
_: v:
lib.optionalAttrs ((v ? "#persistRoot") && (v."#persistRoot" != "")) {
${v."#persistRoot"}.directories = [
{
directory = "${v.path}";
user = "${v."force user"}";
group = "${v."force group"}";
mode = "0770";
}
];
}
))
(lib.flip lib.mapAttrsToList config.services.netbird.tunnels (
_: v: {
"/state".directories = [
{
directory = "/var/lib/${v.stateDir}";
mode = "0770";
}
];
}
))
]
);
} }

View file

@ -1,7 +1,8 @@
{config, ...}: { { config, ... }:
{
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [80]; firewallRuleForNode.elisabeth.allowedTCPPorts = [ 80 ];
}; };
services.freshrss = { services.freshrss = {
enable = true; enable = true;

View file

@ -3,9 +3,11 @@
lib, lib,
nodes, nodes,
... ...
}: let }:
let
vaultwardenDomain = "pw.${config.secrets.secrets.global.domains.web}"; vaultwardenDomain = "pw.${config.secrets.secrets.global.domains.web}";
in { in
{
age.secrets.vaultwarden-env = { age.secrets.vaultwarden-env = {
rekeyFile = config.node.secretsDir + "/vaultwarden-env.age"; rekeyFile = config.node.secretsDir + "/vaultwarden-env.age";
mode = "440"; mode = "440";
@ -43,7 +45,7 @@ in {
inherit (config.secrets.secrets.global.hetzner.users.vaultwarden) subUid path; inherit (config.secrets.secrets.global.hetzner.users.vaultwarden) subUid path;
sshAgeSecret = "vaultwardenHetznerSsh"; sshAgeSecret = "vaultwardenHetznerSsh";
}; };
paths = [config.services.vaultwarden.backupDir]; paths = [ config.services.vaultwarden.backupDir ];
pruneOpts = [ pruneOpts = [
"--keep-daily 10" "--keep-daily 10"
"--keep-weekly 7" "--keep-weekly 7"
@ -64,17 +66,18 @@ in {
mode = "640"; mode = "640";
}; };
services.maddy.ensureCredentials = { services.maddy.ensureCredentials = {
"vaultwarden@${config.secrets.secrets.global.domains.mail_public}".passwordFile = nodes.maddy.config.age.secrets.vaultwardenPasswd.path; "vaultwarden@${config.secrets.secrets.global.domains.mail_public}".passwordFile =
nodes.maddy.config.age.secrets.vaultwardenPasswd.path;
}; };
}; };
system.activationScripts.systemd_env_smtp_passwd = { system.activationScripts.systemd_env_smtp_passwd = {
text = '' text = ''
echo "SMTP_PASSWORD=$(< ${lib.escapeShellArg config.age.secrets.maddyPasswd.path})" > /run/vaultwarden_smtp_passwd echo "SMTP_PASSWORD=$(< ${lib.escapeShellArg config.age.secrets.maddyPasswd.path})" > /run/vaultwarden_smtp_passwd
''; '';
deps = ["agenix"]; deps = [ "agenix" ];
}; };
systemd.services.vaultwarden.serviceConfig.EnvironmentFile = ["/run/vaultwarden_smtp_passwd"]; systemd.services.vaultwarden.serviceConfig.EnvironmentFile = [ "/run/vaultwarden_smtp_passwd" ];
services.vaultwarden = { services.vaultwarden = {
enable = true; enable = true;
@ -107,7 +110,7 @@ in {
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [config.services.vaultwarden.config.rocketPort]; firewallRuleForNode.elisabeth.allowedTCPPorts = [ config.services.vaultwarden.config.rocketPort ];
}; };
# Replace uses of old name # Replace uses of old name

View file

@ -1,11 +1,11 @@
{ config, pkgs, ... }:
{ {
config,
pkgs,
...
}: {
wireguard.elisabeth = { wireguard.elisabeth = {
client.via = "elisabeth"; client.via = "elisabeth";
firewallRuleForNode.elisabeth.allowedTCPPorts = [3000 80]; firewallRuleForNode.elisabeth.allowedTCPPorts = [
3000
80
];
}; };
age.secrets.spotifySecret = { age.secrets.spotifySecret = {
owner = "root"; owner = "root";

View file

@ -101,27 +101,29 @@
}; };
}; };
outputs = { outputs =
self, {
nixpkgs, self,
flake-utils, nixpkgs,
agenix-rekey, flake-utils,
nixos-generators, agenix-rekey,
pre-commit-hooks, nixos-generators,
devshell, pre-commit-hooks,
nixvim, devshell,
nixos-extra-modules, nixvim,
nix-topology, nixos-extra-modules,
... nix-topology,
} @ inputs: let ...
inherit (nixpkgs) lib; }@inputs:
stateVersion = "23.05"; let
in inherit (nixpkgs) lib;
stateVersion = "23.05";
in
{ {
secretsConfig = { secretsConfig = {
# This should be a link to one of the age public keys is './keys' # This should be a link to one of the age public keys is './keys'
masterIdentities = [./keys/PatC.pub]; masterIdentities = [ ./keys/PatC.pub ];
extraEncryptionPubkeys = [./secrets/recipients.txt]; extraEncryptionPubkeys = [ ./secrets/recipients.txt ];
}; };
agenix-rekey = agenix-rekey.configure { agenix-rekey = agenix-rekey.configure {
userFlake = self; userFlake = self;
@ -129,8 +131,7 @@
}; };
inherit stateVersion; inherit stateVersion;
inherit inherit (import ./nix/hosts.nix inputs)
(import ./nix/hosts.nix inputs)
hosts hosts
nixosConfigurations nixosConfigurations
minimalConfigurations minimalConfigurations
@ -139,19 +140,20 @@
nodes = self.nixosConfigurations // self.guestConfigurations; nodes = self.nixosConfigurations // self.guestConfigurations;
inherit inherit
(lib.foldl' lib.recursiveUpdate {} (lib.foldl' lib.recursiveUpdate { } (
(lib.mapAttrsToList lib.mapAttrsToList (import ./nix/generate-installer-package.nix inputs) self.minimalConfigurations
(import ./nix/generate-installer-package.nix inputs) ))
self.minimalConfigurations))
packages packages
; ;
} }
// flake-utils.lib.eachDefaultSystem (system: rec { // flake-utils.lib.eachDefaultSystem (system: rec {
apps.setupHetznerStorageBoxes = import (nixos-extra-modules + "/apps/setup-hetzner-storage-boxes.nix") { apps.setupHetznerStorageBoxes =
inherit pkgs; import (nixos-extra-modules + "/apps/setup-hetzner-storage-boxes.nix")
nixosConfigurations = self.nodes; {
decryptIdentity = builtins.head self.secretsConfig.masterIdentities; inherit pkgs;
}; nixosConfigurations = self.nodes;
decryptIdentity = builtins.head self.secretsConfig.masterIdentities;
};
pkgs = import nixpkgs { pkgs = import nixpkgs {
overlays = overlays =
import ./lib inputs import ./lib inputs
@ -172,7 +174,7 @@
topology = import nix-topology { topology = import nix-topology {
inherit pkgs; inherit pkgs;
modules = [ modules = [
{inherit (self) nixosConfigurations;} { inherit (self) nixosConfigurations; }
./nix/topology.nix ./nix/topology.nix
]; ];
}; };
@ -191,19 +193,17 @@
.${system}; .${system};
}; };
checks.pre-commit-check = checks.pre-commit-check = pre-commit-hooks.lib.${system}.run {
pre-commit-hooks.lib.${system}.run src = lib.cleanSource ./.;
{ hooks = {
src = lib.cleanSource ./.; nixfmt = {
hooks = { enable = true;
nixfmt = { package = pkgs.nixfmt-rfc-style;
enable = true;
package = pkgs.nixfmt-rfc-style;
};
deadnix.enable = true;
statix.enable = true;
}; };
deadnix.enable = true;
statix.enable = true;
}; };
};
devShell = import ./nix/devshell.nix inputs system; devShell = import ./nix/devshell.nix inputs system;
formatter = pkgs.nixfmt-rfc-style; formatter = pkgs.nixfmt-rfc-style;
}); });

View file

@ -1,8 +1,5 @@
{ inputs, lib, ... }:
{ {
inputs,
lib,
...
}: {
imports = [ imports = [
inputs.nixos-hardware.nixosModules.common-gpu-nvidia-nonprime inputs.nixos-hardware.nixosModules.common-gpu-nvidia-nonprime
inputs.nixos-hardware.nixosModules.common-cpu-intel-cpu-only inputs.nixos-hardware.nixosModules.common-cpu-intel-cpu-only
@ -51,9 +48,23 @@
device = "/dev/input/event15"; device = "/dev/input/event15";
}; };
boot.binfmt.emulatedSystems = ["aarch64-linux" "riscv64-linux"]; boot.binfmt.emulatedSystems = [
nix.settings.system-features = ["kvm" "nixos-test"]; "aarch64-linux"
boot.kernelParams = lib.mkForce ["rd.luks.options=timeout=0" "rootflags=x-systemd.device-timeout=0" "nohibernate" "root=fstab" "loglevel=4" "nvidia-drm.modeset=1" "nvidia.NVreg_PreserveVideoMemoryAllocations=1"]; "riscv64-linux"
];
nix.settings.system-features = [
"kvm"
"nixos-test"
];
boot.kernelParams = lib.mkForce [
"rd.luks.options=timeout=0"
"rootflags=x-systemd.device-timeout=0"
"nohibernate"
"root=fstab"
"loglevel=4"
"nvidia-drm.modeset=1"
"nvidia.NVreg_PreserveVideoMemoryAllocations=1"
];
services.netbird.enable = true; services.netbird.enable = true;
# Do not cleanup nix store to prevent having to rebuild packages onca a month # Do not cleanup nix store to prevent having to rebuild packages onca a month

View file

@ -3,7 +3,8 @@
nodes, nodes,
lib, lib,
... ...
}: { }:
{
disko.devices = { disko.devices = {
disk = { disk = {
m2-ssd = rec { m2-ssd = rec {
@ -12,9 +13,15 @@
content = with lib.disko.gpt; { content = with lib.disko.gpt; {
type = "gpt"; type = "gpt";
partitions = { partitions = {
boot = (partEfi "2GiB") // {device = "${device}-part1";}; boot = (partEfi "2GiB") // {
swap = (partSwap "16G") // {device = "${device}-part2";}; device = "${device}-part1";
rpool = (partLuksZfs "m2-ssd" "rpool" "100%") // {device = "${device}-part3";}; };
swap = (partSwap "16G") // {
device = "${device}-part2";
};
rpool = (partLuksZfs "m2-ssd" "rpool" "100%") // {
device = "${device}-part3";
};
}; };
}; };
}; };
@ -24,13 +31,15 @@
content = with lib.disko.gpt; { content = with lib.disko.gpt; {
type = "gpt"; type = "gpt";
partitions = { partitions = {
panzer = (partLuksZfs "sata-hdd" "panzer" "100%") // {device = "${device}-part1";}; panzer = (partLuksZfs "sata-hdd" "panzer" "100%") // {
device = "${device}-part1";
};
}; };
}; };
}; };
}; };
zpool = with lib.disko.zfs; { zpool = with lib.disko.zfs; {
rpool = mkZpool {datasets = impermanenceZfsDatasets;}; rpool = mkZpool { datasets = impermanenceZfsDatasets; };
panzer = mkZpool { panzer = mkZpool {
datasets = { datasets = {
"local" = unmountable; "local" = unmountable;
@ -42,8 +51,8 @@
fileSystems."/state".neededForBoot = true; fileSystems."/state".neededForBoot = true;
fileSystems."/persist".neededForBoot = true; fileSystems."/persist".neededForBoot = true;
fileSystems."/panzer/state".neededForBoot = true; fileSystems."/panzer/state".neededForBoot = true;
boot.initrd.systemd.services."zfs-import-panzer".after = ["cryptsetup.target"]; boot.initrd.systemd.services."zfs-import-panzer".after = [ "cryptsetup.target" ];
boot.initrd.systemd.services."zfs-import-rpool".after = ["cryptsetup.target"]; boot.initrd.systemd.services."zfs-import-rpool".after = [ "cryptsetup.target" ];
wireguard.scrtiny-patrick.client.via = "elisabeth"; wireguard.scrtiny-patrick.client.via = "elisabeth";

View file

@ -1,4 +1,5 @@
{config, ...}: { { config, ... }:
{
networking = { networking = {
inherit (config.secrets.secrets.local.networking) hostId; inherit (config.secrets.secrets.local.networking) hostId;
}; };
@ -20,6 +21,6 @@
}; };
}; };
}; };
networking.nftables.firewall.zones.untrusted.interfaces = ["lan01"]; networking.nftables.firewall.zones.untrusted.interfaces = [ "lan01" ];
wireguard.samba-patrick.client.via = "elisabeth-samba"; wireguard.samba-patrick.client.via = "elisabeth-samba";
} }

View file

@ -3,29 +3,26 @@
minimal, minimal,
lib, lib,
... ...
}: { }:
imports = {
[ imports = [
inputs.nixos-hardware.nixosModules.common-pc inputs.nixos-hardware.nixosModules.common-pc
inputs.nixos-hardware.nixosModules.common-pc-ssd inputs.nixos-hardware.nixosModules.common-pc-ssd
inputs.nixos-hardware.nixosModules.common-cpu-amd inputs.nixos-hardware.nixosModules.common-cpu-amd
inputs.nixos-hardware.nixosModules.common-cpu-amd-pstate inputs.nixos-hardware.nixosModules.common-cpu-amd-pstate
../../config/basic ../../config/basic
../../config/optional/initrd-ssh.nix ../../config/optional/initrd-ssh.nix
../../config/optional/secureboot.nix ../../config/optional/secureboot.nix
../../config/optional/zfs.nix ../../config/optional/zfs.nix
../../config/hardware/physical.nix ../../config/hardware/physical.nix
./blog.nix ./blog.nix
./net.nix ./net.nix
./fs.nix ./fs.nix
] ] ++ lib.lists.optionals (!minimal) [ ./guests.nix ];
++ lib.lists.optionals (!minimal) [
./guests.nix
];
services.xserver = { services.xserver = {
xkb = { xkb = {
layout = "de"; layout = "de";

View file

@ -1,8 +1,5 @@
{ config, lib, ... }:
{ {
config,
lib,
...
}: {
disko.devices = { disko.devices = {
disk = { disk = {
internal-ssd = rec { internal-ssd = rec {
@ -11,8 +8,12 @@
content = with lib.disko.gpt; { content = with lib.disko.gpt; {
type = "gpt"; type = "gpt";
partitions = { partitions = {
boot = (partEfi "1GiB") // {device = "${device}-part1";}; boot = (partEfi "1GiB") // {
rpool = (partLuksZfs "ssd" "rpool" "100%") // {device = "${device}-part2";}; device = "${device}-part1";
};
rpool = (partLuksZfs "ssd" "rpool" "100%") // {
device = "${device}-part2";
};
}; };
}; };
}; };
@ -49,7 +50,7 @@
}; };
zpool = with lib.disko.zfs; { zpool = with lib.disko.zfs; {
rpool = mkZpool {datasets = impermanenceZfsDatasets;}; rpool = mkZpool { datasets = impermanenceZfsDatasets; };
panzer = mkZpool { panzer = mkZpool {
datasets = { datasets = {
"safe/guests" = unmountable; "safe/guests" = unmountable;
@ -128,10 +129,13 @@
wireguard.scrtiny-patrick.server = { wireguard.scrtiny-patrick.server = {
host = config.secrets.secrets.global.domains.web; host = config.secrets.secrets.global.domains.web;
port = 51831; port = 51831;
reservedAddresses = ["10.44.0.0/16" "fd00:1766::/112"]; reservedAddresses = [
"10.44.0.0/16"
"fd00:1766::/112"
];
openFirewall = true; openFirewall = true;
}; };
networking.nftables.firewall.zones.untrusted.interfaces = ["scrtiny-patrick"]; networking.nftables.firewall.zones.untrusted.interfaces = [ "scrtiny-patrick" ];
services.scrutiny = { services.scrutiny = {
enable = true; enable = true;
openFirewall = true; openFirewall = true;
@ -156,6 +160,6 @@
fileSystems."/state".neededForBoot = true; fileSystems."/state".neededForBoot = true;
fileSystems."/persist".neededForBoot = true; fileSystems."/persist".neededForBoot = true;
boot.initrd.systemd.services."zfs-import-panzer".after = ["cryptsetup.target"]; boot.initrd.systemd.services."zfs-import-panzer".after = [ "cryptsetup.target" ];
boot.initrd.systemd.services."zfs-import-renaultft".after = ["cryptsetup.target"]; boot.initrd.systemd.services."zfs-import-renaultft".after = [ "cryptsetup.target" ];
} }

View file

@ -6,122 +6,136 @@
minimal, minimal,
nodes, nodes,
... ...
}: let }:
domainOf = hostName: let let
domains = { domainOf =
adguardhome = "adguardhome"; hostName:
forgejo = "forge"; let
immich = "immich"; domains = {
nextcloud = "nc"; adguardhome = "adguardhome";
ollama = "ai"; forgejo = "forge";
paperless = "ppl"; immich = "immich";
ttrss = "rss"; nextcloud = "nc";
vaultwarden = "pw"; ollama = "ai";
yourspotify = "sptfy"; paperless = "ppl";
apispotify = "apisptfy"; ttrss = "rss";
kanidm = "auth"; vaultwarden = "pw";
oauth2-proxy = "oauth2"; yourspotify = "sptfy";
netbird = "netbird"; apispotify = "apisptfy";
actual = "actual"; kanidm = "auth";
firefly = "money"; oauth2-proxy = "oauth2";
homebox = "homebox"; netbird = "netbird";
octoprint = "print"; actual = "actual";
pr-tracker = "tracker"; firefly = "money";
}; homebox = "homebox";
in "${domains.${hostName}}.${config.secrets.secrets.global.domains.web}"; octoprint = "print";
pr-tracker = "tracker";
};
in
"${domains.${hostName}}.${config.secrets.secrets.global.domains.web}";
# TODO hard coded elisabeth nicht so schön # TODO hard coded elisabeth nicht so schön
ipOf = hostName: ipOf =
if nodes ? ${hostName} hostName:
then nodes.${hostName}.config.wireguard.elisabeth.ipv4 if nodes ? ${hostName} then
else nodes."elisabeth-${hostName}".config.wireguard.elisabeth.ipv4; nodes.${hostName}.config.wireguard.elisabeth.ipv4
in { else
services.nginx = let nodes."elisabeth-${hostName}".config.wireguard.elisabeth.ipv4;
blockOf = hostName: { in
virtualHostExtraConfig ? "", {
maxBodySize ? "500M", services.nginx =
port ? 3000, let
upstream ? hostName, blockOf =
protocol ? "http", hostName:
}: {
upstreams.${hostName} = {
servers."${ipOf upstream}:${toString port}" = {};
extraConfig = ''
zone ${hostName} 64k ;
keepalive 5 ;
'';
};
virtualHosts.${domainOf hostName} = {
forceSSL = true;
useACMEHost = "web";
locations."/" = {
proxyPass = "${protocol}://${hostName}";
proxyWebsockets = true;
X-Frame-Options = "SAMEORIGIN";
};
extraConfig =
''
client_max_body_size ${maxBodySize} ;
''
+ virtualHostExtraConfig;
};
};
proxyProtect = hostName: cfg: allowedGroup:
lib.mkMerge [
(blockOf hostName cfg)
{ {
virtualHosts.${domainOf hostName} = { virtualHostExtraConfig ? "",
locations."/".extraConfig = '' maxBodySize ? "500M",
auth_request /oauth2/auth; port ? 3000,
error_page 401 = /oauth2/sign_in; upstream ? hostName,
protocol ? "http",
# pass information via X-User and X-Email headers to backend, }:
# requires running with --set-xauthrequest flag {
auth_request_set $user $upstream_http_x_auth_request_preferred_username; upstreams.${hostName} = {
# Set the email to our own domain in case user change their mail servers."${ipOf upstream}:${toString port}" = { };
auth_request_set $email "''${upstream_http_x_auth_request_preferred_username}@${config.secrets.secrets.global.domains.web}"; extraConfig = ''
proxy_set_header X-User $user; zone ${hostName} 64k ;
proxy_set_header X-Email $email; keepalive 5 ;
# if you enabled --cookie-refresh, this is needed for it to work with auth_request
auth_request_set $auth_cookie $upstream_http_set_cookie;
add_header Set-Cookie $auth_cookie;
''; '';
locations."/oauth2/" = {
proxyPass = "http://oauth2-proxy";
extraConfig = ''
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri;
'';
};
locations."= /oauth2/auth" = {
proxyPass = "http://oauth2-proxy/oauth2/auth" + lib.optionalString allowedGroup "?allowed_groups=${hostName}_access";
extraConfig = ''
internal;
proxy_set_header X-Scheme $scheme;
# nginx auth_request includes headers but not body
proxy_set_header Content-Length "";
proxy_pass_request_body off;
'';
};
}; };
} virtualHosts.${domainOf hostName} = {
]; forceSSL = true;
in useACMEHost = "web";
locations."/" = {
proxyPass = "${protocol}://${hostName}";
proxyWebsockets = true;
X-Frame-Options = "SAMEORIGIN";
};
extraConfig =
''
client_max_body_size ${maxBodySize} ;
''
+ virtualHostExtraConfig;
};
};
proxyProtect =
hostName: cfg: allowedGroup:
lib.mkMerge [
(blockOf hostName cfg)
{
virtualHosts.${domainOf hostName} = {
locations."/".extraConfig = ''
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
# pass information via X-User and X-Email headers to backend,
# requires running with --set-xauthrequest flag
auth_request_set $user $upstream_http_x_auth_request_preferred_username;
# Set the email to our own domain in case user change their mail
auth_request_set $email "''${upstream_http_x_auth_request_preferred_username}@${config.secrets.secrets.global.domains.web}";
proxy_set_header X-User $user;
proxy_set_header X-Email $email;
# if you enabled --cookie-refresh, this is needed for it to work with auth_request
auth_request_set $auth_cookie $upstream_http_set_cookie;
add_header Set-Cookie $auth_cookie;
'';
locations."/oauth2/" = {
proxyPass = "http://oauth2-proxy";
extraConfig = ''
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri;
'';
};
locations."= /oauth2/auth" = {
proxyPass =
"http://oauth2-proxy/oauth2/auth"
+ lib.optionalString allowedGroup "?allowed_groups=${hostName}_access";
extraConfig = ''
internal;
proxy_set_header X-Scheme $scheme;
# nginx auth_request includes headers but not body
proxy_set_header Content-Length "";
proxy_pass_request_body off;
'';
};
};
}
];
in
lib.mkMerge [ lib.mkMerge [
{ {
enable = true; enable = true;
recommendedSetup = true; recommendedSetup = true;
upstreams.netbird = { upstreams.netbird = {
servers."${ipOf "netbird"}:80" = {}; servers."${ipOf "netbird"}:80" = { };
extraConfig = '' extraConfig = ''
zone netbird 64k ; zone netbird 64k ;
keepalive 5 ; keepalive 5 ;
''; '';
}; };
upstreams.netbird-mgmt = { upstreams.netbird-mgmt = {
servers."${ipOf "netbird"}:3000" = {}; servers."${ipOf "netbird"}:3000" = { };
extraConfig = '' extraConfig = ''
zone netbird 64k ; zone netbird 64k ;
keepalive 5 ; keepalive 5 ;
@ -159,16 +173,16 @@ in {
''; '';
}; };
} }
(blockOf "vaultwarden" {maxBodySize = "1G";}) (blockOf "vaultwarden" { maxBodySize = "1G"; })
(blockOf "forgejo" {maxBodySize = "1G";}) (blockOf "forgejo" { maxBodySize = "1G"; })
(blockOf "immich" {maxBodySize = "5G";}) (blockOf "immich" { maxBodySize = "5G"; })
(proxyProtect "adguardhome" {} true) (proxyProtect "adguardhome" { } true)
(proxyProtect "oauth2-proxy" {} false) (proxyProtect "oauth2-proxy" { } false)
(blockOf "paperless" {maxBodySize = "5G";}) (blockOf "paperless" { maxBodySize = "5G"; })
(proxyProtect "ttrss" {port = 80;} true) (proxyProtect "ttrss" { port = 80; } true)
(blockOf "yourspotify" {port = 80;}) (blockOf "yourspotify" { port = 80; })
#(blockOf "homebox" {}) #(blockOf "homebox" {})
(blockOf "pr-tracker" {}) (blockOf "pr-tracker" { })
{ {
virtualHosts.${domainOf "pr-tracker"} = { virtualHosts.${domainOf "pr-tracker"} = {
locations."/update" = { locations."/update" = {
@ -176,9 +190,9 @@ in {
}; };
}; };
} }
(proxyProtect "ollama" {} true) (proxyProtect "ollama" { } true)
(proxyProtect "octoprint" {} true) (proxyProtect "octoprint" { } true)
(proxyProtect "firefly" {port = 80;} true) (proxyProtect "firefly" { port = 80; } true)
(blockOf "apispotify" { (blockOf "apispotify" {
port = 3000; port = 3000;
upstream = "yourspotify"; upstream = "yourspotify";
@ -187,71 +201,76 @@ in {
maxBodySize = "5G"; maxBodySize = "5G";
port = 80; port = 80;
}) })
(blockOf "kanidm" (blockOf "kanidm" {
{ protocol = "https";
protocol = "https"; virtualHostExtraConfig = ''
virtualHostExtraConfig = '' proxy_ssl_verify off ;
proxy_ssl_verify off ; '';
''; })
})
]; ];
guests = let guests =
mkGuest = guestName: { let
enablePanzer ? false, mkGuest =
enableRenaultFT ? false, guestName:
enableBunker ? false,
enableSharedPaperless ? false,
...
}: {
autostart = true;
zfs."/state" = {
pool = "rpool";
dataset = "local/guests/${guestName}";
};
zfs."/persist" = {
pool = "rpool";
dataset = "safe/guests/${guestName}";
};
zfs."/panzer" = lib.mkIf enablePanzer {
pool = "panzer";
dataset = "safe/guests/${guestName}";
};
zfs."/renaultft" = lib.mkIf enableRenaultFT {
pool = "renaultft";
dataset = "safe/guests/${guestName}";
};
# kinda not necesarry should be removed on next reimaging
zfs."/bunker" = lib.mkIf enableBunker {
pool = "panzer";
dataset = "bunker/guests/${guestName}";
};
zfs."/paperless" = lib.mkIf enableSharedPaperless {
pool = "panzer";
dataset = "bunker/shared/paperless";
};
modules = [
../../config/basic
../../config/services/${guestName}.nix
{ {
node.secretsDir = config.node.secretsDir + "/${guestName}"; enablePanzer ? false,
networking.nftables.firewall.zones.untrusted.interfaces = [config.guests.${guestName}.networking.mainLinkName]; enableRenaultFT ? false,
systemd.network.networks."10-${config.guests.${guestName}.networking.mainLinkName}" = { enableBunker ? false,
DHCP = lib.mkForce "no"; enableSharedPaperless ? false,
address = [ ...
(lib.net.cidr.hostCidr config.secrets.secrets.global.net.ips."${config.guests.${guestName}.nodeName}" config.secrets.secrets.global.net.privateSubnetv4) }:
(lib.net.cidr.hostCidr config.secrets.secrets.global.net.ips."${config.guests.${guestName}.nodeName}" config.secrets.secrets.global.net.privateSubnetv6) {
]; autostart = true;
gateway = [(lib.net.cidr.host 1 config.secrets.secrets.global.net.privateSubnetv4)]; zfs."/state" = {
pool = "rpool";
dataset = "local/guests/${guestName}";
}; };
} zfs."/persist" = {
]; pool = "rpool";
}; dataset = "safe/guests/${guestName}";
};
zfs."/panzer" = lib.mkIf enablePanzer {
pool = "panzer";
dataset = "safe/guests/${guestName}";
};
zfs."/renaultft" = lib.mkIf enableRenaultFT {
pool = "renaultft";
dataset = "safe/guests/${guestName}";
};
# kinda not necesarry should be removed on next reimaging
zfs."/bunker" = lib.mkIf enableBunker {
pool = "panzer";
dataset = "bunker/guests/${guestName}";
};
zfs."/paperless" = lib.mkIf enableSharedPaperless {
pool = "panzer";
dataset = "bunker/shared/paperless";
};
modules = [
../../config/basic
../../config/services/${guestName}.nix
{
node.secretsDir = config.node.secretsDir + "/${guestName}";
networking.nftables.firewall.zones.untrusted.interfaces = [
config.guests.${guestName}.networking.mainLinkName
];
systemd.network.networks."10-${config.guests.${guestName}.networking.mainLinkName}" = {
DHCP = lib.mkForce "no";
address = [
(lib.net.cidr.hostCidr config.secrets.secrets.global.net.ips."${config.guests.${guestName}.nodeName
}" config.secrets.secrets.global.net.privateSubnetv4)
(lib.net.cidr.hostCidr config.secrets.secrets.global.net.ips."${config.guests.${guestName}.nodeName
}" config.secrets.secrets.global.net.privateSubnetv6)
];
gateway = [ (lib.net.cidr.host 1 config.secrets.secrets.global.net.privateSubnetv4) ];
};
}
];
};
mkMicrovm = guestName: cfg: { mkMicrovm = guestName: cfg: {
${guestName} = ${guestName} = mkGuest guestName cfg // {
mkGuest guestName cfg
// {
backend = "microvm"; backend = "microvm";
microvm = { microvm = {
system = "x86_64-linux"; system = "x86_64-linux";
@ -264,46 +283,42 @@ in {
inherit inputs minimal stateVersion; inherit inputs minimal stateVersion;
}; };
}; };
}; };
mkContainer = guestName: cfg: { mkContainer = guestName: cfg: {
${guestName} = ${guestName} = mkGuest guestName cfg // {
mkGuest guestName cfg
// {
backend = "container"; backend = "container";
container.macvlan = "lan"; container.macvlan = "lan";
extraSpecialArgs = { extraSpecialArgs = {
inherit lib nodes inputs minimal stateVersion; inherit
lib
nodes
inputs
minimal
stateVersion
;
}; };
}; };
}; };
in in
{} { }
// mkContainer "adguardhome" {} // mkContainer "adguardhome" { }
// mkContainer "oauth2-proxy" {} // mkContainer "oauth2-proxy" { }
// mkContainer "vaultwarden" {} // mkContainer "vaultwarden" { }
// mkContainer "ddclient" {} // mkContainer "ddclient" { }
// mkContainer "ollama" {} // mkContainer "ollama" { }
// mkContainer "murmur" {} // mkContainer "murmur" { }
#// mkContainer "homebox" {} #// mkContainer "homebox" {}
// mkContainer "pr-tracker" {} // mkContainer "pr-tracker" { }
// mkContainer "ttrss" {} // mkContainer "ttrss" { }
// mkContainer "firefly" {} // mkContainer "firefly" { }
// mkContainer "yourspotify" {} // mkContainer "yourspotify" { }
// mkContainer "netbird" {} // mkContainer "netbird" { }
// mkContainer "kanidm" {} // mkContainer "kanidm" { }
// mkContainer "nextcloud" { // mkContainer "nextcloud" { enablePanzer = true; }
enablePanzer = true; // mkContainer "paperless" { enableSharedPaperless = true; }
} // mkContainer "forgejo" { enablePanzer = true; }
// mkContainer "paperless" { // mkMicrovm "immich" { enablePanzer = true; }
enableSharedPaperless = true;
}
// mkContainer "forgejo" {
enablePanzer = true;
}
// mkMicrovm "immich" {
enablePanzer = true;
}
// mkContainer "samba" { // mkContainer "samba" {
enablePanzer = true; enablePanzer = true;
enableRenaultFT = true; enableRenaultFT = true;

View file

@ -1,15 +1,16 @@
{ config, lib, ... }:
{ {
config,
lib,
...
}: {
networking = { networking = {
inherit (config.secrets.secrets.local.networking) hostId; inherit (config.secrets.secrets.local.networking) hostId;
}; };
systemd.network.networks = { systemd.network.networks = {
"10-lan01" = { "10-lan01" = {
address = [(lib.net.cidr.hostCidr config.secrets.secrets.global.net.ips.${config.node.name} config.secrets.secrets.global.net.privateSubnetv4)]; address = [
gateway = [(lib.net.cidr.host 1 config.secrets.secrets.global.net.privateSubnetv4)]; (lib.net.cidr.hostCidr config.secrets.secrets.global.net.ips.${config.node.name}
config.secrets.secrets.global.net.privateSubnetv4
)
];
gateway = [ (lib.net.cidr.host 1 config.secrets.secrets.global.net.privateSubnetv4) ];
#matchConfig.MACAddress = config.secrets.secrets.local.networking.interfaces.lan01.mac; #matchConfig.MACAddress = config.secrets.secrets.local.networking.interfaces.lan01.mac;
matchConfig.Name = "lan"; matchConfig.Name = "lan";
dhcpV6Config.UseDNS = false; dhcpV6Config.UseDNS = false;
@ -33,8 +34,12 @@
networks = { networks = {
# redo the network cause the livesystem has macvlans # redo the network cause the livesystem has macvlans
"10-lan01" = { "10-lan01" = {
address = [(lib.net.cidr.hostCidr config.secrets.secrets.global.net.ips.${config.node.name} config.secrets.secrets.global.net.privateSubnetv4)]; address = [
gateway = [(lib.net.cidr.host 1 config.secrets.secrets.global.net.privateSubnetv4)]; (lib.net.cidr.hostCidr config.secrets.secrets.global.net.ips.${config.node.name}
config.secrets.secrets.global.net.privateSubnetv4
)
];
gateway = [ (lib.net.cidr.host 1 config.secrets.secrets.global.net.privateSubnetv4) ];
matchConfig.MACAddress = config.secrets.secrets.local.networking.interfaces.lan01.mac; matchConfig.MACAddress = config.secrets.secrets.local.networking.interfaces.lan01.mac;
dhcpV6Config.UseDNS = false; dhcpV6Config.UseDNS = false;
dhcpV4Config.UseDNS = false; dhcpV4Config.UseDNS = false;
@ -46,11 +51,16 @@
}; };
}; };
}; };
networking.nftables.firewall.zones.untrusted.interfaces = ["lan"]; networking.nftables.firewall.zones.untrusted.interfaces = [ "lan" ];
wireguard.elisabeth.server = { wireguard.elisabeth.server = {
host = lib.net.cidr.host config.secrets.secrets.global.net.ips.${config.node.name} config.secrets.secrets.global.net.privateSubnetv4; host =
reservedAddresses = ["10.42.0.0/20" "fd00:1764::/112"]; lib.net.cidr.host config.secrets.secrets.global.net.ips.${config.node.name}
config.secrets.secrets.global.net.privateSubnetv4;
reservedAddresses = [
"10.42.0.0/20"
"fd00:1764::/112"
];
openFirewall = true; openFirewall = true;
}; };
# To be able to ping containers from the host, it is necessary # To be able to ping containers from the host, it is necessary
@ -71,7 +81,7 @@
email = config.secrets.secrets.global.devEmail; email = config.secrets.secrets.global.devEmail;
dnsProvider = "cloudflare"; dnsProvider = "cloudflare";
dnsPropagationCheck = true; dnsPropagationCheck = true;
reloadServices = ["nginx"]; reloadServices = [ "nginx" ];
credentialFiles = { credentialFiles = {
"CF_DNS_API_TOKEN_FILE" = config.age.secrets.cloudflare_token_acme.path; "CF_DNS_API_TOKEN_FILE" = config.age.secrets.cloudflare_token_acme.path;
"CF_ZONE_API_TOKEN_FILE" = config.age.secrets.cloudflare_token_acme.path; "CF_ZONE_API_TOKEN_FILE" = config.age.secrets.cloudflare_token_acme.path;
@ -80,9 +90,9 @@
}; };
security.acme.certs.web = { security.acme.certs.web = {
domain = config.secrets.secrets.global.domains.web; domain = config.secrets.secrets.global.domains.web;
extraDomainNames = ["*.${config.secrets.secrets.global.domains.web}"]; extraDomainNames = [ "*.${config.secrets.secrets.global.domains.web}" ];
}; };
users.groups.acme.members = ["nginx"]; users.groups.acme.members = [ "nginx" ];
environment.persistence."/state".directories = [ environment.persistence."/state".directories = [
{ {
directory = "/var/lib/acme"; directory = "/var/lib/acme";

View file

@ -9,6 +9,11 @@
./fs.nix ./fs.nix
]; ];
boot.mode = "bios"; boot.mode = "bios";
boot.initrd.availableKernelModules = ["virtio_pci" "virtio_net" "virtio_scsi" "virtio_blk"]; boot.initrd.availableKernelModules = [
"virtio_pci"
"virtio_net"
"virtio_scsi"
"virtio_blk"
];
nixpkgs.hostPlatform = "x86_64-linux"; nixpkgs.hostPlatform = "x86_64-linux";
} }

View file

@ -1,8 +1,5 @@
{ config, lib, ... }:
{ {
config,
lib,
...
}: {
disko.devices = { disko.devices = {
disk = { disk = {
drive = rec { drive = rec {
@ -11,9 +8,15 @@
content = with lib.disko.gpt; { content = with lib.disko.gpt; {
type = "gpt"; type = "gpt";
partitions = { partitions = {
grub = partGrub // {device = "${device}-part1";}; grub = partGrub // {
bios = (partEfi "512MiB") // {device = "${device}-part2";}; device = "${device}-part1";
rpool = (partLuksZfs "rpool" "rpool" "100%") // {device = "${device}-part3";}; };
bios = (partEfi "512MiB") // {
device = "${device}-part2";
};
rpool = (partLuksZfs "rpool" "rpool" "100%") // {
device = "${device}-part3";
};
#(lib.attrsets.recursiveUpdate (partLuksZfs "rpool" "rpool" "17GiB" "100%") {content.extraFormatArgs = ["--pbkdf pbkdf2"];}) #(lib.attrsets.recursiveUpdate (partLuksZfs "rpool" "rpool" "17GiB" "100%") {content.extraFormatArgs = ["--pbkdf pbkdf2"];})
}; };
}; };
@ -21,13 +24,11 @@
}; };
zpool = with lib.disko.zfs; { zpool = with lib.disko.zfs; {
rpool = mkZpool {datasets = impermanenceZfsDatasets;}; rpool = mkZpool { datasets = impermanenceZfsDatasets; };
}; };
}; };
fileSystems."/state".neededForBoot = true; fileSystems."/state".neededForBoot = true;
fileSystems."/persist".neededForBoot = true; fileSystems."/persist".neededForBoot = true;
boot.loader.grub.devices = [ boot.loader.grub.devices = [ "/dev/disk/by-id/${config.secrets.secrets.local.disko.drive}" ];
"/dev/disk/by-id/${config.secrets.secrets.local.disko.drive}"
];
} }

View file

@ -1,36 +1,37 @@
{ config, lib, ... }:
{ {
config,
lib,
...
}: {
networking.hostId = config.secrets.secrets.local.networking.hostId; networking.hostId = config.secrets.secrets.local.networking.hostId;
networking.domain = config.secrets.secrets.global.domains.mail_public; networking.domain = config.secrets.secrets.global.domains.mail_public;
boot.initrd.systemd.network = { boot.initrd.systemd.network = {
enable = true; enable = true;
networks = {inherit (config.systemd.network.networks) "lan01";}; networks = {
inherit (config.systemd.network.networks) "lan01";
};
}; };
systemd.network.networks = { systemd.network.networks = {
"lan01" = let "lan01" =
icfg = config.secrets.secrets.local.networking.interfaces.lan01; let
in { icfg = config.secrets.secrets.local.networking.interfaces.lan01;
address = [ in
icfg.hostCidrv4 {
(lib.net.cidr.hostCidr 1 icfg.hostCidrv6) address = [
]; icfg.hostCidrv4
gateway = ["fe80::1"]; (lib.net.cidr.hostCidr 1 icfg.hostCidrv6)
routes = [ ];
{Destination = "172.31.1.1";} gateway = [ "fe80::1" ];
{ routes = [
Gateway = "172.31.1.1"; { Destination = "172.31.1.1"; }
GatewayOnLink = true; {
} Gateway = "172.31.1.1";
]; GatewayOnLink = true;
matchConfig.MACAddress = icfg.mac; }
networkConfig.IPv6PrivacyExtensions = "yes"; ];
linkConfig.RequiredForOnline = "routable"; matchConfig.MACAddress = icfg.mac;
}; networkConfig.IPv6PrivacyExtensions = "yes";
linkConfig.RequiredForOnline = "routable";
};
}; };
age.secrets.cloudflare_token_acme = { age.secrets.cloudflare_token_acme = {
rekeyFile = ./secrets/cloudflare_api_token.age; rekeyFile = ./secrets/cloudflare_api_token.age;
@ -43,25 +44,25 @@
email = config.secrets.secrets.global.devEmail; email = config.secrets.secrets.global.devEmail;
dnsProvider = "cloudflare"; dnsProvider = "cloudflare";
dnsPropagationCheck = true; dnsPropagationCheck = true;
reloadServices = ["nginx"]; reloadServices = [ "nginx" ];
credentialFiles = { credentialFiles = {
"CF_DNS_API_TOKEN_FILE" = config.age.secrets.cloudflare_token_acme.path; "CF_DNS_API_TOKEN_FILE" = config.age.secrets.cloudflare_token_acme.path;
"CF_ZONE_API_TOKEN_FILE" = config.age.secrets.cloudflare_token_acme.path; "CF_ZONE_API_TOKEN_FILE" = config.age.secrets.cloudflare_token_acme.path;
}; };
}; };
}; };
networking.nftables.firewall.zones.untrusted.interfaces = ["lan01"]; networking.nftables.firewall.zones.untrusted.interfaces = [ "lan01" ];
security.acme.certs = { security.acme.certs = {
mail_public = { mail_public = {
domain = config.secrets.secrets.global.domains.mail_public; domain = config.secrets.secrets.global.domains.mail_public;
extraDomainNames = ["*.${config.secrets.secrets.global.domains.mail_public}"]; extraDomainNames = [ "*.${config.secrets.secrets.global.domains.mail_public}" ];
}; };
mail_private = { mail_private = {
domain = config.secrets.secrets.global.domains.mail_private; domain = config.secrets.secrets.global.domains.mail_private;
extraDomainNames = ["*.${config.secrets.secrets.global.domains.mail_private}"]; extraDomainNames = [ "*.${config.secrets.secrets.global.domains.mail_private}" ];
}; };
}; };
users.groups.acme.members = ["maddy"]; users.groups.acme.members = [ "maddy" ];
environment.persistence."/state".directories = [ environment.persistence."/state".directories = [
{ {
directory = "/var/lib/acme"; directory = "/var/lib/acme";

View file

@ -1,8 +1,5 @@
{ inputs, lib, ... }:
{ {
inputs,
lib,
...
}: {
imports = [ imports = [
../../config/basic ../../config/basic
../../config/services/octoprint.nix ../../config/services/octoprint.nix

View file

@ -1,9 +1,10 @@
{lib, ...}: { { lib, ... }:
{
fileSystems = lib.mkForce { fileSystems = lib.mkForce {
"/" = { "/" = {
device = "/dev/disk/by-uuid/44444444-4444-4444-8888-888888888888"; device = "/dev/disk/by-uuid/44444444-4444-4444-8888-888888888888";
fsType = "ext4"; fsType = "ext4";
}; };
}; };
environment.persistence = lib.mkForce {}; environment.persistence = lib.mkForce { };
} }

View file

@ -1,4 +1,5 @@
{config, ...}: { { config, ... }:
{
networking = { networking = {
inherit (config.secrets.secrets.local.networking) hostId; inherit (config.secrets.secrets.local.networking) hostId;
wireless.iwd = { wireless.iwd = {

View file

@ -1,8 +1,5 @@
{ inputs, lib, ... }:
{ {
inputs,
lib,
...
}: {
imports = [ imports = [
inputs.nixos-hardware.nixosModules.common-cpu-intel inputs.nixos-hardware.nixosModules.common-cpu-intel
# for some reasons the cpu-intel includes the gpu as well # for some reasons the cpu-intel includes the gpu as well
@ -45,11 +42,12 @@
layout = "de"; layout = "de";
}; };
libinput = { libinput = {
touchpad = lib.mkForce { touchpad = lib.mkForce { accelSpeed = "0.5"; };
accelSpeed = "0.5";
};
}; };
}; };
nixpkgs.hostPlatform = "x86_64-linux"; nixpkgs.hostPlatform = "x86_64-linux";
nix.settings.system-features = ["kvm" "nixos-test"]; nix.settings.system-features = [
"kvm"
"nixos-test"
];
} }

View file

@ -1,8 +1,5 @@
{ config, lib, ... }:
{ {
config,
lib,
...
}: {
disko.devices = { disko.devices = {
disk = { disk = {
m2-ssd = rec { m2-ssd = rec {
@ -11,15 +8,21 @@
content = with lib.disko.gpt; { content = with lib.disko.gpt; {
type = "gpt"; type = "gpt";
partitions = { partitions = {
boot = (partEfi "1GiB") // {device = "${device}-part1";}; boot = (partEfi "1GiB") // {
swap = (partSwap "16GiB") // {device = "${device}-part2";}; device = "${device}-part1";
rpool = (partLuksZfs "rpool" "rpool" "100%") // {device = "${device}-part3";}; };
swap = (partSwap "16GiB") // {
device = "${device}-part2";
};
rpool = (partLuksZfs "rpool" "rpool" "100%") // {
device = "${device}-part3";
};
}; };
}; };
}; };
}; };
zpool = with lib.disko.zfs; { zpool = with lib.disko.zfs; {
rpool = mkZpool {datasets = impermanenceZfsDatasets;}; rpool = mkZpool { datasets = impermanenceZfsDatasets; };
}; };
}; };
fileSystems."/state".neededForBoot = true; fileSystems."/state".neededForBoot = true;

View file

@ -1,4 +1,5 @@
{config, ...}: { { config, ... }:
{
age.secrets.eduroam = { age.secrets.eduroam = {
rekeyFile = ./secrets/iwd/eduroam.8021x.age; rekeyFile = ./secrets/iwd/eduroam.8021x.age;
path = "/var/lib/iwd/eduroam.8021x"; path = "/var/lib/iwd/eduroam.8021x";
@ -10,7 +11,11 @@
devoloog-sae20.rekeyFile = ./secrets/iwd/devoloog-sae20.age; devoloog-sae20.rekeyFile = ./secrets/iwd/devoloog-sae20.age;
}; };
wireguard.samba-patrick.client.via = "elisabeth-samba"; wireguard.samba-patrick.client.via = "elisabeth-samba";
networking.nftables.firewall.zones.untrusted.interfaces = ["lan01" "lan02" "wlan01"]; networking.nftables.firewall.zones.untrusted.interfaces = [
"lan01"
"lan02"
"wlan01"
];
networking = { networking = {
inherit (config.secrets.secrets.local.networking) hostId; inherit (config.secrets.secrets.local.networking) hostId;
wireless.iwd = { wireless.iwd = {
@ -36,7 +41,7 @@
IPv6PrivacyExtensions = "yes"; IPv6PrivacyExtensions = "yes";
MulticastDNS = true; MulticastDNS = true;
}; };
dns = ["1.1.1.1"]; dns = [ "1.1.1.1" ];
dhcpV4Config.RouteMetric = 10; dhcpV4Config.RouteMetric = 10;
dhcpV6Config.RouteMetric = 10; dhcpV6Config.RouteMetric = 10;
}; };
@ -47,7 +52,7 @@
IPv6PrivacyExtensions = "yes"; IPv6PrivacyExtensions = "yes";
MulticastDNS = true; MulticastDNS = true;
}; };
dns = ["1.1.1.1"]; dns = [ "1.1.1.1" ];
dhcpV4Config.RouteMetric = 10; dhcpV4Config.RouteMetric = 10;
dhcpV6Config.RouteMetric = 10; dhcpV6Config.RouteMetric = 10;
}; };
@ -58,7 +63,7 @@
IPv6PrivacyExtensions = "yes"; IPv6PrivacyExtensions = "yes";
MulticastDNS = true; MulticastDNS = true;
}; };
dns = ["1.1.1.1"]; dns = [ "1.1.1.1" ];
dhcpV4Config.RouteMetric = 40; dhcpV4Config.RouteMetric = 40;
dhcpV6Config.RouteMetric = 40; dhcpV6Config.RouteMetric = 40;
}; };

View file

@ -1,3 +1 @@
inputs: [ inputs: [ (import ./misc.nix inputs) ]
(import ./misc.nix inputs)
]

View file

@ -1,11 +1,9 @@
_inputs: _self: super: let _inputs: _self: super:
let
writeText = text: (super.writeText (builtins.hashString "sha256" "${text}") "${text}"); writeText = text: (super.writeText (builtins.hashString "sha256" "${text}") "${text}");
in { in
lib = {
super.lib lib = super.lib // {
// { inherit writeText;
inherit };
writeText
;
};
} }

View file

@ -1,10 +1,6 @@
{ { lib, config, ... }:
lib, let
config, inherit (lib)
...
}: let
inherit
(lib)
mkEnableOption mkEnableOption
mkMerge mkMerge
attrNames attrNames
@ -18,7 +14,8 @@
mapAttrs' mapAttrs'
listToAttrs listToAttrs
; ;
in { in
{
home-manager.sharedModules = [ home-manager.sharedModules = [
{ {
options.images = { options.images = {
@ -39,26 +36,25 @@ in {
imports = [ imports = [
( (
{config, ...}: { { config, ... }:
{
age.secrets = mkMerge ( age.secrets = mkMerge (
flip map flip map (attrNames config.home-manager.users) (
(attrNames config.home-manager.users)
(
user: user:
mkIf config.home-manager.users.${user}.images.enable ( mkIf config.home-manager.users.${user}.images.enable (
listToAttrs (flip map (attrNames (filterAttrs (_: type: type == "regular") (builtins.readDir ../secrets/img))) listToAttrs (
( flip map (attrNames (filterAttrs (_: type: type == "regular") (builtins.readDir ../secrets/img)))
file: { (file: {
name = "images-${user}-${file}"; name = "images-${user}-${file}";
value = { value = {
name = removeSuffix ".age" file; name = removeSuffix ".age" file;
rekeyFile = ../secrets/img/${file}; rekeyFile = ../secrets/img/${file};
owner = user; owner = user;
group = user; group = user;
}; };
} })
))
) )
)
) )
); );
} }

View file

@ -1,10 +1,6 @@
{ { config, lib, ... }:
config, let
lib, inherit (lib)
...
}: let
inherit
(lib)
flip flip
mapAttrs mapAttrs
attrNames attrNames
@ -13,7 +9,8 @@
mkMerge mkMerge
isAttrs isAttrs
; ;
in { in
{
# Expose a home manager module for each user that allows extending # Expose a home manager module for each user that allows extending
# environment.persistence.${sourceDir}.users.${userName} simply by # environment.persistence.${sourceDir}.users.${userName} simply by
# specifying home.persistence.${sourceDir} in home manager. # specifying home.persistence.${sourceDir} in home manager.
@ -21,74 +18,63 @@ in {
{ {
options.home.persistence = mkOption { options.home.persistence = mkOption {
description = "Additional persistence config for the given source path"; description = "Additional persistence config for the given source path";
default = {}; default = { };
type = types.attrsOf (types.submodule { type = types.attrsOf (
options = { types.submodule {
files = mkOption { options = {
description = "Additional files to persist via NixOS impermanence."; files = mkOption {
type = types.listOf (types.either types.attrs types.str); description = "Additional files to persist via NixOS impermanence.";
default = []; type = types.listOf (types.either types.attrs types.str);
}; default = [ ];
};
directories = mkOption { directories = mkOption {
description = "Additional directories to persist via NixOS impermanence."; description = "Additional directories to persist via NixOS impermanence.";
type = types.listOf (types.either types.attrs types.str); type = types.listOf (types.either types.attrs types.str);
default = []; default = [ ];
};
}; };
}; }
}); );
}; };
} }
]; ];
# For each user that has a home-manager config, merge the locally defined # For each user that has a home-manager config, merge the locally defined
# persistence options that we defined above. # persistence options that we defined above.
imports = let imports =
mkUserFiles = map (x: let
{parentDirectory.mode = "700";} mkUserFiles = map (
// ( x: { parentDirectory.mode = "700"; } // (if isAttrs x then x else { file = x; })
if isAttrs x
then x
else {file = x;}
));
mkUserDirs = map (x:
{mode = "700";}
// (
if isAttrs x
then x
else {directory = x;}
));
in [
{
environment.persistence = mkMerge (
flip map
(attrNames config.home-manager.users)
(
user: let
hmUserCfg = config.home-manager.users.${user};
in
flip mapAttrs hmUserCfg.home.persistence
(_: sourceCfg: {
users.${user} = {
# This needs to be set for allo users with non
# standart home (not /home/<userName>
# due to nixpkgs it
# can't be deduced from homeDirectory
# as there will be infinite recursion
# If this setting is forgotten there
# are assertions in place warning you
home =
{
root = "/root";
}
.${user}
or "/home/${user}";
files = mkUserFiles sourceCfg.files;
directories = mkUserDirs sourceCfg.directories;
};
})
)
); );
} mkUserDirs = map (x: { mode = "700"; } // (if isAttrs x then x else { directory = x; }));
]; in
[
{
environment.persistence = mkMerge (
flip map (attrNames config.home-manager.users) (
user:
let
hmUserCfg = config.home-manager.users.${user};
in
flip mapAttrs hmUserCfg.home.persistence (
_: sourceCfg: {
users.${user} = {
# This needs to be set for allo users with non
# standart home (not /home/<userName>
# due to nixpkgs it
# can't be deduced from homeDirectory
# as there will be infinite recursion
# If this setting is forgotten there
# are assertions in place warning you
home = { root = "/root"; }.${user} or "/home/${user}";
files = mkUserFiles sourceCfg.files;
directories = mkUserDirs sourceCfg.directories;
};
}
)
)
);
}
];
} }

View file

@ -1,58 +1,59 @@
{ { lib, pkgs, ... }:
lib, let
pkgs, inherit (lib)
...
}: let
inherit
(lib)
types types
mkEnableOption mkEnableOption
mkPackageOption mkPackageOption
mkOption mkOption
mkIf mkIf
; ;
settingsFormat = pkgs.formats.json {}; settingsFormat = pkgs.formats.json { };
in { in
{
home-manager.sharedModules = [ home-manager.sharedModules = [
({config, ...}: let (
cfg = settingsFormat.generate "config.json" { { config, ... }:
streamdeck_ui_version = 2; let
state = config.programs.streamdeck-ui.settings; cfg = settingsFormat.generate "config.json" {
}; streamdeck_ui_version = 2;
preStart = pkgs.writeShellScript "streamdeck-setup-config" '' state = config.programs.streamdeck-ui.settings;
${pkgs.coreutils}/bin/cp "${cfg}" "$XDG_RUNTIME_DIR/streamdeck/config.json"
'';
in {
options.programs.streamdeck-ui = {
enable = mkEnableOption "streamdeck-ui";
package = mkPackageOption pkgs "streamdeck-ui" {};
settings = mkOption {
default = {};
type = types.submodule {freeformType = settingsFormat.type;};
description = "Configuration per streamdeck";
}; };
}; preStart = pkgs.writeShellScript "streamdeck-setup-config" ''
config = mkIf config.programs.streamdeck-ui.enable { ${pkgs.coreutils}/bin/cp "${cfg}" "$XDG_RUNTIME_DIR/streamdeck/config.json"
systemd.user = { '';
services = { in
streamdeck = { {
Unit = { options.programs.streamdeck-ui = {
Description = "start streamdeck-ui"; enable = mkEnableOption "streamdeck-ui";
# For some reason this depends on X or wayland running package = mkPackageOption pkgs "streamdeck-ui" { };
ConditionEnvironment = ["DISPLAY"]; settings = mkOption {
default = { };
type = types.submodule { freeformType = settingsFormat.type; };
description = "Configuration per streamdeck";
};
};
config = mkIf config.programs.streamdeck-ui.enable {
systemd.user = {
services = {
streamdeck = {
Unit = {
Description = "start streamdeck-ui";
# For some reason this depends on X or wayland running
ConditionEnvironment = [ "DISPLAY" ];
};
Service = {
Type = "exec";
ExecStart = "${pkgs.streamdeck-ui}/bin/streamdeck --no-ui";
ExecStartPre = preStart;
Environment = ''STREAMDECK_UI_CONFIG=%t/streamdeck/config.json'';
RuntimeDirectory = "streamdeck";
};
Install.WantedBy = [ "graphical-session.target" ];
}; };
Service = {
Type = "exec";
ExecStart = "${pkgs.streamdeck-ui}/bin/streamdeck --no-ui";
ExecStartPre = preStart;
Environment = ''STREAMDECK_UI_CONFIG=%t/streamdeck/config.json'';
RuntimeDirectory = "streamdeck";
};
Install.WantedBy = ["graphical-session.target"];
}; };
}; };
}; };
}; }
}) )
]; ];
} }

View file

@ -3,9 +3,9 @@
pkgs, pkgs,
config, config,
... ...
}: let }:
inherit let
(lib) inherit (lib)
types types
mkEnableOption mkEnableOption
mkPackageOption mkPackageOption
@ -15,13 +15,14 @@
cfg = config.services.actual; cfg = config.services.actual;
configFile = formatType.generate "config.json" cfg.settings; configFile = formatType.generate "config.json" cfg.settings;
formatType = pkgs.formats.json {}; formatType = pkgs.formats.json { };
in { in
{
options.services.actual = { options.services.actual = {
enable = mkEnableOption "actual, a privacy focused app for managing your finances"; enable = mkEnableOption "actual, a privacy focused app for managing your finances";
package = mkPackageOption pkgs "actual" {}; package = mkPackageOption pkgs "actual" { };
settings = mkOption { settings = mkOption {
default = {}; default = { };
type = types.submodule { type = types.submodule {
freeformType = formatType.type; freeformType = formatType.type;
config = { config = {
@ -33,7 +34,7 @@ in {
}; };
}; };
config.systemd.services.actual = { config.systemd.services.actual = {
after = ["network.target"]; after = [ "network.target" ];
environment.ACTUAL_CONFIG_PATH = configFile; environment.ACTUAL_CONFIG_PATH = configFile;
serviceConfig = { serviceConfig = {
ExecStartPre = "${pkgs.coreutils}/bin/ln -sf ${cfg.package}/migrations /var/lib/actual/"; ExecStartPre = "${pkgs.coreutils}/bin/ln -sf ${cfg.package}/migrations /var/lib/actual/";
@ -78,6 +79,6 @@ in {
]; ];
UMask = "0077"; UMask = "0077";
}; };
wantedBy = ["multi-user.target"]; wantedBy = [ "multi-user.target" ];
}; };
} }

View file

@ -1,10 +1,6 @@
{ { lib, config, ... }:
lib, let
config, inherit (lib)
...
}: let
inherit
(lib)
concatLists concatLists
flip flip
mapAttrsToList mapAttrsToList
@ -16,65 +12,86 @@
; ;
cfg = config.users.deterministicIds; cfg = config.users.deterministicIds;
in { in
{
options = { options = {
users.deterministicIds = mkOption { users.deterministicIds = mkOption {
default = {}; default = { };
description = mdDoc '' description = mdDoc ''
Maps a user or group name to its expected uid/gid values. If a user/group is Maps a user or group name to its expected uid/gid values. If a user/group is
used on the system without specifying a uid/gid, this module will assign the used on the system without specifying a uid/gid, this module will assign the
corresponding ids defined here, or show an error if the definition is missing. corresponding ids defined here, or show an error if the definition is missing.
''; '';
type = types.attrsOf (types.submodule { type = types.attrsOf (
options = { types.submodule {
uid = mkOption { options = {
type = types.nullOr types.int; uid = mkOption {
default = null; type = types.nullOr types.int;
description = mdDoc "The uid to assign if it is missing in `users.users.<name>`."; default = null;
description = mdDoc "The uid to assign if it is missing in `users.users.<name>`.";
};
gid = mkOption {
type = types.nullOr types.int;
default = null;
description = mdDoc "The gid to assign if it is missing in `users.groups.<name>`.";
};
}; };
gid = mkOption { }
type = types.nullOr types.int; );
default = null;
description = mdDoc "The gid to assign if it is missing in `users.groups.<name>`.";
};
};
});
}; };
users.users = mkOption { users.users = mkOption {
type = types.attrsOf (types.submodule ({name, ...}: { type = types.attrsOf (
config.uid = let types.submodule (
deterministicUid = cfg.${name}.uid or null; { name, ... }:
in {
mkIf (deterministicUid != null) (mkDefault deterministicUid); config.uid =
})); let
deterministicUid = cfg.${name}.uid or null;
in
mkIf (deterministicUid != null) (mkDefault deterministicUid);
}
)
);
}; };
users.groups = mkOption { users.groups = mkOption {
type = types.attrsOf (types.submodule ({name, ...}: { type = types.attrsOf (
config.gid = let types.submodule (
deterministicGid = cfg.${name}.gid or null; { name, ... }:
in {
mkIf (deterministicGid != null) (mkDefault deterministicGid); config.gid =
})); let
deterministicGid = cfg.${name}.gid or null;
in
mkIf (deterministicGid != null) (mkDefault deterministicGid);
}
)
);
}; };
}; };
config = { config = {
assertions = assertions =
concatLists (flip mapAttrsToList config.users.users (name: user: [ concatLists (
{ flip mapAttrsToList config.users.users (
assertion = user.uid != null; name: user: [
message = "non-deterministic uid detected for '${name}', please assign one via `users.deterministicIds`"; {
assertion = user.uid != null;
message = "non-deterministic uid detected for '${name}', please assign one via `users.deterministicIds`";
}
{
assertion = !user.autoSubUidGidRange;
message = "non-deterministic subUids/subGids detected for: ${name}";
}
]
)
)
++ flip mapAttrsToList config.users.groups (
name: group: {
assertion = group.gid != null;
message = "non-deterministic gid detected for '${name}', please assign one via `users.deterministicIds`";
} }
{ );
assertion = !user.autoSubUidGidRange;
message = "non-deterministic subUids/subGids detected for: ${name}";
}
]))
++ flip mapAttrsToList config.users.groups (name: group: {
assertion = group.gid != null;
message = "non-deterministic gid detected for '${name}', please assign one via `users.deterministicIds`";
});
}; };
} }

View file

@ -3,9 +3,9 @@
lib, lib,
nodes, nodes,
... ...
}: let }:
inherit let
(lib) inherit (lib)
attrNames attrNames
concatMap concatMap
concatStringsSep concatStringsSep
@ -21,16 +21,20 @@
; ;
nodeName = config.node.name; nodeName = config.node.name;
mkForwardedOption = path: mkForwardedOption =
path:
mkOption { mkOption {
type = mkOptionType { type = mkOptionType {
name = "Same type that the receiving option `${concatStringsSep "." path}` normally accepts."; name = "Same type that the receiving option `${concatStringsSep "." path}` normally accepts.";
merge = _loc: defs: merge =
builtins.filter _loc: defs:
(x: builtins.isAttrs x -> ((x._type or "") != "__distributed_config_empty")) builtins.filter (x: builtins.isAttrs x -> ((x._type or "") != "__distributed_config_empty")) (
(map (x: x.value) defs); map (x: x.value) defs
);
};
default = {
_type = "__distributed_config_empty";
}; };
default = {_type = "__distributed_config_empty";};
description = '' description = ''
Anything specified here will be forwarded to `${concatStringsSep "." path}` Anything specified here will be forwarded to `${concatStringsSep "." path}`
on the given node. Forwarding happens as-is to the raw values, on the given node. Forwarding happens as-is to the raw values,
@ -39,28 +43,39 @@
}; };
forwardedOptions = [ forwardedOptions = [
["age" "secrets"] [
["services" "maddy" "ensureCredentials"] "age"
"secrets"
]
[
"services"
"maddy"
"ensureCredentials"
]
]; ];
attrsForEachOption = f: (foldl' (acc: path: recursiveUpdate acc (setAttrByPath path (f path))) {} forwardedOptions); attrsForEachOption =
in { f: (foldl' (acc: path: recursiveUpdate acc (setAttrByPath path (f path))) { } forwardedOptions);
in
{
options.nodes = mkOption { options.nodes = mkOption {
description = "Options forwareded to the given node."; description = "Options forwareded to the given node.";
default = {}; default = { };
type = types.attrsOf (types.submodule { type = types.attrsOf (types.submodule { options = attrsForEachOption mkForwardedOption; });
options = attrsForEachOption mkForwardedOption;
});
}; };
config = let config =
mergeConfigFromOthers = let let
getConfig = path: otherNode: let mergeConfigFromOthers =
cfg = nodes.${otherNode}.config.nodes.${nodeName} or null; let
in getConfig =
optionals (cfg != null) (getAttrFromPath path cfg); path: otherNode:
let
cfg = nodes.${otherNode}.config.nodes.${nodeName} or null;
in
optionals (cfg != null) (getAttrFromPath path cfg);
in
path: mkMerge (concatMap (getConfig path) (attrNames nodes));
in in
path: mkMerge (concatMap (getConfig path) (attrNames nodes));
in
attrsForEachOption mergeConfigFromOthers; attrsForEachOption mergeConfigFromOthers;
} }

View file

@ -3,20 +3,21 @@
config, config,
pkgs, pkgs,
... ...
}: let }:
let
cfg = config.services.homebox; cfg = config.services.homebox;
inherit inherit (lib)
(lib)
mkEnableOption mkEnableOption
mkPackageOption mkPackageOption
mkDefault mkDefault
types types
mkIf mkIf
; ;
in { in
{
options.services.homebox = { options.services.homebox = {
enable = mkEnableOption "homebox"; enable = mkEnableOption "homebox";
package = mkPackageOption pkgs "homebox" {}; package = mkPackageOption pkgs "homebox" { };
settings = lib.mkOption { settings = lib.mkOption {
type = types.attrsOf types.str; type = types.attrsOf types.str;
defaultText = '' defaultText = ''
@ -39,7 +40,7 @@ in {
HBOX_MODE = mkDefault "production"; HBOX_MODE = mkDefault "production";
}; };
systemd.services.homebox = { systemd.services.homebox = {
after = ["network.target"]; after = [ "network.target" ];
environment = cfg.settings; environment = cfg.settings;
serviceConfig = { serviceConfig = {
User = "homebox"; User = "homebox";
@ -86,8 +87,8 @@ in {
# System Call Filtering # System Call Filtering
UMask = "0077"; UMask = "0077";
}; };
wantedBy = ["multi-user.target"]; wantedBy = [ "multi-user.target" ];
}; };
}; };
meta.maintainers = with lib.maintainers; [patrickdag]; meta.maintainers = with lib.maintainers; [ patrickdag ];
} }

View file

@ -3,103 +3,141 @@
pkgs, pkgs,
config, config,
... ...
}: { }:
options.networking.wireless.iwd = let {
inherit options.networking.wireless.iwd =
(lib) let
mkOption inherit (lib)
literalExample mkOption
types literalExample
hasAttrByPath types
; hasAttrByPath
in { ;
networks = mkOption { in
default = {}; {
example = literalExample '' networks = mkOption {
{ "karlsruhe.freifunk.net" = {}; default = { };
}; example = literalExample ''
''; { "karlsruhe.freifunk.net" = {};
description = ''
Declarative configuration of wifi networks for
<citerefentry><refentrytitle>iwd</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
All networks will be stored in
<literal>/var/lib/iwd/&lt;name&gt;.&lt;type&gt;</literal>.
Since each network is stored in its own file, declarative networks can be used in an
environment with imperatively added networks via
<citerefentry><refentrytitle>iwctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
'';
type = types.attrsOf (types.submodule ({config, ...}: {
config.kind =
if (hasAttrByPath ["Security" "Passphrase"] config.settings)
then "psk"
else if !(hasAttrByPath ["Security"] config.settings)
then "open"
else "8021x";
options = {
kind = mkOption {
type = types.enum ["open" "psk" "8021x"];
description = "The type of network. This will determine the file ending. The module will try to determine this automatically so this should only be set when the heuristics fail.";
}; };
settings = mkOption {
type = with types; (attrsOf (attrsOf (oneOf [str path])));
description = ''
Contents of the iwd config file for this network
The lowest level values should be files, that will be read into the config files
'';
default = {};
};
};
}));
};
};
config = let
inherit
(lib)
mkIf
flip
mapAttrsToList
concatStringsSep
;
cfg = config.networking.wireless.iwd;
encoder = pkgs.writeScriptBin "encoder" ''
#! ${pkgs.runtimeShell} -e
# Extract file-ext from network names
ext="$(sed -re 's/.*\.(8021x|open|psk)$/\1/' <<< "$*")"
to_enc="$(sed -re "s/(.*)\.$ext/\1/g" <<< "$*")"
# Encode ssid (excluding file-extensio) as base64 if needed
[[ "$to_enc" =~ ^[[:alnum:]]+$ ]] && { echo "$to_enc.$ext"; exit 0; }
echo "=$(printf "$to_enc" | ${pkgs.unixtools.xxd}/bin/xxd -pu).$ext"
'';
in
mkIf cfg.enable {
systemd.services.iwd = mkIf (cfg.networks != {}) {
path = [encoder];
preStart = let
dataDir = "/var/lib/iwd";
in ''
# Create config files for declaratively defined networks in the NixOS config.
${concatStringsSep "\n" (flip mapAttrsToList cfg.networks (network: config: ''
filename=${dataDir}/"$(encoder '${network}.${config.kind}')"
touch "$filename"
cat >$filename <<EOF
${concatStringsSep "\n" (flip mapAttrsToList config.settings (toplevel: config: ''
[${toplevel}]
${concatStringsSep "\n" (flip mapAttrsToList config (name: value: ''
${name}=$(<${value})
''))}
''))}
EOF
''))}
''; '';
description = ''
Declarative configuration of wifi networks for
<citerefentry><refentrytitle>iwd</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
All networks will be stored in
<literal>/var/lib/iwd/&lt;name&gt;.&lt;type&gt;</literal>.
Since each network is stored in its own file, declarative networks can be used in an
environment with imperatively added networks via
<citerefentry><refentrytitle>iwctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
'';
type = types.attrsOf (
types.submodule (
{ config, ... }:
{
config.kind =
if
(hasAttrByPath [
"Security"
"Passphrase"
] config.settings)
then
"psk"
else if !(hasAttrByPath [ "Security" ] config.settings) then
"open"
else
"8021x";
options = {
kind = mkOption {
type = types.enum [
"open"
"psk"
"8021x"
];
description = "The type of network. This will determine the file ending. The module will try to determine this automatically so this should only be set when the heuristics fail.";
};
settings = mkOption {
type =
with types;
(attrsOf (
attrsOf (oneOf [
str
path
])
));
description = ''
Contents of the iwd config file for this network
The lowest level values should be files, that will be read into the config files
'';
default = { };
};
};
}
)
);
};
};
config =
let
inherit (lib)
mkIf
flip
mapAttrsToList
concatStringsSep
;
cfg = config.networking.wireless.iwd;
encoder = pkgs.writeScriptBin "encoder" ''
#! ${pkgs.runtimeShell} -e
# Extract file-ext from network names
ext="$(sed -re 's/.*\.(8021x|open|psk)$/\1/' <<< "$*")"
to_enc="$(sed -re "s/(.*)\.$ext/\1/g" <<< "$*")"
# Encode ssid (excluding file-extensio) as base64 if needed
[[ "$to_enc" =~ ^[[:alnum:]]+$ ]] && { echo "$to_enc.$ext"; exit 0; }
echo "=$(printf "$to_enc" | ${pkgs.unixtools.xxd}/bin/xxd -pu).$ext"
'';
in
mkIf cfg.enable {
systemd.services.iwd = mkIf (cfg.networks != { }) {
path = [ encoder ];
preStart =
let
dataDir = "/var/lib/iwd";
in
''
# Create config files for declaratively defined networks in the NixOS config.
${concatStringsSep "\n" (
flip mapAttrsToList cfg.networks (
network: config: ''
filename=${dataDir}/"$(encoder '${network}.${config.kind}')"
touch "$filename"
cat >$filename <<EOF
${concatStringsSep "\n" (
flip mapAttrsToList config.settings (
toplevel: config: ''
[${toplevel}]
${concatStringsSep "\n" (
flip mapAttrsToList config (
name: value: ''
${name}=$(<${value})
''
)
)}
''
)
)}
EOF
''
)
)}
'';
}; };
}; };
} }

View file

@ -4,9 +4,9 @@
options, options,
pkgs, pkgs,
... ...
}: let }:
inherit let
(lib) inherit (lib)
any any
attrNames attrNames
attrValues attrValues
@ -40,25 +40,32 @@
; ;
cfg = config.services.kanidm; cfg = config.services.kanidm;
settingsFormat = pkgs.formats.toml {}; settingsFormat = pkgs.formats.toml { };
# Remove null values, so we can document optional values that don't end up in the generated TOML file. # Remove null values, so we can document optional values that don't end up in the generated TOML file.
filterConfig = converge (filterAttrsRecursive (_: v: v != null)); filterConfig = converge (filterAttrsRecursive (_: v: v != null));
serverConfigFile = settingsFormat.generate "server.toml" (filterConfig cfg.serverSettings); serverConfigFile = settingsFormat.generate "server.toml" (filterConfig cfg.serverSettings);
clientConfigFile = settingsFormat.generate "kanidm-config.toml" (filterConfig cfg.clientSettings); clientConfigFile = settingsFormat.generate "kanidm-config.toml" (filterConfig cfg.clientSettings);
unixConfigFile = settingsFormat.generate "kanidm-unixd.toml" (filterConfig cfg.unixSettings); unixConfigFile = settingsFormat.generate "kanidm-unixd.toml" (filterConfig cfg.unixSettings);
certPaths = builtins.map builtins.dirOf [cfg.serverSettings.tls_chain cfg.serverSettings.tls_key]; certPaths = builtins.map builtins.dirOf [
cfg.serverSettings.tls_chain
cfg.serverSettings.tls_key
];
# Merge bind mount paths and remove paths where a prefix is already mounted. # Merge bind mount paths and remove paths where a prefix is already mounted.
# This makes sure that if e.g. the tls_chain is in the nix store and /nix/store is already in the mount # This makes sure that if e.g. the tls_chain is in the nix store and /nix/store is already in the mount
# paths, no new bind mount is added. Adding subpaths caused problems on ofborg. # paths, no new bind mount is added. Adding subpaths caused problems on ofborg.
hasPrefixInList = list: newPath: any (path: hasPrefix (builtins.toString path) (builtins.toString newPath)) list; hasPrefixInList =
mergePaths = foldl' (merged: newPath: let list: newPath: any (path: hasPrefix (builtins.toString path) (builtins.toString newPath)) list;
# If the new path is a prefix to some existing path, we need to filter it out mergePaths = foldl' (
filteredPaths = filter (p: !hasPrefix (builtins.toString newPath) (builtins.toString p)) merged; merged: newPath:
# If a prefix of the new path is already in the list, do not add it let
filteredNew = optional (!hasPrefixInList filteredPaths newPath) newPath; # If the new path is a prefix to some existing path, we need to filter it out
in filteredPaths = filter (p: !hasPrefix (builtins.toString newPath) (builtins.toString p)) merged;
filteredPaths ++ filteredNew) []; # If a prefix of the new path is already in the list, do not add it
filteredNew = optional (!hasPrefixInList filteredPaths newPath) newPath;
in
filteredPaths ++ filteredNew
) [ ];
defaultServiceConfig = { defaultServiceConfig = {
BindReadOnlyPaths = [ BindReadOnlyPaths = [
@ -68,7 +75,7 @@
"-/etc/hosts" "-/etc/hosts"
"-/etc/localtime" "-/etc/localtime"
]; ];
CapabilityBoundingSet = []; CapabilityBoundingSet = [ ];
# ProtectClock= adds DeviceAllow=char-rtc r # ProtectClock= adds DeviceAllow=char-rtc r
DeviceAllow = ""; DeviceAllow = "";
# Implies ProtectSystem=strict, which re-mounts all paths # Implies ProtectSystem=strict, which re-mounts all paths
@ -92,17 +99,21 @@
ProtectKernelModules = true; ProtectKernelModules = true;
ProtectKernelTunables = true; ProtectKernelTunables = true;
ProtectProc = "invisible"; ProtectProc = "invisible";
RestrictAddressFamilies = []; RestrictAddressFamilies = [ ];
RestrictNamespaces = true; RestrictNamespaces = true;
RestrictRealtime = true; RestrictRealtime = true;
RestrictSUIDSGID = true; RestrictSUIDSGID = true;
SystemCallArchitectures = "native"; SystemCallArchitectures = "native";
SystemCallFilter = ["@system-service" "~@privileged @resources @setuid @keyring"]; SystemCallFilter = [
"@system-service"
"~@privileged @resources @setuid @keyring"
];
# Does not work well with the temporary root # Does not work well with the temporary root
#UMask = "0066"; #UMask = "0066";
}; };
mkPresentOption = what: mkPresentOption =
what:
mkOption { mkOption {
description = mdDoc "Whether to ensure that this ${what} is present or absent."; description = mdDoc "Whether to ensure that this ${what} is present or absent.";
type = types.bool; type = types.bool;
@ -111,20 +122,21 @@
filterPresent = filterAttrs (_: v: v.present); filterPresent = filterAttrs (_: v: v.present);
provisionStateJson = pkgs.writeText "provision-state.json" (builtins.toJSON { provisionStateJson = pkgs.writeText "provision-state.json" (
inherit (cfg.provision) groups persons systems; builtins.toJSON { inherit (cfg.provision) groups persons systems; }
}); );
serverPort = serverPort =
# ipv6: # ipv6:
if hasInfix "]:" cfg.serverSettings.bindaddress if hasInfix "]:" cfg.serverSettings.bindaddress then
then last (splitString "]:" cfg.serverSettings.bindaddress) last (splitString "]:" cfg.serverSettings.bindaddress)
else else
# ipv4: # ipv4:
if hasInfix "." cfg.serverSettings.bindaddress if hasInfix "." cfg.serverSettings.bindaddress then
then last (splitString ":" cfg.serverSettings.bindaddress) last (splitString ":" cfg.serverSettings.bindaddress)
# default is 8443 # default is 8443
else "8443"; else
"8443";
# Only recover the admin account if a password should explicitly be provisioned # Only recover the admin account if a password should explicitly be provisioned
# for the account. Otherwise it is not needed for provisioning. # for the account. Otherwise it is not needed for provisioning.
@ -141,28 +153,29 @@
# for the account we set it, otherwise we generate a new one because it is required # for the account we set it, otherwise we generate a new one because it is required
# for provisioning. # for provisioning.
recoverIdmAdmin = recoverIdmAdmin =
if cfg.provision.idmAdminPasswordFile != null if cfg.provision.idmAdminPasswordFile != null then
then '' ''
KANIDM_IDM_ADMIN_PASSWORD=$(< ${cfg.provision.idmAdminPasswordFile}) KANIDM_IDM_ADMIN_PASSWORD=$(< ${cfg.provision.idmAdminPasswordFile})
# We always reset the idm_admin account password if a desired password was specified. # We always reset the idm_admin account password if a desired password was specified.
if ! KANIDM_RECOVER_ACCOUNT_PASSWORD=$KANIDM_IDM_ADMIN_PASSWORD ${cfg.package}/bin/kanidmd recover-account -c ${serverConfigFile} idm_admin --from-environment >/dev/null; then if ! KANIDM_RECOVER_ACCOUNT_PASSWORD=$KANIDM_IDM_ADMIN_PASSWORD ${cfg.package}/bin/kanidmd recover-account -c ${serverConfigFile} idm_admin --from-environment >/dev/null; then
echo "Failed to recover idm_admin account" >&2 echo "Failed to recover idm_admin account" >&2
exit 1 exit 1
fi fi
'' ''
else '' else
# Recover idm_admin account ''
if ! recover_out=$(${cfg.package}/bin/kanidmd recover-account -c ${serverConfigFile} idm_admin -o json); then # Recover idm_admin account
echo "$recover_out" >&2 if ! recover_out=$(${cfg.package}/bin/kanidmd recover-account -c ${serverConfigFile} idm_admin -o json); then
echo "kanidm provision: Failed to recover admin account" >&2 echo "$recover_out" >&2
exit 1 echo "kanidm provision: Failed to recover admin account" >&2
fi exit 1
if ! KANIDM_IDM_ADMIN_PASSWORD=$(grep '{"password' <<< "$recover_out" | ${getExe pkgs.jq} -r .password); then fi
echo "$recover_out" >&2 if ! KANIDM_IDM_ADMIN_PASSWORD=$(grep '{"password' <<< "$recover_out" | ${getExe pkgs.jq} -r .password); then
echo "kanidm provision: Failed to parse password for idm_admin account" >&2 echo "$recover_out" >&2
exit 1 echo "kanidm provision: Failed to parse password for idm_admin account" >&2
fi exit 1
''; fi
'';
postStartScript = pkgs.writeShellScript "post-start" '' postStartScript = pkgs.writeShellScript "post-start" ''
set -euo pipefail set -euo pipefail
@ -191,13 +204,14 @@
KANIDM_PROVISION_IDM_ADMIN_TOKEN=$KANIDM_IDM_ADMIN_PASSWORD \ KANIDM_PROVISION_IDM_ADMIN_TOKEN=$KANIDM_IDM_ADMIN_PASSWORD \
${getExe pkgs.kanidm-provision} --url "${cfg.provision.instanceUrl}" --state ${provisionStateJson} ${optionalString cfg.provision.acceptInvalidCerts "--accept-invalid-certs"} ${getExe pkgs.kanidm-provision} --url "${cfg.provision.instanceUrl}" --state ${provisionStateJson} ${optionalString cfg.provision.acceptInvalidCerts "--accept-invalid-certs"}
''; '';
in { in
{
options.services.kanidm = { options.services.kanidm = {
enableClient = mkEnableOption (mdDoc "the Kanidm client"); enableClient = mkEnableOption (mdDoc "the Kanidm client");
enableServer = mkEnableOption (mdDoc "the Kanidm server"); enableServer = mkEnableOption (mdDoc "the Kanidm server");
enablePam = mkEnableOption (mdDoc "the Kanidm PAM and NSS integration"); enablePam = mkEnableOption (mdDoc "the Kanidm PAM and NSS integration");
package = mkPackageOption pkgs "kanidm" {}; package = mkPackageOption pkgs "kanidm" { };
serverSettings = mkOption { serverSettings = mkOption {
type = types.submodule { type = types.submodule {
@ -253,12 +267,20 @@ in {
log_level = mkOption { log_level = mkOption {
description = mdDoc "Log level of the server."; description = mdDoc "Log level of the server.";
default = "info"; default = "info";
type = types.enum ["info" "debug" "trace"]; type = types.enum [
"info"
"debug"
"trace"
];
}; };
role = mkOption { role = mkOption {
description = mdDoc "The role of this server. This affects the replication relationship and thereby available features."; description = mdDoc "The role of this server. This affects the replication relationship and thereby available features.";
default = "WriteReplica"; default = "WriteReplica";
type = types.enum ["WriteReplica" "WriteReplicaNoUI" "ReadOnlyReplica"]; type = types.enum [
"WriteReplica"
"WriteReplicaNoUI"
"ReadOnlyReplica"
];
}; };
online_backup = { online_backup = {
path = mkOption { path = mkOption {
@ -284,7 +306,7 @@ in {
}; };
}; };
}; };
default = {}; default = { };
description = mdDoc '' description = mdDoc ''
Settings for Kanidm, see Settings for Kanidm, see
[the documentation](https://kanidm.github.io/kanidm/stable/server_configuration.html) [the documentation](https://kanidm.github.io/kanidm/stable/server_configuration.html)
@ -387,227 +409,245 @@ in {
groups = mkOption { groups = mkOption {
description = "Provisioning of kanidm groups"; description = "Provisioning of kanidm groups";
default = {}; default = { };
type = types.attrsOf (types.submodule (groupSubmod: { type = types.attrsOf (
options = { types.submodule (groupSubmod: {
present = mkPresentOption "group"; options = {
present = mkPresentOption "group";
members = mkOption { members = mkOption {
description = "List of kanidm entities (persons, groups, ...) which are part of this group."; description = "List of kanidm entities (persons, groups, ...) which are part of this group.";
type = types.listOf types.str; type = types.listOf types.str;
apply = unique; apply = unique;
default = []; default = [ ];
};
}; };
}; config.members = concatLists (
config.members = concatLists (flip mapAttrsToList cfg.provision.persons ( flip mapAttrsToList cfg.provision.persons (
person: personCfg: person: personCfg:
optional (personCfg.present && builtins.elem groupSubmod.config._module.args.name personCfg.groups) person optional (
)); personCfg.present && builtins.elem groupSubmod.config._module.args.name personCfg.groups
})); ) person
)
);
})
);
}; };
persons = mkOption { persons = mkOption {
description = "Provisioning of kanidm persons"; description = "Provisioning of kanidm persons";
default = {}; default = { };
type = types.attrsOf (types.submodule { type = types.attrsOf (
options = { types.submodule {
present = mkPresentOption "person"; options = {
present = mkPresentOption "person";
displayName = mkOption { displayName = mkOption {
description = "Display name"; description = "Display name";
type = types.str; type = types.str;
example = "My User"; example = "My User";
}; };
legalName = mkOption { legalName = mkOption {
description = "Full legal name"; description = "Full legal name";
type = types.nullOr types.str; type = types.nullOr types.str;
example = "Jane Doe"; example = "Jane Doe";
default = null; default = null;
}; };
mailAddresses = mkOption { mailAddresses = mkOption {
description = "Mail addresses. First given address is considered the primary address."; description = "Mail addresses. First given address is considered the primary address.";
type = types.listOf types.str; type = types.listOf types.str;
example = ["jane.doe@example.com"]; example = [ "jane.doe@example.com" ];
default = []; default = [ ];
}; };
groups = mkOption { groups = mkOption {
description = "List of groups this person should belong to."; description = "List of groups this person should belong to.";
type = types.listOf types.str; type = types.listOf types.str;
apply = unique; apply = unique;
default = []; default = [ ];
};
}; };
}; }
}); );
}; };
systems.oauth2 = mkOption { systems.oauth2 = mkOption {
description = "Provisioning of oauth2 resource servers"; description = "Provisioning of oauth2 resource servers";
default = {}; default = { };
type = types.attrsOf (types.submodule { type = types.attrsOf (
options = { types.submodule {
present = mkPresentOption "oauth2 resource server"; options = {
present = mkPresentOption "oauth2 resource server";
public = mkOption { public = mkOption {
description = "Whether this is a public client (enforces PKCE, doesn't use a basic secret)"; description = "Whether this is a public client (enforces PKCE, doesn't use a basic secret)";
type = types.bool; type = types.bool;
default = false; default = false;
};
displayName = mkOption {
description = "Display name";
type = types.str;
example = "Some Service";
};
originUrl = mkOption {
description = "The origin URL of the service. OAuth2 redirects will only be allowed to sites under this origin. Must end with a slash.";
type = types.strMatching ".*://.*/$";
example = "https://someservice.example.com/";
};
originLanding = mkOption {
description = "When redirecting from the Kanidm Apps Listing page, some linked applications may need to land on a specific page to trigger oauth2/oidc interactions.";
type = types.nullOr types.str;
default = null;
example = "https://someservice.example.com/home";
};
basicSecretFile = mkOption {
description = ''
The basic secret to use for this service. If null, the random secret generated
by kanidm will not be touched. Do NOT use a path from the nix store here!
'';
type = types.nullOr types.path;
example = "/run/secrets/some-oauth2-basic-secret";
default = null;
};
enableLocalhostRedirects = mkOption {
description = "Allow localhost redirects. Only for public clients.";
type = types.bool;
default = false;
};
enableLegacyCrypto = mkOption {
description = "Enable legacy crypto on this client. Allows JWT signing algorthms like RS256.";
type = types.bool;
default = false;
};
allowInsecureClientDisablePkce = mkOption {
description = ''
Disable PKCE on this oauth2 resource server to work around insecure clients
that may not support it. You should request the client to enable PKCE!
Only for non-public clients.
'';
type = types.bool;
default = false;
};
preferShortUsername = mkOption {
description = "Use 'name' instead of 'spn' in the preferred_username claim";
type = types.bool;
default = false;
};
scopeMaps = mkOption {
description = ''
Maps kanidm groups to returned oauth scopes.
See [Scope Relations](https://kanidm.github.io/kanidm/stable/integrations/oauth2.html#scope-relationships) for more information.
'';
type = types.attrsOf (types.listOf types.str);
default = { };
};
supplementaryScopeMaps = mkOption {
description = ''
Maps kanidm groups to additionally returned oauth scopes.
See [Scope Relations](https://kanidm.github.io/kanidm/stable/integrations/oauth2.html#scope-relationships) for more information.
'';
type = types.attrsOf (types.listOf types.str);
default = { };
};
removeOrphanedClaimMaps = mkOption {
description = "Whether claim maps not specified here but present in kanidm should be removed from kanidm.";
type = types.bool;
default = true;
};
claimMaps = mkOption {
description = ''
Adds additional claims (and values) based on which kanidm groups an authenticating party belongs to.
See [Claim Maps](https://kanidm.github.io/kanidm/master/integrations/oauth2.html#custom-claim-maps) for more information.
'';
default = { };
type = types.attrsOf (
types.submodule {
options = {
joinType = mkOption {
description = ''
Determines how multiple values are joined to create the claim value.
See [Claim Maps](https://kanidm.github.io/kanidm/master/integrations/oauth2.html#custom-claim-maps) for more information.
'';
type = types.enum [
"array"
"csv"
"ssv"
];
default = "array";
};
valuesByGroup = mkOption {
description = "Maps kanidm groups to values for the claim.";
default = { };
type = types.attrsOf (types.listOf types.str);
};
};
}
);
};
}; };
}
displayName = mkOption { );
description = "Display name";
type = types.str;
example = "Some Service";
};
originUrl = mkOption {
description = "The origin URL of the service. OAuth2 redirects will only be allowed to sites under this origin. Must end with a slash.";
type = types.strMatching ".*://.*/$";
example = "https://someservice.example.com/";
};
originLanding = mkOption {
description = "When redirecting from the Kanidm Apps Listing page, some linked applications may need to land on a specific page to trigger oauth2/oidc interactions.";
type = types.nullOr types.str;
default = null;
example = "https://someservice.example.com/home";
};
basicSecretFile = mkOption {
description = ''
The basic secret to use for this service. If null, the random secret generated
by kanidm will not be touched. Do NOT use a path from the nix store here!
'';
type = types.nullOr types.path;
example = "/run/secrets/some-oauth2-basic-secret";
default = null;
};
enableLocalhostRedirects = mkOption {
description = "Allow localhost redirects. Only for public clients.";
type = types.bool;
default = false;
};
enableLegacyCrypto = mkOption {
description = "Enable legacy crypto on this client. Allows JWT signing algorthms like RS256.";
type = types.bool;
default = false;
};
allowInsecureClientDisablePkce = mkOption {
description = ''
Disable PKCE on this oauth2 resource server to work around insecure clients
that may not support it. You should request the client to enable PKCE!
Only for non-public clients.
'';
type = types.bool;
default = false;
};
preferShortUsername = mkOption {
description = "Use 'name' instead of 'spn' in the preferred_username claim";
type = types.bool;
default = false;
};
scopeMaps = mkOption {
description = ''
Maps kanidm groups to returned oauth scopes.
See [Scope Relations](https://kanidm.github.io/kanidm/stable/integrations/oauth2.html#scope-relationships) for more information.
'';
type = types.attrsOf (types.listOf types.str);
default = {};
};
supplementaryScopeMaps = mkOption {
description = ''
Maps kanidm groups to additionally returned oauth scopes.
See [Scope Relations](https://kanidm.github.io/kanidm/stable/integrations/oauth2.html#scope-relationships) for more information.
'';
type = types.attrsOf (types.listOf types.str);
default = {};
};
removeOrphanedClaimMaps = mkOption {
description = "Whether claim maps not specified here but present in kanidm should be removed from kanidm.";
type = types.bool;
default = true;
};
claimMaps = mkOption {
description = ''
Adds additional claims (and values) based on which kanidm groups an authenticating party belongs to.
See [Claim Maps](https://kanidm.github.io/kanidm/master/integrations/oauth2.html#custom-claim-maps) for more information.
'';
default = {};
type = types.attrsOf (types.submodule {
options = {
joinType = mkOption {
description = ''
Determines how multiple values are joined to create the claim value.
See [Claim Maps](https://kanidm.github.io/kanidm/master/integrations/oauth2.html#custom-claim-maps) for more information.
'';
type = types.enum ["array" "csv" "ssv"];
default = "array";
};
valuesByGroup = mkOption {
description = "Maps kanidm groups to values for the claim.";
default = {};
type = types.attrsOf (types.listOf types.str);
};
};
});
};
};
});
}; };
}; };
}; };
config = mkIf (cfg.enableClient || cfg.enableServer || cfg.enablePam) { config = mkIf (cfg.enableClient || cfg.enableServer || cfg.enablePam) {
assertions = let assertions =
entityList = type: attrs: flip mapAttrsToList (filterPresent attrs) (name: _: {inherit type name;}); let
entities = entityList =
entityList "group" cfg.provision.groups type: attrs: flip mapAttrsToList (filterPresent attrs) (name: _: { inherit type name; });
++ entityList "person" cfg.provision.persons entities =
++ entityList "oauth2" cfg.provision.systems.oauth2; entityList "group" cfg.provision.groups
++ entityList "person" cfg.provision.persons
++ entityList "oauth2" cfg.provision.systems.oauth2;
# Accumulate entities by name. Track corresponding entity types for later duplicate check. # Accumulate entities by name. Track corresponding entity types for later duplicate check.
entitiesByName = entitiesByName = foldl' (
foldl' ( acc: { type, name }: acc // { ${name} = (acc.${name} or [ ]) ++ [ type ]; }
acc: { ) { } entities;
type,
name,
}:
acc
// {
${name} = (acc.${name} or []) ++ [type];
}
) {}
entities;
assertGroupsKnown = opt: groups: let assertGroupsKnown =
knownGroups = attrNames (filterPresent cfg.provision.groups); opt: groups:
unknownGroups = subtractLists knownGroups groups; let
in { knownGroups = attrNames (filterPresent cfg.provision.groups);
assertion = (cfg.enableServer && cfg.provision.enable) -> unknownGroups == []; unknownGroups = subtractLists knownGroups groups;
message = "${opt} refers to unknown groups: ${toString unknownGroups}"; in
}; {
assertion = (cfg.enableServer && cfg.provision.enable) -> unknownGroups == [ ];
message = "${opt} refers to unknown groups: ${toString unknownGroups}";
};
assertEntitiesKnown = opt: entities: let assertEntitiesKnown =
unknownEntities = subtractLists (attrNames entitiesByName) entities; opt: entities:
in { let
assertion = (cfg.enableServer && cfg.provision.enable) -> unknownEntities == []; unknownEntities = subtractLists (attrNames entitiesByName) entities;
message = "${opt} refers to unknown entities: ${toString unknownEntities}"; in
}; {
in assertion = (cfg.enableServer && cfg.provision.enable) -> unknownEntities == [ ];
message = "${opt} refers to unknown entities: ${toString unknownEntities}";
};
in
[ [
{ {
assertion = !cfg.enableServer || ((cfg.serverSettings.tls_chain or null) == null) || (!isStorePath cfg.serverSettings.tls_chain); assertion =
!cfg.enableServer
|| ((cfg.serverSettings.tls_chain or null) == null)
|| (!isStorePath cfg.serverSettings.tls_chain);
message = '' message = ''
<option>services.kanidm.serverSettings.tls_chain</option> points to <option>services.kanidm.serverSettings.tls_chain</option> points to
a file in the Nix store. You should use a quoted absolute path to a file in the Nix store. You should use a quoted absolute path to
@ -615,7 +655,10 @@ in {
''; '';
} }
{ {
assertion = !cfg.enableServer || ((cfg.serverSettings.tls_key or null) == null) || (!isStorePath cfg.serverSettings.tls_key); assertion =
!cfg.enableServer
|| ((cfg.serverSettings.tls_key or null) == null)
|| (!isStorePath cfg.serverSettings.tls_key);
message = '' message = ''
<option>services.kanidm.serverSettings.tls_key</option> points to <option>services.kanidm.serverSettings.tls_key</option> points to
a file in the Nix store. You should use a quoted absolute path to a file in the Nix store. You should use a quoted absolute path to
@ -639,9 +682,10 @@ in {
{ {
assertion = assertion =
!cfg.enableServer !cfg.enableServer
|| (cfg.serverSettings.domain || (
== null cfg.serverSettings.domain == null
-> cfg.serverSettings.role == "WriteReplica" || cfg.serverSettings.role == "WriteReplicaNoUI"); -> cfg.serverSettings.role == "WriteReplica" || cfg.serverSettings.role == "WriteReplicaNoUI"
);
message = '' message = ''
<option>services.kanidm.serverSettings.domain</option> can only be set if this instance <option>services.kanidm.serverSettings.domain</option> can only be set if this instance
is not a ReadOnlyReplica. Otherwise the db would inherit it from is not a ReadOnlyReplica. Otherwise the db would inherit it from
@ -655,13 +699,14 @@ in {
# If any secret is provisioned, the kanidm package must have some required patches applied to it # If any secret is provisioned, the kanidm package must have some required patches applied to it
{ {
assertion = assertion =
(cfg.provision.enable (
cfg.provision.enable
&& ( && (
cfg.provision.adminPasswordFile cfg.provision.adminPasswordFile != null
!= null
|| cfg.provision.idmAdminPasswordFile != null || cfg.provision.idmAdminPasswordFile != null
|| any (x: x.basicSecretFile != null) (attrValues (filterPresent cfg.provision.systems.oauth2)) || any (x: x.basicSecretFile != null) (attrValues (filterPresent cfg.provision.systems.oauth2))
)) )
)
-> cfg.package.enableSecretProvisioning; -> cfg.package.enableSecretProvisioning;
message = '' message = ''
Specifying an admin account password or oauth2 basicSecretFile requires kanidm to be built with the secret provisioning patches. Specifying an admin account password or oauth2 basicSecretFile requires kanidm to be built with the secret provisioning patches.
@ -669,56 +714,80 @@ in {
''; '';
} }
# Entity names must be globally unique: # Entity names must be globally unique:
(let (
# Filter all names that occurred in more than one entity type. let
duplicateNames = filterAttrs (_: v: builtins.length v > 1) entitiesByName; # Filter all names that occurred in more than one entity type.
in { duplicateNames = filterAttrs (_: v: builtins.length v > 1) entitiesByName;
assertion = cfg.provision.enable -> duplicateNames == {}; in
message = '' {
services.kanidm.provision requires all entity names (group, person, oauth2, ...) to be unique! assertion = cfg.provision.enable -> duplicateNames == { };
${concatLines (mapAttrsToList (name: xs: " - '${name}' used as: ${toString xs}") duplicateNames)}''; message = ''
}) services.kanidm.provision requires all entity names (group, person, oauth2, ...) to be unique!
${concatLines (
mapAttrsToList (name: xs: " - '${name}' used as: ${toString xs}") duplicateNames
)}'';
}
)
] ]
++ flip mapAttrsToList (filterPresent cfg.provision.persons) ( ++ flip mapAttrsToList (filterPresent cfg.provision.persons) (
person: personCfg: person: personCfg:
assertGroupsKnown "services.kanidm.provision.persons.${person}.groups" personCfg.groups assertGroupsKnown "services.kanidm.provision.persons.${person}.groups" personCfg.groups
) )
++ flip mapAttrsToList (filterPresent cfg.provision.groups) ( ++ flip mapAttrsToList (filterPresent cfg.provision.groups) (
group: groupCfg: group: groupCfg:
assertEntitiesKnown "services.kanidm.provision.groups.${group}.members" groupCfg.members assertEntitiesKnown "services.kanidm.provision.groups.${group}.members" groupCfg.members
) )
++ concatLists (flip mapAttrsToList (filterPresent cfg.provision.systems.oauth2) ( ++ concatLists (
oauth2: oauth2Cfg: flip mapAttrsToList (filterPresent cfg.provision.systems.oauth2) (
oauth2: oauth2Cfg:
[ [
(assertGroupsKnown "services.kanidm.provision.systems.oauth2.${oauth2}.scopeMaps" (attrNames oauth2Cfg.scopeMaps)) (assertGroupsKnown "services.kanidm.provision.systems.oauth2.${oauth2}.scopeMaps" (
(assertGroupsKnown "services.kanidm.provision.systems.oauth2.${oauth2}.supplementaryScopeMaps" (attrNames oauth2Cfg.supplementaryScopeMaps)) attrNames oauth2Cfg.scopeMaps
))
(assertGroupsKnown "services.kanidm.provision.systems.oauth2.${oauth2}.supplementaryScopeMaps" (
attrNames oauth2Cfg.supplementaryScopeMaps
))
] ]
++ concatLists (flip mapAttrsToList oauth2Cfg.claimMaps (claim: claimCfg: [ ++ concatLists (
(assertGroupsKnown "services.kanidm.provision.systems.oauth2.${oauth2}.claimMaps.${claim}.valuesByGroup" (attrNames claimCfg.valuesByGroup)) flip mapAttrsToList oauth2Cfg.claimMaps (
# At least one group must map to a value in each claim map claim: claimCfg: [
{ (assertGroupsKnown "services.kanidm.provision.systems.oauth2.${oauth2}.claimMaps.${claim}.valuesByGroup" (
assertion = (cfg.provision.enable && cfg.enableServer) -> any (xs: xs != []) (attrValues claimCfg.valuesByGroup); attrNames claimCfg.valuesByGroup
message = "services.kanidm.provision.systems.oauth2.${oauth2}.claimMaps.${claim} does not specify any values for any group"; ))
} # At least one group must map to a value in each claim map
# Public clients cannot define a basic secret {
{ assertion =
assertion = (cfg.provision.enable && cfg.enableServer && oauth2Cfg.public) -> oauth2Cfg.basicSecretFile == null; (cfg.provision.enable && cfg.enableServer)
message = "services.kanidm.provision.systems.oauth2.${oauth2} is a public client and thus cannot specify a basic secret"; -> any (xs: xs != [ ]) (attrValues claimCfg.valuesByGroup);
} message = "services.kanidm.provision.systems.oauth2.${oauth2}.claimMaps.${claim} does not specify any values for any group";
# Public clients cannot disable PKCE }
{ # Public clients cannot define a basic secret
assertion = (cfg.provision.enable && cfg.enableServer && oauth2Cfg.public) -> !oauth2Cfg.allowInsecureClientDisablePkce; {
message = "services.kanidm.provision.systems.oauth2.${oauth2} is a public client and thus cannot disable PKCE"; assertion =
} (cfg.provision.enable && cfg.enableServer && oauth2Cfg.public) -> oauth2Cfg.basicSecretFile == null;
# Non-public clients cannot enable localhost redirects message = "services.kanidm.provision.systems.oauth2.${oauth2} is a public client and thus cannot specify a basic secret";
{ }
assertion = (cfg.provision.enable && cfg.enableServer && !oauth2Cfg.public) -> !oauth2Cfg.enableLocalhostRedirects; # Public clients cannot disable PKCE
message = "services.kanidm.provision.systems.oauth2.${oauth2} is a non-public client and thus cannot enable localhost redirects"; {
} assertion =
])) (cfg.provision.enable && cfg.enableServer && oauth2Cfg.public)
)); -> !oauth2Cfg.allowInsecureClientDisablePkce;
message = "services.kanidm.provision.systems.oauth2.${oauth2} is a public client and thus cannot disable PKCE";
}
# Non-public clients cannot enable localhost redirects
{
assertion =
(cfg.provision.enable && cfg.enableServer && !oauth2Cfg.public)
-> !oauth2Cfg.enableLocalhostRedirects;
message = "services.kanidm.provision.systems.oauth2.${oauth2} is a non-public client and thus cannot enable localhost redirects";
}
]
)
)
)
);
environment.systemPackages = mkIf cfg.enableClient [cfg.package]; environment.systemPackages = mkIf cfg.enableClient [ cfg.package ];
systemd.tmpfiles.settings."10-kanidm" = { systemd.tmpfiles.settings."10-kanidm" = {
${cfg.serverSettings.online_backup.path}.d = { ${cfg.serverSettings.online_backup.path}.d = {
@ -730,14 +799,16 @@ in {
systemd.services.kanidm = mkIf cfg.enableServer { systemd.services.kanidm = mkIf cfg.enableServer {
description = "kanidm identity management daemon"; description = "kanidm identity management daemon";
wantedBy = ["multi-user.target"]; wantedBy = [ "multi-user.target" ];
after = ["network.target"]; after = [ "network.target" ];
serviceConfig = mkMerge [ serviceConfig = mkMerge [
# Merge paths and ignore existing prefixes needs to sidestep mkMerge # Merge paths and ignore existing prefixes needs to sidestep mkMerge
(defaultServiceConfig (
defaultServiceConfig
// { // {
BindReadOnlyPaths = mergePaths (defaultServiceConfig.BindReadOnlyPaths ++ certPaths); BindReadOnlyPaths = mergePaths (defaultServiceConfig.BindReadOnlyPaths ++ certPaths);
}) }
)
{ {
StateDirectory = "kanidm"; StateDirectory = "kanidm";
StateDirectoryMode = "0700"; StateDirectoryMode = "0700";
@ -754,13 +825,17 @@ in {
cfg.serverSettings.online_backup.path cfg.serverSettings.online_backup.path
]; ];
AmbientCapabilities = ["CAP_NET_BIND_SERVICE"]; AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
CapabilityBoundingSet = ["CAP_NET_BIND_SERVICE"]; CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
# This would otherwise override the CAP_NET_BIND_SERVICE capability. # This would otherwise override the CAP_NET_BIND_SERVICE capability.
PrivateUsers = mkForce false; PrivateUsers = mkForce false;
# Port needs to be exposed to the host network # Port needs to be exposed to the host network
PrivateNetwork = mkForce false; PrivateNetwork = mkForce false;
RestrictAddressFamilies = ["AF_INET" "AF_INET6" "AF_UNIX"]; RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
TemporaryFileSystem = "/:ro"; TemporaryFileSystem = "/:ro";
} }
]; ];
@ -769,9 +844,12 @@ in {
systemd.services.kanidm-unixd = mkIf cfg.enablePam { systemd.services.kanidm-unixd = mkIf cfg.enablePam {
description = "Kanidm PAM daemon"; description = "Kanidm PAM daemon";
wantedBy = ["multi-user.target"]; wantedBy = [ "multi-user.target" ];
after = ["network.target"]; after = [ "network.target" ];
restartTriggers = [unixConfigFile clientConfigFile]; restartTriggers = [
unixConfigFile
clientConfigFile
];
serviceConfig = mkMerge [ serviceConfig = mkMerge [
defaultServiceConfig defaultServiceConfig
{ {
@ -796,7 +874,11 @@ in {
]; ];
# Needs to connect to kanidmd # Needs to connect to kanidmd
PrivateNetwork = mkForce false; PrivateNetwork = mkForce false;
RestrictAddressFamilies = ["AF_INET" "AF_INET6" "AF_UNIX"]; RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
TemporaryFileSystem = "/:ro"; TemporaryFileSystem = "/:ro";
} }
]; ];
@ -805,10 +887,16 @@ in {
systemd.services.kanidm-unixd-tasks = mkIf cfg.enablePam { systemd.services.kanidm-unixd-tasks = mkIf cfg.enablePam {
description = "Kanidm PAM home management daemon"; description = "Kanidm PAM home management daemon";
wantedBy = ["multi-user.target"]; wantedBy = [ "multi-user.target" ];
after = ["network.target" "kanidm-unixd.service"]; after = [
partOf = ["kanidm-unixd.service"]; "network.target"
restartTriggers = [unixConfigFile clientConfigFile]; "kanidm-unixd.service"
];
partOf = [ "kanidm-unixd.service" ];
restartTriggers = [
unixConfigFile
clientConfigFile
];
serviceConfig = { serviceConfig = {
ExecStart = "${cfg.package}/bin/kanidm_unixd_tasks"; ExecStart = "${cfg.package}/bin/kanidm_unixd_tasks";
@ -828,13 +916,18 @@ in {
"/run/kanidm-unixd:/var/run/kanidm-unixd" "/run/kanidm-unixd:/var/run/kanidm-unixd"
]; ];
# CAP_DAC_OVERRIDE is needed to ignore ownership of unixd socket # CAP_DAC_OVERRIDE is needed to ignore ownership of unixd socket
CapabilityBoundingSet = ["CAP_CHOWN" "CAP_FOWNER" "CAP_DAC_OVERRIDE" "CAP_DAC_READ_SEARCH"]; CapabilityBoundingSet = [
"CAP_CHOWN"
"CAP_FOWNER"
"CAP_DAC_OVERRIDE"
"CAP_DAC_READ_SEARCH"
];
IPAddressDeny = "any"; IPAddressDeny = "any";
# Need access to users # Need access to users
PrivateUsers = false; PrivateUsers = false;
# Need access to home directories # Need access to home directories
ProtectHome = false; ProtectHome = false;
RestrictAddressFamilies = ["AF_UNIX"]; RestrictAddressFamilies = [ "AF_UNIX" ];
TemporaryFileSystem = "/:ro"; TemporaryFileSystem = "/:ro";
Restart = "on-failure"; Restart = "on-failure";
}; };
@ -843,29 +936,21 @@ in {
# These paths are hardcoded # These paths are hardcoded
environment.etc = mkMerge [ environment.etc = mkMerge [
(mkIf cfg.enableServer { (mkIf cfg.enableServer { "kanidm/server.toml".source = serverConfigFile; })
"kanidm/server.toml".source = serverConfigFile;
})
(mkIf options.services.kanidm.clientSettings.isDefined { (mkIf options.services.kanidm.clientSettings.isDefined {
"kanidm/config".source = clientConfigFile; "kanidm/config".source = clientConfigFile;
}) })
(mkIf cfg.enablePam { (mkIf cfg.enablePam { "kanidm/unixd".source = unixConfigFile; })
"kanidm/unixd".source = unixConfigFile;
})
]; ];
system.nssModules = mkIf cfg.enablePam [cfg.package]; system.nssModules = mkIf cfg.enablePam [ cfg.package ];
system.nssDatabases.group = optional cfg.enablePam "kanidm"; system.nssDatabases.group = optional cfg.enablePam "kanidm";
system.nssDatabases.passwd = optional cfg.enablePam "kanidm"; system.nssDatabases.passwd = optional cfg.enablePam "kanidm";
users.groups = mkMerge [ users.groups = mkMerge [
(mkIf cfg.enableServer { (mkIf cfg.enableServer { kanidm = { }; })
kanidm = {}; (mkIf cfg.enablePam { kanidm-unixd = { }; })
})
(mkIf cfg.enablePam {
kanidm-unixd = {};
})
]; ];
users.users = mkMerge [ users.users = mkMerge [
(mkIf cfg.enableServer { (mkIf cfg.enableServer {
@ -873,7 +958,7 @@ in {
description = "Kanidm server"; description = "Kanidm server";
isSystemUser = true; isSystemUser = true;
group = "kanidm"; group = "kanidm";
packages = [cfg.package]; packages = [ cfg.package ];
}; };
}) })
(mkIf cfg.enablePam { (mkIf cfg.enablePam {
@ -886,6 +971,10 @@ in {
]; ];
}; };
meta.maintainers = with lib.maintainers; [erictapen Flakebi oddlama]; meta.maintainers = with lib.maintainers; [
erictapen
Flakebi
oddlama
];
meta.buildDocsInSandbox = false; meta.buildDocsInSandbox = false;
} }

View file

@ -1,10 +1,8 @@
{lib, ...}: let { lib, ... }:
inherit let
(lib) inherit (lib) mkOption types;
mkOption in
types {
;
in {
options.node = { options.node = {
secretsDir = mkOption { secretsDir = mkOption {
description = "Path to the secrets directory for this node."; description = "Path to the secrets directory for this node.";

View file

@ -3,9 +3,9 @@
lib, lib,
pkgs, pkgs,
... ...
}: let }:
inherit let
(lib) inherit (lib)
attrNames attrNames
getExe getExe
literalExpression literalExpression
@ -22,8 +22,7 @@
versionOlder versionOlder
; ;
inherit inherit (lib.types)
(lib.types)
attrsOf attrsOf
port port
str str
@ -36,7 +35,8 @@
kernel = config.boot.kernelPackages; kernel = config.boot.kernelPackages;
cfg = config.services.netbird; cfg = config.services.netbird;
in { in
{
meta.maintainers = with maintainers; [ meta.maintainers = with maintainers; [
misuzu misuzu
thubrecht thubrecht
@ -46,16 +46,13 @@ in {
options.services.netbird = { options.services.netbird = {
enable = mkEnableOption (lib.mdDoc "Netbird daemon"); enable = mkEnableOption (lib.mdDoc "Netbird daemon");
package = mkPackageOption pkgs "netbird" {}; package = mkPackageOption pkgs "netbird" { };
tunnels = mkOption { tunnels = mkOption {
type = attrsOf ( type = attrsOf (
submodule ( submodule (
{ name, config, ... }:
{ {
name,
config,
...
}: {
options = { options = {
port = mkOption { port = mkOption {
type = port; type = port;
@ -111,7 +108,7 @@ in {
} }
) )
); );
default = {}; default = { };
description = '' description = ''
Attribute set of Netbird tunnels, each one will spawn a daemon listening on ... Attribute set of Netbird tunnels, each one will spawn a daemon listening on ...
''; '';
@ -124,106 +121,99 @@ in {
services.netbird.tunnels.wt0.stateDir = "netbird"; services.netbird.tunnels.wt0.stateDir = "netbird";
}) })
(mkIf (cfg.tunnels != {}) { (mkIf (cfg.tunnels != { }) {
boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard; boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard;
environment.systemPackages = [cfg.package]; environment.systemPackages = [ cfg.package ];
networking.dhcpcd.denyInterfaces = attrNames cfg.tunnels; networking.dhcpcd.denyInterfaces = attrNames cfg.tunnels;
systemd.network.networks = mkIf config.networking.useNetworkd ( systemd.network.networks = mkIf config.networking.useNetworkd (
mapAttrs' mapAttrs' (
(
name: _: name: _:
nameValuePair "50-netbird-${name}" { nameValuePair "50-netbird-${name}" {
matchConfig = { matchConfig = {
Name = name; Name = name;
}; };
linkConfig = { linkConfig = {
Unmanaged = true; Unmanaged = true;
ActivationPolicy = "manual"; ActivationPolicy = "manual";
}; };
} }
) ) cfg.tunnels
cfg.tunnels
); );
systemd.services = systemd.services = mapAttrs' (
mapAttrs' name:
( {
name: { environment,
environment, stateDir,
stateDir, environmentFile,
environmentFile, userAccess,
userAccess, ...
... }:
}: nameValuePair "netbird-${name}" {
nameValuePair "netbird-${name}" { description = "A WireGuard-based mesh network that connects your devices into a single private network";
description = "A WireGuard-based mesh network that connects your devices into a single private network";
documentation = ["https://netbird.io/docs/"]; documentation = [ "https://netbird.io/docs/" ];
after = ["network.target"]; after = [ "network.target" ];
wantedBy = ["multi-user.target"]; wantedBy = [ "multi-user.target" ];
path = with pkgs; [openresolv]; path = with pkgs; [ openresolv ];
inherit environment; inherit environment;
serviceConfig = { serviceConfig = {
EnvironmentFile = mkIf (environmentFile != null) environmentFile; EnvironmentFile = mkIf (environmentFile != null) environmentFile;
ExecStart = "${getExe cfg.package} service run"; ExecStart = "${getExe cfg.package} service run";
Restart = "always"; Restart = "always";
RuntimeDirectory = stateDir; RuntimeDirectory = stateDir;
StateDirectory = stateDir; StateDirectory = stateDir;
StateDirectoryMode = "0700"; StateDirectoryMode = "0700";
WorkingDirectory = "/var/lib/${stateDir}"; WorkingDirectory = "/var/lib/${stateDir}";
RuntimeDirectoryMode = RuntimeDirectoryMode = if userAccess then "0755" else "0750";
if userAccess
then "0755"
else "0750";
# hardening # hardening
LockPersonality = true; LockPersonality = true;
MemoryDenyWriteExecute = true; MemoryDenyWriteExecute = true;
NoNewPrivileges = true; NoNewPrivileges = true;
PrivateMounts = true; PrivateMounts = true;
PrivateTmp = true; PrivateTmp = true;
ProtectClock = true; ProtectClock = true;
ProtectControlGroups = true; ProtectControlGroups = true;
ProtectHome = true; ProtectHome = true;
ProtectHostname = true; ProtectHostname = true;
ProtectKernelLogs = true; ProtectKernelLogs = true;
ProtectKernelModules = false; # needed to load wg module for kernel-mode WireGuard ProtectKernelModules = false; # needed to load wg module for kernel-mode WireGuard
ProtectKernelTunables = false; ProtectKernelTunables = false;
ProtectSystem = true; ProtectSystem = true;
RemoveIPC = true; RemoveIPC = true;
RestrictNamespaces = true; RestrictNamespaces = true;
RestrictRealtime = true; RestrictRealtime = true;
RestrictSUIDSGID = true; RestrictSUIDSGID = true;
# Hardening # Hardening
#CapabilityBoundingSet = ""; #CapabilityBoundingSet = "";
#PrivateUsers = true; #PrivateUsers = true;
#ProtectProc = "invisible"; #ProtectProc = "invisible";
#ProcSubset = "pid"; #ProcSubset = "pid";
#RestrictAddressFamilies = [ #RestrictAddressFamilies = [
# "AF_INET" # "AF_INET"
# "AF_INET6" # "AF_INET6"
# "AF_NETLINK" # "AF_NETLINK"
#]; #];
#SystemCallArchitectures = "native"; #SystemCallArchitectures = "native";
#SystemCallFilter = [ #SystemCallFilter = [
# "@system-service" # "@system-service"
# "@pkey" # "@pkey"
#]; #];
UMask = "0077"; UMask = "0077";
}; };
stopIfChanged = false; stopIfChanged = false;
} }
) ) cfg.tunnels;
cfg.tunnels;
}) })
]; ];
} }

View file

@ -3,9 +3,9 @@
inputs, inputs,
config, config,
... ...
}: let }:
inherit let
(lib) inherit (lib)
mapAttrs mapAttrs
assertMsg assertMsg
types types
@ -16,22 +16,25 @@
# If the given expression is a bare set, it will be wrapped in a function, # If the given expression is a bare set, it will be wrapped in a function,
# so that the imported file can always be applied to the inputs, similar to # so that the imported file can always be applied to the inputs, similar to
# how modules can be functions or sets. # how modules can be functions or sets.
constSet = x: constSet = x: if builtins.isAttrs x then (_: x) else x;
if builtins.isAttrs x
then (_: x)
else x;
rageImportEncrypted = assert assertMsg (builtins ? extraBuiltins.rageImportEncrypted) "The rageImportEncrypted extra plugin is not loaded"; rageImportEncrypted =
assert assertMsg (
builtins ? extraBuiltins.rageImportEncrypted
) "The rageImportEncrypted extra plugin is not loaded";
builtins.extraBuiltins.rageImportEncrypted; builtins.extraBuiltins.rageImportEncrypted;
# This "imports" an encrypted .nix.age file # This "imports" an encrypted .nix.age file
importEncrypted = path: importEncrypted =
path:
constSet ( constSet (
if builtins.pathExists path if builtins.pathExists path then
then rageImportEncrypted inputs.self.secretsConfig.masterIdentities path rageImportEncrypted inputs.self.secretsConfig.masterIdentities path
else {} else
{ }
); );
cfg = config.secrets; cfg = config.secrets;
in { in
{
options.secrets = { options.secrets = {
defineRageBuiltins = mkOption { defineRageBuiltins = mkOption {
default = true; default = true;
@ -43,7 +46,7 @@ in {
}; };
secretFiles = mkOption { secretFiles = mkOption {
default = {}; default = { };
type = types.attrsOf types.path; type = types.attrsOf types.path;
example = literalExpression "{ local = ./secrets.nix.age; }"; example = literalExpression "{ local = ./secrets.nix.age; }";
description = mdDoc '' description = mdDoc ''
@ -56,28 +59,30 @@ in {
secrets = mkOption { secrets = mkOption {
readOnly = true; readOnly = true;
default = default = mapAttrs (_: x: importEncrypted x inputs) cfg.secretFiles;
mapAttrs (_: x: importEncrypted x inputs) cfg.secretFiles;
description = mdDoc '' description = mdDoc ''
the secrets decrypted from the secretFiles the secrets decrypted from the secretFiles
''; '';
}; };
}; };
config.home-manager.sharedModules = [ config.home-manager.sharedModules = [
({config, ...}: { (
options = { { config, ... }:
userSecretsFile = mkOption { {
default = ../users/${config._module.args.name}/secrets.nix.age; options = {
type = types.path; userSecretsFile = mkOption {
description = "The global secrets attribute that should be exposed to the user"; default = ../users/${config._module.args.name}/secrets.nix.age;
type = types.path;
description = "The global secrets attribute that should be exposed to the user";
};
userSecrets = mkOption {
readOnly = true;
default = importEncrypted config.userSecretsFile inputs;
type = types.unspecified;
description = "User secrets";
};
}; };
userSecrets = mkOption { }
readOnly = true; )
default = importEncrypted config.userSecretsFile inputs;
type = types.unspecified;
description = "User secrets";
};
};
})
]; ];
} }

View file

@ -3,9 +3,9 @@
config, config,
lib, lib,
... ...
}: let }:
inherit let
(lib) inherit (lib)
mkOption mkOption
types types
flip flip
@ -19,83 +19,85 @@
"x-systemd.device-timeout=5s" "x-systemd.device-timeout=5s"
"x-systemd.mount-timeout=5s" "x-systemd.mount-timeout=5s"
]; ];
in { in
{
# Give users the ability to add their own smb shares # Give users the ability to add their own smb shares
home-manager.sharedModules = [ home-manager.sharedModules = [
{ {
options.home.smb = mkOption { options.home.smb = mkOption {
description = "Samba shares to be mountable under $HOME/smb"; description = "Samba shares to be mountable under $HOME/smb";
default = []; default = [ ];
type = types.listOf (types.submodule ({config, ...}: { type = types.listOf (
options = { types.submodule (
localPath = mkOption { { config, ... }:
description = "The path under which the share will be mounted. Defaults to the remotePath"; {
type = types.str; options = {
default = config.remotePath; localPath = mkOption {
}; description = "The path under which the share will be mounted. Defaults to the remotePath";
address = mkOption { type = types.str;
description = "The remote share address"; default = config.remotePath;
type = types.str; };
example = "10.1.2.5"; address = mkOption {
}; description = "The remote share address";
remotePath = mkOption { type = types.str;
description = "The remote share path"; example = "10.1.2.5";
type = types.str; };
example = "data-10"; remotePath = mkOption {
}; description = "The remote share path";
credentials = mkOption { type = types.str;
description = "A smb credential file to access the remote share"; example = "data-10";
type = types.path; };
}; credentials = mkOption {
automatic = mkOption { description = "A smb credential file to access the remote share";
description = "Whether this share should be automatically mounted on boot"; type = types.path;
default = false; };
type = types.bool; automatic = mkOption {
}; description = "Whether this share should be automatically mounted on boot";
}; default = false;
})); type = types.bool;
};
};
}
)
);
}; };
} }
]; ];
imports = [ imports = [
{ {
environment.systemPackages = [pkgs.cifs-utils]; environment.systemPackages = [ pkgs.cifs-utils ];
fileSystems = fileSystems = mkMerge (
mkMerge flip concatMap (attrNames config.home-manager.users) (
( user:
flip let
concatMap parentPath = "/home/${user}/smb";
(attrNames config.home-manager.users) cfg = config.home-manager.users.${user}.home.smb;
( inherit (config.users.users.${user}) uid;
user: let inherit (config.users.groups.${user}) gid;
parentPath = "/home/${user}/smb"; in
cfg = config.home-manager.users.${user}.home.smb; flip map cfg (cfg: {
inherit (config.users.users.${user}) uid; "${parentPath}/${cfg.localPath}" =
inherit (config.users.groups.${user}) gid; let
in options =
flip map cfg ( baseOptions
cfg: { ++ [
"${parentPath}/${cfg.localPath}" = let "uid=${toString uid}"
options = "gid=${toString gid}"
baseOptions "file_mode=0600"
++ [ "dir_mode=0700"
"uid=${toString uid}" "credentials=${cfg.credentials}"
"gid=${toString gid}" ]
"file_mode=0600" ++ (optional (!cfg.automatic) "noauto");
"dir_mode=0700" in
"credentials=${cfg.credentials}" {
] inherit options;
++ (optional (!cfg.automatic) "noauto"); device = "//${cfg.address}/${cfg.remotePath}";
in { fsType = "cifs";
inherit options; };
device = "//${cfg.address}/${cfg.remotePath}"; })
fsType = "cifs"; )
}; );
}
)
)
);
} }
]; ];
} }

View file

@ -1,70 +1,69 @@
{ { self, ... }:
self, system:
... let
}: system: let
pkgs = self.pkgs.${system}; pkgs = self.pkgs.${system};
in in
pkgs.devshell.mkShell { pkgs.devshell.mkShell {
name = "nix-config"; name = "nix-config";
packages = with pkgs; [ packages = with pkgs; [
# Nix # Nix
nil nil
# Misc # Misc
shellcheck shellcheck
pre-commit pre-commit
rage rage
nix nix
nix-diff nix-diff
nix-update nix-update
]; ];
commands = [ commands = [
{ {
package = pkgs.deploy; package = pkgs.deploy;
help = "build and deploy nix configurations"; help = "build and deploy nix configurations";
} }
{ {
package = pkgs.agenix-rekey; package = pkgs.agenix-rekey;
help = "Edit and rekey repository secrets"; help = "Edit and rekey repository secrets";
} }
{ {
package = pkgs.nixfmt-rfc-style; package = pkgs.nixfmt-rfc-style;
help = "Format nix code"; help = "Format nix code";
} }
{ {
package = pkgs.statix; package = pkgs.statix;
help = "Linter for nix"; help = "Linter for nix";
} }
{ {
package = pkgs.deadnix; package = pkgs.deadnix;
help = "Remove dead nix code"; help = "Remove dead nix code";
} }
{ {
package = pkgs.nix-tree; package = pkgs.nix-tree;
help = "Show nix closure tree"; help = "Show nix closure tree";
} }
{ {
package = pkgs.update-nix-fetchgit; package = pkgs.update-nix-fetchgit;
help = "Update fetcher inside nix files"; help = "Update fetcher inside nix files";
} }
{ {
package = pkgs.nvd; package = pkgs.nvd;
help = "List package differences between systems"; help = "List package differences between systems";
} }
{ {
package = pkgs.vulnix; package = pkgs.vulnix;
help = "List vulnerabilities found in your system"; help = "List vulnerabilities found in your system";
} }
]; ];
env = [ env = [
{ {
name = "NIX_CONFIG"; name = "NIX_CONFIG";
value = '' value = ''
plugin-files = ${pkgs.nix-plugins}/lib/nix/plugins plugin-files = ${pkgs.nix-plugins}/lib/nix/plugins
extra-builtins-file = ${../nix}/extra-builtins.nix extra-builtins-file = ${../nix}/extra-builtins.nix
''; '';
} }
]; ];
devshell.startup.pre-commit.text = self.checks.${system}.pre-commit-check.shellHook; devshell.startup.pre-commit.text = self.checks.${system}.pre-commit-check.shellHook;
} }

View file

@ -14,21 +14,40 @@
# ''; # '';
# } # }
# ``` # ```
{exec, ...}: let { exec, ... }:
let
assertMsg = pred: msg: pred || builtins.throw msg; assertMsg = pred: msg: pred || builtins.throw msg;
hasSuffix = suffix: content: let hasSuffix =
lenContent = builtins.stringLength content; suffix: content:
lenSuffix = builtins.stringLength suffix; let
in lenContent = builtins.stringLength content;
lenSuffix = builtins.stringLength suffix;
in
lenContent >= lenSuffix && builtins.substring (lenContent - lenSuffix) lenContent content == suffix; lenContent >= lenSuffix && builtins.substring (lenContent - lenSuffix) lenContent content == suffix;
in { in
{
# Instead of calling rage directly here, we call a wrapper script that will cache the output # Instead of calling rage directly here, we call a wrapper script that will cache the output
# in a predictable path in /tmp, which allows us to only require the password for each encrypted # in a predictable path in /tmp, which allows us to only require the password for each encrypted
# file once. # file once.
rageImportEncrypted = identities: nixFile: rageImportEncrypted =
assert assertMsg (builtins.isPath nixFile) "The file to decrypt must be given as a path to prevent impurity."; identities: nixFile:
assert assertMsg (hasSuffix ".nix.age" nixFile) "The content of the decrypted file must be a nix expression and should therefore end in .nix.age"; assert assertMsg (builtins.isPath nixFile)
exec ([./rage-decrypt-and-cache.sh nixFile] ++ identities); "The file to decrypt must be given as a path to prevent impurity.";
assert assertMsg (hasSuffix ".nix.age" nixFile)
"The content of the decrypted file must be a nix expression and should therefore end in .nix.age";
exec (
[
./rage-decrypt-and-cache.sh
nixFile
]
++ identities
);
# currentSystem # currentSystem
unsafeCurrentSystem = exec ["nix" "eval" "--impure" "--expr" "builtins.currentSystem"]; unsafeCurrentSystem = exec [
"nix"
"eval"
"--impure"
"--expr"
"builtins.currentSystem"
];
} }

View file

@ -1,11 +1,15 @@
{self, ...}: nodeName: nodeAttrs: let { self, ... }:
nodeName: nodeAttrs:
let
#FIXME inherit nodeAttrs. system; #FIXME inherit nodeAttrs. system;
system = "x86_64-linux"; system = "x86_64-linux";
pkgs = self.pkgs.${system}; pkgs = self.pkgs.${system};
disko-script = pkgs.writeShellScriptBin "disko-script" "${nodeAttrs.config.system.build.diskoScript}"; disko-script = pkgs.writeShellScriptBin "disko-script" "${nodeAttrs.config.system.build.diskoScript
}";
disko-mount = pkgs.writeShellScriptBin "disko-mount" "${nodeAttrs.config.system.build.mountScript}"; disko-mount = pkgs.writeShellScriptBin "disko-mount" "${nodeAttrs.config.system.build.mountScript}";
disko-format = pkgs.writeShellScriptBin "disko-format" "${nodeAttrs.config.system.build.formatScript}"; disko-format = pkgs.writeShellScriptBin "disko-format" "${nodeAttrs.config.system.build.formatScript
}";
install-system = pkgs.writeShellScriptBin "install-system" '' install-system = pkgs.writeShellScriptBin "install-system" ''
set -euo pipefail set -euo pipefail
@ -28,7 +32,8 @@
install-system install-system
]; ];
}; };
in { in
{
# Everything required for the installer as a single package, # Everything required for the installer as a single package,
# so it can be used from an existing live system by copying the derivation. # so it can be used from an existing live system by copying the derivation.
packages.${system}.installer-package.${nodeName} = installer-package; packages.${system}.installer-package.${nodeName} = installer-package;

View file

@ -1,7 +1,7 @@
inputs: let inputs:
let
inherit (inputs) self; inherit (inputs) self;
inherit inherit (inputs.nixpkgs.lib)
(inputs.nixpkgs.lib)
concatMapAttrs concatMapAttrs
filterAttrs filterAttrs
flip flip
@ -12,9 +12,12 @@ inputs: let
; ;
# Creates a new nixosSystem with the correct specialArgs, pkgs and name definition # Creates a new nixosSystem with the correct specialArgs, pkgs and name definition
mkHost = {minimal}: name: let mkHost =
pkgs = self.pkgs.x86_64-linux; { minimal }:
in name:
let
pkgs = self.pkgs.x86_64-linux;
in
nixosSystem { nixosSystem {
specialArgs = { specialArgs = {
# Use the correct instance lib that has our overlays # Use the correct instance lib that has our overlays
@ -42,22 +45,30 @@ inputs: let
# to instanciate hosts correctly. # to instanciate hosts correctly.
hosts = builtins.attrNames (filterAttrs (_: type: type == "directory") (builtins.readDir ../hosts)); hosts = builtins.attrNames (filterAttrs (_: type: type == "directory") (builtins.readDir ../hosts));
# Process each nixosHosts declaration and generatea nixosSystem definitions # Process each nixosHosts declaration and generatea nixosSystem definitions
nixosConfigurations = genAttrs hosts (mkHost {minimal = false;}); nixosConfigurations = genAttrs hosts (mkHost {
minimalConfigurations = genAttrs hosts (mkHost {minimal = true;}); minimal = false;
});
minimalConfigurations = genAttrs hosts (mkHost {
minimal = true;
});
# True NixOS nodes can define additional guest nodes that are built # True NixOS nodes can define additional guest nodes that are built
# together with it. We collect all defined guests from each node here # together with it. We collect all defined guests from each node here
# to allow accessing any node via the unified attribute `nodes`. # to allow accessing any node via the unified attribute `nodes`.
guestConfigurations = flip concatMapAttrs self.nixosConfigurations (_: node: guestConfigurations = flip concatMapAttrs self.nixosConfigurations (
flip mapAttrs' (node.config.guests or {}) ( _: node:
flip mapAttrs' (node.config.guests or { }) (
guestName: guestDef: guestName: guestDef:
nameValuePair guestDef.nodeName ( nameValuePair guestDef.nodeName (
if guestDef.backend == "microvm" if guestDef.backend == "microvm" then
then node.config.microvm.vms.${guestName}.config node.config.microvm.vms.${guestName}.config
else node.config.containers.${guestName}.nixosConfiguration else
) node.config.containers.${guestName}.nixosConfiguration
)); )
in { )
);
in
{
inherit inherit
hosts hosts
nixosConfigurations nixosConfigurations

View file

@ -1,4 +1,5 @@
{pkgs, ...}: { { pkgs, ... }:
{
nix.extraOptions = '' nix.extraOptions = ''
experimental-features = nix-command flakes recursive-nix experimental-features = nix-command flakes recursive-nix
''; '';

View file

@ -1 +1 @@
{} { }

View file

@ -71,7 +71,7 @@ stdenv.mkDerivation rec {
homepage = "https://actualbudget.com/"; homepage = "https://actualbudget.com/";
license = licenses.mit; license = licenses.mit;
mainProgram = "actual-server"; mainProgram = "actual-server";
maintainers = with maintainers; [patrickdag]; maintainers = with maintainers; [ patrickdag ];
platforms = ["x86_64-linux"]; platforms = [ "x86_64-linux" ];
}; };
} }

View file

@ -1,7 +1,5 @@
{ { pkgs, fetchurl }:
pkgs, let
fetchurl,
}: let
name = "awakened-poe-trade"; name = "awakened-poe-trade";
version = "3.22.10003"; version = "3.22.10003";
description = "Path of Exile trading app for price checking"; description = "Path of Exile trading app for price checking";
@ -23,17 +21,17 @@
sha256 = "sha256-fZ3PU+yE1n/RytkPFAXQhU85KNQStYcSrdgw+OYfJRg="; sha256 = "sha256-fZ3PU+yE1n/RytkPFAXQhU85KNQStYcSrdgw+OYfJRg=";
}; };
in in
pkgs.appimageTools.wrapType2 { pkgs.appimageTools.wrapType2 {
name = "awakened-poe-trade"; name = "awakened-poe-trade";
src = fetchurl { src = fetchurl {
url = "https://github.com/SnosMe/awakened-poe-trade/releases/download/v${version}/${file}"; url = "https://github.com/SnosMe/awakened-poe-trade/releases/download/v${version}/${file}";
hash = "sha256-b+cDOmU0s0MqP5ZgCacmAon8UqDejG4HcOqi+Uf2dEM="; hash = "sha256-b+cDOmU0s0MqP5ZgCacmAon8UqDejG4HcOqi+Uf2dEM=";
}; };
extraInstallCommands = '' extraInstallCommands = ''
mkdir -p $out/share/applications mkdir -p $out/share/applications
cp ${icon} $out/share/applications/awakened-poe-trade.png cp ${icon} $out/share/applications/awakened-poe-trade.png
cp ${desktopEntry} $out/share/applications/${name}.desktop cp ${desktopEntry} $out/share/applications/${name}.desktop
substituteInPlace $out/share/applications/awakened-poe-trade.desktop --replace /share/ $out/share/ substituteInPlace $out/share/applications/awakened-poe-trade.desktop --replace /share/ $out/share/
''; '';
} }

View file

@ -1,39 +1,43 @@
[ [
(import ./scripts) (import ./scripts)
(_self: super: { (_self: super: {
zsh-histdb-skim = super.callPackage ./zsh-histdb-skim.nix {}; zsh-histdb-skim = super.callPackage ./zsh-histdb-skim.nix { };
zsh-histdb = super.callPackage ./zsh-histdb.nix {}; zsh-histdb = super.callPackage ./zsh-histdb.nix { };
actual = super.callPackage ./actual.nix {}; actual = super.callPackage ./actual.nix { };
pr-tracker = super.callPackage ./pr-tracker.nix {}; pr-tracker = super.callPackage ./pr-tracker.nix { };
homebox = super.callPackage ./homebox.nix {}; homebox = super.callPackage ./homebox.nix { };
deploy = super.callPackage ./deploy.nix {}; deploy = super.callPackage ./deploy.nix { };
mongodb-bin = super.callPackage ./mongodb-bin.nix {}; mongodb-bin = super.callPackage ./mongodb-bin.nix { };
awakened-poe-trade = super.callPackage ./awakened-poe-trade.nix {}; awakened-poe-trade = super.callPackage ./awakened-poe-trade.nix { };
neovim-clean = super.neovim-unwrapped.overrideAttrs (_neovimFinal: neovimPrev: { neovim-clean = super.neovim-unwrapped.overrideAttrs (
nativeBuildInputs = (neovimPrev.nativeBuildInputs or []) ++ [super.makeWrapper]; _neovimFinal: neovimPrev: {
postInstall = nativeBuildInputs = (neovimPrev.nativeBuildInputs or [ ]) ++ [ super.makeWrapper ];
(neovimPrev.postInstall or "") postInstall =
+ '' (neovimPrev.postInstall or "")
wrapProgram $out/bin/nvim --add-flags "--clean" + ''
''; wrapProgram $out/bin/nvim --add-flags "--clean"
}); '';
kanidm = super.kanidm.overrideAttrs (old: let }
provisionSrc = super.fetchFromGitHub { );
owner = "oddlama"; kanidm = super.kanidm.overrideAttrs (
repo = "kanidm-provision"; old:
rev = "v1.1.0"; let
hash = "sha256-pFOFFKh3la/sZGXj+pAM8x4SMeffvvbOvTjPeHS1XPU="; provisionSrc = super.fetchFromGitHub {
}; owner = "oddlama";
in { repo = "kanidm-provision";
patches = rev = "v1.1.0";
old.patches hash = "sha256-pFOFFKh3la/sZGXj+pAM8x4SMeffvvbOvTjPeHS1XPU=";
++ [ };
in
{
patches = old.patches ++ [
"${provisionSrc}/patches/1.2.0-oauth2-basic-secret-modify.patch" "${provisionSrc}/patches/1.2.0-oauth2-basic-secret-modify.patch"
"${provisionSrc}/patches/1.2.0-recover-account.patch" "${provisionSrc}/patches/1.2.0-recover-account.patch"
]; ];
passthru.enableSecretProvisioning = true; passthru.enableSecretProvisioning = true;
doCheck = false; doCheck = false;
}); }
kanidm-provision = super.callPackage ./kanidm-provision.nix {}; );
kanidm-provision = super.callPackage ./kanidm-provision.nix { };
}) })
] ]

View file

@ -3,7 +3,8 @@
writeShellApplication, writeShellApplication,
nvd, nvd,
nix-output-monitor, nix-output-monitor,
}: let }:
let
deploy = writeShellApplication { deploy = writeShellApplication {
name = "deploy"; name = "deploy";
text = '' text = ''
@ -166,7 +167,10 @@
''; '';
}; };
in in
symlinkJoin { symlinkJoin {
name = "deploy and build"; name = "deploy and build";
paths = [deploy build]; paths = [
} deploy
build
];
}

View file

@ -7,7 +7,8 @@
lib, lib,
buildGoModule, buildGoModule,
fetchFromGitHub, fetchFromGitHub,
}: let }:
let
pname = "homebox"; pname = "homebox";
version = "0.10.3"; version = "0.10.3";
src = "${fetchFromGitHub { src = "${fetchFromGitHub {
@ -101,38 +102,38 @@
outputHash = "sha256-BVZSdc8e6v+paMzMYazEdnKSNw+OnCpjSzGSEKxVl24="; outputHash = "sha256-BVZSdc8e6v+paMzMYazEdnKSNw+OnCpjSzGSEKxVl24=";
}; };
in in
buildGoModule { buildGoModule {
inherit pname version; inherit pname version;
src = "${src}/backend"; src = "${src}/backend";
vendorHash = "sha256-TtFz+dDpoMs3PAQjiYQm1+Q6prn4Hiaf7xqWt41oY7w="; vendorHash = "sha256-TtFz+dDpoMs3PAQjiYQm1+Q6prn4Hiaf7xqWt41oY7w=";
CGO_ENABLED = 0; CGO_ENABLED = 0;
GOOS = "linux"; GOOS = "linux";
doCheck = false; doCheck = false;
# options used by upstream: # options used by upstream:
# https://github.com/simulot/immich-go/blob/0.13.2/.goreleaser.yaml # https://github.com/simulot/immich-go/blob/0.13.2/.goreleaser.yaml
ldflags = [ ldflags = [
"-s" "-s"
"-w" "-w"
"-extldflags=-static" "-extldflags=-static"
"-X main.version=${version}" "-X main.version=${version}"
]; ];
preBuild = '' preBuild = ''
ldflags+=" -X main.commit=$(cat COMMIT)" ldflags+=" -X main.commit=$(cat COMMIT)"
ldflags+=" -X main.date=$(cat SOURCE_DATE)" ldflags+=" -X main.date=$(cat SOURCE_DATE)"
mkdir -p ./app/api/static/public mkdir -p ./app/api/static/public
cp -r ${frontend}/* ./app/api/static/public cp -r ${frontend}/* ./app/api/static/public
''; '';
meta = with lib; { meta = with lib; {
mainProgram = "api"; mainProgram = "api";
homepage = "https://hay-kot.github.io/homebox/"; homepage = "https://hay-kot.github.io/homebox/";
maintainers = with maintainers; [patrickdag]; maintainers = with maintainers; [ patrickdag ];
license = licenses.agpl3Only; license = licenses.agpl3Only;
description = "A inventory and organization system built for the Home User"; description = "A inventory and organization system built for the Home User";
platforms = platforms.all; platforms = platforms.all;
}; };
} }

View file

@ -19,8 +19,11 @@ rustPlatform.buildRustPackage rec {
meta = with lib; { meta = with lib; {
description = "A small utility to help with kanidm provisioning"; description = "A small utility to help with kanidm provisioning";
homepage = "https://github.com/oddlama/kanidm-provision"; homepage = "https://github.com/oddlama/kanidm-provision";
license = with licenses; [asl20 mit]; license = with licenses; [
maintainers = with maintainers; [oddlama]; asl20
mit
];
maintainers = with maintainers; [ oddlama ];
mainProgram = "kanidm-provision"; mainProgram = "kanidm-provision";
}; };
} }

View file

@ -11,27 +11,26 @@ stdenv.mkDerivation {
pname = "mongodb-bin"; pname = "mongodb-bin";
version = "1.0.0"; version = "1.0.0";
srcs = [ srcs = [
( (fetchurl {
fetchurl { url = "https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2204-6.0.14.tgz";
url = "https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2204-6.0.14.tgz"; hash = "sha256-1MW3pVIffdxq63gY64ozM1erWM2ou2L8T+MTfG+ZPLg=";
hash = "sha256-1MW3pVIffdxq63gY64ozM1erWM2ou2L8T+MTfG+ZPLg="; })
} (fetchurl {
) url = "https://downloads.mongodb.com/compass/mongosh-2.1.5-linux-x64.tgz";
( hash = "sha256-R1GGB0ZGqmpJtMUNF2+EJK6iNiChHuoHyOf2vKDcOKA=";
fetchurl { })
url = "https://downloads.mongodb.com/compass/mongosh-2.1.5-linux-x64.tgz";
hash = "sha256-R1GGB0ZGqmpJtMUNF2+EJK6iNiChHuoHyOf2vKDcOKA=";
}
)
]; ];
sourceRoot = "."; sourceRoot = ".";
nativeBuildInputs = [ nativeBuildInputs = [ autoPatchelfHook ];
autoPatchelfHook
];
buildPhase = '' buildPhase = ''
mkdir -p $out/bin mkdir -p $out/bin
cp mongosh-2.1.5-linux-x64/bin/mongosh $out/bin/mongo cp mongosh-2.1.5-linux-x64/bin/mongosh $out/bin/mongo
cp mongodb-linux-x86_64-ubuntu2204-6.0.14/bin/mongod $out/bin/mongod cp mongodb-linux-x86_64-ubuntu2204-6.0.14/bin/mongod $out/bin/mongod
''; '';
buildInputs = [openssl curl xz libgcc]; buildInputs = [
openssl
curl
xz
libgcc
];
} }

View file

@ -56,7 +56,7 @@ buildNpmPackage rec {
homepage = "https://github.com/ollama-webui/ollama-webui"; homepage = "https://github.com/ollama-webui/ollama-webui";
license = licenses.mit; license = licenses.mit;
mainProgram = pname; mainProgram = pname;
maintainers = with maintainers; [malteneuss]; maintainers = with maintainers; [ malteneuss ];
platforms = platforms.all; platforms = platforms.all;
}; };
} }

View file

@ -18,8 +18,11 @@ rustPlatform.buildRustPackage {
cargoHash = "sha256-9bhKtg2g5H4zGn7yVCjTazeXfeoKjtAKAlzkLkCraiw="; cargoHash = "sha256-9bhKtg2g5H4zGn7yVCjTazeXfeoKjtAKAlzkLkCraiw=";
nativeBuildInputs = [pkg-config]; nativeBuildInputs = [ pkg-config ];
buildInputs = [openssl systemd]; buildInputs = [
openssl
systemd
];
meta = with lib; { meta = with lib; {
description = "Nixpkgs pull request channel tracker"; description = "Nixpkgs pull request channel tracker";
@ -29,7 +32,7 @@ rustPlatform.buildRustPackage {
''; '';
platforms = platforms.linux; platforms = platforms.linux;
license = licenses.agpl3Plus; license = licenses.agpl3Plus;
maintainers = with maintainers; [patrickdag]; maintainers = with maintainers; [ patrickdag ];
mainProgram = "pr-tracker"; mainProgram = "pr-tracker";
}; };
} }

View file

@ -7,7 +7,12 @@
}: }:
writeShellApplication { writeShellApplication {
name = "clone-term"; name = "clone-term";
runtimeInputs = [ps procps xdotool jq]; runtimeInputs = [
ps
procps
xdotool
jq
];
text = '' text = ''
if [[ ''${XDG_CURRENT_DESKTOP-} == sway ]]; then if [[ ''${XDG_CURRENT_DESKTOP-} == sway ]]; then

View file

@ -1,7 +1,7 @@
_final: prev: { _final: prev: {
scripts = { scripts = {
usbguardw = prev.callPackage ./usbguardw.nix {}; usbguardw = prev.callPackage ./usbguardw.nix { };
clone-term = prev.callPackage ./clone-term.nix {}; clone-term = prev.callPackage ./clone-term.nix { };
impermanence-o = prev.callPackage ./impermanence-orphan.nix {}; impermanence-o = prev.callPackage ./impermanence-orphan.nix { };
}; };
} }

View file

@ -1,5 +1,5 @@
{writers}: { writers }:
writers.writePython3Bin "find-orphaned" {} '' writers.writePython3Bin "find-orphaned" { } ''
import sys import sys
import os import os
if len(sys.argv) != 2: if len(sys.argv) != 2:

View file

@ -1,4 +1,4 @@
{writeShellApplication}: { writeShellApplication }:
writeShellApplication { writeShellApplication {
name = "usguardw"; name = "usguardw";
text = '' text = ''

View file

@ -6,7 +6,7 @@
rustPlatform.buildRustPackage rec { rustPlatform.buildRustPackage rec {
pname = "zsh-histd-skim"; pname = "zsh-histd-skim";
version = "0.8.6"; version = "0.8.6";
buildInputs = [sqlite]; buildInputs = [ sqlite ];
src = fetchFromGitHub { src = fetchFromGitHub {
owner = "m42e"; owner = "m42e";
repo = "zsh-histdb-skim"; repo = "zsh-histdb-skim";

View file

@ -1,4 +1,5 @@
{pkgs, ...}: { { pkgs, ... }:
{
home.packages = [ home.packages = [
pkgs.xclip pkgs.xclip
pkgs.xdragon pkgs.xdragon

View file

@ -79,13 +79,15 @@ MOD: TAGS: pkgs:
"${MOD}-m " = "spawn ${pkgs.thunderbird}/bin/thunderbird"; "${MOD}-m " = "spawn ${pkgs.thunderbird}/bin/thunderbird";
"Menu" = "spawn rofi -show drun"; "Menu" = "spawn rofi -show drun";
} }
// builtins.listToAttrs (map (x: { // builtins.listToAttrs (
map (x: {
name = "${MOD}-${x}"; name = "${MOD}-${x}";
value = "use_index ${x}"; value = "use_index ${x}";
}) }) TAGS
TAGS) )
// builtins.listToAttrs (map (x: { // builtins.listToAttrs (
map (x: {
name = "${MOD}-Shift-${x}"; name = "${MOD}-Shift-${x}";
value = "move_index ${x}"; value = "move_index ${x}";
}) }) TAGS
TAGS) )

Some files were not shown because too many files have changed in this diff Show more