From e469eab2b83649e94f702436b3ff07e133c0b2ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Gro=C3=9Fmann?= Date: Thu, 11 Jan 2024 22:42:03 +0100 Subject: [PATCH] feat: adguard home feat: implement extra module containers feat: final smb confi feat: final nextcloud confi feat: rework networks ip feat: move acme and ddclient to server --- flake.lock | 6 +- flake.nix | 1 - hosts.toml | 4 + hosts/desktopnix/default.nix | 3 +- hosts/desktopnix/fs.nix | 1 + hosts/desktopnix/net.nix | 9 +- hosts/elisabeth/default.nix | 34 ++++ hosts/elisabeth/fs.nix | 128 ++++++++++++ hosts/elisabeth/guests.nix | 130 ++++++++++++ hosts/elisabeth/net.nix | 42 ++++ .../secrets/generated/dhparams.pem.age | 20 ++ .../generated/initrd_host_ed25519_key.age | Bin 0 -> 1205 bytes .../secrets/nextcloud/generated/ncpasswd.age | Bin 0 -> 801 bytes hosts/elisabeth/secrets/secrets.nix.age | Bin 0 -> 1116 bytes hosts/gojo/default.nix | 1 - hosts/gojo/fs.nix | 4 +- hosts/maddy/fs.nix | 4 +- hosts/patricknix/default.nix | 1 - hosts/patricknix/fs.nix | 4 +- hosts/testienix/default.nix | 1 - hosts/testienix/fs.nix | 8 +- .../testienix/secrets/generated/ncpasswd.age | 16 ++ lib/default.nix | 1 - modules/config/system.nix | 3 + modules/hardware/intel.nix | 4 - modules/services/adguardhome.nix | 65 ++++++ modules/services/containers.nix | 118 ----------- modules/services/ddclient.nix | 4 +- modules/services/nextcloud.nix | 190 ++++++++---------- modules/services/samba.nix | 36 ++-- nix/hosts.nix | 17 +- secrets/secrets.nix.age | Bin 4351 -> 4551 bytes 32 files changed, 574 insertions(+), 281 deletions(-) create mode 100644 hosts/elisabeth/default.nix create mode 100644 hosts/elisabeth/fs.nix create mode 100644 hosts/elisabeth/guests.nix create mode 100644 hosts/elisabeth/net.nix create mode 100644 hosts/elisabeth/secrets/generated/dhparams.pem.age create mode 100644 hosts/elisabeth/secrets/generated/initrd_host_ed25519_key.age create mode 100644 hosts/elisabeth/secrets/nextcloud/generated/ncpasswd.age create mode 100644 hosts/elisabeth/secrets/secrets.nix.age create mode 100644 hosts/testienix/secrets/generated/ncpasswd.age delete mode 100644 modules/hardware/intel.nix create mode 100644 modules/services/adguardhome.nix delete mode 100644 modules/services/containers.nix diff --git a/flake.lock b/flake.lock index e1414be..5370373 100644 --- a/flake.lock +++ b/flake.lock @@ -1056,11 +1056,11 @@ "pre-commit-hooks": "pre-commit-hooks_2" }, "locked": { - "lastModified": 1704938286, - "narHash": "sha256-/uv+N2v5ixqYz7SG8R5GWOTdrNKboHEp85BR5Jdz6qE=", + "lastModified": 1704999567, + "narHash": "sha256-Whj1PFPomS/f97OD30CRrETTH/dmnUJdjEevDLJG4MM=", "owner": "oddlama", "repo": "nixos-extra-modules", - "rev": "c55f465ba1f369852ab4122a9fa42c85b4a571de", + "rev": "4744a2844cd74ca9b122fbaaae5ae97159c0d30e", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 1243864..a5fe818 100644 --- a/flake.nix +++ b/flake.nix @@ -165,7 +165,6 @@ modules = [ ./nix/installer-configuration.nix ./modules/config/ssh.nix - {system.stateVersion = stateVersion;} ]; format = { diff --git a/hosts.toml b/hosts.toml index ed7aa3d..ca3ca94 100644 --- a/hosts.toml +++ b/hosts.toml @@ -17,3 +17,7 @@ system = "x86_64-linux" [maddy] type = "nixos" system = "x86_64-linux" + +[elisabeth] +type = "nixos" +system = "x86_64-linux" diff --git a/hosts/desktopnix/default.nix b/hosts/desktopnix/default.nix index 27b6551..7321aef 100644 --- a/hosts/desktopnix/default.nix +++ b/hosts/desktopnix/default.nix @@ -1,6 +1,8 @@ {inputs, ...}: { imports = [ inputs.nixos-hardware.nixosModules.common-gpu-nvidia-nonprime + inputs.nixos-hardware.nixosModules.common-cpu-intel + inputs.nixos-hardware.nixosModules.common-pc inputs.nixos-hardware.nixosModules.common-pc inputs.nixos-hardware.nixosModules.common-pc-hdd inputs.nixos-hardware.nixosModules.common-pc-ssd @@ -12,7 +14,6 @@ ../../modules/optional/xserver.nix ../../modules/optional/secureboot.nix - ../../modules/hardware/intel.nix ../../modules/hardware/nintendo.nix ../../modules/hardware/nvidia.nix ../../modules/hardware/physical.nix diff --git a/hosts/desktopnix/fs.nix b/hosts/desktopnix/fs.nix index f2e3531..2e521cd 100644 --- a/hosts/desktopnix/fs.nix +++ b/hosts/desktopnix/fs.nix @@ -43,4 +43,5 @@ fileSystems."/state".neededForBoot = true; fileSystems."/persist".neededForBoot = true; fileSystems."/panzer/state".neededForBoot = true; + boot.initrd.systemd.services."zfs-import-panzer".after = ["cryptsetup.target"]; } diff --git a/hosts/desktopnix/net.nix b/hosts/desktopnix/net.nix index 7a47fdb..7309a48 100644 --- a/hosts/desktopnix/net.nix +++ b/hosts/desktopnix/net.nix @@ -4,19 +4,12 @@ }; systemd.network.networks = { "01-lan1" = { - address = ["192.168.178.30/24"]; - gateway = ["192.168.178.1"]; + DHCP = "yes"; matchConfig.MACAddress = config.secrets.secrets.local.networking.lan1.mac; - dns = ["192.168.178.2"]; networkConfig = { IPv6PrivacyExtensions = "yes"; MulticastDNS = true; }; }; }; - - networking.extraHosts = '' - 192.168.178.32 pgrossmann.org - 192.168.178.32 nc.pgrossmann.org - ''; } diff --git a/hosts/elisabeth/default.nix b/hosts/elisabeth/default.nix new file mode 100644 index 0000000..49a8631 --- /dev/null +++ b/hosts/elisabeth/default.nix @@ -0,0 +1,34 @@ +{ + inputs, + minimal, + lib, + ... +}: { + imports = + [ + inputs.nixos-hardware.nixosModules.common-pc + inputs.nixos-hardware.nixosModules.common-pc-ssd + inputs.nixos-hardware.nixosModules.common-pc-hdd + inputs.nixos-hardware.nixosModules.common-cpu-amd + inputs.nixos-hardware.nixosModules.common-cpu-amd-pstate + + ../../modules/config + ../../modules/optional/initrd-ssh.nix + + ../../modules/hardware/physical.nix + ../../modules/hardware/zfs.nix + + ../../modules/services/acme.nix + ../../modules/services/ddclient.nix + + ./net.nix + ./fs.nix + ] + ++ lib.lists.optionals (!minimal) [ + ./guests.nix + ]; + services.xserver = { + layout = "de"; + xkbVariant = "bone"; + }; +} diff --git a/hosts/elisabeth/fs.nix b/hosts/elisabeth/fs.nix new file mode 100644 index 0000000..5cf1d03 --- /dev/null +++ b/hosts/elisabeth/fs.nix @@ -0,0 +1,128 @@ +{ + config, + lib, + ... +}: { + disko.devices = { + disk = { + internal-ssd = { + type = "disk"; + device = "/dev/disk/by-id/${config.secrets.secrets.local.disko.nvme}"; + content = with lib.disko.gpt; { + type = "table"; + format = "gpt"; + partitions = [ + (partEfi "boot" "0%" "1GiB") + (partLuksZfs "ssd" "rpool" "1GiB" "100%") + ]; + }; + }; + "4TB-hdd-1" = { + type = "disk"; + device = "/dev/disk/by-id/${config.secrets.secrets.local.disko."4TB-1"}"; + content = lib.disko.content.luksZfs "hdd-4TB-1" "renaultft"; + }; + "4TB-hdd-2" = { + type = "disk"; + device = "/dev/disk/by-id/${config.secrets.secrets.local.disko."4TB-2"}"; + content = lib.disko.content.luksZfs "hdd-4TB-2" "renaultft"; + }; + "4TB-hdd-3" = { + type = "disk"; + device = "/dev/disk/by-id/${config.secrets.secrets.local.disko."4TB-3"}"; + content = lib.disko.content.luksZfs "hdd-4TB-3" "renaultft"; + }; + "8TB-hdd-1" = { + type = "disk"; + device = "/dev/disk/by-id/${config.secrets.secrets.local.disko."8TB-1"}"; + content = lib.disko.content.luksZfs "hdd-8TB-1" "panzer"; + }; + "8TB-hdd-2" = { + type = "disk"; + device = "/dev/disk/by-id/${config.secrets.secrets.local.disko."8TB-2"}"; + content = lib.disko.content.luksZfs "hdd-8TB-2" "panzer"; + }; + "8TB-hdd-3" = { + type = "disk"; + device = "/dev/disk/by-id/${config.secrets.secrets.local.disko."8TB-3"}"; + content = lib.disko.content.luksZfs "hdd-8TB-3" "panzer"; + }; + }; + + zpool = with lib.disko.zfs; { + rpool = mkZpool {datasets = impermanenceZfsDatasets;}; + panzer = mkZpool { + datasets = { + "safe/guests" = unmountable; + }; + }; + renaultft = mkZpool { + datasets = { + "safe/guests" = unmountable; + }; + }; + }; + }; + + services.zrepl = { + enable = true; + settings = { + global = { + logging = [ + { + type = "syslog"; + level = "info"; + format = "human"; + } + ]; + # TODO Monitoring + }; + jobs = [ + #{ + # type = "push"; + # name = "push-to-remote"; + #} + { + type = "snap"; + name = "mach-schnipp-schusss"; + filesystems = { + "panzer/safe<" = true; + "rpool/local/state<" = true; + "rpool/safe<" = true; + "renaultft/safe<" = true; + }; + snapshotting = { + type = "periodic"; + prefix = "zrepl-"; + interval = "10m"; + timestamp_format = "iso-8601"; + }; + pruning = { + keep = [ + { + type = "regex"; + regex = "^zrepl-.*$"; + negate = true; + } + { + type = "grid"; + grid = lib.concatStringsSep " | " [ + "1x1d(keep=all)" + "142x1h(keep=2)" + "90x1d(keep=2)" + "500x7d" + ]; + regex = "^zrepl-.*$"; + } + ]; + }; + } + ]; + }; + }; + + fileSystems."/state".neededForBoot = true; + fileSystems."/persist".neededForBoot = true; + boot.initrd.systemd.services."zfs-import-panzer".after = ["cryptsetup.target"]; + boot.initrd.systemd.services."zfs-import-renaultft".after = ["cryptsetup.target"]; +} diff --git a/hosts/elisabeth/guests.nix b/hosts/elisabeth/guests.nix new file mode 100644 index 0000000..2832a13 --- /dev/null +++ b/hosts/elisabeth/guests.nix @@ -0,0 +1,130 @@ +{ + config, + stateVersion, + inputs, + lib, + minimal, + nodes, + ... +}: let + adguardhomedomain = "adguardhome.${config.secrets.secrets.global.domains.web}"; + nextclouddomain = "nc.${config.secrets.secrets.global.domains.web}"; +in { + services.nginx = { + enable = true; + recommendedSetup = true; + upstreams.adguardhome = { + servers."TODO:3000" = {}; + + extraConfig = '' + zone adguardhome 64k ; + keepalive 5 ; + ''; + }; + virtualHosts.${adguardhomedomain} = { + forceSSL = true; + useACMEHost = "web"; + locations."/" = { + proxyPass = "http://adguardhome"; + proxyWebsockets = true; + }; + extraConfig = '' + allow 192.168.178.0/24; + deny all; + ''; + }; + upstreams.nextcloud = { + servers."TODO:80" = {}; + + extraConfig = '' + zone nextcloud 64k ; + keepalive 5 ; + ''; + }; + virtualHosts.${nextclouddomain} = { + forceSSL = true; + useACMEHost = "web"; + locations."/".proxyPass = "http://nextcloud"; + extraConfig = '' + client_max_body_size 4G ; + ''; + }; + }; + guests = let + mkGuest = guestName: { + enablePanzer ? false, + enableRenaultFT ? 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}"; + }; + modules = [ + ../../modules/config + ../../modules/services/${guestName}.nix + { + node.secretsDir = ./secrets/${guestName}; + systemd.network.networks."10-${config.guests.${guestName}.networking.mainLinkName}" = { + DHCP = lib.mkForce "no"; + address = [(lib.net.cidr.host config.secrets.secrets.global.net.ips.${config.guests.${guestName}.nodeName} config.secrets.secrets.global.net.privateSubnet)]; + gateway = [(lib.net.cidr.host 1 config.secrets.secrets.global.net.privateSubnet)]; + }; + } + ]; + }; + + #deadnix: skip + mkMicrovm = guestName: cfg: { + ${guestName} = + mkGuest guestName cfg + // { + backend = "microvm"; + microvm = { + system = "x86_64-linux"; + macvtap = "lan"; + baseMac = config.repo.secrets.local.networking.interfaces.lan.mac; + }; + extraSpecialArgs = { + inherit (inputs.self) nodes; + inherit (inputs.self.pkgs.x86_64-linux) lib; + inherit inputs minimal stateVersion; + }; + }; + }; + + mkContainer = guestName: cfg: { + ${guestName} = + mkGuest guestName cfg + // { + backend = "container"; + container.macvlan = "lan"; + extraSpecialArgs = { + inherit lib nodes inputs minimal stateVersion; + }; + }; + }; + in + {} + // mkContainer "adguardhome" {} + // mkContainer "nextcloud" { + enablePanzer = true; + } + // mkContainer "samba" { + enablePanzer = true; + enableRenaultFT = true; + }; +} diff --git a/hosts/elisabeth/net.nix b/hosts/elisabeth/net.nix new file mode 100644 index 0000000..dd4cda1 --- /dev/null +++ b/hosts/elisabeth/net.nix @@ -0,0 +1,42 @@ +{ + config, + lib, + ... +}: { + networking = { + inherit (config.secrets.secrets.local.networking) hostId; + }; + systemd.network.networks = { + "lan01" = { + address = [(lib.net.cidr.host config.secrets.secrets.global.net.ips.${config.node.name} config.secrets.secrets.global.net.privateSubnet)]; + gateway = [(lib.net.cidr.host 1 config.secrets.secrets.global.net.privateSubnet)]; + #matchConfig.MACAddress = config.secrets.secrets.local.networking.interfaces.lan01.mac; + matchConfig.Name = "lan"; + networkConfig = { + IPv6PrivacyExtensions = "yes"; + MulticastDNS = true; + }; + }; + }; + boot.initrd.systemd.network = { + enable = true; + networks = { + # redo the network cause the livesystem has macvlans + "lan01" = { + address = [(lib.net.cidr.host config.secrets.secrets.global.net.ips.${config.node.name} config.secrets.secrets.global.net.privateSubnet)]; + gateway = [(lib.net.cidr.host 1 config.secrets.secrets.global.net.privateSubnet)]; + matchConfig.MACAddress = config.secrets.secrets.local.networking.interfaces.lan01.mac; + networkConfig = { + IPv6PrivacyExtensions = "yes"; + MulticastDNS = true; + }; + }; + }; + }; + # To be able to ping containers from the host, it is necessary + # to create a macvlan on the host on the VLAN 1 network. + networking.macvlans.lan = { + interface = "lan01"; + mode = "bridge"; + }; +} diff --git a/hosts/elisabeth/secrets/generated/dhparams.pem.age b/hosts/elisabeth/secrets/generated/dhparams.pem.age new file mode 100644 index 0000000..d432a23 --- /dev/null +++ b/hosts/elisabeth/secrets/generated/dhparams.pem.age @@ -0,0 +1,20 @@ +age-encryption.org/v1 +-> X25519 WretELIMVw/omsoHEMGR7PsFsfiUEfyUmlKMzmrw+wA +IW+zJKWSMfZiKs1LQwuAtej7ZDEvDt5oY+wfWpZoB1c +-> piv-p256 XTQkUA A5MNklHowU6rYbcJBT/+dW0v9Gex5IJ1sC5ksuRsfu1k +VPN/pCvMXi6Uc1uk6yuySK/e8bSjJ66zm4W62leQpBk +-> piv-p256 ZFgiIw Ah5jjfu6nrqXrW7YqfIEKWF3PrLOmEEM5LhRvi5EJVmE +MaVt5imJLBgM3NEw7tc18g9jMwPRl9c5RgCFzDIl8hk +-> piv-p256 5vmPtQ AqViuuU1xW/ngBTWFMjZax9SaQyZ/COo0fHNOwq/8Hkb +MDD3bD8PMS3AWPougqz/BXGGZGGnFPafZ0dc7Xqa0VM +-> piv-p256 ZFgiIw AuTg62739Zom64yEb4FZfA5lyeW9YP9h+3iDQJcQZSuM +TtwsPfCJi6bYH8tpPSdf9ZQlpXUC6t/AT1wM2aCXcNM +-> "-grease +n7GU3iZJjAz/ul8nNXzXYtrR +--- mvuAEeT2IOYZKF9u/htBSJSAxKuzLjx4hR65yyHzPK4 +f?e.*'L 7ծB=Q&c?*W#"$Ee~?1Aa1ElƶxƼ\f'Xb"Kf ohy|I5h%?CK R5C! T7'=zIS&A&lv$JYRL DVO"åў LS|\St,Ŏǀwɬ,]>Q8uL~L&h `pX)l +`ӸL/R Dja5@=5|@R- a돐Xs^`ab^0qrٙF%CYk&%ʪ7VUj2.K'c!Ȱ+A [ r&j[Gt2D}-Hž40奣&W,;}ؒ5ð +Zm^[=L a( +4=N8==KQl^mA?*$+VǗ%UZ"2B7b>M(bAV^پa1bgA,:ŭR.RZ6M! BQEl +ӹ~q= JR{#fwQC>cOfq.eY@_붹1j;CzbcR>;X)_F)9C}lNlsx&QP$tqE8LiSNVCO-SCrKRQ@I;96R@_9% z9ru(JN|%?MwJ^(@aEGr>3KcLH^}48(lf>hMuY^b$MdiXapB5_!+yjB9Wdm=9t@?C4 zQb{=VPC7(bNlc8#SuDU2dDz88lRynbQXW;7MBWauh@zNmNE`ulRX&IniVP(YjhGON zH%#?tClh5zpFh=^mfU7P?XzeaY%UY%LsrI1(SAkmR3e%udw;%x0-C*!vUY^7%Yd_F zt^*+vSKSUu201EUEby={R}%;iQ_F+q&M*X-D?lNpIMO+Y4*5%PI+03Hu@q#2%Vs0^ zkOj*uE=BZC))+HmQNRfJHN;l5MZ86^rqRJL?!+{=XmsI#(x_E!Q5KSPF3xKy`b#1z zIg(&HUd!agjDvKEkvJpO%XUSvU}=)`DE^H2koEsB$q|I%dN_c%wX$fg6iq%~)u}3e zf5L$VscOt#RFM#=W>vbX%kWuSI-3Q8s06CMa1fS_m>+A%CQ~HrNHg9LUV>OFB1%%_ zfrWAbkAV^jq*&00;z>_HkMPrC%uKz|RzPR!ZMtLuh+-zKh9+^wB;9Z8t>*IypA5+n zR^=o0&IBrnj9`m40E~4ta#^gw<)RSB$tD3zCK+9|>J8ZOQpRiZ0@ixKFUM4n%#{ck zi6js{2Iq`;5lJaf6bskn0A2T$;~v`QBTH}?))@>2eKdqPaGNz0Mk$TwXm>fs*y^=V z#R4Tn-W70dqzp{SGjuVzc&f~G&hn*sGN&wq-kWa7H&61zrJo);2F)`xg4e z=xVp)TlZg+=ggWqY3}^g>46XX_PzYXSaI*|*Vhl8A0hq?WO^FCD8k%*%-Vc)Zc|g6 zZ1F6;aC!WxmF07-?H{eXu*f~R=gq;U$f?@(N#i=ySNcbVPal0|%BA)ozH`P!<@}Eu z4i5L+yB3^(tUA6qut~_QnniD0+x$58)V#6ry>uc&m!xzb`N17JhC>;@3 zX!vwcJbS^g-&6gjtJFGlclH={dnj=7%V7U|TXtd&>ZOjW^wYTNBT?cM`kl4R$W53~%*&UY_ N_e>aBaPVvk`X5WL-LU`w literal 0 HcmV?d00001 diff --git a/hosts/elisabeth/secrets/nextcloud/generated/ncpasswd.age b/hosts/elisabeth/secrets/nextcloud/generated/ncpasswd.age new file mode 100644 index 0000000000000000000000000000000000000000..f729091a769edf8c31f16c845f3f67c3f8eba61c GIT binary patch literal 801 zcmY+?JFnYh0DxgSz+fTNsS`t_0;;frW5-VHp+!FSiS5`)e2p)#IF9`#{(Mj3o9Ixf zL)9P9&invogj7}SawG<%7AA^V7+?TV#LyMmp~LwFk6!8ZK{yPvpz4a_KF>bOD}dEF zGQ2f#2#Ug)feg<_Ss}0xtb`dS_{}J7D;t{_@>~uC4yR8&pDy$^!U2M28Cl7va*=34 zIFm7&1ko)=3AgPKT#+ zYH`W(W?(Nh1NP`Djp%5`Sa!*NK__J%37q7MAd1j}EaPd>$PME_V{Id(UXUp{v%Qo( zEz<&2CSN9Fj*FG-Y_~F51E)OfxYN9fy<|~!svflgOC%FDF;T`Vd1DLA(3=oRyJDC_!N^$HeKKu;xc6dnRU5wxf|Vk>eF#H7vpHXD(bthZf> zqQe8r6rSaa(w2$nPAAWTvC?W#)pII8_&a}IFXEJ?5S8pRk8ro!g~0<|)OMQ;PED=1 zCeWknRXFPAdarJ13~FyImzTc8hoB1mLpVU!pM1f(1tQPb6V9Q;7K>f`WRr2>;Qf(K zrg)~O__~Jq0h{emYB8E;lGp}xVW*8rRV2ZHv+;&Rk%Hjyu4`StdlAAi64 z_!U-@&9lG$eedpzr(a$F`-6MmU6{A;kbgdV_3^Ji^$sa#`|HA&X{O8`?hc|Ej568n2ga7~l literal 0 HcmV?d00001 diff --git a/hosts/elisabeth/secrets/secrets.nix.age b/hosts/elisabeth/secrets/secrets.nix.age new file mode 100644 index 0000000000000000000000000000000000000000..17813a16370386ece1ee66cba9fafe24b64daced GIT binary patch literal 1116 zcmY+>{c9Tq9LMppv|0lj>xOlp({y6R#w>TaTyjx6yd-ydXmXe5TvF_Ixl1mWr@LH| z%Y#;!9UD|PR_5#!?8Md;W@|T7A1ad-!FC#DXv?Hjkap7jvMLIdDeJTf{?NbR_5Oao zG#A&zbBt6Ki@cCKC`g=E29=saDoO{#ph*R)<7ibd_Jm2fikfS_5-#gNb3UT4=IjX~ zi(wTI0OOL|tP_+4zYJ6|f;TN9kd&x-IX@KxB~y$rU|3M>;Yx%$=1Ssx4vYLNh`g*3 zb+AE21-$7XszN!RTcE4nXt@UEOU_s_A0&A&E5bBLfND0&B~iibj0IB|nNWt4QAf$` z@arK{GyyR|*x*I1g$SOq8G=qJA11VVL0_#1E)v~lMRAUIlvOAY3j|UGWslga7N(Ri zQkg09{mb0FsSA`mFAmUF{3stuxUn|ry25Y2}HDiF)VbxL`;=#hHs0)*0YCoYw zQbiz$7nj2TarpW@{I~YIN(gv>6K9C^wILF<-^$HsRr%H^k!UEa0ZY z5h{eZ3EhAH>d)%?2}#3Aak>y!CRlw)m**{F-eAR$43a10j1h^%(!od&cX}fMme%4H z8f73YnxYbAMlc$6W(I+IZ7u17ZJ7)w+AUzz zkI+Re;$bX?%2t1|D4X;wxK*RksMOYQz?bI}Wiz-{nRZxkM$qY^1R}v0j~C2gqvWyG zFta2H%CU2QDS*C*rZ2y+_VC3^Q>RvoM|MTS=gMO%jgg;qLN56CYZE50C3Ae{Bi?lT z<6dI-&AH_HBz67uJF5$wp{4tm4>TR7KONKl^3G=9PGRlXt-0j`9jW1fGSGc$NWKg| z`hjlb!!KLknB37)A8bCTE)EU!%nsard6a#ty>C>wIG4GQ{57o*(Y~tp1Jni@W^juJu=%dhgwu+0#Agd2gZN-!a@y zEfGwkgBpCi^|x1FN?koNbbhT7F^+n>8VY!<-n(+;Mrq@@rHN0-AIB%L%JNy@iL2MA bNw!^GIZ1Xm=8x`N*=>FLlJD!l;XnQX+~A X25519 +i63saSU8RBHO56nE65z4pFN72weFIH0MO2B6kKFX1o +O03ucWspS7ERnPwqPVVDLpokcR+VDGfeema+7VCdUcE +-> piv-p256 XTQkUA A222BiQ7aVaSdbpTgH0zop6Yc7iD3o9p+DpBT/53cfsI +NiOx77wz7D8tIWOitsVynStIGjUlDaXfYdjAvjvpV68 +-> piv-p256 ZFgiIw AgR85lUO7c+rwARcAkHBzoWONva7zDwCZ8hGNhP5FNXb +iIWz22A6YIzLlEbOhA6AwIVS5B4mOOLUMUvjel/QPPM +-> piv-p256 5vmPtQ Ao50NLd25O1sdk96G8a3acSjhOwfq+DbHiVl6q/E+2+3 +4R9ScWJsjyLxUqVTaKfzmsMvbZQH8shiqPbIGshbNpA +-> piv-p256 ZFgiIw A90xpxtG8MMDmsQpgx5fRavYIrmlv0rkcjev3LZYKRnS +2Wc3c2LPcWcRfL5+yH/GNWwblkofSrY/Bj7AxuOPX8g +-> `tr=>/j-grease Fr#5$ANy D0UHo: aD +F8V7YAtFk4XQjKdsN/pwtYnH +--- wCUeLyGHUi/Qwc7INkFXilCQz/N5rRgVtZ3TQu6jfgU +Ln626:F֕tWRm\mbiPWEd![/B +k4`hCLS \ No newline at end of file diff --git a/lib/default.nix b/lib/default.nix index 4288ee8..e5ad4a1 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -1,4 +1,3 @@ inputs: [ - (import ./containers.nix inputs) (import ./misc.nix inputs) ] diff --git a/modules/config/system.nix b/modules/config/system.nix index 97c5ead..7e60859 100644 --- a/modules/config/system.nix +++ b/modules/config/system.nix @@ -1,10 +1,13 @@ { inputs, lib, + stateVersion, pkgs, config, ... }: { + system.stateVersion = stateVersion; + age.rekey = { inherit (inputs.self.secretsConfig) diff --git a/modules/hardware/intel.nix b/modules/hardware/intel.nix deleted file mode 100644 index a2a9139..0000000 --- a/modules/hardware/intel.nix +++ /dev/null @@ -1,4 +0,0 @@ -{ - powerManagement.cpuFreqGovernor = "powersave"; - hardware.cpu.intel.updateMicrocode = true; -} diff --git a/modules/services/adguardhome.nix b/modules/services/adguardhome.nix new file mode 100644 index 0000000..484f09c --- /dev/null +++ b/modules/services/adguardhome.nix @@ -0,0 +1,65 @@ +{config, ...}: { + services.adguardhome = { + enable = true; + mutableSettings = false; + openFirewall = true; # opens webinterface firewall + settings = { + bind_port = 3000; + bind_host = "0.0.0.0"; + dns = { + bind_hosts = ["TODO"]; + anonymize_client_ip = true; + upstream_dns = [ + "1.0.0.1" + "2606:4700:4700::1111" + "8.8.8.8" + "2001:4860:4860::8844" + ]; + bootstrap_dns = [ + "1.0.0.1" + "2606:4700:4700::1111" + "8.8.8.8" + "2001:4860:4860::8844" + ]; + }; + user_rules = '' + ||${config.secrets.secrets.global.domains.web}^$dnsrewrite=TODO + ''; + dhcp.enabled = false; + ratelimit = 60; + users = [ + { + name = "patrick"; + password = "$2b$05$Dapc2LWUfebNOgIeBcaf2OVhW7uKmthmp9Ptykn96Iw1UE5pt2U72"; + } + ]; + filters = [ + { + name = "AdGuard DNS filter"; + url = "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt"; + enabled = true; + } + { + name = "AdaAway Default Blocklist"; + url = "https://adaway.org/hosts.txt"; + enabled = true; + } + { + name = "OISD (Big)"; + url = "https://big.oisd.nl"; + enabled = true; + } + ]; + }; + }; + networking.firewall = { + allowedTCPPorts = [53]; + allowedUDPPorts = [53]; + }; + environment.persistence."/persist".directories = [ + { + directory = "/var/lib/private/AdGuardHome"; + mode = "0700"; + } + ]; +} diff --git a/modules/services/containers.nix b/modules/services/containers.nix deleted file mode 100644 index cffabbb..0000000 --- a/modules/services/containers.nix +++ /dev/null @@ -1,118 +0,0 @@ -{ - config, - lib, - utils, - pkgs, - ... -}: let - inherit - (lib) - mapAttrs' - concatStrings - nameValuePair - mapAttrsToList - flip - types - mkOption - mkEnableOption - mdDoc - mkIf - disko - makeBinPath - escapeShellArg - mkMerge - ; -in { - options.containers = mkOption { - type = types.attrsOf (types.submodule ( - {name, ...}: { - options = { - zfs = { - enable = mkEnableOption (mdDoc "persistent data on separate zfs dataset"); - - pool = mkOption { - type = types.str; - description = mdDoc "The host's zfs pool on which the dataset resides"; - }; - - dataset = mkOption { - type = types.str; - default = "safe/containers/${name}"; - description = mdDoc "The host's dataset that should be used for this containers persistent data (will automatically be created)"; - }; - - mountpoint = mkOption { - type = types.str; - description = mdDoc "The host's mountpoint for the containers dataset"; - }; - }; - }; - } - )); - }; - config.system.activationScripts = let - mkDir = paths: (concatStrings (flip map paths (path: '' - [[ -d "${path}" ]] || ${pkgs.coreutils}/bin/mkdir -p "${path}" - ''))); - in - flip mapAttrs' config.containers ( - name: value: - nameValuePair "mkContainerFolder-${name}" (mkDir (mapAttrsToList (_: x: x.hostPath) value.bindMounts)) - ); - config.disko = mkMerge (flip mapAttrsToList config.containers - ( - _: cfg: { - devices.zpool = mkIf cfg.zfs.enable { - ${cfg.zfs.pool}.datasets."${cfg.zfs.dataset}" = - disko.zfs.filesystem cfg.zfs.mountpoint; - }; - - # Ensure that the zfs dataset exists before it is mounted. - } - )); - config.systemd = mkMerge (flip mapAttrsToList config.containers - ( - name: cfg: { - services = let - fsMountUnit = "${utils.escapeSystemdPath cfg.zfs.mountpoint}.mount"; - in - mkIf cfg.zfs.enable { - # Ensure that the zfs dataset exists before it is mounted. - "zfs-ensure-${utils.escapeSystemdPath cfg.zfs.mountpoint}" = { - wantedBy = [fsMountUnit]; - before = [fsMountUnit]; - after = [ - "zfs-import-${utils.escapeSystemdPath cfg.zfs.pool}.service" - "zfs-mount.target" - ]; - unitConfig.DefaultDependencies = "no"; - serviceConfig.Type = "oneshot"; - script = let - poolDataset = "${cfg.zfs.pool}/${cfg.zfs.dataset}"; - diskoDataset = config.disko.devices.zpool.${cfg.zfs.pool}.datasets.${cfg.zfs.dataset}; - in '' - export PATH=${makeBinPath [pkgs.zfs]}":$PATH" - if ! zfs list -H -o type ${escapeShellArg poolDataset} &>/dev/null ; then - ${diskoDataset._create} - fi - ''; - }; - - # Ensure that the zfs dataset has the correct permissions when mounted - "zfs-chown-${utils.escapeSystemdPath cfg.zfs.mountpoint}" = { - after = [fsMountUnit]; - unitConfig.DefaultDependencies = "no"; - serviceConfig.Type = "oneshot"; - script = '' - chmod 755 ${escapeShellArg cfg.zfs.mountpoint} - ''; - }; - - "container@${name}" = { - requires = [fsMountUnit "zfs-chown-${utils.escapeSystemdPath cfg.zfs.mountpoint}.service"]; - after = [fsMountUnit "zfs-chown-${utils.escapeSystemdPath cfg.zfs.mountpoint}.service"]; - }; - }; - } - )); -} diff --git a/modules/services/ddclient.nix b/modules/services/ddclient.nix index 4480011..db59b05 100644 --- a/modules/services/ddclient.nix +++ b/modules/services/ddclient.nix @@ -5,11 +5,11 @@ }; services.ddclient = { enable = true; - zone = config.secrets.secrets.global.domains.mail; + zone = config.secrets.secrets.global.domains.web; protocol = "Cloudflare"; username = "token"; use = "web, web='https://cloudflare.com/cdn-cgi/trace', web-skip='ip='"; passwordFile = config.age.secrets.cloudflare_token_dns.path; - domains = [config.secrets.secrets.global.domains.mail]; + domains = [config.secrets.secrets.global.domains.web]; }; } diff --git a/modules/services/nextcloud.nix b/modules/services/nextcloud.nix index 984a42f..badf481 100644 --- a/modules/services/nextcloud.nix +++ b/modules/services/nextcloud.nix @@ -1,122 +1,92 @@ { lib, - stateVersion, + pkgs, config, - #deadnix: skip - pkgs, # not unused needed for the usage of attrs later to contains pkgs ... -} @ attrs: let - hostName = "nc.${config.secrets.secrets.global.domains.mail}"; +}: let + hostName = "nc.${config.secrets.secrets.global.domains.web}"; in { - imports = [./containers.nix ./ddclient.nix ./acme.nix]; - services.nginx = { - enable = true; - recommendedSetup = true; - upstreams.nextcloud = { - servers."192.168.178.33:80" = {}; - - extraConfig = '' - zone nextcloud 64k ; - keepalive 5 ; - ''; - }; - virtualHosts.${hostName} = { - forceSSL = true; - useACMEHost = "mail"; - locations."/".proxyPass = "http://nextcloud"; - extraConfig = '' - client_max_body_size 4G ; - ''; + systemd.network.networks = { + "TODO" = { + address = ["192.168.178.33/24"]; + gateway = ["192.168.178.1"]; + matchConfig.Name = "lan01*"; + dns = ["192.168.178.2"]; + networkConfig = { + IPv6PrivacyExtensions = "yes"; + MulticastDNS = true; + }; }; }; - containers.nextcloud = lib.containers.mkConfig "nextcloud" attrs { - zfs = { - enable = true; - pool = "panzer"; + environment.persistence."/persist".directories = [ + { + directory = "/var/lib/postgresql/"; + user = "postgres"; + group = "postgres"; + mode = "750"; + } + ]; + environment.persistence."/panzer".directories = [ + { + directory = config.services.nextcloud.home; + user = "nextcloud"; + group = "nextcloud"; + mode = "750"; + } + ]; + age.secrets.ncpasswd = { + generator.script = "alnum"; + mode = "440"; + owner = "nextcloud"; + }; + services.postgresql.package = pkgs.postgresql_16; + services.nginx.virtualHosts.${hostName}.extraConfig = '' + allow TODO; + deny all; + ''; + + services.nextcloud = { + inherit hostName; + enable = true; + package = pkgs.nextcloud28; + configureRedis = true; + config.adminpassFile = config.age.secrets.ncpasswd.path; # Kinda ok just remember to instanly change after first setup + config.adminuser = "admin"; + extraApps = with config.services.nextcloud.package.packages.apps; { + inherit contacts calendar tasks notes maps phonetrack; + }; + maxUploadSize = "4G"; + extraAppsEnable = true; + database.createLocally = true; + phpOptions."opcache.interned_strings_buffer" = "32"; + extraOptions = { + default_phone_region = "DE"; + trusted_proxies = ["TODO"]; + overwriteprotocol = "https"; + enabledPreviewProviders = [ + "OC\\Preview\\BMP" + "OC\\Preview\\GIF" + "OC\\Preview\\JPEG" + "OC\\Preview\\Krita" + "OC\\Preview\\MarkDown" + "OC\\Preview\\MP3" + "OC\\Preview\\OpenDocument" + "OC\\Preview\\PNG" + "OC\\Preview\\TXT" + "OC\\Preview\\XBitmap" + "OC\\Preview\\HEIC" + ]; }; config = { - config, - pkgs, - ... - }: { - #TODO enable recommended nginx setup - systemd.network.networks = { - "lan01" = { - address = ["192.168.178.33/24"]; - gateway = ["192.168.178.1"]; - matchConfig.Name = "lan01*"; - dns = ["192.168.178.2"]; - networkConfig = { - IPv6PrivacyExtensions = "yes"; - MulticastDNS = true; - }; - }; - }; - environment.persistence."/persist".directories = [ - { - directory = config.services.nextcloud.home; - user = "nextcloud"; - group = "nextcloud"; - mode = "750"; - } - ]; - services.nextcloud = { - inherit hostName; - enable = true; - package = pkgs.nextcloud28; - configureRedis = true; - config.adminpassFile = "${pkgs.writeText "adminpass" "test123"}"; # DON'T DO THIS IN PRODUCTION - the password file will be world-readable in the Nix Store! - config.adminuser = "admin"; - extraApps = with config.services.nextcloud.package.packages.apps; { - inherit contacts calendar tasks notes maps; - }; - maxUploadSize = "2G"; - extraAppsEnable = true; - database.createLocally = true; - phpOptions."opcache.interned_strings_buffer" = "32"; - extraOptions = { - default_phone_region = "DE"; - trusted_proxies = ["192.168.178.32"]; - overwriteprotocol = "https"; - enabledPreviewProviders = [ - "OC\\Preview\\BMP" - "OC\\Preview\\GIF" - "OC\\Preview\\JPEG" - "OC\\Preview\\Krita" - "OC\\Preview\\MarkDown" - "OC\\Preview\\MP3" - "OC\\Preview\\OpenDocument" - "OC\\Preview\\PNG" - "OC\\Preview\\TXT" - "OC\\Preview\\XBitmap" - "OC\\Preview\\HEIC" - ]; - }; - config = { - dbtype = "pgsql"; - }; - }; - - system.stateVersion = stateVersion; - - networking = { - firewall = { - enable = true; - allowedTCPPorts = [80]; - }; - # Use systemd-resolved inside the container - useHostResolvConf = lib.mkForce false; - }; - - services.resolved.enable = true; + dbtype = "pgsql"; }; }; -} -#wireguard -#samba/printer finding -#vaultwarden -#maddy -#kanidm -#remote backups -#immich + networking = { + firewall.allowedTCPPorts = [80]; + # Use systemd-resolved inside the container + useHostResolvConf = lib.mkForce false; + }; + + services.resolved.enable = true; +} diff --git a/modules/services/samba.nix b/modules/services/samba.nix index 4d0e197..661f58b 100644 --- a/modules/services/samba.nix +++ b/modules/services/samba.nix @@ -3,17 +3,16 @@ lib, ... }: { - services.samba-wsdd.enable = true; # make shares visible for windows 10 clients - networking.firewall.allowedTCPPorts = [ - 5357 # wsdd - ]; - networking.firewall.allowedUDPPorts = [ - 3702 # wsdd - ]; + services.samba-wsdd = { + enable = true; # make shares visible for windows 10 clients + openFirewall = true; + }; services.samba = { enable = true; securityType = "user"; openFirewall = true; + enableWinbindd = false; + enableNmbd = false; extraConfig = lib.concatLines [ '' logging = systemd @@ -56,10 +55,12 @@ name, user ? "smb", group ? "smb", + persistRoot ? "/panzer", }: cfg: { "${name}" = { "path" = "/media/smb/${name}"; + "#persistRoot" = persistRoot; "read only" = "no"; "guest ok" = "no"; "create mask" = "0640"; @@ -101,7 +102,10 @@ user = "family"; group = "family"; } {}) - ((mkShare {name = "media";}) + (mkShare { + name = "media"; + persistRoot = "/renaultft"; + } { "read only" = "yes"; "write list" = "@family"; @@ -149,10 +153,14 @@ })); }; - environment.persistence."/panzer/persist".directories = lib.flip lib.mapAttrsToList config.services.samba.shares (_: v: { - directory = "${v.path}"; - user = "${v."force user"}"; - group = "${v."force group"}"; - mode = "0770"; - }); + environment.persistence = lib.mkMerge (lib.flip lib.mapAttrsToList config.services.samba.shares (_: v: { + ${v."#persistRoot"}.directories = [ + { + directory = "${v.path}"; + user = "${v."force user"}"; + group = "${v."force group"}"; + mode = "0770"; + } + ]; + })); } diff --git a/nix/hosts.nix b/nix/hosts.nix index c2de247..6215649 100644 --- a/nix/hosts.nix +++ b/nix/hosts.nix @@ -48,13 +48,18 @@ inputs: let nixosConfigurations = flip mapAttrs nixosHosts (mkHost {minimal = false;}); minimalConfigurations = flip mapAttrs nixosHosts (mkHost {minimal = true;}); - # True NixOS nodes can define additional microvms (guest nodes) that are built - # together with the true host. We collect all defined microvm nodes - # from each node here to allow accessing any node via the unified attribute `nodes`. + # True NixOS nodes can define additional guest nodes that are built + # together with it. We collect all defined guests from each node here + # to allow accessing any node via the unified attribute `nodes`. guestConfigurations = flip concatMapAttrs self.nixosConfigurations (_: node: - mapAttrs' - (vm: _: nameValuePair vm {inherit (node.config.containers.${vm}) config;}) - (node.config.containers or {})); + flip mapAttrs' (node.config.guests or {}) ( + guestName: guestDef: + nameValuePair guestDef.nodeName ( + if guestDef.backend == "microvm" + then node.config.microvm.vms.${guestName}.config + else node.config.containers.${guestName}.nixosConfiguration + ) + )); in { inherit hosts diff --git a/secrets/secrets.nix.age b/secrets/secrets.nix.age index bc9aca29b430fd89dc3dfe66313636d95ff5cdf7..0d5bdad23571ddbabf3de9a9318886fb09a3963c 100644 GIT binary patch delta 4545 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 delta 4343 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 lT~)D*+&SaNO+(})oWmb9BY@dp`