ctucx.git: nixfiles

ctucx' nixfiles

commit d87d6856da63c5c358a38ee5e28f403d92d15fbc
parent 80bc076559a6cb8c7a5038c3194465b14076487e
Author: Katja (ctucx) <git@ctu.cx>
Date: Tue, 4 Mar 2025 11:12:53 +0100

machines: refactor
54 files changed, 1452 insertions(+), 1622 deletions(-)
R
machines/briefkasten/router/default.nix -> configurations/nixos/configure/router/default.nix
|
0
A
configurations/nixos/configure/router/ppp.nix
|
64
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
R
machines/briefkasten/router/ruleset.nft -> configurations/nixos/configure/router/ruleset.nft
|
0
A
configurations/nixos/configure/router/systemd-networkd.nix
|
204
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
R
machines/briefkasten/smarthome/default.nix -> configurations/nixos/configure/smarthome/default.nix
|
0
R
machines/briefkasten/smarthome/homebridge.nix -> configurations/nixos/configure/smarthome/homebridge.nix
|
0
A
configurations/nixos/configure/smarthome/influxdb2.nix
|
36
++++++++++++++++++++++++++++++++++++
A
configurations/nixos/configure/smarthome/mosquitto.nix
|
47
+++++++++++++++++++++++++++++++++++++++++++++++
A
configurations/nixos/configure/smarthome/mqtt-webui/config.nix
|
216
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
configurations/nixos/configure/smarthome/mqtt-webui/default.nix
|
36
++++++++++++++++++++++++++++++++++++
R
machines/briefkasten/smarthome/mqtt-webui/extra-css/extra.css -> configurations/nixos/configure/smarthome/mqtt-webui/extra-css/extra.css
|
0
A
configurations/nixos/configure/smarthome/mqtt-webui/extra-css/manifest.json
|
13
+++++++++++++
A
configurations/nixos/configure/smarthome/telegraf.nix
|
40
++++++++++++++++++++++++++++++++++++++++
A
configurations/nixos/configure/smarthome/zigbee2mqtt.nix
|
102
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M
configurations/nixos/default.nix
|
20
+++++++++++---------
A
configurations/nixos/services/mailserver/default.nix
|
199
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
configurations/nixos/services/mailserver/rules-katja.sieve
|
123
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
R
machines/hector/matrix/synapse.nix -> configurations/nixos/services/matrix-synapse.nix
|
0
A
configurations/nixos/services/mautrix-whatsapp.nix
|
61
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
configurations/nixos/websites/dendrite.ctucx.de.nix
|
52
++++++++++++++++++++++++++++++++++++++++++++++++++++
R
machines/hector/grafana/dashboards/SmartHome.json -> configurations/nixos/websites/grafana.ctu.cx/dashboards/SmartHome.json
|
0
R
machines/hector/grafana/dashboards/node-exporter.json -> configurations/nixos/websites/grafana.ctu.cx/dashboards/node-exporter.json
|
0
A
configurations/nixos/websites/grafana.ctu.cx/default.nix
|
82
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M
flake.nix
|
8
+-------
M
machines/briefkasten/default.nix
|
24
++++++++++--------------
M
machines/briefkasten/hardware-configuration.nix
|
18
++----------------
M
machines/briefkasten/impermanence.nix
|
9
+--------
D
machines/briefkasten/router/ppp.nix
|
64
----------------------------------------------------------------
D
machines/briefkasten/router/systemd-networkd.nix
|
204
-------------------------------------------------------------------------------
D
machines/briefkasten/smarthome/influxdb2.nix
|
36
------------------------------------
D
machines/briefkasten/smarthome/mosquitto.nix
|
48
------------------------------------------------
D
machines/briefkasten/smarthome/mqtt-webui/config.nix
|
217
-------------------------------------------------------------------------------
D
machines/briefkasten/smarthome/mqtt-webui/default.nix
|
41
-----------------------------------------
D
machines/briefkasten/smarthome/mqtt-webui/extra-css/manifest.json
|
14
--------------
D
machines/briefkasten/smarthome/telegraf.nix
|
41
-----------------------------------------
D
machines/briefkasten/smarthome/zigbee2mqtt.nix
|
102
-------------------------------------------------------------------------------
M
machines/briefkasten/syncthing.nix
|
5
+++++
M
machines/hector/default.nix
|
50
++++++++++++++++++++++++--------------------------
D
machines/hector/grafana/default.nix
|
82
-------------------------------------------------------------------------------
M
machines/hector/hardware-configuration.nix
|
42
+++++++++++++-----------------------------
D
machines/hector/mail/default.nix
|
199
-------------------------------------------------------------------------------
D
machines/hector/mail/rules-katja.sieve
|
123
-------------------------------------------------------------------------------
D
machines/hector/matrix/default.nix
|
11
-----------
D
machines/hector/matrix/mautrix-whatsapp.nix
|
64
----------------------------------------------------------------
D
machines/hector/syncthing.nix
|
10
----------
M
machines/seifenkiste/default.nix
|
3
++-
M
machines/seifenkiste/hardware-configuration.nix
|
40
++++++++++------------------------------
M
machines/trabbi/default.nix
|
39
++++++++++++++++-----------------------
M
machines/trabbi/hardware-configuration.nix
|
40
++++++++++++----------------------------
M
machines/wanderduene/default.nix
|
45
++++++++++++++++++---------------------------
D
machines/wanderduene/dendrite.nix
|
82
-------------------------------------------------------------------------------
M
machines/wanderduene/hardware-configuration.nix
|
40
++++++++++++----------------------------
M
machines/wanderduene/rclone-restic-server.nix
|
8
+++-----
M
secrets/hector/knot-keys.age
|
70
+++++++++++++++++++++++++++++++++++++---------------------------------
diff --git a/machines/briefkasten/router/default.nix b/configurations/nixos/configure/router/default.nix
diff --git a/configurations/nixos/configure/router/ppp.nix b/configurations/nixos/configure/router/ppp.nix
@@ -0,0 +1,64 @@
+{ config, utils, pkgs, ... }:
+
+{
+
+  age.secrets.pppd-env.file = ./. + "/../../../../secrets/${config.networking.hostName}/pppd-env.age";
+
+  services.pppd = {
+    enable = true;
+    peers.dtagdsl = {
+      config = ''
+        plugin pppoe.so dtagdsl
+        user "''${DTAG_PPP_USER}"
+        password "''${DTAG_PPP_PASS}"
+        hide-password
+        ifname ppp-dtagdsl
+        persist
+
+        maxfail 0
+        holdoff 5
+
+        noipdefault
+
+        lcp-echo-interval 20
+        lcp-echo-failure 3
+
+        mtu 1492
+        defaultroute
+        replacedefaultroute
+        +ipv6
+      '';
+    };
+  };
+
+  environment.etc."ppp/peers/dtagdsl".enable = false;
+  environment.etc."ip-up.d/1systemd-networkd" = {
+    mode = "755";
+    text = ''
+      #!{pkgs.bash}/bin/bash
+      networkctl reconfigure "$PPP_IFACE";
+    '';
+  };
+
+  systemd.services."pppd-dtagdsl".serviceConfig = let
+    preStart = ''
+      mkdir -p /etc/ppp/peers
+
+      # Created files only readable by root
+      umask u=rw,g=,o=
+
+      # Copy config and substitute env-vars
+      rm -f /etc/ppp/peers/dtagdsl
+      ${pkgs.envsubst}/bin/envsubst -i "${config.environment.etc."ppp/peers/dtagdsl".source}" > /etc/ppp/peers/dtagdsl
+    '';
+
+    preStartFile = utils.systemdUtils.lib.makeJobScript { name = "pppd-dtagdsl-pre-start"; text = preStart; enableStrictShellChecks = true; };
+  in {
+    EnvironmentFile = config.age.secrets.pppd-env.path;
+    ExecStartPre = [
+      # "+" marks script to be executed without priviledge restrictions
+      "+${preStartFile}"
+    ];
+  };
+
+}
diff --git a/machines/briefkasten/router/ruleset.nft b/configurations/nixos/configure/router/ruleset.nft
diff --git a/configurations/nixos/configure/router/systemd-networkd.nix b/configurations/nixos/configure/router/systemd-networkd.nix
@@ -0,0 +1,204 @@
+{ config, ... }:
+
+{
+
+#  systemd.services."systemd-networkd".serviceConfig.Environment = "SYSTEMD_LOG_LEVEL=debug";
+
+  age.secrets.wireguard-privkey = {
+    file  = ./. + "../../../../../secrets/${config.networking.hostName}/wireguard-privkey.age";
+    mode  = "640";
+    owner = "root";
+    group = "systemd-network";
+  };
+
+  systemd.network = {
+    enable             = true;
+    wait-online.enable = false;
+
+    config.networkConfig = {
+      IPv6Forwarding = true;
+    };
+
+    links."5-dtagdsl" = {
+      matchConfig.PermanentMACAddress = "d0:37:45:06:de:de";
+      linkConfig.Name = "dtagdsl";
+    };
+
+    links."5-iphone" = {
+      matchConfig.PermanentMACAddress = "aa:ab:b5:18:95:d9";
+      linkConfig.Name = "iphone";
+    };
+
+    netdevs."20-brlan" = {
+      netdevConfig = {
+        Kind = "bridge";
+        Name = "brlan";
+      };
+    };
+
+    netdevs."10-wg-wanderduene" = {
+      netdevConfig = {
+        Kind = "wireguard";
+        Name = "wg-wanderduene";
+      };
+
+      wireguardConfig = {
+        PrivateKeyFile = config.age.secrets.wireguard-privkey.path;
+        ListenPort     = 51820;
+        FirewallMark   = 51820;
+      };
+
+      wireguardPeers = [{
+        Endpoint            = "wanderduene.ctu.cx:51820";
+        PublicKey           = "hOUeP8RFchzJXyy8DceTFKN9f1VHi9GzZQii0dX2zww=";
+        AllowedIPs          = [ "::/0" ];
+        PersistentKeepalive = 10;
+      }];
+    };
+
+    networks = {
+      "5-dtagdsl" = {
+        matchConfig.Name = "dtagdsl";
+
+        linkConfig.RequiredForOnline      = false;
+        networkConfig.LinkLocalAddressing = false;
+      };
+
+      "5-enp1s0" = {
+        matchConfig.Name = "enp1s0";
+
+        networkConfig.ConfigureWithoutCarrier = true;
+        bridge = [ "brlan" ];
+      };
+
+      "5-iphone" = {
+        matchConfig.Name   = "iphone";
+
+        networkConfig.DHCP = true;
+      };
+
+      "10-ppp-dtagdsl" = {
+        matchConfig.Name = "ppp-dtagdsl";
+
+        networkConfig = {
+          KeepConfiguration    = true;
+          IPv6AcceptRA         = true;
+          DHCP                 = "ipv6";
+        };
+
+        ipv6AcceptRAConfig = {
+          UseDNS = false;
+        };
+
+        dhcpV6Config = {
+          WithoutRA            = "solicit";
+          PrefixDelegationHint = "::/56";
+          IAID                 = 0;
+          UseDNS               = false;
+        };
+      };
+
+      "10-wg-wanderduene" = {
+        matchConfig.Name = "wg-wanderduene";
+
+        linkConfig.RequiredForOnline = false;
+        routes  = [{
+          Destination = "::/0";
+          Table       = "1234";
+        }];
+      };
+
+      "20-brlan" = {
+        matchConfig = {
+          Name   = "brlan";
+          Driver = "bridge";
+        };
+
+        address = [ "10.0.0.1/8" "2a03:4000:4d:5e:acab::1/112" ];
+
+        routingPolicyRules = [
+          {
+            From                 = "2a03:4000:4d:5e:acab::/112";
+            Table                = 254;
+            Priority             = 1900;
+            SuppressPrefixLength = 0;
+          }
+          {
+            From     = "2a03:4000:4d:5e:acab::/112";
+            Table    = 1234;
+            Priority = 2000;
+          }
+        ];
+
+        networkConfig = {
+          ConfigureWithoutCarrier = true;
+          IPv4Forwarding          = true;
+          IPMasquerade            = "ipv4";
+
+          DHCPPrefixDelegation    = true;
+          IPv6PrivacyExtensions   = false;
+          IPv6AcceptRA            = false;
+          IPv6SendRA              = true;
+
+          DHCPServer              = true;
+          DNS                     = "10.0.0.1";
+        };
+
+        dhcpPrefixDelegationConfig = {
+          UplinkInterface = "ppp-dtagdsl";
+          Announce        = true;
+          SubnetId        = 0;
+          Token           = "::1";
+        };
+
+        ipv6SendRAConfig = {
+          EmitDNS = true;
+          DNS     = "_link_local";
+        };
+
+        ipv6PREF64Prefixes = [{
+          Prefix = "64:ff9b::/96";
+        }];
+
+        dhcpServerConfig = {
+          PersistLeases = true;
+          PoolOffset    = 100;
+          PoolSize      = 100;
+          EmitDNS       = true;
+          DNS           = "10.0.0.1";
+          IPv6OnlyPreferredSec = 300;
+        };
+
+        dhcpServerStaticLeases = [
+          {
+            # accesspoint
+            MACAddress = "48:a9:8a:8e:dd:0b";
+            Address    = "10.0.0.2";
+          }
+          {
+            # pbx
+            MACAddress = "34:31:c4:46:88:31";
+            Address    = "10.0.0.3";
+          }
+          {
+            # scanner
+            MACAddress = "5c:f3:70:b9:35:9c";
+            Address    = "10.0.0.4";
+          }
+          {
+            # printer
+            MACAddress = "70:77:81:2a:e7:96";
+            Address    = "10.0.0.5";
+          }
+          {
+            # katja x13
+            MACAddress = "04:CF:4B:76:93:55";
+            Address    = "10.0.0.10";
+          }
+        ];
+      };
+
+    };
+  };
+
+}
diff --git a/machines/briefkasten/smarthome/default.nix b/configurations/nixos/configure/smarthome/default.nix
diff --git a/machines/briefkasten/smarthome/homebridge.nix b/configurations/nixos/configure/smarthome/homebridge.nix
diff --git a/configurations/nixos/configure/smarthome/influxdb2.nix b/configurations/nixos/configure/smarthome/influxdb2.nix
@@ -0,0 +1,36 @@
+{ config, pkgs, lib, ... }:
+
+{
+
+  dns.zones."ctu.cx".subdomains."influx.home".AAAA = [ config.networking.primaryIP ];
+
+  age.secrets.restic-influxdb.file   = ./. + "/../../../../secrets/${config.networking.hostName}/restic/influxdb.age";
+  age.secrets.influx-backup-env.file = ./. + "/../../../../secrets/${config.networking.hostName}/influx/backup_env.age";
+
+  systemd.services.restic-backup-influxdb.serviceConfig.EnvironmentFile = config.age.secrets.influx-backup-env.path;
+
+  restic-backups.influxdb = {
+    user          = "influxdb2";
+    passwordFile  = config.age.secrets.restic-influxdb.path;
+    influxBuckets = [ "mqttData" ];
+  };
+
+  systemd.services.influxdb2 = {
+    serviceConfig.ExecStartPost = "${pkgs.bash}/bin/bash -c 'until ${pkgs.netcat}/bin/nc -z ::1 8086; do sleep 0.2; done'";
+    onFailure                   = [ "email-notify@%i.service" ];
+  };
+
+  services.influxdb2.enable = true;
+  services.influxdb2.settings.http-bind-address = "[::1]:8086";
+
+  services.nginx = {
+    enable = true;
+    virtualHosts."influx.home.ctu.cx" = {
+      useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
+      forceSSL    = true;
+      kTLS        = true;
+      locations."/".proxyPass   = "http://${toString config.services.influxdb2.settings.http-bind-address}/";
+    };
+  };
+
+}
diff --git a/configurations/nixos/configure/smarthome/mosquitto.nix b/configurations/nixos/configure/smarthome/mosquitto.nix
@@ -0,0 +1,47 @@
+{ inputs, config, pkgs, ... }:
+
+{
+
+  age.secrets = {
+    mosquitto-passwd-katja = {
+      file  = ./. + "/../../../../secrets/${config.networking.hostName}/mosquitto/passwd-katja.age";
+      owner = "mosquitto";
+    };
+  };
+
+  services = {
+    mosquitto = {
+      enable      = true;
+      persistence = false;
+      settings = {
+        max_keepalive = 60;
+      };
+      listeners = [
+
+        {
+          port = 1883;
+          omitPasswordAuth = true;
+          users = {};
+          settings = {
+            allow_anonymous = true;
+          };
+          acl = [ "topic readwrite #" "pattern readwrite #" ];
+        }
+
+        {
+          address = "::1";
+          port    = 9005;
+          users = {
+            katja.passwordFile = config.age.secrets.mosquitto-passwd-katja.path;
+          };
+          settings = {
+            protocol = "websockets";
+          };
+          acl = [ "topic readwrite #" "pattern readwrite #" ];
+        }
+
+      ];
+    };
+  };
+
+}
diff --git a/configurations/nixos/configure/smarthome/mqtt-webui/config.nix b/configurations/nixos/configure/smarthome/mqtt-webui/config.nix
@@ -0,0 +1,216 @@
+let
+
+  Switch = name: topic: {
+    title         = name;
+    type          = "switch";
+    icon          = "icons/power_button.png";
+    topic.get     = topic;
+    topic.set     = "${topic}/set";
+    transform.get = "return (message.state == 'ON') ? true : false";
+    transform.set = "return JSON.stringify({state: (input) ? 'ON' : 'OFF'})";
+  };
+
+  BrighnessSlider = name: topic: {
+    title           = name;
+    type            = "slider";
+    icon            = "icons/bulb.png";
+    sliderMinValue  = 0;
+    sliderMaxValue  = 254;
+    sliderStepValue = 1;
+    topic.get       = topic;
+    topic.set       = "${topic}/set";
+    transform.get   = "return message.brightness";
+    transform.set   = "return JSON.stringify({brightness: Number(input)})";
+  };
+
+  ColorTemperatureSlider = name: topic: {
+    title           = name;
+    type            = "slider";
+    icon            = "icons/bulb.png";
+    sliderMinValue  = 250;
+    sliderMaxValue  = 454;
+    sliderStepValue = 1;
+    topic.get       = topic;
+    topic.set       = "${topic}/set";
+    transform.get   = "return message.color_temp";
+    transform.set   = "return JSON.stringify({color_temp: Number(input)})";
+  };
+
+  DimmableLamp = name: topic: {
+    title = name;
+    items = [
+      (Switch          "Power"     topic)
+      (BrighnessSlider "Brighness" topic)
+    ];
+  };
+
+  WhiteSpectrumLamp = name: topic: {
+    title = name;
+    items = [
+      (Switch                 "Power"             topic)
+      (BrighnessSlider        "Brighness"         topic)
+      (ColorTemperatureSlider "Color Temperature" topic)
+    ];
+  };
+
+  ColorSpectrumLamp = name: topic: {
+    title = name;
+    items = [
+      (Switch                 "Power"             topic)
+      (BrighnessSlider        "Brighness"         topic)
+      (ColorTemperatureSlider "Color Temperature" topic)
+
+      {
+        title           = "Color";
+        type            = "select";
+        icon            = "icons/bulb.png";
+        topic.get       = topic;
+        topic.set       = "${topic}/set";
+        transform.get   = "return message.color.x + ','+message.color.y";
+        transform.set   = "return JSON.stringify({color: {x: input.split(',')[0], y: input.split(',')[1]}})";
+        selectOptions   = [
+          {
+            label = "Red";
+            value = "0.71,0.26";
+          }
+          {
+            label = "Green";
+            value = "0.19,0.78";
+          }
+          {
+            label = "Blue";
+            value = "0.09,0.13";
+          }
+        ];
+      }
+    ];
+  };
+
+
+in {
+
+  appName  = "Smart-Home";
+  extraCSS = "/extra.css";
+  pages    =  [
+    {
+      id        = "mainpage";
+      icon     = "favicon-512x512.png";
+      title    = "Smart-Home";
+      sections = [
+
+        (WhiteSpectrumLamp "Hallway" "zigbee2mqtt/ikea_lamp_hallway")
+
+        (WhiteSpectrumLamp "Living room" "zigbee2mqtt/ikea_lamp_livingroom")
+
+        (WhiteSpectrumLamp "Sleeping room" "zigbee2mqtt/ikea_lamp_sleepingroom")
+
+        (DimmableLamp "Desk" "zigbee2mqtt/tuya_led_stripe_desk")
+
+        (ColorSpectrumLamp "RGB Lamp" "zigbee2mqtt/ikea_lamp_rgb")
+
+        {
+          title = "Temperature-Sensors";
+          items = [
+            {
+              title     = "Fridge";
+              type      = "text";
+              topic     = "zigbee2mqtt/tuya_sensor_fridge";
+              icon      = "icons/temperature.png";
+              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C - ' + message.humidity + ' %'";
+            }
+            {
+              title     = "Bathroom";
+              type      = "text";
+              topic     = "zigbee2mqtt/tuya_sensor_bathroom";
+              icon      = "icons/temperature.png";
+              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C - ' + message.humidity + ' %'";
+            }
+
+            {
+              title     = "Door";
+              type      = "text";
+              topic     = "zigbee2mqtt/tuya_sensor_l";
+              icon      = "icons/temperature.png";
+              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C - ' + message.humidity + ' %'";
+            }
+            {
+              title     = "Bed";
+              type      = "text";
+              topic     = "zigbee2mqtt/tuya_sensor_l2";
+              icon      = "icons/temperature.png";
+              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C - ' + message.humidity + ' %'";
+            }
+
+          ];
+        }
+
+        {
+          items = [
+            {
+              title = "Departures";
+              type = "text";
+              icon = "icons/electric_range.png";
+              link = "#departures";
+            }
+            {
+              title = "Grafana-Dashboard";
+              type = "text";
+              icon = "icons/sun.png";
+              link = "https://grafana.ctu.cx/d/FRDYqjEGz/smarthome-influx?orgId=1&refresh=5s";
+            }
+          ];
+        }
+
+      ];
+    }
+
+    {
+      id       = "departures";
+      title    = "Departures";
+      sections = [
+        {
+          items = [
+            {
+              type      = "html";
+              topic     = "departures2mqtt";
+              html      = "<div class=\"loader\"></div>";
+              transform = ''
+                  clearInterval(globalThis.departuresUpdater)
+                  globalThis.departuresUpdater = setInterval(() => {
+                    let element = document.querySelector('[data-last-updated]');
+                    element.textContent = 'Last Update: ' + (Math.floor(Date.now() / 1000) - element.dataset.lastUpdated) + ' seconds ago';
+                  }, 10000)
+
+                  let output = "";
+
+                  Object.entries(message.departures).forEach((data) => {
+                     output += '<div class="departures"><div class="table-column table-title">' + data[0] + '</div><div class="table">';
+
+                     output += '<div class="table-row line-column"><div class="table-column table-heading">Linie</div>';
+                     data[1].forEach((departure) => output += '<div class="table-column">' + departure.line + '</div>');
+                     output += '</div>';
+
+                     output += '<div class="table-row direction-column"><div class="table-column table-heading">Richtung</div>';
+                     data[1].forEach((departure) => output += '<div class="table-column">' + departure.direction + '</div>');
+                     output += '</div>';
+
+                     output += '<div class="table-row dep-column"><div class="table-column table-heading">Abfahrt</div>';
+                     data[1].forEach((departure) => output += '<div class="table-column">' + (Number(departure.departure_in) ? departure.departure_in + " min" : "sofort") + '</div>');
+                     output += '</div>';
+
+                     output += '</div></div>';
+                  });
+
+                  output += '<div class="lastUpdated" data-last-updated="' + message.lastUpdated + '">Last Update: ' + (Math.floor(Date.now() / 1000) - message.lastUpdated) + ' seconds ago</div>';
+
+                  return output;
+              '';
+            }
+          ];
+        }
+      ];
+    }
+
+  ];
+
+}
diff --git a/configurations/nixos/configure/smarthome/mqtt-webui/default.nix b/configurations/nixos/configure/smarthome/mqtt-webui/default.nix
@@ -0,0 +1,36 @@
+{ inputs, lib, pkgs, config, ... }:
+
+let
+  configFile = pkgs.writeTextDir "config.json" (builtins.toJSON (import ./config.nix));
+
+in {
+
+  dns.zones."ctu.cx".subdomains."smart.home".AAAA = [ config.networking.primaryIP ];
+
+  services.nginx = {
+    enable = true;
+    virtualHosts."smart.home.ctu.cx" = {
+      useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
+      forceSSL    = true;
+      kTLS        = true;
+
+      locations  = {
+        "/" = {
+          root  = "${pkgs.buildEnv {
+            name  = "mqtt-webui-env";
+            paths = [
+              pkgs.mqtt-webui
+              configFile
+              ./extra-css
+            ];
+          }}/";
+        };
+        "/mqtt" = {
+          proxyPass       = "http://[::1]:9005";
+          proxyWebsockets = true;
+        };
+      };
+    };
+  };
+
+}
diff --git a/machines/briefkasten/smarthome/mqtt-webui/extra-css/extra.css b/configurations/nixos/configure/smarthome/mqtt-webui/extra-css/extra.css
diff --git a/configurations/nixos/configure/smarthome/mqtt-webui/extra-css/manifest.json b/configurations/nixos/configure/smarthome/mqtt-webui/extra-css/manifest.json
@@ -0,0 +1,13 @@
+{
+	"name": "Smart-Home",
+	"short_name": "Smart-Home",
+	"start_url": ".",
+	"display": "standalone",
+	"icons": [
+		{
+			"src": "favicon-512x512.png",
+			"sizes": "512x512",
+			"type": "image/png"
+		}
+	]
+}
diff --git a/configurations/nixos/configure/smarthome/telegraf.nix b/configurations/nixos/configure/smarthome/telegraf.nix
@@ -0,0 +1,40 @@
+{ inputs, config, ... }:
+
+{
+
+  age.secrets.telegraf-env = {
+    file  = ./. + "/../../../../secrets/${config.networking.hostName}/telegraf/secrets.env.age";
+    owner = "telegraf";
+  };
+
+  services.telegraf = {
+    enable = true;
+    environmentFiles = [ config.age.secrets.telegraf-env.path ];
+    extraConfig = {
+      inputs = {
+        mqtt_consumer = {
+          servers     = [ "tcp://[::1]:1883" ];
+          topics      = [
+            "zigbee2mqtt/tuya_sensor_fridge"
+            "zigbee2mqtt/tuya_sensor_bathroom"
+            "zigbee2mqtt/tuya_sensor_sleepingroom"
+            "zigbee2mqtt/tuya_sensor_l2"
+          ];
+          data_format = "json";
+          fielddrop   = [ "newBatt" "weakBatt" ];
+        };
+      };
+      outputs = {
+        influxdb_v2 = [
+          {
+            urls         = [ "http://${toString config.services.influxdb2.settings.http-bind-address}/" ];
+            organization = "smarthome";
+            bucket       = "mqttData";
+            token        = "\${INFLUX_TOKEN_MQTTDATA}";
+          }
+        ];
+      };
+    };
+  };
+
+}
diff --git a/configurations/nixos/configure/smarthome/zigbee2mqtt.nix b/configurations/nixos/configure/smarthome/zigbee2mqtt.nix
@@ -0,0 +1,102 @@
+{ inputs, pkgs, lib, config, ... }:
+
+{
+
+  dns.zones."ctu.cx".subdomains."zigbee2mqtt.home".AAAA = [ config.networking.primaryIP ];
+
+  age.secrets = {
+    "zigbee2mqtt-htpasswd" = {
+      file  = ./. + "/../../../../secrets/${config.networking.hostName}/zigbee2mqtt/htpasswd.age";
+      owner = "nginx";
+    };
+    "zigbee2mqtt-secrets.yaml" = {
+      file  = ./. + "/../../../../secrets/${config.networking.hostName}/zigbee2mqtt/secrets.age";
+      owner = "zigbee2mqtt";
+    };
+  };
+
+  systemd.services.zigbee2mqtt = {
+    requires      = [ "mosquitto.service" ];
+    after         = [ "mosquitto.service" ];
+    onFailure     = [ "email-notify@%i.service" ];
+    serviceConfig = {
+      Restart       = lib.mkForce "always";
+      RuntimeMaxSec = "1d";
+    };
+  };
+
+  services = {
+    udev.extraRules = ''
+      SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{serial}=="00_12_4B_00_25_9B_C1_FC", SYMLINK+="zigbee0"
+      ATTR{idVendor}=="0451", ATTR{idProduct}=="16a8", ENV{ID_MM_DEVICE_IGNORE}="1"
+      SUBSYSTEM=="tty", ATTRS{idVendor}=="0451", ATTRS{idProduct}=="16a8", SYMLINK+="zigbee0"
+    '';
+
+    nginx = {
+      enable = true;
+      virtualHosts."zigbee2mqtt.${config.networking.domain}" = {
+        useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
+        forceSSL    = true;
+        kTLS        = true;
+        locations."/" = {
+          proxyPass       = "http://[::1]:${toString config.services.zigbee2mqtt.settings.frontend.port}";
+          proxyWebsockets = true;
+          extraConfig     = ''
+            auth_basic           Auth;
+            auth_basic_user_file ${config.age.secrets.zigbee2mqtt-htpasswd.path};
+          '';
+        };
+      };
+    };
+
+    zigbee2mqtt = {
+      enable   = true;
+      package  = pkgs.zigbee2mqtt;
+      settings = {
+        homeassistant = false;
+        permit_join   = false;
+
+        mqtt = {
+          base_topic = "zigbee2mqtt";
+          server     = "mqtt://[::1]";
+        };
+
+        serial = {
+          port        = "/dev/zigbee0";
+          disable_led = true;
+        };
+
+        frontend = {
+          port = 8422;
+          host = "::1";
+        };
+
+        advanced = {
+          log_level   = "info";
+          log_output  = [ "console" ];
+          channel     = 26;
+          network_key = "!${config.age.secrets."zigbee2mqtt-secrets.yaml".path} network_key";
+        };
+
+        device_options.retain = true;
+        devices = {
+          "0x84fd27fffe6b9ddd".friendly_name = "ikea_lamp_hallway";
+          "0x94deb8fffe52e639".friendly_name = "ikea_lamp_rgb";
+          "0x84fd27fffe44369e".friendly_name = "ikea_lamp_sleepingroom";
+          "0x84fd27fffea515fc".friendly_name = "ikea_lamp_livingroom";
+
+#          "0x804b50fffe42a74e".friendly_name = "ikea_remote_l_door";
+#          "0x842e14fffe977bfa".friendly_name = "ikea_remote_l_bed";
+
+          "0xa4c138da0f6d23de".friendly_name = "tuya_led_stripe_desk";
+
+          "0xa4c1389d5f391891".friendly_name = "tuya_sensor_fridge";
+          "0xa4c13809f76bcdc2".friendly_name = "tuya_sensor_bathroom";
+          "0xa4c13882b76fa1ac".friendly_name = "tuya_sensor_sleepingroom";
+          "0xa4c138ebeae2efd2".friendly_name = "tuya_sensor_l2";
+        };
+
+      };
+    };
+  };
+}
diff --git a/configurations/nixos/default.nix b/configurations/nixos/default.nix
@@ -9,8 +9,11 @@ in {
     ctucxConfig.common
   ];
 
-  age.secrets.katja-systempassword.file = ../../secrets/passwords/katja.age;
-  age.secrets.acmeTSIGKey.file          = ./. + "/../../secrets/${config.networking.hostName}/acme-tsig-key.age";
+  boot.loader.efi.canTouchEfiVariables = lib.mkDefault true;
+  boot.loader.systemd-boot.enable      = lib.mkDefault true;
+
+  networking.hostName = lib.mkDefault args.name;
+  networking.domain   = lib.mkDefault "ctu.cx";
 
   deployment = {
     buildOnTarget = lib.mkDefault false;

@@ -19,6 +22,12 @@ in {
     targetPort    = lib.mkDefault (lib.head config.services.openssh.ports);
   };
 
+  i18n.defaultLocale    = "en_US.UTF-8";
+  i18n.supportedLocales = ["de_DE.UTF-8/UTF-8" "en_US.UTF-8/UTF-8"];
+
+  age.secrets.katja-systempassword.file = ../../secrets/passwords/katja.age;
+  age.secrets.acmeTSIGKey.file          = ./. + "/../../secrets/${config.networking.hostName}/acme-tsig-key.age";
+
   system = {
     nixos.revision      = lib.mkIf (inputs.nixpkgs.sourceInfo ? rev) inputs.nixpkgs.sourceInfo.rev;
     nixos.versionSuffix = ".${lib.substring 0 8 (inputs.nixpkgs.sourceInfo.lastModifiedDate or inputs.nixpkgs.sourceInfo.lastModified or "19700101")}.${inputs.nixpkgs.sourceInfo.shortRev or "dirty"}";

@@ -41,12 +50,6 @@ in {
     };
   };
 
-  networking.hostName = lib.mkDefault args.name;
-  networking.domain   = lib.mkDefault "ctu.cx";
-
-  i18n.defaultLocale    = "en_US.UTF-8";
-  i18n.supportedLocales = ["de_DE.UTF-8/UTF-8" "en_US.UTF-8/UTF-8"];
-
   systemd.services.nginx.onFailure = [ "email-notify@%i.service" ];
 
   nix = {

@@ -65,7 +68,6 @@ in {
     gc.dates     = "18:00";
   };
 
-
   services = {
     timesyncd.enable = true;
     fstrim.enable    = true;
diff --git a/configurations/nixos/services/mailserver/default.nix b/configurations/nixos/services/mailserver/default.nix
@@ -0,0 +1,199 @@
+{ inputs, pkgs, config, ... }:
+
+let
+  mailAutoConfig = ''
+    <?xml version="1.0" encoding="UTF-8"?>
+    <clientConfig version="1.1">
+     <emailProvider id="ctu.cx">
+       <domain>ctu.cx</domain>
+       <displayName>${config.networking.fqdn}</displayName>
+       <displayShortName>${config.networking.domain}</displayShortName>
+       <incomingServer type="imap">
+         <hostname>${config.networking.fqdn}</hostname>
+         <port>993</port>
+         <socketType>SSL</socketType>
+         <authentication>password-cleartext</authentication>
+         <username>%EMAILADDRESS%</username>
+       </incomingServer>
+       <outgoingServer type="smtp">
+         <hostname>${config.networking.fqdn}</hostname>
+         <port>465</port>
+         <socketType>SSL</socketType>
+         <authentication>password-cleartext</authentication>
+         <username>%EMAILADDRESS%</username>
+       </outgoingServer>
+     </emailProvider>
+    </clientConfig>
+  '';
+
+in {
+
+  age.secrets.restic-mail.file               = ./. + "/../../../../secrets/${config.networking.hostName}/restic/mail.age";
+  age.secrets.mail-password-katja.file       = ./. + "/../../../../secrets/${config.networking.hostName}/mail/password-katja-ctu.cx.age";
+  age.secrets.mail-password-gts.file         = ./. + "/../../../../secrets/${config.networking.hostName}/mail/password-gts-ctu.cx.age";
+  age.secrets.mail-password-gts-zug.file     = ./. + "/../../../../secrets/${config.networking.hostName}/mail/password-gts-zuggeschmack.de.age";
+  age.secrets.mail-password-info-zug.file    = ./. + "/../../../../secrets/${config.networking.hostName}/mail/password-info-zuggeschmack.de.age";
+  age.secrets.mail-password-vaultwarden.file = ./. + "/../../../../secrets/${config.networking.hostName}/mail/password-vaultwarden-ctu.cx.age";
+
+  dns.zones = with pkgs.dns.lib.combinators; let
+    TXT   = [ "v=spf1 a mx ip4:${config.networking.primaryIP4} +ip6:${config.networking.primaryIP} ~all" ];
+    DMARC = "v=DMARC1; p=none";
+    MX    = with mx; [ (mx 10 "${config.networking.fqdn}.") ];
+  in {
+    "ctu.cx" = {
+      inherit MX TXT;
+
+      SRV = [
+        { proto = "tcp"; service = "imaps"; priority = 0; weight = 1; port = 993; target = "${config.networking.fqdn}."; }
+        { proto = "tcp"; service = "imap"; priority = 0; weight = 1; port = 143; target = "${config.networking.fqdn}."; }
+        { proto = "tcp"; service = "submission"; priority = 0; weight = 1; port = 587; target = "${config.networking.fqdn}."; }
+      ];
+
+      subdomains = {
+        autoconfig.CNAME         = [ config.networking.hostName ];
+        _dmarc.TXT               = [ DMARC ];
+        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKryfX99NkcU5Xe4AmG+kO/sfuYSXk5RqJhzxS4uMqERE8UszgEGdteXcD8pqON2MfDmA3G6cA+Oa+N4tIWdIYNwTISVXXMGdHvjFIsVUEW0turM104tXESELaPRntkCvDBk/yOgsBDRZQHSx5MdGwpzeRC8TLdCbalh3W0jp5PQIDAQAB" ];
+      };
+    };
+
+    "katja.wtf" = {
+      inherit MX TXT;
+
+      subdomains = {
+        _dmarc.TXT               = [ DMARC ];
+        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDk63I3fkv/p1TMrcU8T1gjli0hJZp92JjK+YhOJAAdpeKMuePMI1vi/aw1Ribx9fBXGlMbNERIu4fiYWF9SsrMKqYFayD8CWbok+enZl+yQSh+UgP7jhXo0zNSTec6DK/AczZh+HbpzMSxCKQhVdjCx60F6tnh/m2OxddAw5oe6QIDAQAB" ];
+      };
+    };
+
+    "ctucx.de" = {
+      inherit MX TXT;
+
+      subdomains = {
+        _dmarc.TXT               = [ DMARC ];
+        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5fu690bKYCZLPAFfQQK+nl+aAmtetaWBKCWzGj6pt7HjpFjystgtgnQ6+DZLFXWUp8GRfMEycySB5kQULtYtSMUmx0gQBnTTLsRj+e55/CYUllLV6YXb5uca7LuVhlWPpH3sCr6TvC2VFWe4t0UC3uIXhYPrCm6p8OE7g+TdHHwIDAQAB" ];
+      };
+    };
+
+    "zuggeschmack.de" = {
+      inherit MX TXT;
+
+      subdomains = {
+        _dmarc.TXT               = [ DMARC ];
+        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDf0PX32wWq068cThCnAaX1RJMBiMo6pGfT/VOp9/IzXWmNO2aSyWEFp5lVwYFJnlGX1Sg1uvThICVDiscOqG5jBUAc0gl3SPEBFJ0cqLl7CYhD3Nkvgc8+7zn4huKvFGYXRSDqQm+AL4SSEjZ8hF+N9bGxt0bYu2WlGwZX8mTptwIDAQAB" ];
+      };
+    };
+
+    "thein.ovh" = {
+      inherit MX TXT;
+
+      subdomains = {
+        _dmarc.TXT               = [ DMARC ];
+        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8oumqNkHboF/S4dnKue+hEC3V226ToMmL/fmXqbAhsW88m+jUuLgZE8Nl7kc/lzD9yY7JmCXcWFzoLJWE8xusfmT1yMOW9sQmee7g0tHsm1fVqFMUetmC4+QuqAdvjIGU5QndjdWHP/gssIoLPT7lCNUL4/lkaPmFiiDyvaMpkQIDAQAB" ];
+      };
+    };
+  };
+
+  security.acme.certs."${config.networking.fqdn}".reloadServices = [
+    "postfix.service"
+    "dovecot2.service"
+  ];
+
+  services.nginx = {
+    enable = true;
+    virtualHosts = {
+      "${config.networking.fqdn}" = {
+        useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
+        forceSSL    = true;
+      };
+
+      "autoconfig.ctu.cx" = {
+        useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
+        forceSSL    = true;
+        locations."= /mail/config-v1.1.xml".return = "200 '${mailAutoConfig}'";
+      };
+    };
+  };
+
+  services.redis.servers.rspamd.bind = "::1";
+
+  services.dovecot2.sieve.extensions = [
+    "editheader"
+  ];
+
+  mailserver = {
+    enable  = true;
+    fqdn    = config.networking.fqdn;
+
+    openFirewall        = true;
+    localDnsResolver    = false;
+    virusScanning       = false;
+
+    redis.address       = "[::1]";
+
+    certificateScheme   = "manual";
+    certificateFile     = "${config.security.acme.certs.${config.networking.fqdn}.directory}/fullchain.pem";
+    keyFile             = "${config.security.acme.certs.${config.networking.fqdn}.directory}/key.pem";
+
+    enableManageSieve   = true;
+    enableSubmission    = true;
+    enableSubmissionSsl = true;
+    enableImap          = true;
+    enableImapSsl       = true;
+    enablePop3          = false;
+    enablePop3Ssl       = false;
+
+    mailDirectory       = "/var/lib/mailboxes";
+    sieveDirectory      = "/var/lib/sieve";
+    dkimKeyDirectory    = "/var/lib/dkimKeys";
+
+    domains = [
+      "ctu.cx"
+      "ctucx.de"
+      "katja.wtf"
+      "thein.ovh"
+      "zuggeschmack.de"
+    ];
+
+    loginAccounts = {
+      "katja@ctu.cx" = {
+        hashedPasswordFile = config.age.secrets.mail-password-katja.path;
+        sieveScript = builtins.readFile ./rules-katja.sieve;
+        aliases = [
+          "@ctu.cx"
+          "@ctucx.de"
+          "@thein.ovh"
+          "@katja.wtf"
+        ];
+      };
+
+      "vaultwarden@ctu.cx" = {
+        hashedPasswordFile = config.age.secrets.mail-password-vaultwarden.path;
+      };
+
+      "gts@ctu.cx" = {
+        hashedPasswordFile = config.age.secrets.mail-password-gts.path;
+      };
+
+      "gts@zuggeschmack.de" = {
+        hashedPasswordFile = config.age.secrets.mail-password-gts-zug.path;
+      };
+
+      "info@zuggeschmack.de" = {
+        hashedPasswordFile = config.age.secrets.mail-password-info-zug.path;
+        aliases = [
+          "@zuggeschmack.de"
+        ];
+      };
+    };
+  };
+
+  restic-backups.mail = {
+    passwordFile = config.age.secrets.restic-mail.path;
+    paths        = [
+      "/var/lib/mailboxes"
+      "/var/lib/dkimKeys"
+      "/var/lib/sieve"
+    ];
+  };
+
+}
diff --git a/configurations/nixos/services/mailserver/rules-katja.sieve b/configurations/nixos/services/mailserver/rules-katja.sieve
@@ -0,0 +1,123 @@
+require [
+  "variables", "date", "regex",
+  "fileinto", "mailbox", "editheader",
+  "imap4flags"
+];
+
+
+if currentdate :matches "year" "*" { set "year" "${1}"; }
+if currentdate :matches "month" "*" { set "month" "${1}"; }
+
+
+if address :is "to" "le0nth3in@gmail.com" {
+  if header :matches "Subject" "*" {
+     set "subject" "${1}";
+  }
+
+  deleteheader "Subject";
+  addheader :last "Subject" "[OLD GMAIL] ${subject}";
+}
+
+if address :contains "to" "@thein.ovh" {
+  if header :matches "Subject" "*" {
+     set "subject" "${1}";
+  }
+
+  deleteheader "Subject";
+  addheader :last "Subject" "[OLD DOMAIN] ${subject}";
+}
+
+if address :contains "to" "leah" {
+  if header :matches "Subject" "*" {
+     set "subject" "${1}";
+  }
+
+  deleteheader "Subject";
+  addheader :last "Subject" "[OLD NAME] ${subject}";
+}
+
+if header :contains "From" [
+  "newsletter",
+  "ikea@hej.news.email.ikea.de",
+  "inside@cowboy.com",
+  "hello@cowboy.com",
+  "News@InsideApple.Apple.com",
+  "no-reply@newsletter.ab-in-die-box.de",
+  "info@join.netflix.com",
+  "marketing.support@porkbun.com",
+  "update@email.flixbus.com",
+  "telekom@email-telekom.de",
+  "rewe-bonus@mailing.rewe.de"
+] {
+  fileinto :create "INBOX.Newsletters";
+  stop;
+}
+
+if header :contains "subject" [ "Fahrgastrechteanträge", "Fahrgastrechteantrag" ] {
+  fileinto :create "Archive.FGR${year}";
+  stop;
+}
+
+if header :contains "From" [ "ebon@mailing.rewe.de" ] {
+  fileinto :create "INBOX.REWE-Bons";
+  stop;
+}
+
+if header :contains "From" [ "@amazon.de", "@audible.de" ] {
+  fileinto :create "INBOX.Amazon";
+  stop;
+}
+
+if header :contains "From" [ "@apple.com", "@email.apple.com", "@id.apple.com" ] {
+  fileinto :create "INBOX.Apple";
+  stop;
+}
+
+if header :contains "From" [ "@bahn.de", "@mailing.bahn.de", "@mail.bahncard.bahn.de", "@deutschebahn.com" ] {
+  fileinto :create "INBOX.Bahn";
+  stop;
+}
+
+if header :contains "From" [
+  "@bunq.com", "@hello.bunq.com", "@update.bunq.com",
+  "@gls.de",
+  "@paypal.de", "@paypal.com", "@emails.paypal.com"
+] {
+  fileinto :create "INBOX.Banking";
+  stop;
+}
+
+if header :contains "From" [
+  "@hetzner.com",
+  "@netcup.de",
+  "@ovh.de"
+] {
+  fileinto :create "INBOX.Hosting";
+  stop;
+}
+
+
+if header :contains "From" [ "@dhl.de", "@dhl.com" ] {
+  fileinto :create "INBOX.DHL";
+  stop;
+}
+
+if header :contains "From" [ "@duolingo.com" ] {
+  fileinto :create "INBOX.Duolingo";
+  stop;
+}
+
+if header :contains "From" [ "@ebay.de", "@ebay.com" ] {
+  fileinto :create "INBOX.eBay";
+  stop;
+}
+
+if header :contains "From" [ "@google.com", "@accounts.google.com" ] {
+  fileinto :create "INBOX.Google";
+  stop;
+}
+
+if header :contains "From" [ "@mein-grundeinkommen.de" ] {
+  fileinto :create "INBOX.MeinGrundeinkommen";
+  stop;
+}
diff --git a/machines/hector/matrix/synapse.nix b/configurations/nixos/services/matrix-synapse.nix
diff --git a/configurations/nixos/services/mautrix-whatsapp.nix b/configurations/nixos/services/mautrix-whatsapp.nix
@@ -0,0 +1,61 @@
+{ config, pkgs, ...  }:
+
+{
+
+  users.users.matrix-synapse.extraGroups = [ "mautrix-whatsapp" ];
+
+  services = {
+    mautrix-whatsapp.enable   = true;
+    mautrix-whatsapp.settings = {
+      homeserver.address = "https://matrix.ctu.cx";
+      homeserver.domain  = "ctu.cx";
+
+      metrics.enabled = true;
+
+      whatsapp.os_name = "Mautrix-WhatsApp bridge (ctu.cx)";
+
+      appservice = {
+        address  = "http://localhost:29318";
+        hostname = "[::1]";
+        port     =  29318;
+
+        id = "whatsapp";
+
+        database.type = "sqlite3-fk-wal";
+        database.uri  = "file:/var/lib/mautrix-whatsapp/mautrix-whatsapp.db?_txlock=immediate";
+      };
+
+      bridge = {
+        command_prefix       = "!wa";
+        displayname_template = "{{if .BusinessName}}{{.BusinessName}}{{else if .PushName}}{{.PushName}}{{else}}{{.JID}}{{end}} (WA)";
+        username_template    = "whatsapp_{{.}}";
+
+        delivery_receipts         = true;
+        message_status_events     = true;
+        message_error_notices     = true;
+        call_start_notices        = true;
+        identity_change_notices   = true;
+        user_avatar_sync          = true;
+        personal_filtering_spaces = true;
+
+        encryption.allow = true;
+
+        permissions = {
+          "ctu.cx" = "user";
+        };
+
+        history_sync = {
+          backfill = true;
+          message_count = 250;
+          request_full_sync = true;
+        };
+
+        relay = {
+          enabled = true;
+        };
+      };
+
+    };
+  };
+
+}
diff --git a/configurations/nixos/websites/dendrite.ctucx.de.nix b/configurations/nixos/websites/dendrite.ctucx.de.nix
@@ -0,0 +1,52 @@
+{ pkgs, lib, config, ... }:
+
+{
+
+  dns.zones."ctucx.de".subdomains.dendrite.CNAME = [ "${config.networking.fqdn}." ];
+
+  users.groups.dendrite = {};
+  users.users.dendrite = {
+    isSystemUser = true;
+    home = "/var/lib/dendrite";
+    group = "dendrite";
+  };
+
+  services.dendrite = {
+    enable = true;
+    openRegistration = false;
+    settings = {
+      global.server_name = "dendrite.ctucx.de";
+      global.private_key = "/var/lib/dendrite/private_key.pem";
+
+      global.well_known_server_name = "dendrite.ctucx.de:443";
+      global.well_known_client_name = "https://dendrite.ctucx.de";
+
+      client_api.registration_disabled = true;
+      client_api.registration_shared_secret = "joihgpiufgpueiuessqiegp87tf-e8d7pgwiugbdpiugp87dfo87ugfodiujpfd87g97dpg97dp97";
+    };
+  };
+
+  services.nginx = {
+    enable       = true;
+    virtualHosts = {
+      "dendrite.ctucx.de" = {
+        useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
+        forceSSL    = true;
+        kTLS        = true;
+        locations   = {
+          "/.well-known".proxyPass = "http://[::1]:8008";
+          "/_matrix".proxyPass = "http://[::1]:8008";
+          "/".root             = pkgs.cinny.override {
+            conf = {
+              defaultHomeserver = 0;
+              homeserverList    = [ "dendrite.ctucx.de" ];
+              allowCustomHomesevrers = false;
+            };
+          };
+        };
+      };
+
+    };
+  };
+
+}
diff --git a/machines/hector/grafana/dashboards/SmartHome.json b/configurations/nixos/websites/grafana.ctu.cx/dashboards/SmartHome.json
diff --git a/machines/hector/grafana/dashboards/node-exporter.json b/configurations/nixos/websites/grafana.ctu.cx/dashboards/node-exporter.json
diff --git a/configurations/nixos/websites/grafana.ctu.cx/default.nix b/configurations/nixos/websites/grafana.ctu.cx/default.nix
@@ -0,0 +1,82 @@
+{ inputs, config, lib, pkgs, ... }:
+
+{
+
+  age.secrets.grafana-influx-token-mqttData = {
+    file  = ../../../../secrets/briefkasten/influx/grafana_token_mqttData.age;
+    owner = "grafana";
+  };
+
+  dns.zones."ctu.cx".subdomains.grafana.CNAME = [ "${config.networking.fqdn}." ];
+
+  systemd.services.grafana.onFailure = [ "email-notify@%i.service" ];
+
+  services = {
+    grafana = {
+      enable  = true;
+
+      settings = {
+        server = {
+          domain    = "grafana.ctu.cx";
+          root_url  = "https://grafana.ctu.cx/";
+          http_addr = "::1";
+          http_port = 3001;
+        };
+        security.allow_embedding = true;
+        "users".auto_assign_org_role = "Viewer";
+#        "users".viewers_can_edit = true;
+        "users".home_page = "/d/FRDYqjEGz/smarthome-influx";
+        "auth".disable_login_form = true;
+        "auth.basic".enabled = false;
+        "auth.anonymous".enabled = true;
+        "auth.anonymous".org_name = "Main Org.";
+        "auth.anonymous".org_role = "Viewer";
+      };
+
+      provision = {
+        enable      = true;
+        datasources.settings.datasources = [
+
+          {
+            name      = "Prometheus";
+            type      = "prometheus";
+            url       = "https://prometheus.ctu.cx/";
+            isDefault = true;
+            editable  = false;
+            jsonData.timeInterval = "20s";
+          }
+
+          {
+            name                   = "InfluxDB (mqttData)";
+            type                   = "influxdb";
+            url                    = "https://influx.home.ctu.cx";
+            orgId                  = 1;
+            database               = "mqttData";
+            editable               = false;
+            jsonData.version       = "Flux";
+            jsonData.organization  = "katja";
+            jsonData.defaultBucket = "mqttData";
+            secureJsonData.token   = "$__file{${config.age.secrets.grafana-influx-token-mqttData.path}}";
+          }
+
+        ];
+
+        dashboards.settings.providers = [{
+          folder = "provisioned";
+          options.path = ./dashboards;
+        }];
+      };
+    };
+
+    nginx = {
+      enable = true;
+      virtualHosts."grafana.ctu.cx" = {
+        useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
+        forceSSL    = true;
+        kTLS        = true;
+        locations."/".proxyPass = "http://[::1]:${toString config.services.grafana.settings.server.http_port}/";
+      };
+    };
+  };
+
+}
diff --git a/flake.nix b/flake.nix
@@ -65,13 +65,7 @@
         ];
       };
 
-      seifenkiste      = import ./machines/seifenkiste;
-      briefkasten      = import ./machines/briefkasten;
-
-      trabbi           = import ./machines/trabbi;
-      wanderduene      = import ./machines/wanderduene;
-      hector           = import ./machines/hector;
-    };
+    } // loadDir ./machines;
 
     colmenaHive         = inputs.colmena.lib.makeHive self.outputs.colmena;
     nixosConfigurations = (import (inputs.colmena + "/src/nix/hive/eval.nix") {
diff --git a/machines/briefkasten/default.nix b/machines/briefkasten/default.nix
@@ -6,11 +6,15 @@
     ./hardware-configuration.nix
     ./impermanence.nix
 
+    ctucxConfig.programs.yt-dlp
+    ctucxConfig.programs.ocrmypdf
+
+    ctucxConfig.configure.router
+    ctucxConfig.configure.smarthome
+
     ctucxConfig.services.prometheus-exporters
     ctucxConfig.services.restic-server
 
-    ctucxConfig.programs.yt-dlp
-    ctucxConfig.programs.ocrmypdf
 
     ctucxConfig.websites."music.home.ctu.cx"
     ctucxConfig.websites."audiobooks.home.ctu.cx"

@@ -19,11 +23,7 @@
     # syncthing (and it's backup)
     ./syncthing.nix
 
-    ./smarthome
-
     ./scanner-sftp.nix
-
-    ./router
   ];
 
   age.secrets = {

@@ -41,14 +41,7 @@
     kernelModules = [ "intel_rapl_common" ];
 
     # seems to make realtek ethernet faster?
-    kernelParams = [
-      "pcie_aspm=off"
-    ];
-
-    loader = {
-      systemd-boot.enable = true;
-      efi.canTouchEfiVariables = true;
-    };
+    kernelParams = [ "pcie_aspm=off" ];
 
     initrd.network = {
       enable = true;

@@ -92,6 +85,9 @@
     email-notify.enable = true;
   };
 
+  powerManagement.cpuFreqGovernor    = "powersave";
+  hardware.cpu.intel.updateMicrocode = true;
+
   system.stateVersion = "22.11"; # Did you read the comment?
   home-manager.users.katja.home.stateVersion = "22.11";
 
diff --git a/machines/briefkasten/hardware-configuration.nix b/machines/briefkasten/hardware-configuration.nix
@@ -1,17 +1,10 @@
-# Do not modify this file!  It was generated by ‘nixos-generate-config’
-# and may be overwritten by future invocations.  Please make changes
-# to /etc/nixos/configuration.nix instead.
 { config, lib, pkgs, modulesPath, ... }:
 
 {
-  imports =
-    [ (modulesPath + "/installer/scan/not-detected.nix")
-    ];
 
-  boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod" "r8169" ];
-  boot.initrd.kernelModules = [ ];
   boot.kernelModules = [ "kvm-intel" ];
-  boot.extraModulePackages = [ ];
+  boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod" "r8169" ];
+  boot.initrd.luks.devices."nix-store".device = "/dev/disk/by-uuid/d1e0568c-042a-4e76-8901-30bcb9fb8efb";
 
   fileSystems."/" = {
     device = "tmpfs";

@@ -35,11 +28,4 @@
     fsType = "vfat";
   };
 
-  boot.initrd.luks.devices."nix-store".device = "/dev/disk/by-uuid/d1e0568c-042a-4e76-8901-30bcb9fb8efb";
-
-
-  swapDevices = [ ];
-
-  powerManagement.cpuFreqGovernor = lib.mkDefault "powersave";
-  hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
 }
diff --git a/machines/briefkasten/impermanence.nix b/machines/briefkasten/impermanence.nix
@@ -2,11 +2,6 @@
 
 {
 
-  services.syncthing = {
-    dataDir    = "/nix/persist/home/katja/syncthing";
-    configDir  = "/nix/persist/home/katja/.config/syncthing";
-  };
-
   services.openssh.hostKeys = [
     {
       bits = 4096;

@@ -34,9 +29,7 @@
   programs.fuse.userAllowOther = true;
 
   home-manager.users.katja = {
-    imports = [
-      inputs.impermanence.homeManagerModules.default
-    ];
+    imports = [ inputs.impermanence.homeManagerModules.default ];
 
     home.persistence."/nix/persist/home/katja" = {
       allowOther = true;
diff --git a/machines/briefkasten/router/ppp.nix b/machines/briefkasten/router/ppp.nix
@@ -1,64 +0,0 @@
-{ config, utils, pkgs, ... }:
-
-{
-
-  age.secrets.pppd-env.file = ./. + "/../../../secrets/${config.networking.hostName}/pppd-env.age";
-
-  services.pppd = {
-    enable = true;
-    peers.dtagdsl = {
-      config = ''
-        plugin pppoe.so dtagdsl
-        user "''${DTAG_PPP_USER}"
-        password "''${DTAG_PPP_PASS}"
-        hide-password
-        ifname ppp-dtagdsl
-        persist
-
-        maxfail 0
-        holdoff 5
-
-        noipdefault
-
-        lcp-echo-interval 20
-        lcp-echo-failure 3
-
-        mtu 1492
-        defaultroute
-        replacedefaultroute
-        +ipv6
-      '';
-    };
-  };
-
-  environment.etc."ppp/peers/dtagdsl".enable = false;
-  environment.etc."ip-up.d/1systemd-networkd" = {
-    mode = "755";
-    text = ''
-      #!{pkgs.bash}/bin/bash
-      networkctl reconfigure "$PPP_IFACE";
-    '';
-  };
-
-  systemd.services."pppd-dtagdsl".serviceConfig = let
-    preStart = ''
-      mkdir -p /etc/ppp/peers
-
-      # Created files only readable by root
-      umask u=rw,g=,o=
-
-      # Copy config and substitute env-vars
-      rm -f /etc/ppp/peers/dtagdsl
-      ${pkgs.envsubst}/bin/envsubst -i "${config.environment.etc."ppp/peers/dtagdsl".source}" > /etc/ppp/peers/dtagdsl
-    '';
-
-    preStartFile = utils.systemdUtils.lib.makeJobScript { name = "pppd-dtagdsl-pre-start"; text = preStart; enableStrictShellChecks = true; };
-  in {
-    EnvironmentFile = config.age.secrets.pppd-env.path;
-    ExecStartPre = [
-      # "+" marks script to be executed without priviledge restrictions
-      "+${preStartFile}"
-    ];
-  };  
-
-}
diff --git a/machines/briefkasten/router/systemd-networkd.nix b/machines/briefkasten/router/systemd-networkd.nix
@@ -1,204 +0,0 @@
-{ config, ... }:
-
-{
-
-#  systemd.services."systemd-networkd".serviceConfig.Environment = "SYSTEMD_LOG_LEVEL=debug";
-
-  age.secrets.wireguard-privkey = {
-    file  = ./. + "../../../../secrets/${config.networking.hostName}/wireguard-privkey.age";
-    mode  = "640";
-    owner = "root";
-    group = "systemd-network";
-  };
-
-  systemd.network = {
-    enable             = true;
-    wait-online.enable = false;
-
-    config.networkConfig = {
-      IPv6Forwarding = true;
-    };
-
-    links."5-dtagdsl" = {
-      matchConfig.PermanentMACAddress = "d0:37:45:06:de:de";
-      linkConfig.Name = "dtagdsl";
-    };
-
-    links."5-iphone" = {
-      matchConfig.PermanentMACAddress = "aa:ab:b5:18:95:d9";
-      linkConfig.Name = "iphone";
-    };
-
-    netdevs."20-brlan" = {
-      netdevConfig = {
-        Kind = "bridge";
-        Name = "brlan";
-      };
-    };
-
-    netdevs."10-wg-wanderduene" = {
-      netdevConfig = {
-        Kind = "wireguard";
-        Name = "wg-wanderduene";
-      };
-
-      wireguardConfig = {
-        PrivateKeyFile = config.age.secrets.wireguard-privkey.path;
-        ListenPort     = 51820;
-        FirewallMark   = 51820;
-      };
-
-      wireguardPeers = [{
-        Endpoint            = "wanderduene.ctu.cx:51820";
-        PublicKey           = "hOUeP8RFchzJXyy8DceTFKN9f1VHi9GzZQii0dX2zww=";
-        AllowedIPs          = [ "::/0" ];
-        PersistentKeepalive = 10;
-      }];
-    };
-
-    networks = {
-      "5-dtagdsl" = {
-        matchConfig.Name = "dtagdsl";
-
-        linkConfig.RequiredForOnline      = false;
-        networkConfig.LinkLocalAddressing = false;
-      };
-
-      "5-enp1s0" = {
-        matchConfig.Name = "enp1s0";
-
-        networkConfig.ConfigureWithoutCarrier = true;
-        bridge = [ "brlan" ];
-      };
-
-      "5-iphone" = {
-        matchConfig.Name   = "iphone";
-
-        networkConfig.DHCP = true;
-      };
-
-      "10-ppp-dtagdsl" = {
-        matchConfig.Name = "ppp-dtagdsl";
-
-        networkConfig = {
-          KeepConfiguration    = true;
-          IPv6AcceptRA         = true;
-          DHCP                 = "ipv6";
-        };
-
-        ipv6AcceptRAConfig = {
-          UseDNS = false;
-        };
-
-        dhcpV6Config = {
-          WithoutRA            = "solicit";
-          PrefixDelegationHint = "::/56";
-          IAID                 = 0;
-          UseDNS               = false;
-        };
-      };
-
-      "10-wg-wanderduene" = {
-        matchConfig.Name = "wg-wanderduene";
-
-        linkConfig.RequiredForOnline = false;
-        routes  = [{
-          Destination = "::/0";
-          Table       = "1234";
-        }];
-      };
-
-      "20-brlan" = {
-        matchConfig = {
-          Name   = "brlan";
-          Driver = "bridge";
-        };
-
-        address = [ "10.0.0.1/8" "2a03:4000:4d:5e:acab::1/112" ];
-
-        routingPolicyRules = [
-          {
-            From                 = "2a03:4000:4d:5e:acab::/112";
-            Table                = 254;
-            Priority             = 1900;
-            SuppressPrefixLength = 0;
-          }
-          {
-            From     = "2a03:4000:4d:5e:acab::/112";
-            Table    = 1234;
-            Priority = 2000;
-          }
-        ];
-
-        networkConfig = {
-          ConfigureWithoutCarrier = true;
-          IPv4Forwarding          = true;
-          IPMasquerade            = "ipv4";
-
-          DHCPPrefixDelegation    = true;
-          IPv6PrivacyExtensions   = false;
-          IPv6AcceptRA            = false;
-          IPv6SendRA              = true;
-
-          DHCPServer              = true;
-          DNS                     = "10.0.0.1";
-        };
-
-        dhcpPrefixDelegationConfig = {
-          UplinkInterface = "ppp-dtagdsl";
-          Announce        = true;
-          SubnetId        = 0;
-          Token           = "::1"; 
-        };
-
-        ipv6SendRAConfig = {
-          EmitDNS = true;
-          DNS     = "_link_local";
-        };
-
-        ipv6PREF64Prefixes = [{
-          Prefix = "64:ff9b::/96";
-        }];
-
-        dhcpServerConfig = {
-          PersistLeases = true;
-          PoolOffset    = 100;
-          PoolSize      = 100;
-          EmitDNS       = true;
-          DNS           = "10.0.0.1";
-          IPv6OnlyPreferredSec = 300;
-        };
-
-        dhcpServerStaticLeases = [
-          {
-            # accesspoint
-            MACAddress = "48:a9:8a:8e:dd:0b";
-            Address    = "10.0.0.2";
-          }
-          {
-            # pbx
-            MACAddress = "34:31:c4:46:88:31";
-            Address    = "10.0.0.3";
-          }
-          {
-            # scanner
-            MACAddress = "5c:f3:70:b9:35:9c";
-            Address    = "10.0.0.4";
-          }
-          {
-            # printer
-            MACAddress = "70:77:81:2a:e7:96";
-            Address    = "10.0.0.5";
-          }
-          {
-            # katja x13
-            MACAddress = "04:CF:4B:76:93:55";
-            Address    = "10.0.0.10";
-          }
-        ];
-      };
-
-    };
-  };
-
-}
diff --git a/machines/briefkasten/smarthome/influxdb2.nix b/machines/briefkasten/smarthome/influxdb2.nix
@@ -1,36 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-{
-
-  dns.zones."ctu.cx".subdomains."influx.home".AAAA = [ config.networking.primaryIP ];
-
-  age.secrets.restic-influxdb.file   = ./. + "/../../../secrets/${config.networking.hostName}/restic/influxdb.age";
-  age.secrets.influx-backup-env.file = ./. + "/../../../secrets/${config.networking.hostName}/influx/backup_env.age";
-
-  systemd.services.restic-backup-influxdb.serviceConfig.EnvironmentFile = config.age.secrets.influx-backup-env.path;
-
-  restic-backups.influxdb = {
-    user          = "influxdb2";
-    passwordFile  = config.age.secrets.restic-influxdb.path;
-    influxBuckets = [ "mqttData" ];
-  };
-
-  systemd.services.influxdb2 = {
-    serviceConfig.ExecStartPost = "${pkgs.bash}/bin/bash -c 'until ${pkgs.netcat}/bin/nc -z ::1 8086; do sleep 0.2; done'";
-    onFailure                   = [ "email-notify@%i.service" ];
-  };
-
-  services.influxdb2.enable = true;
-  services.influxdb2.settings.http-bind-address = "[::1]:8086";
-
-  services.nginx = {
-    enable = true;
-    virtualHosts."influx.home.ctu.cx" = {
-      useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
-      forceSSL    = true;
-      kTLS        = true;
-      locations."/".proxyPass   = "http://${toString config.services.influxdb2.settings.http-bind-address}/";
-    };
-  };
-
-}
diff --git a/machines/briefkasten/smarthome/mosquitto.nix b/machines/briefkasten/smarthome/mosquitto.nix
@@ -1,47 +0,0 @@
-{ inputs, config, pkgs, ... }:
-
-{
-
-  age.secrets = {
-    mosquitto-passwd-katja = {
-      file  = ./. + "/../../../secrets/${config.networking.hostName}/mosquitto/passwd-katja.age";
-      owner = "mosquitto";
-    };
-  };
-
-  services = {
-    mosquitto = {
-      enable      = true;
-      persistence = false;
-      settings = {
-        max_keepalive = 60;
-      };
-      listeners = [
-
-        {
-          port = 1883;
-          omitPasswordAuth = true;
-          users = {};
-          settings = {
-            allow_anonymous = true;
-          };
-          acl = [ "topic readwrite #" "pattern readwrite #" ];
-        }
-
-        {
-          address = "::1";
-          port    = 9005;
-          users = {
-            katja.passwordFile = config.age.secrets.mosquitto-passwd-katja.path;
-          };
-          settings = {
-            protocol = "websockets";
-          };
-          acl = [ "topic readwrite #" "pattern readwrite #" ];
-        }
-
-      ];
-    };
-  };
-
-}-
\ No newline at end of file
diff --git a/machines/briefkasten/smarthome/mqtt-webui/config.nix b/machines/briefkasten/smarthome/mqtt-webui/config.nix
@@ -1,216 +0,0 @@
-let
-
-  Switch = name: topic: {
-    title         = name;
-    type          = "switch";
-    icon          = "icons/power_button.png";
-    topic.get     = topic;
-    topic.set     = "${topic}/set";
-    transform.get = "return (message.state == 'ON') ? true : false";
-    transform.set = "return JSON.stringify({state: (input) ? 'ON' : 'OFF'})";
-  };
-
-  BrighnessSlider = name: topic: {
-    title           = name;
-    type            = "slider";
-    icon            = "icons/bulb.png";
-    sliderMinValue  = 0;
-    sliderMaxValue  = 254;
-    sliderStepValue = 1;
-    topic.get       = topic;
-    topic.set       = "${topic}/set";
-    transform.get   = "return message.brightness";
-    transform.set   = "return JSON.stringify({brightness: Number(input)})";
-  };
-
-  ColorTemperatureSlider = name: topic: {
-    title           = name;
-    type            = "slider";
-    icon            = "icons/bulb.png";
-    sliderMinValue  = 250;
-    sliderMaxValue  = 454;
-    sliderStepValue = 1;
-    topic.get       = topic;
-    topic.set       = "${topic}/set";
-    transform.get   = "return message.color_temp";
-    transform.set   = "return JSON.stringify({color_temp: Number(input)})";
-  };
-
-  DimmableLamp = name: topic: {
-    title = name;
-    items = [
-      (Switch          "Power"     topic)
-      (BrighnessSlider "Brighness" topic)
-    ];
-  };
-
-  WhiteSpectrumLamp = name: topic: {
-    title = name;
-    items = [
-      (Switch                 "Power"             topic)
-      (BrighnessSlider        "Brighness"         topic)
-      (ColorTemperatureSlider "Color Temperature" topic)
-    ];
-  };
-
-  ColorSpectrumLamp = name: topic: {
-    title = name;
-    items = [
-      (Switch                 "Power"             topic)
-      (BrighnessSlider        "Brighness"         topic)
-      (ColorTemperatureSlider "Color Temperature" topic)
-
-      {
-        title           = "Color";
-        type            = "select";
-        icon            = "icons/bulb.png";
-        topic.get       = topic;
-        topic.set       = "${topic}/set";
-        transform.get   = "return message.color.x + ','+message.color.y";
-        transform.set   = "return JSON.stringify({color: {x: input.split(',')[0], y: input.split(',')[1]}})";
-        selectOptions   = [
-          {
-            label = "Red";
-            value = "0.71,0.26";
-          }
-          {
-            label = "Green";
-            value = "0.19,0.78";
-          }
-          {
-            label = "Blue";
-            value = "0.09,0.13";
-          }
-        ];
-      }
-    ];
-  };
-
-
-in {
-
-  appName  = "Smart-Home";
-  extraCSS = "/extra.css";
-  pages    =  [
-    {
-      id        = "mainpage";
-      icon     = "favicon-512x512.png";
-      title    = "Smart-Home";
-      sections = [
-
-        (WhiteSpectrumLamp "Hallway" "zigbee2mqtt/ikea_lamp_hallway")
-
-        (WhiteSpectrumLamp "Living room" "zigbee2mqtt/ikea_lamp_livingroom")
-
-        (WhiteSpectrumLamp "Sleeping room" "zigbee2mqtt/ikea_lamp_sleepingroom")
-
-        (DimmableLamp "Desk" "zigbee2mqtt/tuya_led_stripe_desk")
-
-        (ColorSpectrumLamp "RGB Lamp" "zigbee2mqtt/ikea_lamp_rgb")
-
-        {
-          title = "Temperature-Sensors";
-          items = [
-            {
-              title     = "Fridge";
-              type      = "text";
-              topic     = "zigbee2mqtt/tuya_sensor_fridge";
-              icon      = "icons/temperature.png";
-              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C - ' + message.humidity + ' %'";
-            }
-            {
-              title     = "Bathroom";
-              type      = "text";
-              topic     = "zigbee2mqtt/tuya_sensor_bathroom";
-              icon      = "icons/temperature.png";
-              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C - ' + message.humidity + ' %'";
-            }
-
-            {
-              title     = "Door";
-              type      = "text";
-              topic     = "zigbee2mqtt/tuya_sensor_l";
-              icon      = "icons/temperature.png";
-              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C - ' + message.humidity + ' %'";
-            }
-            {
-              title     = "Bed";
-              type      = "text";
-              topic     = "zigbee2mqtt/tuya_sensor_l2";
-              icon      = "icons/temperature.png";
-              transform = "return Math.round((message.temperature + Number.EPSILON) * 100) / 100 + ' °C - ' + message.humidity + ' %'";
-            }
-
-          ];
-        }
-
-        {
-          items = [
-            {
-              title = "Departures";
-              type = "text";
-              icon = "icons/electric_range.png";
-              link = "#departures";
-            }
-            {
-              title = "Grafana-Dashboard";
-              type = "text";
-              icon = "icons/sun.png";
-              link = "https://grafana.ctu.cx/d/FRDYqjEGz/smarthome-influx?orgId=1&refresh=5s";
-            }
-          ];
-        }
-
-      ];
-    }
-
-    {
-      id       = "departures";
-      title    = "Departures";
-      sections = [
-        {
-          items = [
-            {
-              type      = "html";
-              topic     = "departures2mqtt";
-              html      = "<div class=\"loader\"></div>";
-              transform = ''
-                  clearInterval(globalThis.departuresUpdater)
-                  globalThis.departuresUpdater = setInterval(() => {
-                    let element = document.querySelector('[data-last-updated]');
-                    element.textContent = 'Last Update: ' + (Math.floor(Date.now() / 1000) - element.dataset.lastUpdated) + ' seconds ago';
-                  }, 10000)
-
-                  let output = "";
-
-                  Object.entries(message.departures).forEach((data) => {
-                     output += '<div class="departures"><div class="table-column table-title">' + data[0] + '</div><div class="table">';
-
-                     output += '<div class="table-row line-column"><div class="table-column table-heading">Linie</div>';
-                     data[1].forEach((departure) => output += '<div class="table-column">' + departure.line + '</div>');
-                     output += '</div>';
-
-                     output += '<div class="table-row direction-column"><div class="table-column table-heading">Richtung</div>';
-                     data[1].forEach((departure) => output += '<div class="table-column">' + departure.direction + '</div>');
-                     output += '</div>';
-
-                     output += '<div class="table-row dep-column"><div class="table-column table-heading">Abfahrt</div>';
-                     data[1].forEach((departure) => output += '<div class="table-column">' + (Number(departure.departure_in) ? departure.departure_in + " min" : "sofort") + '</div>');
-                     output += '</div>';
-
-                     output += '</div></div>';
-                  });
-
-                  output += '<div class="lastUpdated" data-last-updated="' + message.lastUpdated + '">Last Update: ' + (Math.floor(Date.now() / 1000) - message.lastUpdated) + ' seconds ago</div>';
-
-                  return output;
-              '';
-            }
-          ];
-        }
-      ];
-    }
-
-  ];
-
-}-
\ No newline at end of file
diff --git a/machines/briefkasten/smarthome/mqtt-webui/default.nix b/machines/briefkasten/smarthome/mqtt-webui/default.nix
@@ -1,40 +0,0 @@
-{ inputs, lib, pkgs, config, ... }:
-
-let
-
-  configFile = pkgs.writeTextDir "config.json" (builtins.toJSON (import ./config.nix));
-
-in {
-
-  dns.zones."ctu.cx".subdomains."smart.home".AAAA = [ config.networking.primaryIP ];
-
-  services = {
-    nginx    = {
-      enable = true;
-      virtualHosts."smart.home.ctu.cx" = {
-        useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
-        forceSSL    = true;
-        kTLS        = true;
-
-        locations  = {
-          "/" = {
-            root  = "${pkgs.buildEnv {
-              name  = "mqtt-webui-env";
-              paths = [
-                pkgs.mqtt-webui
-                configFile
-                ./extra-css
-              ];
-            }}/";
-          };
-          "/mqtt" = {
-            proxyPass       = "http://[::1]:9005";
-            proxyWebsockets = true;
-          };
-        };
-      };
-    };
-
-  };
-
-}-
\ No newline at end of file
diff --git a/machines/briefkasten/smarthome/mqtt-webui/extra-css/manifest.json b/machines/briefkasten/smarthome/mqtt-webui/extra-css/manifest.json
@@ -1,13 +0,0 @@
-{
-	"name": "Smart-Home",
-	"short_name": "Smart-Home",
-	"start_url": ".",
-	"display": "standalone",
-	"icons": [
-		{
-			"src": "favicon-512x512.png",
-			"sizes": "512x512",
-			"type": "image/png"
-		}
-	]
-}-
\ No newline at end of file
diff --git a/machines/briefkasten/smarthome/telegraf.nix b/machines/briefkasten/smarthome/telegraf.nix
@@ -1,40 +0,0 @@
-{ inputs, config, ... }:
-
-{
-
-  age.secrets.telegraf-env = {
-    file  = ./. + "/../../../secrets/${config.networking.hostName}/telegraf/secrets.env.age";
-    owner = "telegraf";
-  };
-
-  services.telegraf = {
-    enable = true;
-    environmentFiles = [ config.age.secrets.telegraf-env.path ];
-    extraConfig = {
-      inputs = {
-        mqtt_consumer = {
-          servers     = [ "tcp://[::1]:1883" ];
-          topics      = [
-            "zigbee2mqtt/tuya_sensor_fridge"
-            "zigbee2mqtt/tuya_sensor_bathroom"
-            "zigbee2mqtt/tuya_sensor_sleepingroom"
-            "zigbee2mqtt/tuya_sensor_l2"
-          ];
-          data_format = "json";
-          fielddrop   = [ "newBatt" "weakBatt" ];
-        };
-      };
-      outputs = {
-        influxdb_v2 = [
-          {
-            urls         = [ "http://${toString config.services.influxdb2.settings.http-bind-address}/" ];
-            organization = "smarthome";
-            bucket       = "mqttData";
-            token        = "\${INFLUX_TOKEN_MQTTDATA}";
-          }
-        ];
-      };
-    };
-  };
-
-}-
\ No newline at end of file
diff --git a/machines/briefkasten/smarthome/zigbee2mqtt.nix b/machines/briefkasten/smarthome/zigbee2mqtt.nix
@@ -1,102 +0,0 @@
-{ inputs, pkgs, lib, config, ... }:
-
-{
-
-  dns.zones."ctu.cx".subdomains."zigbee2mqtt.home".AAAA = [ config.networking.primaryIP ];
-
-  age.secrets = {
-    "zigbee2mqtt-htpasswd" = {
-      file  = ./. + "/../../../secrets/${config.networking.hostName}/zigbee2mqtt/htpasswd.age";
-      owner = "nginx";
-    };
-    "zigbee2mqtt-secrets.yaml" = {
-      file  = ./. + "/../../../secrets/${config.networking.hostName}/zigbee2mqtt/secrets.age";
-      owner = "zigbee2mqtt";
-    };
-  };
-
-  systemd.services.zigbee2mqtt = {
-    requires      = [ "mosquitto.service" ];
-    after         = [ "mosquitto.service" ];
-    onFailure     = [ "email-notify@%i.service" ];
-    serviceConfig = {
-      Restart       = lib.mkForce "always";
-      RuntimeMaxSec = "1d";
-    };
-  };
-
-  services = {
-    udev.extraRules = ''
-      SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{serial}=="00_12_4B_00_25_9B_C1_FC", SYMLINK+="zigbee0"
-      ATTR{idVendor}=="0451", ATTR{idProduct}=="16a8", ENV{ID_MM_DEVICE_IGNORE}="1"
-      SUBSYSTEM=="tty", ATTRS{idVendor}=="0451", ATTRS{idProduct}=="16a8", SYMLINK+="zigbee0"
-    '';
-
-    nginx = {
-      enable = true;
-      virtualHosts."zigbee2mqtt.${config.networking.domain}" = {
-        useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
-        forceSSL    = true;
-        kTLS        = true;
-        locations."/" = {
-          proxyPass       = "http://[::1]:${toString config.services.zigbee2mqtt.settings.frontend.port}";
-          proxyWebsockets = true;
-          extraConfig     = ''
-            auth_basic           Auth;
-            auth_basic_user_file ${config.age.secrets.zigbee2mqtt-htpasswd.path};
-          '';
-        };
-      };
-    };
-
-    zigbee2mqtt = {
-      enable   = true;
-      package  = pkgs.zigbee2mqtt;
-      settings = {
-        homeassistant = false;
-        permit_join   = false;
-
-        mqtt = {
-          base_topic = "zigbee2mqtt";
-          server     = "mqtt://[::1]";
-        };
-
-        serial = {
-          port        = "/dev/zigbee0";
-          disable_led = true;
-        };
-
-        frontend = {
-          port = 8422;
-          host = "::1";
-        };
-
-        advanced = {
-          log_level   = "info";
-          log_output  = [ "console" ];
-          channel     = 26;
-          network_key = "!${config.age.secrets."zigbee2mqtt-secrets.yaml".path} network_key";
-        };
-
-        device_options.retain = true;
-        devices = {
-          "0x84fd27fffe6b9ddd".friendly_name = "ikea_lamp_hallway";
-          "0x94deb8fffe52e639".friendly_name = "ikea_lamp_rgb";
-          "0x84fd27fffe44369e".friendly_name = "ikea_lamp_sleepingroom";
-          "0x84fd27fffea515fc".friendly_name = "ikea_lamp_livingroom";
-
-#          "0x804b50fffe42a74e".friendly_name = "ikea_remote_l_door";
-#          "0x842e14fffe977bfa".friendly_name = "ikea_remote_l_bed";
-
-          "0xa4c138da0f6d23de".friendly_name = "tuya_led_stripe_desk";
-
-          "0xa4c1389d5f391891".friendly_name = "tuya_sensor_fridge";
-          "0xa4c13809f76bcdc2".friendly_name = "tuya_sensor_bathroom";
-          "0xa4c13882b76fa1ac".friendly_name = "tuya_sensor_sleepingroom";
-          "0xa4c138ebeae2efd2".friendly_name = "tuya_sensor_l2";
-        };
-
-      };
-    };
-  };
-}
diff --git a/machines/briefkasten/syncthing.nix b/machines/briefkasten/syncthing.nix
@@ -15,6 +15,11 @@ in {
 
   imports = [ ctucxConfig.services.syncthing ];
   
+  services.syncthing = {
+    dataDir    = "/nix/persist/home/katja/syncthing";
+    configDir  = "/nix/persist/home/katja/.config/syncthing";
+  };
+
   age.secrets = lib.mapAttrs' (
     name: path: lib.nameValuePair "restic-syncthing-${name}" {
       file = ./. + "/../../secrets/${config.networking.hostName}/restic/syncthing-${name}.age";
diff --git a/machines/hector/default.nix b/machines/hector/default.nix
@@ -7,7 +7,9 @@
 
     ctucxConfig.services.prometheus-exporters
     ctucxConfig.services.dns-server
+    ctucxConfig.services.syncthing
 
+    # website / webservices
     ctucxConfig.websites."ctu.cx"
     ctucxConfig.websites."things.ctu.cx"
     ctucxConfig.websites."bikemap.ctu.cx"

@@ -16,7 +18,7 @@
 
     # monitoring
     ctucxConfig.websites."prometheus.ctu.cx"
-    ./grafana
+    ctucxConfig.websites."grafana.ctu.cx"
 
     # cal-/card-dav server (radicale)
     ctucxConfig.websites."dav.ctu.cx"

@@ -29,11 +31,13 @@
 
     # fediverse server (gotosocial)
     ctucxConfig.websites."fedi.ctu.cx"
-    ./matrix
-    ./mail
 
-    ./syncthing.nix
+    # mailserver
+    ctucxConfig.services.mailserver
 
+    # matrix server
+    ctucxConfig.services.matrix-synapse
+    ctucxConfig.services.mautrix-whatsapp
   ];
 
   dns.zones."ctu.cx".subdomains."${config.networking.hostName}" = (pkgs.dns.lib.combinators.host config.networking.primaryIP4 config.networking.primaryIP);

@@ -41,33 +45,26 @@
   age.secrets.restic-server-briefkasten.file = ../../secrets/restic-server/briefkasten.age;
   age.secrets.restic-server-wanderduene.file = ../../secrets/restic-server/wanderduene.age;
 
-  boot = {
-    loader = {
-      systemd-boot.enable = true;
-      efi.canTouchEfiVariables = true;
+  boot.initrd.network = {
+    enable = true;
+    ssh    = {
+      enable         = true;
+      port           = 22;
+      hostKeys       = [ /etc/ssh/ssh_host_rsa_key ];
+      authorizedKeys = with lib; concatLists (mapAttrsToList (name: user: if elem "wheel" user.extraGroups then user.openssh.authorizedKeys.keys else []) config.users.users);
     };
 
-    initrd.network = {
-      enable = true;
-      ssh    = {
-        enable         = true;
-        port           = 22;
-        hostKeys       = [ /etc/ssh/ssh_host_rsa_key ];
-        authorizedKeys = with lib; concatLists (mapAttrsToList (name: user: if elem "wheel" user.extraGroups then user.openssh.authorizedKeys.keys else []) config.users.users);
-      };
-
-      postCommands = ''
-        ip link set dev ens3 up
+    postCommands = ''
+      ip link set dev ens3 up
 
-        ip addr add ${config.networking.primaryIP}/128 dev ens3
-        ip route add default via fe80::1 dev ens3 onlink
+      ip addr add ${config.networking.primaryIP}/128 dev ens3
+      ip route add default via fe80::1 dev ens3 onlink
 
-        ip addr add ${config.networking.primaryIP4}/22 dev ens3
-        ip route add default via 194.59.204.1 dev ens3 onlink
+      ip addr add ${config.networking.primaryIP4}/22 dev ens3
+      ip route add default via 194.59.204.1 dev ens3 onlink
 
-        echo 'cryptsetup-askpass' >> /root/.profile
-      '';
-    };
+      echo 'cryptsetup-askpass' >> /root/.profile
+    '';
   };
 
   networking = {

@@ -109,6 +106,7 @@
     };
   };
 
+  services.syncthing.dataDir   = "/home/katja/syncthing";
   services.email-notify.enable = true;
 
   system.stateVersion = "24.11";
diff --git a/machines/hector/grafana/default.nix b/machines/hector/grafana/default.nix
@@ -1,82 +0,0 @@
-{ inputs, config, lib, pkgs, ... }:
-
-{
-
-  age.secrets.grafana-influx-token-mqttData = {
-    file  = ../../../secrets/briefkasten/influx/grafana_token_mqttData.age;
-    owner = "grafana";
-  };
-
-  dns.zones."ctu.cx".subdomains.grafana.CNAME = [ "${config.networking.fqdn}." ];
-
-  systemd.services.grafana.onFailure = [ "email-notify@%i.service" ];
-
-  services = {
-    grafana = {
-      enable  = true;
-
-      settings = {
-        server = {
-          domain    = "grafana.ctu.cx";
-          root_url  = "https://grafana.ctu.cx/";
-          http_addr = "::1";
-          http_port = 3001;
-        };
-        security.allow_embedding = true;
-        "users".auto_assign_org_role = "Viewer";
-#        "users".viewers_can_edit = true;
-        "users".home_page = "/d/FRDYqjEGz/smarthome-influx";
-        "auth".disable_login_form = true;
-        "auth.basic".enabled = false;
-        "auth.anonymous".enabled = true;
-        "auth.anonymous".org_name = "Main Org.";
-        "auth.anonymous".org_role = "Viewer";
-      };
-
-      provision = {
-        enable      = true;
-        datasources.settings.datasources = [
-
-          {
-            name      = "Prometheus";
-            type      = "prometheus";
-            url       = "https://prometheus.ctu.cx/";
-            isDefault = true;
-            editable  = false;
-            jsonData.timeInterval = "20s";
-          }
-
-          {
-            name                   = "InfluxDB (mqttData)";
-            type                   = "influxdb";
-            url                    = "https://influx.home.ctu.cx";
-            orgId                  = 1;
-            database               = "mqttData";
-            editable               = false;
-            jsonData.version       = "Flux";
-            jsonData.organization  = "katja";
-            jsonData.defaultBucket = "mqttData";
-            secureJsonData.token   = "$__file{${config.age.secrets.grafana-influx-token-mqttData.path}}";
-          }
-
-        ];
-
-        dashboards.settings.providers = [{
-          folder = "provisioned";
-          options.path = ./dashboards;
-        }];
-      };
-    };
-
-    nginx = {
-      enable = true;
-      virtualHosts."grafana.ctu.cx" = {
-        useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
-        forceSSL    = true;
-        kTLS        = true;
-        locations."/".proxyPass = "http://[::1]:${toString config.services.grafana.settings.server.http_port}/";
-      };
-    };
-  };
-
-}
diff --git a/machines/hector/hardware-configuration.nix b/machines/hector/hardware-configuration.nix
@@ -1,39 +1,23 @@
-# Do not modify this file!  It was generated by ‘nixos-generate-config’
-# and may be overwritten by future invocations.  Please make changes
-# to /etc/nixos/configuration.nix instead.
 { config, lib, pkgs, modulesPath, ... }:
 
 {
-  imports =
-    [ (modulesPath + "/profiles/qemu-guest.nix")
-    ];
 
-  boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "virtio_pci" "sr_mod" "virtio_blk" ];
-  boot.initrd.kernelModules = [ ];
-  boot.kernelModules = [ ];
-  boot.extraModulePackages = [ ];
-
-  fileSystems."/" =
-    { device = "/dev/disk/by-uuid/1986cef8-3b9a-4473-a44f-08f48abd7e6e";
-      fsType = "ext4";
-    };
+  imports = [
+    (modulesPath + "/profiles/qemu-guest.nix")
+  ];
 
+  boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "virtio_pci" "sr_mod" "virtio_blk" ];
   boot.initrd.luks.devices."root".device = "/dev/disk/by-uuid/b99dc901-2104-4193-909c-034d5d46d4bf";
 
-  fileSystems."/boot" =
-    { device = "/dev/disk/by-uuid/25F5-CEC1";
-      fsType = "vfat";
-      options = [ "fmask=0022" "dmask=0022" ];
-    };
-
-  swapDevices = [ ];
+  fileSystems."/" = {
+    device = "/dev/disk/by-uuid/1986cef8-3b9a-4473-a44f-08f48abd7e6e";
+    fsType = "ext4";
+  };
 
-  # Enables DHCP on each ethernet and wireless interface. In case of scripted networking
-  # (the default) this is the recommended approach. When using systemd-networkd it's
-  # still possible to use this option, but it's recommended to use it in conjunction
-  # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
-  networking.useDHCP = lib.mkDefault true;
-  # networking.interfaces.ens3.useDHCP = lib.mkDefault true;
+  fileSystems."/boot" = {
+    device = "/dev/disk/by-uuid/25F5-CEC1";
+    fsType = "vfat";
+    options = [ "fmask=0022" "dmask=0022" ];
+  };
 
-  nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
 }
diff --git a/machines/hector/mail/default.nix b/machines/hector/mail/default.nix
@@ -1,199 +0,0 @@
-{ inputs, pkgs, config, ... }:
-
-let
-  mailAutoConfig = ''
-    <?xml version="1.0" encoding="UTF-8"?>
-    <clientConfig version="1.1">
-     <emailProvider id="ctu.cx">
-       <domain>ctu.cx</domain>
-       <displayName>${config.networking.fqdn}</displayName>
-       <displayShortName>${config.networking.domain}</displayShortName>
-       <incomingServer type="imap">
-         <hostname>${config.networking.fqdn}</hostname>
-         <port>993</port>
-         <socketType>SSL</socketType>
-         <authentication>password-cleartext</authentication>
-         <username>%EMAILADDRESS%</username>
-       </incomingServer>
-       <outgoingServer type="smtp">
-         <hostname>${config.networking.fqdn}</hostname>
-         <port>465</port>
-         <socketType>SSL</socketType>
-         <authentication>password-cleartext</authentication>
-         <username>%EMAILADDRESS%</username>
-       </outgoingServer>
-     </emailProvider>
-    </clientConfig>
-  '';
-
-in {
-
-  age.secrets.restic-mail.file               = ./. + "/../../../secrets/${config.networking.hostName}/restic/mail.age";
-  age.secrets.mail-password-katja.file       = ./. + "/../../../secrets/${config.networking.hostName}/mail/password-katja-ctu.cx.age";
-  age.secrets.mail-password-gts.file         = ./. + "/../../../secrets/${config.networking.hostName}/mail/password-gts-ctu.cx.age";
-  age.secrets.mail-password-gts-zug.file     = ./. + "/../../../secrets/${config.networking.hostName}/mail/password-gts-zuggeschmack.de.age";
-  age.secrets.mail-password-info-zug.file    = ./. + "/../../../secrets/${config.networking.hostName}/mail/password-info-zuggeschmack.de.age";
-  age.secrets.mail-password-vaultwarden.file = ./. + "/../../../secrets/${config.networking.hostName}/mail/password-vaultwarden-ctu.cx.age";
-
-  dns.zones = with pkgs.dns.lib.combinators; let
-    TXT   = [ "v=spf1 a mx ip4:${config.networking.primaryIP4} +ip6:${config.networking.primaryIP} ~all" ];
-    DMARC = "v=DMARC1; p=none";
-    MX    = with mx; [ (mx 10 "${config.networking.fqdn}.") ];
-  in {
-    "ctu.cx" = {
-      inherit MX TXT;
-
-      SRV = [
-        { proto = "tcp"; service = "imaps"; priority = 0; weight = 1; port = 993; target = "${config.networking.fqdn}."; }
-        { proto = "tcp"; service = "imap"; priority = 0; weight = 1; port = 143; target = "${config.networking.fqdn}."; }
-        { proto = "tcp"; service = "submission"; priority = 0; weight = 1; port = 587; target = "${config.networking.fqdn}."; }
-      ];
-
-      subdomains = {
-        autoconfig.CNAME         = [ config.networking.hostName ];
-        _dmarc.TXT               = [ DMARC ];
-        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKryfX99NkcU5Xe4AmG+kO/sfuYSXk5RqJhzxS4uMqERE8UszgEGdteXcD8pqON2MfDmA3G6cA+Oa+N4tIWdIYNwTISVXXMGdHvjFIsVUEW0turM104tXESELaPRntkCvDBk/yOgsBDRZQHSx5MdGwpzeRC8TLdCbalh3W0jp5PQIDAQAB" ];
-      };
-    };
-
-    "katja.wtf" = {
-      inherit MX TXT;
-
-      subdomains = {
-        _dmarc.TXT               = [ DMARC ];
-        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDk63I3fkv/p1TMrcU8T1gjli0hJZp92JjK+YhOJAAdpeKMuePMI1vi/aw1Ribx9fBXGlMbNERIu4fiYWF9SsrMKqYFayD8CWbok+enZl+yQSh+UgP7jhXo0zNSTec6DK/AczZh+HbpzMSxCKQhVdjCx60F6tnh/m2OxddAw5oe6QIDAQAB" ];
-      };
-    };
-
-    "ctucx.de" = {
-      inherit MX TXT;
-
-      subdomains = {
-        _dmarc.TXT               = [ DMARC ];
-        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5fu690bKYCZLPAFfQQK+nl+aAmtetaWBKCWzGj6pt7HjpFjystgtgnQ6+DZLFXWUp8GRfMEycySB5kQULtYtSMUmx0gQBnTTLsRj+e55/CYUllLV6YXb5uca7LuVhlWPpH3sCr6TvC2VFWe4t0UC3uIXhYPrCm6p8OE7g+TdHHwIDAQAB" ];
-      };
-    };
-
-    "zuggeschmack.de" = {
-      inherit MX TXT;
-
-      subdomains = {
-        _dmarc.TXT               = [ DMARC ];
-        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDf0PX32wWq068cThCnAaX1RJMBiMo6pGfT/VOp9/IzXWmNO2aSyWEFp5lVwYFJnlGX1Sg1uvThICVDiscOqG5jBUAc0gl3SPEBFJ0cqLl7CYhD3Nkvgc8+7zn4huKvFGYXRSDqQm+AL4SSEjZ8hF+N9bGxt0bYu2WlGwZX8mTptwIDAQAB" ];
-      };
-    };
-
-    "thein.ovh" = {
-      inherit MX TXT;
-
-      subdomains = {
-        _dmarc.TXT               = [ DMARC ];
-        "mail._domainkey".TXT    = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8oumqNkHboF/S4dnKue+hEC3V226ToMmL/fmXqbAhsW88m+jUuLgZE8Nl7kc/lzD9yY7JmCXcWFzoLJWE8xusfmT1yMOW9sQmee7g0tHsm1fVqFMUetmC4+QuqAdvjIGU5QndjdWHP/gssIoLPT7lCNUL4/lkaPmFiiDyvaMpkQIDAQAB" ];
-      };
-    };
-  };
-
-  security.acme.certs."${config.networking.fqdn}".reloadServices = [
-    "postfix.service"
-    "dovecot2.service"
-  ];
-
-  services.nginx = {
-    enable = true;
-    virtualHosts = {
-      "${config.networking.fqdn}" = {
-        useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
-        forceSSL    = true;
-      };
-
-      "autoconfig.ctu.cx" = {
-        useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
-        forceSSL    = true;
-        locations."= /mail/config-v1.1.xml".return = "200 '${mailAutoConfig}'";
-      };
-    };
-  };
-
-  services.redis.servers.rspamd.bind = "::1";
-
-  services.dovecot2.sieve.extensions = [
-    "editheader"
-  ];
-
-  mailserver = {
-    enable  = true;
-    fqdn    = config.networking.fqdn;
-
-    openFirewall        = true;
-    localDnsResolver    = false;
-    virusScanning       = false;
-
-    redis.address       = "[::1]";
-
-    certificateScheme   = "manual";
-    certificateFile     = "${config.security.acme.certs.${config.networking.fqdn}.directory}/fullchain.pem";
-    keyFile             = "${config.security.acme.certs.${config.networking.fqdn}.directory}/key.pem";
-
-    enableManageSieve   = true;
-    enableSubmission    = true;
-    enableSubmissionSsl = true;
-    enableImap          = true;
-    enableImapSsl       = true;
-    enablePop3          = false;
-    enablePop3Ssl       = false;
-
-    mailDirectory       = "/var/lib/mailboxes";
-    sieveDirectory      = "/var/lib/sieve";
-    dkimKeyDirectory    = "/var/lib/dkimKeys";
-
-    domains = [
-      "ctu.cx"
-      "ctucx.de"
-      "katja.wtf"
-      "thein.ovh"
-      "zuggeschmack.de"
-    ];
-
-    loginAccounts = {
-      "katja@ctu.cx" = {
-        hashedPasswordFile = config.age.secrets.mail-password-katja.path;
-        sieveScript = builtins.readFile ./rules-katja.sieve;
-        aliases = [
-          "@ctu.cx"
-          "@ctucx.de"
-          "@thein.ovh"
-          "@katja.wtf"
-        ];
-      };
-
-      "vaultwarden@ctu.cx" = {
-        hashedPasswordFile = config.age.secrets.mail-password-vaultwarden.path;      	
-      };
-
-      "gts@ctu.cx" = {
-        hashedPasswordFile = config.age.secrets.mail-password-gts.path;
-      };
-
-      "gts@zuggeschmack.de" = {
-        hashedPasswordFile = config.age.secrets.mail-password-gts-zug.path;
-      };
-
-      "info@zuggeschmack.de" = {
-        hashedPasswordFile = config.age.secrets.mail-password-info-zug.path;
-        aliases = [
-          "@zuggeschmack.de"
-        ];
-      };
-    };
-  };
-
-  restic-backups.mail = {
-    passwordFile = config.age.secrets.restic-mail.path;
-    paths        = [
-      "/var/lib/mailboxes"
-      "/var/lib/dkimKeys"
-      "/var/lib/sieve"
-    ];
-  };
-
-}
diff --git a/machines/hector/mail/rules-katja.sieve b/machines/hector/mail/rules-katja.sieve
@@ -1,123 +0,0 @@
-require [
-  "variables", "date", "regex", 
-  "fileinto", "mailbox", "editheader",
-  "imap4flags"
-];
-
-
-if currentdate :matches "year" "*" { set "year" "${1}"; }
-if currentdate :matches "month" "*" { set "month" "${1}"; }
-
-
-if address :is "to" "le0nth3in@gmail.com" {
-  if header :matches "Subject" "*" {
-     set "subject" "${1}";
-  }
-
-  deleteheader "Subject";
-  addheader :last "Subject" "[OLD GMAIL] ${subject}";
-}
-
-if address :contains "to" "@thein.ovh" {
-  if header :matches "Subject" "*" {
-     set "subject" "${1}";
-  }
-
-  deleteheader "Subject";
-  addheader :last "Subject" "[OLD DOMAIN] ${subject}";
-}
-
-if address :contains "to" "leah" {
-  if header :matches "Subject" "*" {
-     set "subject" "${1}";
-  }
-
-  deleteheader "Subject";
-  addheader :last "Subject" "[OLD NAME] ${subject}";
-}
-
-if header :contains "From" [
-  "newsletter",
-  "ikea@hej.news.email.ikea.de",
-  "inside@cowboy.com",
-  "hello@cowboy.com",
-  "News@InsideApple.Apple.com",
-  "no-reply@newsletter.ab-in-die-box.de",
-  "info@join.netflix.com",
-  "marketing.support@porkbun.com",
-  "update@email.flixbus.com",
-  "telekom@email-telekom.de",
-  "rewe-bonus@mailing.rewe.de"
-] {
-  fileinto :create "INBOX.Newsletters";
-  stop;
-}
-
-if header :contains "subject" [ "Fahrgastrechteanträge", "Fahrgastrechteantrag" ] {
-  fileinto :create "Archive.FGR${year}";
-  stop;
-}
-
-if header :contains "From" [ "ebon@mailing.rewe.de" ] {
-  fileinto :create "INBOX.REWE-Bons";
-  stop;
-}
-
-if header :contains "From" [ "@amazon.de", "@audible.de" ] {
-  fileinto :create "INBOX.Amazon";
-  stop;
-}
-
-if header :contains "From" [ "@apple.com", "@email.apple.com", "@id.apple.com" ] {
-  fileinto :create "INBOX.Apple";
-  stop;
-}
-
-if header :contains "From" [ "@bahn.de", "@mailing.bahn.de", "@mail.bahncard.bahn.de", "@deutschebahn.com" ] {
-  fileinto :create "INBOX.Bahn";
-  stop;
-}
-
-if header :contains "From" [
-  "@bunq.com", "@hello.bunq.com", "@update.bunq.com",
-  "@gls.de",
-  "@paypal.de", "@paypal.com", "@emails.paypal.com"
-] {
-  fileinto :create "INBOX.Banking";
-  stop;
-}
-
-if header :contains "From" [
-  "@hetzner.com",
-  "@netcup.de",
-  "@ovh.de"
-] {
-  fileinto :create "INBOX.Hosting";
-  stop;
-}
-
-
-if header :contains "From" [ "@dhl.de", "@dhl.com" ] {
-  fileinto :create "INBOX.DHL";
-  stop;
-}
-
-if header :contains "From" [ "@duolingo.com" ] {
-  fileinto :create "INBOX.Duolingo";
-  stop;
-}
-
-if header :contains "From" [ "@ebay.de", "@ebay.com" ] {
-  fileinto :create "INBOX.eBay";
-  stop;
-}
-
-if header :contains "From" [ "@google.com", "@accounts.google.com" ] {
-  fileinto :create "INBOX.Google";
-  stop;
-}
-
-if header :contains "From" [ "@mein-grundeinkommen.de" ] {
-  fileinto :create "INBOX.MeinGrundeinkommen";
-  stop;
-}
diff --git a/machines/hector/matrix/default.nix b/machines/hector/matrix/default.nix
@@ -1,10 +0,0 @@
-{ ... }:
-
-{
-
-  imports = [
-    ./synapse.nix
-    ./mautrix-whatsapp.nix
-  ];
-  
-}-
\ No newline at end of file
diff --git a/machines/hector/matrix/mautrix-whatsapp.nix b/machines/hector/matrix/mautrix-whatsapp.nix
@@ -1,63 +0,0 @@
-{ config, pkgs, ...  }:
-
-{
-
-  users.users.matrix-synapse.extraGroups = [
-    "mautrix-whatsapp"
-  ];
-
-  services.mautrix-whatsapp = {
-    enable   = true;
-    settings = {
-      homeserver.address = "https://matrix.ctu.cx";
-      homeserver.domain  = "ctu.cx";
-
-      metrics.enabled = true;
-
-      whatsapp.os_name = "Mautrix-WhatsApp bridge (ctu.cx)";
-
-      appservice = {
-        address  = "http://localhost:29318";
-        hostname = "[::1]";
-        port     =  29318;
-
-        id = "whatsapp";
-
-        database.type = "sqlite3-fk-wal";
-        database.uri  = "file:/var/lib/mautrix-whatsapp/mautrix-whatsapp.db?_txlock=immediate";
-      };
-
-      bridge = {
-        command_prefix       = "!wa";
-        displayname_template = "{{if .BusinessName}}{{.BusinessName}}{{else if .PushName}}{{.PushName}}{{else}}{{.JID}}{{end}} (WA)";
-        username_template    = "whatsapp_{{.}}";
-
-        delivery_receipts         = true;
-        message_status_events     = true;
-        message_error_notices     = true;
-        call_start_notices        = true;
-        identity_change_notices   = true;
-        user_avatar_sync          = true;
-        personal_filtering_spaces = true;
-
-        encryption.allow = true;
-
-        permissions = {
-          "ctu.cx" = "user";
-        };
-
-        history_sync = {
-          backfill = true;
-          message_count = 250;
-          request_full_sync = true;
-        };
-
-        relay = {
-          enabled = true;
-        };
-      };
-
-    };
-  };
-
-}-
\ No newline at end of file
diff --git a/machines/hector/syncthing.nix b/machines/hector/syncthing.nix
@@ -1,9 +0,0 @@
-{ ctucxConfig, ... }:
-
-{
-
-  imports = [ ctucxConfig.services.syncthing ];
-
-  services.syncthing.dataDir = "/home/katja/syncthing";
-
-}-
\ No newline at end of file
diff --git a/machines/seifenkiste/default.nix b/machines/seifenkiste/default.nix
@@ -15,7 +15,6 @@
 
   boot = {
     loader.systemd-boot.enable = lib.mkForce false;
-    loader.efi.canTouchEfiVariables = true;
 
     lanzaboote = {
       enable = true;

@@ -44,6 +43,8 @@
     { id = "2c7c:030a"; path = "${pkgs.modemmanager}/share/ModemManager/fcc-unlock.available.d/2c7c"; }
   ];
 
+  hardware.cpu.intel.updateMicrocode = true;
+
   system.stateVersion = "24.11";
   home-manager.users.katja.home.stateVersion = "24.11";
 }
diff --git a/machines/seifenkiste/hardware-configuration.nix b/machines/seifenkiste/hardware-configuration.nix
@@ -1,41 +1,21 @@
-# Do not modify this file!  It was generated by ‘nixos-generate-config’
-# and may be overwritten by future invocations.  Please make changes
-# to /etc/nixos/configuration.nix instead.
 { config, lib, pkgs, modulesPath, ... }:
 
 {
-  imports =
-    [ (modulesPath + "/installer/scan/not-detected.nix")
-    ];
 
-  boot.initrd.availableKernelModules = [ "xhci_pci" "thunderbolt" "nvme" ];
-  boot.initrd.kernelModules = [ ];
   boot.kernelModules = [ "kvm-intel" ];
-  boot.extraModulePackages = [ ];
-
-  fileSystems."/" =
-    { device = "/dev/disk/by-uuid/f81ba7a3-4b34-4c58-9588-78f8920b2f00";
-      fsType = "ext4";
-    };
 
+  boot.initrd.availableKernelModules = [ "xhci_pci" "thunderbolt" "nvme" ];
   boot.initrd.luks.devices."root".device = "/dev/disk/by-uuid/b3184874-df78-4d02-9412-b060eb37e038";
 
-  fileSystems."/boot" =
-    { device = "/dev/disk/by-uuid/9315-B4DE";
-      fsType = "vfat";
-      options = [ "fmask=0022" "dmask=0022" ];
-    };
-
-  swapDevices = [ ];
+  fileSystems."/" = {
+    device = "/dev/disk/by-uuid/f81ba7a3-4b34-4c58-9588-78f8920b2f00";
+    fsType = "ext4";
+  };
 
-  # Enables DHCP on each ethernet and wireless interface. In case of scripted networking
-  # (the default) this is the recommended approach. When using systemd-networkd it's
-  # still possible to use this option, but it's recommended to use it in conjunction
-  # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
-  networking.useDHCP = lib.mkDefault true;
-  # networking.interfaces.wlp0s20f3.useDHCP = lib.mkDefault true;
-  # networking.interfaces.wwp0s20f0u2.useDHCP = lib.mkDefault true;
+  fileSystems."/boot" = {
+    device  = "/dev/disk/by-uuid/9315-B4DE";
+    fsType  = "vfat";
+    options = [ "fmask=0022" "dmask=0022" ];
+  };
 
-  nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
-  hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
 }
diff --git a/machines/trabbi/default.nix b/machines/trabbi/default.nix
@@ -16,31 +16,24 @@
   age.secrets.restic-server-briefkasten.file = ../../secrets/restic-server/briefkasten.age;
   age.secrets.restic-server-wanderduene.file = ../../secrets/restic-server/wanderduene.age;
 
-  boot = {
-    loader = {
-      systemd-boot.enable = true;
-      efi.canTouchEfiVariables = true;
+  boot.initrd.network = {
+    enable = true;
+    ssh    = {
+      enable         = true;
+      port           = 22;
+      hostKeys       = [ /etc/ssh/ssh_host_rsa_key ];
+      authorizedKeys = with lib; concatLists (mapAttrsToList (name: user: if elem "wheel" user.extraGroups then user.openssh.authorizedKeys.keys else []) config.users.users);
     };
 
-    initrd.network = {
-      enable = true;
-      ssh    = {
-        enable         = true;
-        port           = 22;
-        hostKeys       = [ /etc/ssh/ssh_host_rsa_key ];
-        authorizedKeys = with lib; concatLists (mapAttrsToList (name: user: if elem "wheel" user.extraGroups then user.openssh.authorizedKeys.keys else []) config.users.users);
-      };
-
-      postCommands = ''
-        ip link set dev ens3 up
-        ip addr add ${config.networking.primaryIP}/128 dev ens3
-        ip route add default via fe80::1 dev ens3 onlink
-
-        ip addr add ${config.networking.primaryIP4}/22 dev ens3
-        ip route add default via ${config.networking.defaultGateway.address} dev ens3 onlink
-        echo 'cryptsetup-askpass' >> /root/.profile
-      '';
-    };
+    postCommands = ''
+      ip link set dev ens3 up
+      ip addr add ${config.networking.primaryIP}/128 dev ens3
+      ip route add default via fe80::1 dev ens3 onlink
+
+      ip addr add ${config.networking.primaryIP4}/22 dev ens3
+      ip route add default via ${config.networking.defaultGateway.address} dev ens3 onlink
+      echo 'cryptsetup-askpass' >> /root/.profile
+    '';
   };
 
   networking = {
diff --git a/machines/trabbi/hardware-configuration.nix b/machines/trabbi/hardware-configuration.nix
@@ -1,38 +1,22 @@
-# Do not modify this file!  It was generated by ‘nixos-generate-config’
-# and may be overwritten by future invocations.  Please make changes
-# to /etc/nixos/configuration.nix instead.
 { config, lib, pkgs, modulesPath, ... }:
 
 {
-  imports =
-    [ (modulesPath + "/profiles/qemu-guest.nix")
-    ];
 
-  boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "virtio_pci" "sr_mod" "virtio_blk" ];
-  boot.initrd.kernelModules = [ ];
-  boot.kernelModules = [ ];
-  boot.extraModulePackages = [ ];
-
-  fileSystems."/" =
-    { device = "/dev/disk/by-uuid/83c8b9f3-d5e2-46ac-a4d8-5dd02a1fd757";
-      fsType = "ext4";
-    };
+  imports = [
+    (modulesPath + "/profiles/qemu-guest.nix")
+  ];
 
+  boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "virtio_pci" "sr_mod" "virtio_blk" ];
   boot.initrd.luks.devices."root".device = "/dev/disk/by-uuid/492ea477-0dc2-4c4d-a1e1-5cc1073126fa";
 
-  fileSystems."/boot" =
-    { device = "/dev/disk/by-uuid/4751-075E";
-      fsType = "vfat";
-    };
-
-  swapDevices = [ ];
+  fileSystems."/" = {
+    device = "/dev/disk/by-uuid/83c8b9f3-d5e2-46ac-a4d8-5dd02a1fd757";
+    fsType = "ext4";
+  };
 
-  # Enables DHCP on each ethernet and wireless interface. In case of scripted networking
-  # (the default) this is the recommended approach. When using systemd-networkd it's
-  # still possible to use this option, but it's recommended to use it in conjunction
-  # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
-  networking.useDHCP = lib.mkDefault true;
-  # networking.interfaces.ens3.useDHCP = lib.mkDefault true;
+  fileSystems."/boot" = {
+    device = "/dev/disk/by-uuid/4751-075E";
+    fsType = "vfat";
+  };
 
-  nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
 }
diff --git a/machines/wanderduene/default.nix b/machines/wanderduene/default.nix
@@ -12,12 +12,10 @@
     ctucxConfig.services.dns-server
 
     ctucxConfig.websites."ip.ctu.cx"
+    ctucxConfig.websites."dendrite.ctucx.de"
 
     ./rclone-restic-server.nix
-
     ./syncthing.nix
-
-    ./dendrite.nix
   ];
 
   documentation.nixos.enable = false;

@@ -30,34 +28,27 @@
     group = "systemd-network";
   };
 
-  boot = {
-    # Use the systemd-boot EFI boot loader.
-    loader.systemd-boot.enable = true;
-    loader.efi.canTouchEfiVariables = true;
-
-    kernel.sysctl."net.ipv6.conf.all.proxy_ndp" = true;
-
-    initrd.network = {
-      enable = true;
-      ssh    = {
-        enable         = true;
-        port           = 22;
-        hostKeys       = [ /etc/ssh/ssh_host_rsa_key ];
-        authorizedKeys = with lib; concatLists (mapAttrsToList (name: user: if elem "wheel" user.extraGroups then user.openssh.authorizedKeys.keys else []) config.users.users);
-      };
+  boot.kernel.sysctl."net.ipv6.conf.all.proxy_ndp" = true;
+  boot.initrd.network = {
+    enable = true;
+    ssh    = {
+      enable         = true;
+      port           = 22;
+      hostKeys       = [ /etc/ssh/ssh_host_rsa_key ];
+      authorizedKeys = with lib; concatLists (mapAttrsToList (name: user: if elem "wheel" user.extraGroups then user.openssh.authorizedKeys.keys else []) config.users.users);
+    };
 
-      postCommands = ''
-        ip link set dev ens3 up
+    postCommands = ''
+      ip link set dev ens3 up
 
-        ip addr add ${config.networking.primaryIP}/128 dev ens3
-        ip route add default via fe80::1 dev ens3 onlink
+      ip addr add ${config.networking.primaryIP}/128 dev ens3
+      ip route add default via fe80::1 dev ens3 onlink
 
-        ip addr add ${config.networking.primaryIP4}/22 dev ens3
-        ip route add default via 194.36.144.1 dev ens3 onlink
+      ip addr add ${config.networking.primaryIP4}/22 dev ens3
+      ip route add default via 194.36.144.1 dev ens3 onlink
 
-        echo 'cryptsetup-askpass' >> /root/.profile
-      '';
-    };
+      echo 'cryptsetup-askpass' >> /root/.profile
+    '';
   };
 
   networking = {
diff --git a/machines/wanderduene/dendrite.nix b/machines/wanderduene/dendrite.nix
@@ -1,81 +0,0 @@
-{ pkgs, lib, config, ... }:
-
-{
-
-  dns.zones."ctu.cx".subdomains.dendrite.CNAME = [ "${config.networking.fqdn}." ];
-
-  age.secrets = {
-  #   restic-matrix-synapse.file        = ./. + "/../../../secrets/${config.networking.hostName}/restic/matrix-synapse.age";
-  #   matrix-sliding-sync-env.file      = ./. + "/../../../secrets/${config.networking.hostName}/matrix-dendrite/sliding-sync-env.age";
-    matrix-private-key = {
-      file  = ./. + "/../../secrets/${config.networking.hostName}/matrix-dendrite/private-key.age";
-      owner = "dendrite";
-    };
-  };
-
-
-  services.matrix-synapse.sliding-sync = {
-    enable = false;
-    environmentFile = config.age.secrets.matrix-sliding-sync-env.path;
-    settings = {
-      SYNCV3_SERVER   = "https://dendrite.ctu.cx";
-      SYNCV3_BINDADDR = "[::1]:8009";
-    };
-  };
-
-  users.groups.dendrite = {};
-  users.users.dendrite = {
-    isSystemUser = true;
-    home = "/var/lib/dendrite";
-    group = "dendrite";
-  };
-
-  systemd.services.dendrite.serviceConfig = {
-    DynamicUser = lib.mkForce false;
-    User = "dendrite";
-    Group = "dendrite";
-  };
-
-  services.dendrite = {
-    enable = true;
-    openRegistration = false;
-    settings = {
-      global.server_name = "dendrite.ctu.cx";
-      global.private_key = config.age.secrets.matrix-private-key.path;
-
-      global.well_known_server_name = "dendrite.ctu.cx:443";
-      global.well_known_client_name = "https://dendrite.ctu.cx";
-
-      client_api.registration_disabled = true;
-    };
-  };
-
-  services.nginx = {
-    enable       = true;
-    virtualHosts = {
-      "dendrite.ctu.cx" = {
-        enableACME = true;
-        forceSSL   = true;
-        kTLS       = true;
-        locations  = {
-          "/.well-known".proxyPass = "http://[::1]:8008";
-          "/_matrix".proxyPass = "http://[::1]:8008";
-          "/_matrix/client/unstable/org.matrix.msc3575/".proxyPass = "http://[::1]:8009/_matrix/client/unstable/org.matrix.msc3575/";
-#            "/_synapse".proxyPass = "http://[::1]:8008";
-#            "/admin/".alias = "${pkgs.synapse-admin}/";
-          "/".root             = pkgs.cinny.override {
-            conf = {
-              defaultHomeserver = 0;
-              homeserverList    = [
-                "dendrite.ctu.cx"
-              ];
-              allowCustomHomesevrers = false;
-            };
-          };
-        };
-      };
-
-    };
-  };
-  
-}-
\ No newline at end of file
diff --git a/machines/wanderduene/hardware-configuration.nix b/machines/wanderduene/hardware-configuration.nix
@@ -1,38 +1,22 @@
-# Do not modify this file!  It was generated by ‘nixos-generate-config’
-# and may be overwritten by future invocations.  Please make changes
-# to /etc/nixos/configuration.nix instead.
 { config, lib, pkgs, modulesPath, ... }:
 
 {
-  imports =
-    [ (modulesPath + "/profiles/qemu-guest.nix")
-    ];
 
-  boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "virtio_pci" "sr_mod" "virtio_blk" ];
-  boot.initrd.kernelModules = [ ];
-  boot.kernelModules = [ ];
-  boot.extraModulePackages = [ ];
-
-  fileSystems."/" =
-    { device = "/dev/disk/by-uuid/8db47ae4-c5e4-4297-aaec-31a6669f4dc4";
-      fsType = "ext4";
-    };
+  imports = [
+    (modulesPath + "/profiles/qemu-guest.nix")
+  ];
 
+  boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "virtio_pci" "sr_mod" "virtio_blk" ];
   boot.initrd.luks.devices."root".device = "/dev/disk/by-uuid/3b1fb60e-e443-4cf8-b5ea-acb5a502e3bd";
 
-  fileSystems."/boot" =
-    { device = "/dev/disk/by-uuid/D496-A891";
-      fsType = "vfat";
-    };
-
-  swapDevices = [ ];
+  fileSystems."/" = {
+    device = "/dev/disk/by-uuid/8db47ae4-c5e4-4297-aaec-31a6669f4dc4";
+    fsType = "ext4";
+  };
 
-  # Enables DHCP on each ethernet and wireless interface. In case of scripted networking
-  # (the default) this is the recommended approach. When using systemd-networkd it's
-  # still possible to use this option, but it's recommended to use it in conjunction
-  # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
-  networking.useDHCP = lib.mkDefault true;
-  # networking.interfaces.ens3.useDHCP = lib.mkDefault true;
+  fileSystems."/boot" = {
+    device = "/dev/disk/by-uuid/D496-A891";
+    fsType = "vfat";
+  };
 
-  nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
 }
diff --git a/machines/wanderduene/rclone-restic-server.nix b/machines/wanderduene/rclone-restic-server.nix
@@ -13,12 +13,12 @@
 
   age.secrets = {
     rclone-config = {
-      file  = ../../secrets/wanderduene/rclone-config.age;
+      file  = ./. + "/../../secrets/${config.networking.hostName}/rclone-config.age";
       owner = "rclone-restic-server";
     };
 
     restic-server-htpasswd = {
-      file  = ../../secrets/wanderduene/restic-server-htpasswd.age;
+      file  = ./. + "/../../secrets/${config.networking.hostName}/restic-server-htpasswd.age";
       owner = "nginx";
     };
   };

@@ -69,5 +69,4 @@
     };
   };
 
-
-}-
\ No newline at end of file
+}
diff --git a/secrets/hector/knot-keys.age b/secrets/hector/knot-keys.age
@@ -1,35 +1,39 @@
 -----BEGIN AGE ENCRYPTED FILE-----
-YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkM3Rob242Z2IzYzZRQzVh
-T0RsMHJaZTVsc0xWY3E3WTBNYTZrckhObDFZCjFsVjZPYzZIUkErVHlJQVcwVHQv
-U1NSRE9DTWtrMVVCVnJudGFGRHhPRXMKLT4gc3NoLWVkMjU1MTkgeWFMSFNRIEZV
-L1owZzIyK0tHQnp1SFk3cWlZUjcycThWSTNzZXJOTmtrUWp6SGZjMmcKNXlIVnBr
-N3EvNzNEOVpMcWtVVEhuYzJCd1djZE94dGQyY0lXczNITTVkYwotPiBVM1YtZ3Jl
-YXNlICs2dj4gdWsKeG1FdFpLWEx5UU9FT1hNbW9ZWTIvK0k1RXBpdTJGL1k5MHFi
-US93THRjTzJMSERUclhIcXZEM25rOG5WaWdTeAo5QUlibzdSTnZOWExjSm1YUFND
-bitRCi0tLSAvRFNubVU2VkUxTHhmbFIyYWZWbmdWcTAwK1FWZTZ0dFRHdEZLMVIw
-M0c4Cv79lH/bA6fAx0nQSXvVvCwK5DbxqL1jcN/1/ISpRS7JVVtHxKV9R9Ka/N2F
-oRMFsjeHZGIlBqVFz1KvtedJvJ92GOym0dY8rOvYvzJYbkMyf1FSayxVWv+7lrqB
-tv51nWtK81wfR2I4W+Z7M3gPLE7JLRyGDQ/C1yiAqk7JpN/G4RwhU2E/7ODDJlVp
-y2laCbPMl0xUZMHO0ZEsBMRBi30j8hlssOOJ4o/zN4Ny3/yKhQtNaD1ARi8ojBV8
-814wWaL4w4sGiNBcZWR62zo46WuckDrvhyXanLPwCip1LFTZqtTq5fhvNo+N/Ank
-s1HFlfFveouweeTDJRsXQUDmsbfV99hZsDgC4qOnelVgMkrOOoMGOeehsujrdLqg
-BZrnPIZLtIa9IKHe9NyBrL3u34B7jmRqmwq2lBQZHkBDqQ4zOm08Pg3NmjbcD9Y3
-DEBsVCZE6B6Kw9kbB2Rk0QQzaI4NG2jgAmO7RowuIymvyjFCq/EkUM6JFVyiEkYd
-PCAf92VfILyuENrFYO37zwwi55VM13E0ozSu1+9jHcT49wikdZqT/n4fckeMj/SC
-H4BkP0F7s1BIOJok5Sdk2bEcdqOZo75KylkIQIF2NOxiImJV5VI9l6+tT75bKcaX
-sLttIRtyUkSRrAWtu/KCBSQvnoMRG3sCtY9g9YFx53Re1RdCTcxPMXUt86rknZT7
-bicLWQ2DW0xL+j8XUxz6RxQ8AYWNrETlpHuCA/cCf8YnThY1wuamfTzbsu6kXjlO
-u+f1RzLmzpLJ3yfySHHmld42Dc+v3TY3mZC28KG4pkl6Pkp4/mHcUYg2zEPPXrgS
-GN0dvL63gLGu4Z0KTFNudF2SSjckKNyiWSoFPpOnQ1lsm5mjPzOk2olV0KjA5gYM
-bABBe0YwIs2OpXbt4Ikfue7XWuzFmY7QcIAYvNI9xtu/KXAwM7qKlrUjOf8y9Ea9
-Z5IlW/VukmvKfHiVYOnD0ca677MvMkSZpoSLdu1vkYfOdSFEqBTqWfxPhvyRCx3T
-1/aA1es7mH7u6vVps+xLFmGhQIMMzdqt9jaAvn9xRU8xrhddD5MCM3B7p2aAgLLc
-gDf+6D3lOMH/0+pl0LAxIvHOAmG8+y21ttK5PGoZtjwldLnhB+nJ4vnT38YlRWjD
-7HJNVuvDZRnZODdV6nT5VQYz3EOPOWVe2kBeBZ+ItSYM8Ms1XnsWMI3oxMUZiQB9
-9adreq0gsnYyMRTnstqadZnPv1oz6Oo2zzfAcDsfMnJk6a7dAvY6hjcz/pCtLwKy
-97aL1//w4j97ER/rhN6IO+tpOJHR5aVJJZ3IMbxC+hX9QUJCEu8Ewm9ANqWNIFt6
-c0PNQCmqxapPeFh6tbtfC3LSvkSXw/8koQ52dneR+I73df7Behe/REANyFB/gdS/
-yG9KX6zsplephttzlL9B0pAUJ+A96yE8Hb3qeAnQ+pvyyXvjauP92drGW7NER7sb
-1kY3nOmVYhqRM9Ch+Tih/y6y/L1pi/OcE4beMVaAXIozdmoJXrTMVeathD+dmux1
-qSy7PAd/EH2ygNdgsBL81rQnsHeHQCdxLZWUiEu6QKVf9mZCR6l6RhKNWO37Glg=
+YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnRWNUY3AxaThsbXI5VCto
+bENnaGhXRkhIbVBPOXZJbkxJUUdpMjBsbVVnCmUyWVNhby9CLy8yclBhb0UzRmFn
+VEtiL05lSGFhUkJub2Z2eC9Hc0ZlQ1kKLT4gc3NoLWVkMjU1MTkgeWFMSFNRIHZI
+WXc0Zm5aSGhJRWJGSGd1WGRmaGFhMlpPVTZwNGpwZUlpdUZic29VQncKRkIwUGVj
+OW5lMlFpRE1sbXI0cnExMVY3UFNZYVluUWdvaHE4dWRNM2NNWQotPiBBR09XNi1n
+cmVhc2UKcGxiLzhpOEc0MmwwOUJ2aUllUzY5OVZYRlZHUFFpRXNkdGtyUFA1QzN0
+M2JIK0UKLS0tIFZVU1BEeTFXWVlPb1pSSGJ0OVptMzZwMUVDSm9Ed2JpSW5uOXRT
+SDhxZGMKsp/ngseET20lP83j5KMO6bYZiQiHxdrPq3rCZnVNI0vXSykMLlJCqfBG
+pAo/q+g903ywCzugu9oQnyz+PlH5naYs/lrNjU6DAqNDtWhcLbxOemTxsYnEo7z4
+ft0A0jqWBmqiJdDoe2Z2Tu/g3DNQKD/x6aIX9m0c9o4hgZYSuEhDMEsahEbcPPjl
+OignpceSIgb9VYlhqo0uRIoSuXL+dYiFWZ5387Y2b+wqInDy3h+ya0H1tNw5tr2S
+f7C70pywe4JysNLm+Z5hvElZoOfdH6kanzJTQDTkcSw0i5Xi3YPs3DtIICtihg30
+/X+ejava4eaZsWviExPHD2ulL+J7zkL8wc21HcN9yNsE/gGMma5N++15FcM6eGTW
+1k99ts0T/ZtuJAy9Q3MlFowFOO8LwJaf0GgGIYQa7ygYb5iQV1PY5QRtcg/rfE0p
+i+bCUh69Buq6HtQ1XU1+HT1j8/AZRD6pba2BEJSUboQ0nf7maA6/45DAF7HyfO5I
+r8vo0p2+DVW5NOw4A29NcORpps1ZdcwQffaArNKTSZ8o+Qknxu0lAokncL8M0+qD
+9kLZkCQ5hGt7Mln/Q4cw2NYkPmB1zQulGV90lI4DP3GTjlVqeNnEtiGdcAZ8fgjY
+TJhc7f6XWNy3I2UPbSzE5H/fIbI8o2uztOQunkzLPYT6Au0U++qPrVnzTCjqJE8W
+ZxDT8r9ONg1hIhBU/LL09jRutY+zbRwNQznWR0ZA4zxBFhrdcg85e4hjevRD6iRM
+zaB5N66rAykSWm0HhekrD7vOj7cMG5P5ApmgH4379Z1+w+V4h1DY+0kDvAlMqaGm
+KXptf0fzOO6xinjEUfLqpBKGjHmmUUgfvBlqxoX5KaLA8agiYXR9n+coANxV4XCj
++u64TF5q1jnxkiAeQzdM136nB+CA889MCcHwiXkPt6bwrLDOdX1njOxItljPoM1Q
+dBgxLaxGF4uAMzgPATNpE29CdQQbhuM7NnuTf2qBpyIPcSLFr/c6rFCnokEzp3xt
+TZk+DSZtkXEuEt5r2Ly5dR+OnDmk5oVXrZXU19dw/bA3AhHkaginFKx6Sd3LGjx9
+xpd/mqxNZoiLmx5bKrWXGjTqPkzCUziYp/3zdBG7s6pVHOxvg3IlzmEkJCh/KP7d
+/RAVc/a4BJLNsJjy34bRBhnFMcJoWRA2MDeEKzuG7QbMPD9mMNwcEdOl6SETNhfB
+tZOHDYJJ0mRikyMjpfrIjeg1MJ1nOGvwq0zGa1v/YgCfu/0cGAAV6UMB3enJ9PxY
+NDx0FyPTSl8aNtx44Sus0vkpWoKcsb/iMKFv8AMgZSr0bdRXMhbRmjad61dJXEv+
+A8Z4CAWP6eUMEt9f/fAJAOK33NUuQWfcJDdrut0m/UISd9sg26skbGkF3CUJ8ajb
+E7Bx9dNnMkcMtOc8hQ7d1rWjEfPsHyfyzVDyOpFC1WmtmKNaeQpTKyJudU0Z4j9r
+gn15eBuG1Dpxvaxe2jNUz8+BfxNXwh79ALqzS6E08wVeueR9Dn6j2nhKZRCqBpsS
+vD4cDSXtgQC06ecM3JA6uBboSxEUyDrdghZCj8vpH+sPL7RA1YO5fdnvsM81anUz
+Lyxv0TIACf2MoYv1jFhCQixyLu3EOxLQQn9/GNip3J1KeXL39Epjofqi+2/y22G/
+lylbpMIGXhwxT5ER89WkvbEwuxv6D/wrjuWleFsNXXyYyajLcODrL4mktioVKKge
+GUbEm3U6T8Fv6M425Je1Yl9bjmEnr1v2bPbWtAZrnk39xwSOFFNhoV/FNaB9lYlu
+wnbY7gR71uB+K0VwBqxPbFJXBmtQCZ6uw+AZw9P4fuF2xSRVPm4im6Q8gLZqye3Z
+5YHUyBVdErPdZW+hvAxy+YHP2S3unDnJJkJpF77BqKq81yoCrGwE2RWlAbFDQg==
 -----END AGE ENCRYPTED FILE-----