ctucx.git: nixfiles

ctucx' nixfiles

commit 0366dc389ad0daaf12adf79b03509b7eee85fad5
parent 1f5ebac39729e033c10113e0c4e66e9958e9fef3
Author: Katja (ctucx) <git@ctu.cx>
Date: Tue, 4 Mar 2025 11:32:09 +0100

modules: refactor
15 files changed, 501 insertions(+), 488 deletions(-)
M
flake.nix
|
19
+++++++++++++++++--
A
modules/darwin/default.nix
|
19
+++++++++++++++++++
D
modules/default.nix
|
49
-------------------------------------------------
D
modules/linux/dns.nix
|
229
-------------------------------------------------------------------------------
D
modules/linux/email-notify.nix
|
35
-----------------------------------
D
modules/linux/gotosocial.nix
|
173
-------------------------------------------------------------------------------
A
modules/nixos/default.nix
|
27
+++++++++++++++++++++++++++
A
modules/nixos/dns.nix
|
229
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
modules/nixos/email-notify.nix
|
35
+++++++++++++++++++++++++++++++++++
R
modules/linux/gitolite.nix -> modules/nixos/gitolite.nix
|
0
R
modules/linux/gnome.nix -> modules/nixos/gnome.nix
|
0
A
modules/nixos/gotosocial.nix
|
174
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
R
modules/linux/restic-backups.nix -> modules/nixos/restic-backups.nix
|
0
R
modules/linux/vnstati/default.nix -> modules/nixos/vnstati/default.nix
|
0
R
modules/linux/vnstati/vnstati-html.nix -> modules/nixos/vnstati/vnstati-html.nix
|
0
diff --git a/flake.nix b/flake.nix
@@ -14,6 +14,9 @@
       );
     };
 
+    ctucxModules.darwin = loadDir ./modules/darwin;
+    ctucxModules.nixos  = loadDir ./modules/nixos;
+
     ctucxConfig.common = loadDir ./configurations/common;
     ctucxConfig.darwin = inputs.nixpkgs.lib.recursiveUpdate ctucxConfig.common (loadDir ./configurations/darwin);
     ctucxConfig.nixos  = inputs.nixpkgs.lib.recursiveUpdate ctucxConfig.common (loadDir ./configurations/nixos);

@@ -33,7 +36,11 @@
         };
 
         modules = [
+          inputs.lixModule.nixosModules.default
+          inputs.homeManager.darwinModules.default
+          inputs.agenix.darwinModules.default
           ctucxConfig.darwin.default
+          ctucxModules.darwin.default
           ./machines/blechkasten
         ];
       };

@@ -51,13 +58,21 @@
 
         specialArgs = {
           inherit inputs;
+          ctucxConfig = ctucxConfig.nixos;
         };
       };
 
       defaults = {
         imports = [
+          inputs.lixModule.nixosModules.default
+          inputs.impermanence.nixosModules.default
+          inputs.homeManager.nixosModules.default
+          inputs.agenix.nixosModules.default
+          inputs.lanzaboote.nixosModules.lanzaboote
+          inputs.simpleNixosMailserver.nixosModules.default
+          inputs.ctucxThings.nixosModule
+          ctucxModules.nixos.default
           ctucxConfig.nixos.default
-          ./modules
         ];
       };
 

@@ -77,7 +92,7 @@
         dns         = inputs.dnsNix;
         std         = inputs.nixStd.lib;
         unstable    = inputs.nixpkgsUnstable.legacyPackages.${prev.system};
-        ctucxConfig = ctucxConfig;
+        inherit ctucxConfig ctucxModules;
       })
 
       inputs.colmena.overlay
diff --git a/modules/darwin/default.nix b/modules/darwin/default.nix
@@ -0,0 +1,19 @@
+
+{ lib ... }:
+
+{
+
+  imports = [
+    ./quirks.nix
+    ./hidutil.nix
+    ./syncthing.nix
+    ./skhd.nix
+  ];
+
+  options = {
+    networking.primaryIP     = lib.mkOption { type = lib.types.str; default = ""; };
+    networking.primaryIP4    = lib.mkOption { type = lib.types.str; default = ""; };
+    networking.secondaryIP4  = lib.mkOption { type = lib.types.str; default = ""; };
+  };
+
+}
diff --git a/modules/default.nix b/modules/default.nix
@@ -1,49 +0,0 @@
-
-{ inputs, lib, currentSystem, ... }:
-
-{
-
-  disabledModules = [
-    "services/misc/gitolite.nix"
-    "services/web-apps/gotosocial.nix"
-  ];
-
-  imports = (builtins.concatLists [
-    [
-      inputs.lixModule.nixosModules.default
-    ]
-
-    (if (currentSystem == "x86_64-linux") then [
-      inputs.impermanence.nixosModules.default
-      inputs.homeManager.nixosModules.default
-      inputs.agenix.nixosModules.default
-      inputs.lanzaboote.nixosModules.lanzaboote
-      inputs.simpleNixosMailserver.nixosModules.default
-      inputs.ctucxThings.nixosModule
-
-      ./linux/restic-backups.nix
-      ./linux/vnstati
-      ./linux/gitolite.nix
-      ./linux/email-notify.nix
-      ./linux/dns.nix
-      ./linux/gotosocial.nix
-      ./linux/gnome.nix
-    ] else [])
-
-    (if (currentSystem == "aarch64-darwin") then [
-      inputs.homeManager.darwinModules.default
-      inputs.agenix.darwinModules.default
-      ./darwin/quirks.nix
-      ./darwin/hidutil.nix
-      ./darwin/syncthing.nix
-      ./darwin/skhd.nix
-    ] else [])
-  ]);
-
-  options = {
-    networking.primaryIP     = lib.mkOption { type = lib.types.str; default = ""; };
-    networking.primaryIP4    = lib.mkOption { type = lib.types.str; default = ""; };
-    networking.secondaryIP4  = lib.mkOption { type = lib.types.str; default = ""; };
-  };
-
-}
diff --git a/modules/linux/dns.nix b/modules/linux/dns.nix
@@ -1,229 +0,0 @@
-{ currentSystem, nodes, config, lib, pkgs, ... }:
-
-#
-# this module requires lix' experimental `pipe-operator` feature!
-#
-
-with lib;
-
-let
-  cfg = config.dns;
-
-  filterDNSServerAddresses = nodes: isPrimary: lib.flatten (
-    nodes
-    |> lib.filterAttrs (hostName: nodeCfg: nodeCfg.config.dns.enable && nodeCfg.config.dns.primary == isPrimary)
-    |> lib.mapAttrsToList (
-      hostName: nodeCfg: [
-        (lib.mkIf (nodeCfg.config.networking.primaryIP  != "") nodeCfg.config.networking.primaryIP)
-        (lib.mkIf (nodeCfg.config.networking.primaryIP4 != "") nodeCfg.config.networking.primaryIP4)
-      ]
-    )
-  );
-
-  filterDNSServerSecondaries = nodes: (
-    nodes
-    |> lib.filterAttrs (hostName: nodeCfg: nodeCfg.config.dns.enable && !nodeCfg.config.dns.primary)
-    |> lib.mapAttrs(
-      hostName: nodeCfg: {
-        address = [
-          (lib.mkIf (nodeCfg.config.networking.primaryIP  != "") nodeCfg.config.networking.primaryIP)
-          (lib.mkIf (nodeCfg.config.networking.primaryIP4 != "") nodeCfg.config.networking.primaryIP4)
-        ];
-      }
-    )
-  );
-
-in {
-
-  options.dns = {
-    enable = mkEnableOption "nix-powered DNS";
-
-    primary = lib.mkOption {
-      type    = lib.types.bool;
-      default = true;
-    };
-
-    zonesDir = lib.mkOption {
-      type    = lib.types.str;
-      default = "nixZones";
-    };
-
-    dataDir = lib.mkOption {
-      type    = lib.types.str;
-      default = "/var/lib/knot";
-    };
-
-    keyFiles = lib.mkOption {
-      type    = types.listOf types.path;
-      default = [];
-    };
-
-    # contains dns entries defined on the local host
-    zones = mkOption {
-      type    = lib.types.attrsOf pkgs.dns.lib.types.subzone;
-      default = {};
-    };
-
-    # contains dns entries defined on the local host and on remote hosts, merged together
-    allZones = mkOption {
-      type    = lib.types.attrsOf pkgs.dns.lib.types.zone;
-      default = {};
-    };
-
-    extraZones = mkOption {
-      type    = (pkgs.formats.yaml { }).type;
-      default = {};
-    };
-
-    extraACL = mkOption {
-      type    = (pkgs.formats.yaml { }).type;
-      default = {};
-    };
-  };
-
-  config = mkIf cfg.enable {
-    networking.firewall.allowedTCPPorts = [ 53 ];
-    networking.firewall.allowedUDPPorts = [ 53 ];
-
-    # serve records defined in all host configs
-    dns.allZones = mkMerge (
-      nodes
-      |> mapAttrsToList ( name: host: host.config.dns.zones )
-    );
-
-    environment.etc = lib.mkIf cfg.primary (
-      cfg.allZones
-      |> lib.mapAttrs' (name: zone: {
-         name = "${cfg.zonesDir}/${name}.zone";
-         value = { source = pkgs.dns.util."${currentSystem}".writeZone name zone; };
-      })
-    );
-
-    services.knot = let
-      primaryAddresses   = filterDNSServerAddresses   nodes true;
-      secondaryAddresses = filterDNSServerAddresses   nodes false;
-      secondaries        = filterDNSServerSecondaries nodes;
-    in {
-	    enable   = true;
-	    keyFiles = lib.mkIf (cfg.keyFiles != []) cfg.keyFiles;
-	    settings = {
-	      log.syslog.any = "info";
-
-        server.listen = [
-          (lib.mkIf (config.networking.primaryIP  != "") "${config.networking.primaryIP}@53") 
-          (lib.mkIf (config.networking.primaryIP4 != "") "${config.networking.primaryIP4}@53") 
-        ];
-
-        mod-rrl.default.rate-limit = 200;
-        mod-rrl.default.slip       = 2;
-
-        remote = { primary.address = primaryAddresses; } // secondaries;
-
-        acl = {
-          allowTransfer.address = secondaryAddresses;
-          allowTransfer.action  = "transfer";
-
-          allowNotify.address   = primaryAddresses;
-          allowNotify.action    = "notify";
-        } // cfg.extraACL;
-
-        template = let
-          notify = {
-            acl          = "allowTransfer";
-            notify       = builtins.attrNames secondaries;
-          };
-
-          catalog = {
-            catalog-role = "member";
-            catalog-zone = "catalog.";
-          };
-
-        in {
-
-          default = {
-            semantic-checks = true;
-            global-module   = "mod-rrl/default";
-          };
-
-          notifyZone  = notify;
-          extraZone   = notify // catalog;
-          primaryZone = notify // catalog // {
-            storage         = "${cfg.dataDir}/nixZones";
-
-            journal-content = "all";
-
-            zonefile-sync   = -1;
-            zonefile-load   = "difference-no-serial";
-          };
-
-          secondaryZone = {
-            master = "primary";
-            acl    = "allowNotify";
-
-            journal-content = "all";
-
-            zonefile-sync   = -1;
-            zonefile-load   = "none";
-          };
-        };
-
-        zone = if !cfg.primary then {
-          "catalog.".catalog-role     = "interpret";
-          "catalog.".catalog-template = "secondaryZone";
-          "catalog.".template         = "secondaryZone";
-        } else {
-          "catalog.".catalog-role  = "generate";
-          "catalog.".template      = "notifyZone";
-        } // (lib.mapAttrs (name: zone: {
-          template = "primaryZone";
-        }) cfg.allZones) // (lib.mapAttrs (name: zone: zone // {
-          template = "extraZone";
-          acl      = lib.mkIf (builtins.hasAttr "acl" zone) (lib.flatten [ [ "allowTransfer" ] zone.acl ]);
-        }) cfg.extraZones);
-	    };
-  	};
-
-    systemd.tmpfiles.settings = {
-      knotDataDir."${cfg.dataDir}".d = {
-        group = "knot";
-        user  = "knot";
-        mode  = "770";
-        age   = "-";
-      };
-
-      knotZones."${cfg.dataDir}/nixZones".d = lib.mkIf cfg.primary {
-        group = "knot";
-        user  = "knot";
-        mode  = "770";
-        age   = "-";
-      };
-    };
-
-    systemd.services.knot = lib.mkIf cfg.primary {
-      reloadTriggers = (
-        cfg.allZones
-        |> lib.mapAttrsToList (name: zone: pkgs.dns.util."${currentSystem}".writeZone name zone)
-       ) ++ (
-        cfg.extraZones
-        |> lib.mapAttrsToList (name: zone: (if (builtins.hasAttr "storage" zone) then "${zone.storage}/${zone.file}" else "${zone.file}"))
-      );
-
-      preStart = ''
-        set -euo pipefail
-
-        cp --dereference /etc/${cfg.zonesDir}/* ${cfg.dataDir}/nixZones
-        chmod -R 770 ${cfg.dataDir}/nixZones
-      ''; 
-
-      serviceConfig.ExecReload = lib.mkForce (pkgs.writeShellScript "knot-reload" ''
-        set -eou pipefail
-
-        cp --dereference /etc/${cfg.zonesDir}/* ${cfg.dataDir}/nixZones
-        chmod -R 770 ${cfg.dataDir}/nixZones
-
-        ${config.services.knot.package}/bin/knotc reload
-      '');
-    };
-  };
-
-}
diff --git a/modules/linux/email-notify.nix b/modules/linux/email-notify.nix
@@ -1,35 +0,0 @@
-{ pkgs, lib, config, ... }:
-
-{
-
-  options.services.email-notify.enable = lib.mkEnableOption "Enable a service which can be used to send emails";
-
-  config = lib.mkIf config.services.email-notify.enable {
-    age.secrets.password-leah-at-f2k1-de.file = ../../secrets/passwords/leah-at-f2k1-de.age;
-
-    programs.msmtp = {
-      enable      = true;
-      setSendmail = false;
-      accounts    = {
-        default = {
-          auth         = true;
-          tls          = true;
-          host         = "rx300.kunbox.net";
-          port         = 587;
-          user         = "leah@f2k1.de";
-          from         = "${config.networking.fqdn} <leah@f2k1.de>";
-          passwordeval = "cat ${config.age.secrets.password-leah-at-f2k1-de.path}";
-        };
-      };
-    };
-
-    systemd.services."email-notify@" = {
-      serviceConfig = {
-        ExecStart = ''
-          ${pkgs.runtimeShell} -c "{ echo -n 'Subject:[${config.networking.fqdn}] Service failed: %i\n\n' &  ${pkgs.systemd}/bin/systemctl status %i;} | ${pkgs.msmtp}/bin/msmtp -v notify@ctu.cx"
-        '';
-      };
-    };
-  };
-
-}
diff --git a/modules/linux/gotosocial.nix b/modules/linux/gotosocial.nix
@@ -1,173 +0,0 @@
-{ options, config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  cfg            = config.services.gotosocial;
-  settingsFormat = pkgs.formats.json {};
-
-in {
-
-  options = {
-    services.gotosocial = with lib; {
-      enable = mkEnableOption "GoToSocial ActivityPub Server";
-
-      package = mkOption {
-        type    = types.package;
-        default = pkgs.gotosocial;
-      };
-
-      user = mkOption {
-        type    = types.str;
-        default = "gotosocial";
-      };
-
-      group = mkOption {
-        type    = types.str;
-        default = "gotosocial";
-      };
-
-      stateDir = mkOption {
-        type     = types.str;
-        default  = "/var/lib/gotosocial";
-        readOnly = true;
-      };
-
-      environmentFile = lib.mkOption {
-        type = lib.types.nullOr lib.types.path;
-        default = null;
-      };
-
-      settings = lib.mkOption {
-        type = lib.types.submodule {
-          freeformType = settingsFormat.type;
-
-          options.host = lib.mkOption {
-            type = lib.types.nullOr lib.types.str;
-            default = null;
-            description = ''
-              Hostname that this server will be reachable at. Defaults to localhost for local testing,
-              but you should *definitely* change this when running for real, or your server won't work at all.
-              DO NOT change this after your server has already run once, or you will break things!
-            '';
-          };
-
-          options.port = lib.mkOption {
-            type = lib.types.port;
-            default = 8080;
-            description = ''
-              Int. Listen port for the GoToSocial webserver + API. If you're running behind a reverse proxy and/or in a docker,
-              container, just set this to whatever you like (or leave the default), and make sure it's forwarded properly.
-              If you are running with built-in letsencrypt enabled, and running GoToSocial directly on a host machine, you will
-              probably want to set this to 443 (standard https port), unless you have other services already using that port.
-              This *MUST NOT* be the same as the letsencrypt port specified below, unless letsencrypt is turned off.
-            '';
-          };
-        };
-
-        default = {};
-        description = ''
-          Configuration for GoToSocial, see
-          <link xlink:href="https://docs.gotosocial.org/en/latest/">
-          for supported values.
-        '';
-      };
-    };
-  };
-
-  config = lib.mkIf cfg.enable (let
-    configFile = settingsFormat.generate "gotosocial-config.yaml" cfg.settings;
-  in {
-    assertions = [
-      {
-        assertion = cfg.settings.host != null;
-        message = "You have to define a hostname for GoToSocial, it cannot be changed later without starting over!";
-      }
-    ];
-
-    services.gotosocial.settings = { # Defaults
-      user                    = lib.mkDefault "gotosocial";
-      group                   = lib.mkDefault "gotosocial";
-
-      storage-local-base-path = lib.mkDefault "/var/lib/gotosocial"; # SystemD StateDirectory
-
-      web-template-base-dir   = lib.mkDefault "${cfg.package}/share/web/template/";
-      web-asset-base-dir      = lib.mkDefault "${cfg.package}/share/web/assets/";
-    };
-
-    users = {
-      users."${cfg.user}" = {
-        home         = cfg.stateDir;
-        group        = cfg.group;
-        isSystemUser = true;
-      };
-      groups."${cfg.group}" = {};
-    };
-
-    environment.etc."gotosocial.yaml".source = configFile;
-
-    environment.systemPackages = [
-      (pkgs.writeShellScriptBin "gotosocial" ''
-        exec ${cfg.package}/bin/gotosocial --config-path ${configFile} "$@"
-      '')
-    ];
-
-    systemd.services = {
-      gotosocial = {
-        description     = "GoToSocial ActivityPub Server";
-        after           = [ "network-online.target" ];
-        wantedBy        = [ "multi-user.target" ];
-        onFailure       = [ "email-notify@%i.service" ];
-
-        serviceConfig = {
-          User = cfg.user;
-          Group = cfg.group;
-
-          Type = "exec";
-          WorkingDirectory   = "~";
-          StateDirectory     = lib.mkIf (cfg.settings.storage-local-base-path != "/var/lib/gotosocial") "gotosocial";
-          ReadOnlyPaths      = [ cfg.package ];
-          ReadWritePaths     = [ cfg.settings.storage-local-base-path ];
-          StateDirectoryMode = "750";
-
-          Restart    = "always";
-          RestartSec = 3;
-
-          EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
-          ExecStart       = "${cfg.package}/bin/gotosocial --config-path ${configFile} server start";
-
-          NoNewPrivileges = true;
-          PrivateTmp      = true;
-          PrivateDevices  = false;
-
-          RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
-          RestrictNamespaces      = true;
-          RestrictRealtime        = true;
-        
-          ProtectSystem         = "full";
-          ProtectControlGroups  = true;
-          ProtectKernelModules  = true;
-          ProtectKernelTunables = true;
-
-          DevicePolicy     = "closed";
-          LockPersonality  = true;
-          SystemCallFilter = "~@clock @debug @module @mount @obsolete @reboot @setuid @swap";
-
-          CapabilityBoundingSet = [
-            "~CAP_RAWIO CAP_MKNOD"
-            "~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE"
-            "~CAP_SYS_BOOT CAP_SYS_TIME CAP_SYS_MODULE CAP_SYS_PACCT"
-            "~CAP_LEASE CAP_LINUX_IMMUTABLE CAP_IPC_LOCK"
-            "~CAP_BLOCK_SUSPEND CAP_WAKE_ALARM"
-            "~CAP_SYS_TTY_CONFIG"
-            "~CAP_MAC_ADMIN CAP_MAC_OVERRIDE"
-            "~CAP_NET_ADMIN CAP_NET_BROADCAST CAP_NET_RAW"
-            "~CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_SYSLOG"
-          ];
-        };
-      };
-    };
-
-  });
-
-}
diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix
@@ -0,0 +1,27 @@
+
+{ lib, ... }:
+
+{
+
+  disabledModules = [
+    "services/misc/gitolite.nix"
+    "services/web-apps/gotosocial.nix"
+  ];
+
+  imports = [
+    ./vnstati
+    ./restic-backups.nix
+    ./gitolite.nix
+    ./email-notify.nix
+    ./dns.nix
+    ./gotosocial.nix
+    ./gnome.nix
+  ];
+
+  options = {
+    networking.primaryIP     = lib.mkOption { type = lib.types.str; default = ""; };
+    networking.primaryIP4    = lib.mkOption { type = lib.types.str; default = ""; };
+    networking.secondaryIP4  = lib.mkOption { type = lib.types.str; default = ""; };
+  };
+
+}
diff --git a/modules/nixos/dns.nix b/modules/nixos/dns.nix
@@ -0,0 +1,229 @@
+{ nodes, config, lib, pkgs, ... }:
+
+#
+# this module requires lix' experimental `pipe-operator` feature!
+#
+
+with lib;
+
+let
+  cfg = config.dns;
+
+  filterDNSServerAddresses = nodes: isPrimary: lib.flatten (
+    nodes
+    |> lib.filterAttrs (hostName: nodeCfg: nodeCfg.config.dns.enable && nodeCfg.config.dns.primary == isPrimary)
+    |> lib.mapAttrsToList (
+      hostName: nodeCfg: [
+        (lib.mkIf (nodeCfg.config.networking.primaryIP  != "") nodeCfg.config.networking.primaryIP)
+        (lib.mkIf (nodeCfg.config.networking.primaryIP4 != "") nodeCfg.config.networking.primaryIP4)
+      ]
+    )
+  );
+
+  filterDNSServerSecondaries = nodes: (
+    nodes
+    |> lib.filterAttrs (hostName: nodeCfg: nodeCfg.config.dns.enable && !nodeCfg.config.dns.primary)
+    |> lib.mapAttrs(
+      hostName: nodeCfg: {
+        address = [
+          (lib.mkIf (nodeCfg.config.networking.primaryIP  != "") nodeCfg.config.networking.primaryIP)
+          (lib.mkIf (nodeCfg.config.networking.primaryIP4 != "") nodeCfg.config.networking.primaryIP4)
+        ];
+      }
+    )
+  );
+
+in {
+
+  options.dns = {
+    enable = mkEnableOption "nix-powered DNS";
+
+    primary = lib.mkOption {
+      type    = lib.types.bool;
+      default = true;
+    };
+
+    zonesDir = lib.mkOption {
+      type    = lib.types.str;
+      default = "nixZones";
+    };
+
+    dataDir = lib.mkOption {
+      type    = lib.types.str;
+      default = "/var/lib/knot";
+    };
+
+    keyFiles = lib.mkOption {
+      type    = types.listOf types.path;
+      default = [];
+    };
+
+    # contains dns entries defined on the local host
+    zones = mkOption {
+      type    = lib.types.attrsOf pkgs.dns.lib.types.subzone;
+      default = {};
+    };
+
+    # contains dns entries defined on the local host and on remote hosts, merged together
+    allZones = mkOption {
+      type    = lib.types.attrsOf pkgs.dns.lib.types.zone;
+      default = {};
+    };
+
+    extraZones = mkOption {
+      type    = (pkgs.formats.yaml { }).type;
+      default = {};
+    };
+
+    extraACL = mkOption {
+      type    = (pkgs.formats.yaml { }).type;
+      default = {};
+    };
+  };
+
+  config = mkIf cfg.enable {
+    networking.firewall.allowedTCPPorts = [ 53 ];
+    networking.firewall.allowedUDPPorts = [ 53 ];
+
+    # serve records defined in all host configs
+    dns.allZones = mkMerge (
+      nodes
+      |> mapAttrsToList ( name: host: host.config.dns.zones )
+    );
+
+    environment.etc = lib.mkIf cfg.primary (
+      cfg.allZones
+      |> lib.mapAttrs' (name: zone: {
+         name = "${cfg.zonesDir}/${name}.zone";
+         value = { source = pkgs.dns.util."${config.nixpkgs.system}".writeZone name zone; };
+      })
+    );
+
+    services.knot = let
+      primaryAddresses   = filterDNSServerAddresses   nodes true;
+      secondaryAddresses = filterDNSServerAddresses   nodes false;
+      secondaries        = filterDNSServerSecondaries nodes;
+    in {
+	    enable   = true;
+	    keyFiles = lib.mkIf (cfg.keyFiles != []) cfg.keyFiles;
+	    settings = {
+	      log.syslog.any = "info";
+
+        server.listen = [
+          (lib.mkIf (config.networking.primaryIP  != "") "${config.networking.primaryIP}@53") 
+          (lib.mkIf (config.networking.primaryIP4 != "") "${config.networking.primaryIP4}@53") 
+        ];
+
+        mod-rrl.default.rate-limit = 200;
+        mod-rrl.default.slip       = 2;
+
+        remote = { primary.address = primaryAddresses; } // secondaries;
+
+        acl = {
+          allowTransfer.address = secondaryAddresses;
+          allowTransfer.action  = "transfer";
+
+          allowNotify.address   = primaryAddresses;
+          allowNotify.action    = "notify";
+        } // cfg.extraACL;
+
+        template = let
+          notify = {
+            acl          = "allowTransfer";
+            notify       = builtins.attrNames secondaries;
+          };
+
+          catalog = {
+            catalog-role = "member";
+            catalog-zone = "catalog.";
+          };
+
+        in {
+
+          default = {
+            semantic-checks = true;
+            global-module   = "mod-rrl/default";
+          };
+
+          notifyZone  = notify;
+          extraZone   = notify // catalog;
+          primaryZone = notify // catalog // {
+            storage         = "${cfg.dataDir}/nixZones";
+
+            journal-content = "all";
+
+            zonefile-sync   = -1;
+            zonefile-load   = "difference-no-serial";
+          };
+
+          secondaryZone = {
+            master = "primary";
+            acl    = "allowNotify";
+
+            journal-content = "all";
+
+            zonefile-sync   = -1;
+            zonefile-load   = "none";
+          };
+        };
+
+        zone = if !cfg.primary then {
+          "catalog.".catalog-role     = "interpret";
+          "catalog.".catalog-template = "secondaryZone";
+          "catalog.".template         = "secondaryZone";
+        } else {
+          "catalog.".catalog-role  = "generate";
+          "catalog.".template      = "notifyZone";
+        } // (lib.mapAttrs (name: zone: {
+          template = "primaryZone";
+        }) cfg.allZones) // (lib.mapAttrs (name: zone: zone // {
+          template = "extraZone";
+          acl      = lib.mkIf (builtins.hasAttr "acl" zone) (lib.flatten [ [ "allowTransfer" ] zone.acl ]);
+        }) cfg.extraZones);
+	    };
+  	};
+
+    systemd.tmpfiles.settings = {
+      knotDataDir."${cfg.dataDir}".d = {
+        group = "knot";
+        user  = "knot";
+        mode  = "770";
+        age   = "-";
+      };
+
+      knotZones."${cfg.dataDir}/nixZones".d = lib.mkIf cfg.primary {
+        group = "knot";
+        user  = "knot";
+        mode  = "770";
+        age   = "-";
+      };
+    };
+
+    systemd.services.knot = lib.mkIf cfg.primary {
+      reloadTriggers = (
+        cfg.allZones
+        |> lib.mapAttrsToList (name: zone: pkgs.dns.util."${config.nixpkgs.system}".writeZone name zone)
+       ) ++ (
+        cfg.extraZones
+        |> lib.mapAttrsToList (name: zone: (if (builtins.hasAttr "storage" zone) then "${zone.storage}/${zone.file}" else "${zone.file}"))
+      );
+
+      preStart = ''
+        set -euo pipefail
+
+        cp --dereference /etc/${cfg.zonesDir}/* ${cfg.dataDir}/nixZones
+        chmod -R 770 ${cfg.dataDir}/nixZones
+      ''; 
+
+      serviceConfig.ExecReload = lib.mkForce (pkgs.writeShellScript "knot-reload" ''
+        set -eou pipefail
+
+        cp --dereference /etc/${cfg.zonesDir}/* ${cfg.dataDir}/nixZones
+        chmod -R 770 ${cfg.dataDir}/nixZones
+
+        ${config.services.knot.package}/bin/knotc reload
+      '');
+    };
+  };
+
+}
diff --git a/modules/nixos/email-notify.nix b/modules/nixos/email-notify.nix
@@ -0,0 +1,35 @@
+{ pkgs, lib, config, ... }:
+
+{
+
+  options.services.email-notify.enable = lib.mkEnableOption "Enable a service which can be used to send emails";
+
+#   config = lib.mkIf config.services.email-notify.enable {
+#     age.secrets.password-leah-at-f2k1-de.file = ../../secrets/passwords/leah-at-f2k1-de.age;
+# 
+#     programs.msmtp = {
+#       enable      = true;
+#       setSendmail = false;
+#       accounts    = {
+#         default = {
+#           auth         = true;
+#           tls          = true;
+#           host         = "rx300.kunbox.net";
+#           port         = 587;
+#           user         = "leah@f2k1.de";
+#           from         = "${config.networking.fqdn} <leah@f2k1.de>";
+#           passwordeval = "cat ${config.age.secrets.password-leah-at-f2k1-de.path}";
+#         };
+#       };
+#     };
+# 
+#     systemd.services."email-notify@" = {
+#       serviceConfig = {
+#         ExecStart = ''
+#           ${pkgs.runtimeShell} -c "{ echo -n 'Subject:[${config.networking.fqdn}] Service failed: %i\n\n' &  ${pkgs.systemd}/bin/systemctl status %i;} | ${pkgs.msmtp}/bin/msmtp -v notify@ctu.cx"
+#         '';
+#       };
+#     };
+#   };
+
+}
diff --git a/modules/linux/gitolite.nix b/modules/nixos/gitolite.nix
diff --git a/modules/linux/gnome.nix b/modules/nixos/gnome.nix
diff --git a/modules/nixos/gotosocial.nix b/modules/nixos/gotosocial.nix
@@ -0,0 +1,174 @@
+{ options, config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg            = config.services.gotosocial;
+  settingsFormat = pkgs.formats.json {};
+
+in {
+
+  options = {
+    services.gotosocial = with lib; {
+      enable = mkEnableOption "GoToSocial ActivityPub Server";
+
+      package = mkOption {
+        type    = types.package;
+        default = pkgs.gotosocial;
+      };
+
+      user = mkOption {
+        type    = types.str;
+        default = "gotosocial";
+      };
+
+      group = mkOption {
+        type    = types.str;
+        default = "gotosocial";
+      };
+
+      stateDir = mkOption {
+        type     = types.str;
+        default  = "/var/lib/gotosocial";
+        readOnly = true;
+      };
+
+      environmentFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+      };
+
+      settings = lib.mkOption {
+        type = lib.types.submodule {
+          freeformType = settingsFormat.type;
+
+          options.host = lib.mkOption {
+            type = lib.types.nullOr lib.types.str;
+            default = null;
+            description = ''
+              Hostname that this server will be reachable at. Defaults to localhost for local testing,
+              but you should *definitely* change this when running for real, or your server won't work at all.
+              DO NOT change this after your server has already run once, or you will break things!
+            '';
+          };
+
+          options.port = lib.mkOption {
+            type = lib.types.port;
+            default = 8080;
+            description = ''
+              Int. Listen port for the GoToSocial webserver + API. If you're running behind a reverse proxy and/or in a docker,
+              container, just set this to whatever you like (or leave the default), and make sure it's forwarded properly.
+              If you are running with built-in letsencrypt enabled, and running GoToSocial directly on a host machine, you will
+              probably want to set this to 443 (standard https port), unless you have other services already using that port.
+              This *MUST NOT* be the same as the letsencrypt port specified below, unless letsencrypt is turned off.
+            '';
+          };
+        };
+
+        default = {};
+        description = ''
+          Configuration for GoToSocial, see
+          <link xlink:href="https://docs.gotosocial.org/en/latest/">
+          for supported values.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable (let
+    configFile = settingsFormat.generate "gotosocial-config.yaml" cfg.settings;
+  in {
+    assertions = [
+      {
+        assertion = cfg.settings.host != null;
+        message = "You have to define a hostname for GoToSocial, it cannot be changed later without starting over!";
+      }
+    ];
+
+    services.gotosocial.settings = { # Defaults
+      user                    = lib.mkDefault "gotosocial";
+      group                   = lib.mkDefault "gotosocial";
+
+      storage-local-base-path = lib.mkDefault "/var/lib/gotosocial"; # SystemD StateDirectory
+
+      web-template-base-dir   = lib.mkDefault "${cfg.package}/share/web/template/";
+      web-asset-base-dir      = lib.mkDefault "${cfg.package}/share/web/assets/";
+    };
+
+    users = {
+      users."${cfg.user}" = {
+        home         = cfg.stateDir;
+        group        = cfg.group;
+        isSystemUser = true;
+      };
+      groups."${cfg.group}" = {};
+    };
+
+    environment.etc."gotosocial.yaml".source = configFile;
+
+    environment.systemPackages = [
+      (pkgs.writeShellScriptBin "gotosocial" ''
+        exec ${cfg.package}/bin/gotosocial --config-path ${configFile} "$@"
+      '')
+    ];
+
+    systemd.services = {
+      gotosocial = {
+        description     = "GoToSocial ActivityPub Server";
+        wants           = [ "network-online.target" ];
+        after           = [ "network-online.target" ];
+        wantedBy        = [ "multi-user.target" ];
+        onFailure       = [ "email-notify@%i.service" ];
+
+        serviceConfig = {
+          User = cfg.user;
+          Group = cfg.group;
+
+          Type = "exec";
+          WorkingDirectory   = "~";
+          StateDirectory     = lib.mkIf (cfg.settings.storage-local-base-path != "/var/lib/gotosocial") "gotosocial";
+          ReadOnlyPaths      = [ cfg.package ];
+          ReadWritePaths     = [ cfg.settings.storage-local-base-path ];
+          StateDirectoryMode = "750";
+
+          Restart    = "always";
+          RestartSec = 3;
+
+          EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
+          ExecStart       = "${cfg.package}/bin/gotosocial --config-path ${configFile} server start";
+
+          NoNewPrivileges = true;
+          PrivateTmp      = true;
+          PrivateDevices  = false;
+
+          RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
+          RestrictNamespaces      = true;
+          RestrictRealtime        = true;
+        
+          ProtectSystem         = "full";
+          ProtectControlGroups  = true;
+          ProtectKernelModules  = true;
+          ProtectKernelTunables = true;
+
+          DevicePolicy     = "closed";
+          LockPersonality  = true;
+          SystemCallFilter = "~@clock @debug @module @mount @obsolete @reboot @setuid @swap";
+
+          CapabilityBoundingSet = [
+            "~CAP_RAWIO CAP_MKNOD"
+            "~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE"
+            "~CAP_SYS_BOOT CAP_SYS_TIME CAP_SYS_MODULE CAP_SYS_PACCT"
+            "~CAP_LEASE CAP_LINUX_IMMUTABLE CAP_IPC_LOCK"
+            "~CAP_BLOCK_SUSPEND CAP_WAKE_ALARM"
+            "~CAP_SYS_TTY_CONFIG"
+            "~CAP_MAC_ADMIN CAP_MAC_OVERRIDE"
+            "~CAP_NET_ADMIN CAP_NET_BROADCAST CAP_NET_RAW"
+            "~CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_SYSLOG"
+          ];
+        };
+      };
+    };
+
+  });
+
+}
diff --git a/modules/linux/restic-backups.nix b/modules/nixos/restic-backups.nix
diff --git a/modules/linux/vnstati/default.nix b/modules/nixos/vnstati/default.nix
diff --git a/modules/linux/vnstati/vnstati-html.nix b/modules/nixos/vnstati/vnstati-html.nix