ctucx.git: nixfiles

ctucx' nixfiles

commit 4a8bb042c8bf920873943575b7de2f7e9446640a
parent db5b06a9c63223aeb81cb8d0c51fa1cc9950cbc9
Author: Leah (ctucx) <leah@ctu.cx>
Date: Sun, 23 Jan 2022 23:58:57 +0100

add host: lollo (router and home-server)
22 files changed, 1372 insertions(+), 0 deletions(-)
A
machines/lollo/configuration.nix
|
41
+++++++++++++++++++++++++++++++++++++++++
A
machines/lollo/hardware-configuration.nix
|
31
+++++++++++++++++++++++++++++++
A
machines/lollo/node-exporter.nix
|
19
+++++++++++++++++++
A
machines/lollo/restic-server.nix
|
30
++++++++++++++++++++++++++++++
A
machines/lollo/router/default.nix
|
21
+++++++++++++++++++++
A
machines/lollo/router/dnsmasq.nix
|
121
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
machines/lollo/router/hostapd.nix
|
17
+++++++++++++++++
A
machines/lollo/router/nftables.nix
|
15
+++++++++++++++
A
machines/lollo/router/ruleset.nft
|
83
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
machines/lollo/router/systemd-networkd.nix
|
124
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
machines/lollo/smarthome/default.nix
|
14
++++++++++++++
A
machines/lollo/smarthome/influxdb2.nix
|
18
++++++++++++++++++
A
machines/lollo/smarthome/mbusd.nix
|
37
+++++++++++++++++++++++++++++++++++++
A
machines/lollo/smarthome/serial2tcp.nix
|
39
+++++++++++++++++++++++++++++++++++++++
A
machines/lollo/smarthome/smartied.nix
|
361
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
machines/lollo/smarthome/zigbee2mqtt.nix
|
150
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
machines/lollo/syncthing.nix
|
26
++++++++++++++++++++++++++
A
machines/lollo/websites/default.nix
|
11
+++++++++++
A
machines/lollo/websites/music.home.ctu.cx.nix
|
87
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
machines/lollo/websites/wiki.home.ctu.cx.nix
|
115
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M
nix/sources.json
|
12
++++++++++++
A
secrets/.keep-folder
|
0
diff --git a/machines/lollo/configuration.nix b/machines/lollo/configuration.nix
@@ -0,0 +1,41 @@
+{ config, pkgs, ... }:
+
+{
+  imports = [
+    ./hardware-configuration.nix
+    ../../configurations/common.nix
+    ./node-exporter.nix
+
+    ./router
+    ./smarthome
+    ./websites
+
+    ../../configurations/programs/pipewire.nix
+    ../../configurations/programs/spotifyd.nix
+    ./syncthing.nix
+    ./restic-server.nix
+  ];
+
+  boot.loader = {
+    systemd-boot.enable      = true;
+    efi.canTouchEfiVariables = true;
+  };
+
+  time.timeZone = "Europe/Berlin";
+
+  networking = {
+    hostName        = "lollo";
+    domain          = "home.ctu.cx";
+    useDHCP         = false;
+  };
+
+  users.users.leah.extraGroups = [ "audio" ];
+
+  environment.systemPackages = with pkgs; [
+    wireguard-tools
+  ];
+
+  system.stateVersion = "21.11"; # Did you read the comment?
+  home-manager.users.leah.home.stateVersion = "21.11";
+}
+
diff --git a/machines/lollo/hardware-configuration.nix b/machines/lollo/hardware-configuration.nix
@@ -0,0 +1,31 @@
+# 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 = [ "ahci" "xhci_pci" "nvme" "usbhid" "usb_storage" "sd_mod" "sr_mod" ];
+  boot.initrd.kernelModules = [ ];
+  boot.kernelModules = [ "kvm-intel" ];
+  boot.extraModulePackages = [ ];
+
+  fileSystems."/" =
+    { device = "/dev/disk/by-uuid/53f739d1-5668-422e-81b5-34c1f60ecba8";
+      fsType = "ext4";
+    };
+
+  fileSystems."/boot" =
+    { device = "/dev/disk/by-uuid/1344-D403";
+      fsType = "vfat";
+    };
+
+  swapDevices = [ ];
+
+  hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
+  # high-resolution display
+  hardware.video.hidpi.enable = lib.mkDefault true;
+}
diff --git a/machines/lollo/node-exporter.nix b/machines/lollo/node-exporter.nix
@@ -0,0 +1,19 @@
+{config, lib, pkgs, ...}:
+
+{
+
+  services = {
+    prometheus.exporters.node.enable = true;
+    nginx = {
+      enable = true;
+      virtualHosts."lollo.ctu.cx" = {
+        enableACME = true;
+        forceSSL   = true;
+        locations."/node-exporter" = {
+          proxyPass   = "http://127.0.0.1:9100/metrics";
+        };
+      };
+    };
+  };
+
+}
diff --git a/machines/lollo/restic-server.nix b/machines/lollo/restic-server.nix
@@ -0,0 +1,30 @@
+{config, lib, pkgs, ...}:
+
+{
+
+  services = {
+    restic.server = {
+      enable     = true;
+      appendOnly = true;
+      extraFlags = [ "--no-auth" ];
+      dataDir    = "/var/lib/restic";
+    };
+
+    nginx = {
+      enable = true;
+      virtualHosts."restic.lollo.ctu.cx" = {
+        enableACME = true;
+        forceSSL   = true;
+        locations."/" = {
+          proxyPass   = "http://127.0.0.1:8000/";
+          extraConfig = ''
+            client_max_body_size 10G;
+            auth_basic           Auth;
+            auth_basic_user_file /var/lib/secrets/restic-auth;
+          '';
+        };
+      };
+    };
+  };
+
+}
diff --git a/machines/lollo/router/default.nix b/machines/lollo/router/default.nix
@@ -0,0 +1,21 @@
+{ ... }:
+
+{
+
+  imports = [
+    ./systemd-networkd.nix
+    ./nftables.nix
+    ./dnsmasq.nix
+    ./hostapd.nix
+  ];
+
+  boot = {
+    kernel.sysctl."net.ipv4.ip_forward"          = true;
+    kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
+  };
+
+  services.avahi.interfaces = [ "brlan" ];
+  networking.useDHCP        = false;
+
+}
+
diff --git a/machines/lollo/router/dnsmasq.nix b/machines/lollo/router/dnsmasq.nix
@@ -0,0 +1,121 @@
+{ config, pkgs, ... }:
+
+let
+  dnsmasq-lease-overview = pkgs.fetchgit {
+    url    = "https://cgit.ctu.cx/dnsmasq-lease-overview/";
+    rev    = "84c47b17361fd3273f5d7902a407d467ae937cfd";
+    sha256 = "1vs589r2ff31lf8wp4kh84ckzfd72wc1bxjf7kxwl8sgm18rijnp";
+  };
+
+in {
+  services.resolved.enable = false;
+  services.dnsmasq = {
+    enable      = true;
+    extraConfig = ''
+      local-service
+      no-resolv
+      no-hosts
+      domain-needed
+      bogus-priv
+
+
+      server=1.1.1.1
+      server=1.0.0.1
+      server=8.8.8.8
+      server=8.8.4.4
+
+
+      local=/home.ctu.cx/
+      domain=home.ctu.cx
+
+      auth-ttl=600
+      auth-server=home.ctu.cx, wg-pbb
+      auth-zone=home.ctu.cx,           10.0.0.1/24,   195.39.246.32/28,   2a0f:4ac0:acab::1/64
+
+      host-record=home.ctu.cx,                        195.39.246.41,      2a0f:4ac0:acab::1
+      host-record=lollo.ctu.cx,                       195.39.246.41,      2a0f:4ac0:acab::1
+      host-record=lollo.home.ctu.cx,                  195.39.246.41,      2a0f:4ac0:acab::1
+      host-record=legacy.home.ctu.cx,                 195.39.246.41,      2a0f:4ac0:acab::1
+      host-record=dnsmasq.home.ctu.cx,                195.39.246.41,      2a0f:4ac0:acab::1
+      host-record=music.home.ctu.cx,                  195.39.246.41,      2a0f:4ac0:acab::1
+      host-record=influx.home.ctu.cx,                 195.39.246.41,      2a0f:4ac0:acab::1
+      host-record=wiki.home.ctu.cx,                   195.39.246.41,      2a0f:4ac0:acab::1
+      host-record=pbx.home.ctu.cx,                    195.39.246.46
+
+      address=/fritz.box/192.168.178.1
+      address=/lollo/10.0.0.1
+
+
+      enable-ra
+      quiet-ra
+
+      dhcp-authoritative
+      dhcp-rapid-commit
+      dhcp-sequential-ip
+
+      dhcp-range=private, 10.0.0.100,          10.0.0.200,                           255.255.255.0,                  48h
+      dhcp-range=public,  195.39.246.34,       static,                               255.255.255.240, 195.39.246.47, 48h
+      dhcp-range=         2a0f:4ac0:acab::100, 2a0f:4ac0:acab::01ff, ra-names,slaac, 64,                             48h
+
+      dhcp-option=option6:information-refresh-time, 6h
+      dhcp-option=option6:dns-server,               [2a0f:4ac0:acab::1]
+      dhcp-option=private, option:router,           10.0.0.1
+      dhcp-option=private, option:dns-server,       10.0.0.1
+      dhcp-option=public,  option:router,           195.39.246.41
+      dhcp-option=public,  option:dns-server,       195.39.246.41
+
+      dhcp-host=f4:06:8d:df:1f:e3,                                          accesspoint,      10.0.0.2
+      dhcp-host=2a0f:4ac0:acab::12a,                                        garmin-vivo-3,    10.0.0.210
+
+      dhcp-host=id:04:ea:56:f2:b4:6c,                                       isa-x390,         [2a0f:4ac0:acab::36]
+      dhcp-host=04:ea:56:f2:b4:6c,                                          isa-x390,         195.39.246.36
+
+      dhcp-host=id:ac:67:5d:12:2f:5a,                                       isa-p2max,        [2a0f:4ac0:acab::37]
+      dhcp-host=ac:67:5d:12:2f:5a,                                          isa-p2max,        195.39.246.37
+
+      dhcp-host=id:00:01:00:01:29:1c:39:07:f4:5c:89:c1:dc:b1,               isabelles-mbp,    [2a0f:4ac0:acab::38]
+      dhcp-host=f4:5c:89:c1:dc:b1,                                          isabelles-mbp,    195.39.246.38
+
+      dhcp-host=id:e8:6a:64:f4:49:e7,                                       stasicontainer,   [2a0f:4ac0:acab::42]
+      dhcp-host=e8:6a:64:f4:49:e7,                                          stasicontainer,   195.39.246.42
+
+      dhcp-host=id:04:ea:56:f3:0b:5b,                                       coladose,         [2a0f:4ac0:acab::43]
+      dhcp-host=04:ea:56:f3:0b:5b, e8:6a:64:d6:e3:33,                       coladose,         195.39.246.43
+
+      dhcp-host=34:31:c4:46:88:31,                                          fritz7312,        195.39.246.46
+    '';
+  };
+
+  services.phpfpm.pools.dnsmasq  = {
+    user  = "dnsmasq";
+    group = "dnsmasq";
+    settings = {
+      pm                     = "dynamic";
+      "listen.owner"         = config.services.nginx.user;
+      "pm.max_children"      = 1;
+      "pm.start_servers"     = 1;
+      "pm.min_spare_servers" = 1;
+      "pm.max_spare_servers" = 1;
+      "pm.max_requests"      = 500;
+    };
+  };
+
+  services.nginx = {
+    enable = true;
+    virtualHosts."dnsmasq.home.ctu.cx" = {
+      enableACME = true;
+      forceSSL   = true;
+      root       = dnsmasq-lease-overview;
+      locations  = {
+        "/".tryFiles           = "$uri $uri/ /index.php?$query_string";
+        "/".index              = "index.php index.html";
+        "~ \.php$".extraConfig = ''
+          fastcgi_pass  unix:${config.services.phpfpm.pools.dnsmasq.socket};
+          fastcgi_index index.php;
+        '';
+      };
+    };
+  };
+
+}
+
diff --git a/machines/lollo/router/hostapd.nix b/machines/lollo/router/hostapd.nix
@@ -0,0 +1,17 @@
+{ ... }:
+
+let
+  secrets = import ../../../secrets;
+
+in {
+
+  services.hostapd = {
+    enable        = false;
+    ssid          = "hostapd.home.ctu.cx";
+    wpaPassphrase = secrets.hosts.lollo.hostapd.passphrase;
+    interface     = "wlp3s0";
+    extraConfig   = "bridge=brlan";
+  };
+
+}
+
diff --git a/machines/lollo/router/nftables.nix b/machines/lollo/router/nftables.nix
@@ -0,0 +1,15 @@
+{ config, pkgs, ... }:
+
+{
+
+  networking = {
+    firewall.enable = false;
+
+    nftables = {
+      enable      = true;
+      rulesetFile = ./ruleset.nft;
+    };
+  };
+
+}
+
diff --git a/machines/lollo/router/ruleset.nft b/machines/lollo/router/ruleset.nft
@@ -0,0 +1,83 @@
+flush ruleset
+
+table inet firewall {
+    chain inbound {
+        # By default, drop all traffic unless it meets a filter
+        # criteria specified by the rules that follow below.
+        type filter hook input priority 0;
+        policy drop;
+
+        # Allow traffic from established and related packets.
+        ct state established,related accept
+
+        # Drop invalid packets.
+        ct state invalid drop
+
+        # Allow local connections.
+        iifname lo accept
+        iifname brlan accept
+
+        # Allow all ICMP and IGMP traffic, but enforce a rate limit
+        # to help prevent some types of flood attacks.
+        ip protocol icmp limit rate 5/second accept
+        ip protocol igmp limit rate 5/second accept
+        #ip6 protocol ipv6-icmp icmpv6-type redirect drop
+        #ip6 protocol ipv6-icmp icmpv6-type 139 drop
+        ip6 nexthdr ipv6-icmp limit rate 5/second accept
+
+        # Allow some ports
+        tcp dport ssh accept comment "ssh"
+        tcp dport domain accept comment "dns (tcp)"
+        udp dport domain accept comment "dns (udp)"
+        tcp dport http accept comment "http"
+        tcp dport https accept comment "https"
+        tcp dport 22000 accept comment "syncthing"
+        udp dport 21027 accept comment "syncthing"
+        tcp dport 5201 accept comment "iperf3 (tcp)"
+        udp dport 5201 accept comment "iperf3 (udp)"
+    }
+
+    chain forward {
+        # By default, drop all traffic unless it meets a filter
+        type filter hook forward priority 0;
+        policy drop;
+
+        # Allow traffic from established and related packets.
+        ct state established,related accept
+
+        # Drop invalid packets.
+        ct state invalid drop
+
+        # local clients can do whatever
+        iifname brlan accept
+
+        # Allow all ICMP and IGMP traffic, but enforce a rate limit
+        # to help prevent some types of flood attacks.
+        ip protocol icmp limit rate 5/second accept
+        ip6 nexthdr ipv6-icmp limit rate 5/second accept
+        ip protocol igmp limit rate 5/second accept
+
+        #make public ips world accessible 
+        ip daddr 195.39.246.33/28 accept
+    }
+
+    chain outbound {
+        # Allow all outbound traffic
+        type filter hook output priority 0
+        policy accept
+    }
+
+}
+
+table ip nat {
+    chain prerouting {
+        type nat hook prerouting priority -100
+        policy accept
+    }
+
+    chain postrouting {
+        type nat hook postrouting priority 0
+        policy accept
+        oifname enp2s0 masquerade
+    }
+}
diff --git a/machines/lollo/router/systemd-networkd.nix b/machines/lollo/router/systemd-networkd.nix
@@ -0,0 +1,124 @@
+{ ... }:
+
+{
+
+  systemd.network = {
+    enable   = true;
+    netdevs = {
+
+      "20-brlan" = {
+        netdevConfig = {
+          Kind = "bridge";
+          Name = "brlan";
+        };
+      };
+
+      "30-enp2s0.5" = {
+        netdevConfig = {
+          Kind = "vlan";
+          Name = "enp2s0.5";
+        };
+        vlanConfig = {
+          Id = 5;
+        };
+      };
+
+      "40-wg-pbb" = {
+        netdevConfig = {
+          Kind = "wireguard";
+          Name = "wg-pbb";
+        };
+        wireguardConfig = {
+          PrivateKeyFile = "/var/lib/secrets/wg-pbb.privkey";
+          ListenPort     = 51820;
+          FirewallMark   = 51820;
+        };
+        wireguardPeers = [{
+          wireguardPeerConfig={
+            Endpoint            = "195.39.247.172:51820";
+            PublicKey           = "QOQTpxvT122fiKBcN4QDADOjoDDzEW9sMWn/qngVF0Q=";
+            AllowedIPs          = [ "0.0.0.0/0" "::/0" ];
+            PersistentKeepalive = 1;
+#            RouteTable          = "off";
+          };
+        }];
+      };
+
+    };
+
+    networks = {
+
+      "10-enp2s0" = {
+        matchConfig = {
+          Name = "enp2s0";
+        };
+        DHCP = "yes";
+        vlan = [ "enp2s0.5" ];
+      };
+
+      "20-brlan" = {
+        matchConfig = {
+          Name   = "brlan";
+          Driver = "bridge";
+        };
+        DHCP    = "no";
+        address = [
+          "10.0.0.1/24"
+          "195.39.246.41/28"
+          "2a0f:4ac0:acab::1/62"
+        ];
+        routingPolicyRules = [
+          { routingPolicyRuleConfig = {
+            From                 = "195.39.246.32/28";
+            Table                = 254;
+            Priority             = 1900;
+            SuppressPrefixLength = 0;
+          };}
+          { routingPolicyRuleConfig = {
+            From                 = "2a0f:4ac0:acab::/62";
+            Table                = 254;
+            Priority             = 1900;
+            SuppressPrefixLength = 0;
+          };}
+          { routingPolicyRuleConfig = {
+            From     = "195.39.246.32/28";
+            Table    = 1234;
+            Priority = 2000;
+          };}
+          { routingPolicyRuleConfig = {
+            From     = "2a0f:4ac0:acab::/62";
+            Table    = 1234;
+            Priority = 2000;
+          };}
+        ];
+      };
+
+      "30-enp2s0.5" = {
+        matchConfig = {
+          Name = "enp2s0.5";
+        };
+        bridge = [ "brlan" ];
+      };
+
+      "40-wg-pbb" = {
+        matchConfig = {
+          Name = "wg-pbb";
+        };
+        linkConfig = {
+          MTUBytes = "1472";
+        };
+        routes = [
+          { routeConfig = {
+            Destination = "0.0.0.0/0";
+            Table       = "1234";
+          };}
+          { routeConfig = {
+            Destination = "::/0";
+            Table       = "1234";
+          };}
+        ];
+      };
+
+    };
+  };
+}
diff --git a/machines/lollo/smarthome/default.nix b/machines/lollo/smarthome/default.nix
@@ -0,0 +1,14 @@
+{config, lib, pkgs, ...}:
+
+{
+
+  imports = [
+    ./zigbee2mqtt.nix
+    ./mbusd.nix
+    ./serial2tcp.nix
+
+    ./influxdb2.nix
+    ./smartied.nix
+  ];
+
+}
diff --git a/machines/lollo/smarthome/influxdb2.nix b/machines/lollo/smarthome/influxdb2.nix
@@ -0,0 +1,18 @@
+{config, lib, pkgs, ...}:
+
+{
+
+  systemd.services.influxdb2.serviceConfig.ExecStartPost = "${pkgs.bash}/bin/bash -c 'until ${pkgs.netcat}/bin/nc -z 127.0.0.1 8086; do sleep 0.2; done'";
+
+  services.influxdb2.enable = true;
+
+  services.nginx = {
+    enable = true;
+    virtualHosts."influx.home.ctu.cx" = {
+      enableACME = true;
+      forceSSL   = true;
+      locations."/".proxyPass   = "http://127.0.0.1:8086/";
+    };
+  };
+
+}
diff --git a/machines/lollo/smarthome/mbusd.nix b/machines/lollo/smarthome/mbusd.nix
@@ -0,0 +1,37 @@
+{config, lib, pkgs, ...}:
+
+let
+  mbusd = pkgs.stdenv.mkDerivation rec {
+    pname = "mbusd";
+    version = "0.5.0";
+
+    src = pkgs.fetchFromGitHub {
+      owner = "3cky";
+      repo = pname;
+      rev = "v${version}";
+      sha256 = "1mvrwr02vcsgf9lc9bq4mhr0s6ww5z7ml7lwpyrl4axpz59i4l9s";
+    };
+
+    nativeBuildInputs = with pkgs; [ cmake pkgconfig ];
+  };
+
+in {
+
+  services.udev.extraRules = ''SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{serial}=="1337", SYMLINK+="modbus0"'';
+
+  systemd.services.mbusd = {
+    wantedBy = [ "multi-user.target" ];
+    requires = [ "network-online.target" "dev-modbus0.device" ];
+    wants    = [ "network-online.target" "dev-modbus0.device" ];
+    after    = [ "network-online.target" "dev-modbus0.device" ];
+
+    serviceConfig = {
+      ExecStart      = "${mbusd}/bin/mbusd -d -v2 -L - -p /dev/modbus0 -s 9600 -m 8n1 -C 32 -N 3 -R 100 -W 500 -T 60";
+      Restart        = "on-failure";
+      RestartSec     = "1";
+      StandardOutput = "journal";
+      StandardError  = "journal";
+    };
+  };
+
+}
diff --git a/machines/lollo/smarthome/serial2tcp.nix b/machines/lollo/smarthome/serial2tcp.nix
@@ -0,0 +1,39 @@
+{ pkgs, ...}:
+
+let
+  serial2tcp = pkgs.nimPackages.buildNimPackage {
+    pname       = "serial2tcp";
+    version     = "0.1.0";
+    nimBinOnly  = true;
+
+    src = pkgs.fetchgit {
+      url    = "https://cgit.ctu.cx/serial2tcp";
+      rev    = "6a81ddbc0e68cbb4e020a3ddc81cd93947a8da5f";
+      sha256 = "1b8iigq3r21sg02wqsxs26y74m44q92phn18510y3nxm617s5k5w";
+    };
+  };
+
+in {
+
+  services.udev.extraRules = ''SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="tempsensors0"'';
+
+  systemd.services.serial2tcp = {
+    wantedBy = [ "multi-user.target" ];
+    requires = [ "network-online.target" "dev-tempsensors0.device" ];
+    wants    = [ "network-online.target" "dev-tempsensors0.device" ];
+    after    = [ "network-online.target" "dev-tempsensors0.device" ];
+
+    serviceConfig = {
+      ExecStartPre = "${pkgs.coreutils}/bin/stty -F /dev/tempsensors0 raw -echo -echoe -echok speed 9600";
+      ExecStart    = "${serial2tcp}/bin/serial2tcp";
+      Restart      = "on-failure";
+      RestartSec   = "5";
+    };
+
+    environment = {
+      PORT          = "2342";
+      SERIAL_DEVICE = "/dev/tempsensors0";
+    };
+  };
+
+}
diff --git a/machines/lollo/smarthome/smartied.nix b/machines/lollo/smarthome/smartied.nix
@@ -0,0 +1,361 @@
+{ pkgs, ...}:
+
+let
+  secrets = import ../../../secrets;
+
+  ws = pkgs.fetchFromGitHub {
+    owner  = "treeform";
+    repo   = "ws";
+    rev    = "0.4.3";
+    sha256 = "03dyd36y5r8zbvcwih3nsvd7fa13vm6hdz7v0wglgv7mjpwpfik5";
+  };
+
+  nmqtt = pkgs.fetchFromGitHub {
+    owner  = "zevv";
+    repo   = "nmqtt";
+    rev    = "v1.0.4";
+    sha256 = "1by0xyqz754dny19lf8rpkg42passnj0rs6rk3jr763m1zr803mc";
+  };
+
+  smartied = pkgs.nimPackages.buildNimPackage {
+    pname       = "smartied";
+    version     = "0.1.0";
+    nimBinOnly  = true;
+    buildInputs = [ pkgs.libmodbus ws nmqtt ];
+
+    src = pkgs.fetchgit {
+      url    = "https://cgit.ctu.cx/smartied";
+      rev    = "6b0b4163a30caf95468f1bf724b53b9e02344b29";
+      sha256 = "0zv91hxhxsqs1qrijlkqkr81w4p14qrn33xh0kj4sxiplgxfz78v";
+    };
+  };
+
+  smartiePWA = import "${pkgs.fetchgit {
+    url    = "https://cgit.ctu.cx/smartie-pwa/";
+    rev    = "3fe40dfd8211f05751c3df98402eb2367636a6f7";
+    sha256 = "1zvhgmbbfxgxjaq8wbw3b8k95h5p5rqk28g55av7rpxxgmkcmz7y";
+  }}" {};
+
+  config = {
+    devices = {
+      "modbus-20" = {
+        type          = "RelayBoard";
+        firstRegister = 0;
+        count         = 4;
+        address       = 20;
+      };
+
+      "modbus-60" = {
+        type    = "PowerMeter";
+        model   = "SDM120";
+        address = 60;
+      };
+
+      "lacrosse-l1" = {
+        type = "LacrosseTempSensor";
+        id   = "27";
+      };
+
+      "lacrosse-l2" = {
+        type = "LacrosseTempSensor";
+        id   = "3a";
+      };
+
+      "lacrosse-kuehlschrank" = {
+        type = "LacrosseTempSensor";
+        id   = "33";
+      };
+
+      "lacrosse-bad" = {
+        type = "LacrosseTempSensor";
+        id   = "5";
+      };
+
+      "tradfri-lamp-l" = {
+        type       = "Zigbee2MqttLamp";
+        lampType   = "WhiteSpectrum";
+        deviceName = "ikea_lamp_l";
+      };
+
+      "tradfri-lamp-i" = {
+        type       = "Zigbee2MqttLamp";
+        lampType   = "WhiteSpectrum";
+        deviceName = "ikea_lamp_i";
+      };
+
+      "tradfri-lamp-hallway" = {
+        type       = "Zigbee2MqttLamp";
+        lampType   = "WhiteSpectrum";
+        deviceName = "ikea_lamp_hallway";
+      };
+
+      "tradfri-lamp-kitchen" = {
+        type       = "Zigbee2MqttLamp";
+        lampType   = "WhiteSpectrum";
+        deviceName = "ikea_lamp_kitchen";
+      };
+
+      "tradfri-lamp-bathroom" = {
+        type       = "Zigbee2MqttLamp";
+        lampType   = "WhiteSpectrum";
+        deviceName = "ikea_lamp_bathroom";
+      };
+
+      "tradfri-lamp-i-rgb" = {
+        type       = "Zigbee2MqttLamp";
+        lampType   = "RGB";
+        deviceName = "ikea_lamp_i_rgb";
+      };
+
+      "tradfri-co-l" = {
+        type       = "Zigbee2MqttRelay";
+        deviceName = "ikea_control_outlet_l";
+      };
+
+      "tradfri-co-i-desk" = {
+        type       = "Zigbee2MqttRelay";
+        deviceName = "ikea_control_outlet_i_desk";
+      };
+
+      "tradfri-button-l-desk" = {
+        type       = "Zigbee2MqttRemote";
+        deviceName = "ikea_button_l_desk";
+        actions    = {
+          on = [{
+            type       = "SwitchStateAction";
+            deviceName = "tradfri-co-l";
+            toggle     = true;
+          }];
+        };
+      };
+
+      "tradfri-motion-sensor" = {
+        type          = "Zigbee2MqttMotionSensor";
+        deviceName    = "ikea_motionsensor";
+        occupyActions = [{
+          type       = "SwitchStateAction";
+          deviceName = "tradfri-lamp-bathroom";
+          relay      = 0;
+          state      = true;
+        }];
+        clearActions = [{
+          type       = "SwitchStateAction";
+          deviceName = "tradfri-lamp-bathroom";
+          relay      = 0;
+          state      = false;
+        }];
+      };
+    };
+
+    clientConfigs = {
+      "smarthome-pwa" = {
+        views = [
+          {
+            url  = "room_l";
+            name = "Room (L)";
+            icon = "lightbulb";
+            type = "switches";
+            switches = [
+              {
+                name    = "Ceiling Light";
+                device  = "tradfri-lamp-l";
+                relay   = 0;
+              }
+              {
+                name   = "Lights under Desk";
+                device = "modbus-20";
+                relay  = 1;
+              }
+              {
+                name   = "Bed";
+                device = "tradfri-co-l";
+                relay  = 0;
+              }
+              {
+                name   = "PC-Speaker";
+                device = "modbus-20";
+                relay  = 0;
+              }
+            ];
+          }
+          {
+            url  = "room_i";
+            name = "Room (I)";
+            icon = "lightbulb";
+            type = "switches";
+            switches = [
+              {
+                name   = "Ceiling Light";
+                device = "tradfri-lamp-i";
+                relay  = 0;
+              }
+              {
+                name   = "RGB Lamp";
+                device = "tradfri-lamp-i-rgb";
+                relay  = 0;
+              }
+              {
+                name   = "Desk";
+                device = "tradfri-co-i-desk";
+                relay  = 0;
+              }
+            ];
+          }
+          {
+            url      = "room_others";
+            name     = "Other Rooms";
+            icon     = "lightbulb";
+            type     = "switches";
+            switches = [
+              {
+                name   = "Hallway: Ceiling Light";
+                device = "tradfri-lamp-hallway";
+                relay  = 0;
+              }
+              {
+                name   = "Kitchen: Ceiling Light";
+                device = "tradfri-lamp-kitchen";
+                relay  = 0;
+              }
+              {
+                name   = "Bathroom: Ceiling Light";
+                device = "tradfri-lamp-bathroom";
+                relay  = 0;
+              }
+            ];
+          }
+          {
+            url    = "powermeter";
+            name   = "Power Meter";
+            icon   = "power";
+            type   = "powermeter";
+            meters = [
+              {
+                name   = "Zimmer";
+                device = "modbus-60";
+              }
+            ];
+          }
+          {
+            url     = "temperature";
+            name    = "Temperature";
+            icon    = "brightness_7";
+            type    = "temperature";
+            sensors = [
+              {
+                name   = "Kühlschrank";
+                device = "lacrosse-kuehlschrank";
+              }
+              {
+                name   = "L Zimmer";
+                device = "lacrosse-l1";
+              }
+              {
+                name   = "L Zimmer2";
+                device = "lacrosse-l2";
+              }
+              {
+                name   = "Bad";
+                device = "lacrosse-bad";
+              }
+            ];
+          }
+          {
+            url         = "departures";
+            name        = "Departures";
+            icon        = "departure_board";
+            type        = "departures";
+            source      = "https://f2k1.de/haltestellen.php";
+          }
+          {
+            url         = "fritzbox";
+            name        = "Fritz!Box";
+            icon        = "router";
+            type        = "redirect";
+            destination = "http://192.168.178.1/";
+          }
+          {
+            url         = "grafana";
+            name        = "Grafana-Dashboard";
+            icon        = "multiline_chart";
+            type        = "redirect";
+            destination = "https://grafana.ctu.cx";
+          }
+          {
+            url         = "fahrrad";
+            name        = "Fahrradkarte";
+            icon        = "directions_bike";
+            type        = "redirect";
+            destination = "https://www.nextbike.de/de/kielregion/";
+          }
+          {
+            url        = "settings";
+            name       = "Settings";
+            icon       = "settings";
+            type       = "settings";
+            sourceLink = "https://cgit.ctu.cx/smarthome-pwa";
+          }
+        ];
+      };
+    };
+
+    serverConfig = {
+      frontendPort  = 5000;
+      accessToken   = secrets.hosts.lollo.smartied.accessToken;
+      modbus.host   = "10.0.0.1";
+      modbus.port   = 502;
+      mqtt.host     = "10.0.0.1";
+      mqtt.port     = 1883;
+      lacrosse.host = "10.0.0.1";
+      lacrosse.port = 2342;
+      powermeterUpdateIntervalSec = 20;
+
+      influx = {
+        host      = "10.0.0.1";
+        port      = 8086;
+        authToken = secrets.hosts.lollo.smartied.influxToken;
+        powermetersDatabase = "powermeters";
+        sensorsDatabase     = "sensors";
+      };
+    };
+  };
+
+  configFile = pkgs.writeText "smartied-config.json" (builtins.toJSON config);
+
+in {
+
+  systemd.services.smartied = {
+    wantedBy = [ "multi-user.target" ];
+    requires = [ "network-online.target" "mbusd.service" "serial2tcp.service" "influxdb2.service" "zigbee2mqtt.service" ];
+    after    = [ "network-online.target" "mbusd.service" "serial2tcp.service" "influxdb2.service" "zigbee2mqtt.service" ];
+
+    serviceConfig = {
+      ExecStart    = "${smartied}/bin/smartied";
+      Restart      = "on-failure";
+      RestartSec   = "5";
+    };
+
+    environment = {
+      CONFIG_PATH = configFile;
+    };
+  };
+
+  services.nginx = {
+    enable = true;
+    virtualHosts."home.ctu.cx" = {
+      enableACME = true;
+      forceSSL   = true;
+      locations = {
+        "/"   = {
+          root  = smartiePWA;
+          index = "index.html";
+        };
+        "/ws" = {
+          proxyPass       = "http://127.0.0.1:5000/ws";
+          proxyWebsockets = true;
+        };
+      };
+    };
+  };
+
+}
diff --git a/machines/lollo/smarthome/zigbee2mqtt.nix b/machines/lollo/smarthome/zigbee2mqtt.nix
@@ -0,0 +1,150 @@
+{config, lib, pkgs, ...}:
+
+let
+  pkgsUnstable = import <nixpkgsUnstable> {};
+  secrets      = import ../../../secrets;
+
+in {
+
+  systemd.services.zigbee2mqtt = {
+    requires = [ "mosquitto.service" ];
+    after    = [ "mosquitto.service" ];
+  };
+
+  services = {
+    udev.extraRules = ''
+      ATTR{idVendor}=="0451", ATTR{idProduct}=="16a8", ENV{ID_MM_DEVICE_IGNORE}="1"
+      SUBSYSTEM=="tty", ATTRS{idVendor}=="0451", ATTRS{idProduct}=="16a8", SYMLINK+="zigbee0"
+    '';
+
+    mosquitto = {
+      enable      = true;
+      persistence = false;
+      settings = {
+        max_keepalive = 60;
+      };
+      listeners = [{
+        port = 1883;
+        omitPasswordAuth = true;
+        users = {};
+        settings = {
+          allow_anonymous = true;
+        };
+        acl = [ "topic readwrite #" "pattern readwrite #" ];
+      }];
+    };
+
+    zigbee2mqtt = {
+      enable   = true;
+      package  = pkgsUnstable.zigbee2mqtt;
+      settings = {
+        homeassistant = false;
+        permit_join   = false;
+
+        mqtt = {
+          base_topic = "zigbee2mqtt";
+          server     = "mqtt://127.0.0.1";
+        };
+
+        serial = {
+          port        = "/dev/zigbee0";
+          disable_led = true;
+        };
+
+        frontend = {
+          port = 8422;
+          host = "0.0.0.0";
+        };
+
+        advanced = {
+          log_level   = "info";
+          log_output  = [ "console" ];
+          network_key = secrets.hosts.lollo.zigbee2mqtt.network_key;
+        };
+
+        devices = {
+          "0x84fd27fffeaaa597".friendly_name = "ikea_lamp_i";
+          "0x84fd27fffe6b9ddd".friendly_name = "ikea_lamp_l";
+          "0x84fd27fffe44369e".friendly_name = "ikea_lamp_kitchen";
+          "0x84fd27fffe3a0b93".friendly_name = "ikea_lamp_bathroom";
+          "0x84fd27fffea515fc".friendly_name = "ikea_lamp_hallway";
+          "0x842e14fffe57daae".friendly_name = "ikea_lamp_i_rgb";
+
+          "0x5c0272fffec9006c".friendly_name = "ikea_remote_i_door";
+          "0x804b50fffe42a74e".friendly_name = "ikea_remote_l_door";
+          "0x5c0272fffeca585a".friendly_name = "ikea_remote_kitchen_door";
+          "0x842e14fffe1ab485".friendly_name = "ikea_remote_bathroom";
+          "0x804b50fffe7df0be".friendly_name = "ikea_remote_hallway_entrancedoor";
+          "0x0c4314fffe194a18".friendly_name = "ikea_remote_hallway";
+          "0x0c4314fffe194ca3".friendly_name = "ikea_remote_i_desk";
+
+
+          "0x588e81fffe3ec895".friendly_name = "ikea_button_l_desk";
+
+          "0xcc86ecfffe8bf621".friendly_name = "ikea_control_outlet_i_desk";
+          "0x588e81fffebcdc1e".friendly_name = "ikea_control_outlet_l";
+
+          "0x847127fffecd89b6".friendly_name = "ikea_motionsensor";
+        };
+
+        groups = {
+          "1" = {
+            friendly_name = "room_i";
+            retain        = false;
+            transition    = 2;
+            optimistic    = true;
+            devices = [
+              "ikea_lamp_i"
+              "ikea_lamp_i_rgb"
+            ];
+          };
+          "2" = {
+            friendly_name = "room_l";
+            retain        = false;
+            transition    = 2;
+            optimistic    = true;
+            devices = [
+              "ikea_lamp_l"
+            ];
+          };
+          "3" = {
+            friendly_name = "room_kitchen";
+            retain        = false;
+            transition    = 2;
+            optimistic    = true;
+            devices = [
+              "ikea_lamp_kitchen"
+            ];
+          };
+          "4" = {
+            friendly_name = "room_bathroom";
+            retain        = false;
+            transition    = 2;
+            optimistic    = true;
+            devices = [
+              "ikea_lamp_bathroom"
+            ];
+          };
+          "5" = {
+            friendly_name = "room_hallway";
+            retain        = false;
+            transition    = 2;
+            optimistic    = true;
+            devices = [
+              "ikea_lamp_hallway"
+            ];
+          };
+          "6" = {
+            friendly_name = "room_i_desk";
+            retain        = false;
+            transition    = 2;
+            optimistic    = true;
+            devices = [
+              "ikea_control_outlet_i_desk"
+            ];
+          };
+        };
+      };
+    };
+  };
+}
diff --git a/machines/lollo/syncthing.nix b/machines/lollo/syncthing.nix
@@ -0,0 +1,26 @@
+{config, lib, pkgs, ...}:
+
+{
+
+  imports = [
+    ../../configurations/programs/syncthing.nix
+  ];
+
+  services = {
+    syncthing = {
+      guiAddress = "0.0.0.0:8384";
+    };
+
+    nginx = {
+      enable = true;
+      virtualHosts."syncthing.lollo.ctu.cx" = {
+        enableACME = true;
+        forceSSL   = true;
+        locations."/" = {
+          proxyPass   = "http://127.0.0.1:8384/";
+        };
+      };
+    };
+  };
+
+}
diff --git a/machines/lollo/websites/default.nix b/machines/lollo/websites/default.nix
@@ -0,0 +1,11 @@
+{ ... }:
+
+{
+
+  imports = [
+    ./wiki.home.ctu.cx.nix
+    ./music.home.ctu.cx.nix
+  ];
+
+}
+
diff --git a/machines/lollo/websites/music.home.ctu.cx.nix b/machines/lollo/websites/music.home.ctu.cx.nix
@@ -0,0 +1,87 @@
+{ config, pkgs, lib, ... }:
+
+let
+  webmusic-nginx = pkgs.fetchgit {
+    url    = "https://cgit.ctu.cx/webmusic-nginx";
+    rev    = "ac42fd4ab6820f5e840b13cbd03f3cdf0ae149ff";
+    sha256 = "00griw6qn3qw2g3ga5nn5p7dk0xac9wa2ni35n4a4yasd1y71xx8";
+  };
+
+in {
+
+  systemd.services.nginx.serviceConfig.ProtectHome = lib.mkForce false;
+
+  environment.systemPackages = [ pkgs.bindfs ];
+
+  fileSystems."/mnt/music_originals" = {
+    device = "/home/leah/syncthing/Music (Originals)";
+    fsType = "fuse.bindfs";
+    options = [ "ro" "perms=0000:a+rX" ];
+  };
+
+  services.nginx = {
+    enable = true;
+    virtualHosts."music.home.ctu.cx" = {
+      enableACME = true;
+      forceSSL   = true;
+      root       = "/mnt/music_originals";
+      locations  = {
+
+        "~ ^(.*/)$".extraConfig = ''
+          autoindex on;
+          autoindex_exact_size off;
+          autoindex_format xml;
+
+          xslt_string_param path $uri;
+          xslt_stylesheet ${webmusic-nginx}/webmusic.xslt;
+
+          auth_basic 'Auth required';
+          auth_basic_user_file /var/lib/secrets/music-auth;
+        '';
+
+        "~(.*)playlist.m3u$".extraConfig = ''
+          set $url http://127.0.0.1:81$1;
+          proxy_pass $url;
+          proxy_set_header Domain $scheme://$host;
+          proxy_hide_header 'Content-Type';
+          add_header 'Content-Type' 'text/plain';
+        '';
+
+        "/assets/".alias = "${webmusic-nginx}/";
+      };
+
+      extraConfig = ''
+        satisfy any;
+        allow 2a0f:4ac0:acab::/48;
+        allow 10.0.0.0/8;
+        allow 195.39.246.32/28;
+        allow 195.39.247.48/29;
+      '';
+    };
+
+    appendHttpConfig = ''
+      server {
+        server_name webmusic.local;
+        listen 81;
+        access_log off;
+
+        allow 127.0.0.1;
+        deny all;
+
+        root /mnt/music_originals;
+
+        location / {
+          autoindex on;
+          autoindex_exact_size off;
+          autoindex_format xml;
+
+          xslt_string_param domain $http_domain;
+          xslt_string_param path $uri;
+          xslt_stylesheet ${webmusic-nginx}/webmusic-playlist.xslt;
+        }
+      }
+    '';
+  };
+
+}
+
diff --git a/machines/lollo/websites/wiki.home.ctu.cx.nix b/machines/lollo/websites/wiki.home.ctu.cx.nix
@@ -0,0 +1,115 @@
+{ config, pkgs, lib, ... }:
+
+let
+  PineDocsConfig = {
+    title                = "ctucx.wiki";
+    content_dir          = "/home/leah/syncthing/Wiki";
+    index                = "index.md";
+    layout               = "wiki";
+    color_scheme         = "pinedocs";
+    highlight_theme      = "darcula";
+    code_transparent_bg  = false;
+    open_dirs            = "all";
+    render_footer        = true;
+    exclude_files        = [ ".git" ".db" ".swp" ".stfolder" ".stversions" ];
+    show_file_extension  = true;
+    menu_link_format     = "default";
+    render_max_file_size = 50;
+  };
+
+  fetcher      = (args: pkgs.fetchurl { inherit (args) name urls sha256; });
+  cacheEntries = [
+    { name = "stephank_composer-plugin-nixify-1.1.0.0"; filename = "stephank/composer-plugin-nixify/6b00aedf28221acbb64a87222a0eb819404901f2.zip"; sha256 = "ac7cc480698f8717fb9fce4077b81303d37fe6ab2b89c8547cd7f9451598ee1e"; urls = [ "https://api.github.com/repos/stephank/composer-plugin-nixify/zipball/d93c4348388d714d7d81b41e34ccb2ae9c2131c2" ]; }
+    { name = "symfony_deprecation-contracts-2.2.0.0"; filename = "symfony/deprecation-contracts/99b9801994a098b194130905f1f0df2d1f43254c.zip"; sha256 = "20bc7b59da2900146ab95c1b7340b2b934e3ca54a773539254f846a5404fc55b"; urls = [ "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5fa56b4074d1ae755beb55617ddafe6f5d78f665" ]; }
+    { name = "symfony_polyfill-ctype-1.18.1.0"; filename = "symfony/polyfill-ctype/6b6206c0107a01483ce3308f2596bf1b08154055.zip"; sha256 = "dbd2243692a5c57b9b902c4f87dbe1f5eefc340170347ed8049035b8cde7f19e"; urls = [ "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454" ]; }
+    { name = "symfony_polyfill-mbstring-1.18.1.0"; filename = "symfony/polyfill-mbstring/f5074ca4813832ac106584ce48b1812575d61847.zip"; sha256 = "a4f0b3117da1ca3fddefac6e8fd04f37c8d033aa3d3a79aef50323f19484cec4"; urls = [ "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a" ]; }
+    { name = "symfony_yaml-5.1.7.0"; filename = "symfony/yaml/c1cfb60fb6318351d391fcd682a08fdc1711e238.zip"; sha256 = "4c811349d0a57fafc04a1dac58832f08aa7df4fc4f94732d3bd6136aeb4ea15e"; urls = [ "https://api.github.com/repos/symfony/yaml/zipball/e147a68cb66a8b510f4b7481fe4da5b2ab65ec6a" ]; }
+    { name = "twig_twig-3.1.0.0"; filename = "twig/twig/ad4f94d1500e3eb72e2a188822192a144b07a2b5.zip"; sha256 = "43dab6f872af1c8e5bc4efc93a9fe40503eaa821f4969a03ef0d87eb2ff5b16d"; urls = [ "https://api.github.com/repos/twigphp/Twig/zipball/9a29e1fa7b5431969f96878b8662e3fcb18601b7" ]; }
+  ];
+
+  # Shell snippet to collect all project dependencies.
+  collectCacheScript = pkgs.writeText "collect-cache.sh" (
+    pkgs. lib.strings.concatMapStrings (args: ''
+      (
+        cacheFile=${lib.strings.escapeShellArg args.filename}
+        cacheFilePath="$COMPOSER_CACHE_DIR/files/$cacheFile"
+        mkdir -p "$(dirname "$cacheFilePath")"
+        cp ${lib.strings.escapeShellArg (fetcher args)} "$cacheFilePath"
+      )
+    '') cacheEntries
+  );
+
+  PineDocs = pkgs.stdenv.mkDerivation rec {
+    pname    = "PineDocs";
+    version  = "1.2.2";
+    src      = pkgs.fetchFromGitHub {
+      owner  = "xy2z";
+      repo   = "PineDocs";
+      rev    = "${version}";
+      sha256 = "0khxhgp4gnjgcabd14bkpqy9597xzm022ha28g2k1mslhazbs4sv";
+    };
+
+    buildInputs = with pkgs; [ php unzip ];
+
+    # Defines the shell alias to run Composer.
+    postHook = ''composer () {
+      php "$NIX_COMPOSER_PATH" "$@"
+    }'';
+
+    configurePhase = ''
+      # Set the cache directory for Composer.
+      export COMPOSER_CACHE_DIR="$NIX_BUILD_TOP/.composer/cache";
+
+      # Build the cache directory contents.
+      source ${collectCacheScript};
+
+      # Store the absolute path to Composer for the 'composer' alias.
+      export NIX_COMPOSER_PATH="$(readlink -f ${lib.strings.escapeShellArg pkgs.phpPackages.composer.src})";
+
+      # Run normal Composer install to complete dependency installation.
+      composer install;
+    '';
+
+    installPhase = ''
+      mv $PWD $out;
+      touch $out/config/config.yaml;
+      echo '${builtins.toJSON PineDocsConfig}' > $out/config/config.yaml;
+    '';
+  };
+
+in {
+
+  systemd.services.phpfpm-pinedocs.serviceConfig.ProtectHome = lib.mkForce false;
+
+  services.phpfpm.pools.pinedocs  = {
+    user  = "leah";
+    group = "users";
+    settings = {
+      pm                     = "dynamic";
+      "listen.owner"         = config.services.nginx.user;
+      "pm.max_children"      = 2;
+      "pm.start_servers"     = 2;
+      "pm.min_spare_servers" = 1;
+      "pm.max_spare_servers" = 2;
+      "pm.max_requests"      = 500;
+    };
+  };
+
+  services.nginx = {
+    enable = true;
+    virtualHosts."wiki.home.ctu.cx" = {
+      enableACME = true;
+      forceSSL   = true;
+      root       = "${PineDocs}/public";
+      locations  = {
+        "/".index              = "index.php index.html";
+        "~ \.php$".extraConfig = ''
+          fastcgi_pass  unix:${config.services.phpfpm.pools.pinedocs.socket};
+          fastcgi_index index.php;
+        '';
+      };
+    };
+  };
+
+}
+
diff --git a/nix/sources.json b/nix/sources.json
@@ -34,5 +34,17 @@
         "type": "tarball",
         "url": "https://github.com/NixOS/nixpkgs/archive/330da7601fd570b9e5e5d19f32789c57df4ddceb.tar.gz",
         "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+    },
+    "nixpkgsUnstable": {
+        "branch": "nixpkgs-unstable",
+        "description": "Nix Packages collection",
+        "homepage": "",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "a3d847c3bd3a3b75b3057d7b3730d3308dd8fd59",
+        "sha256": "1s9afl5kg8fv8vkd5b42mckiswgmbqj7aqhaxg246glwhy405iy1",
+        "type": "tarball",
+        "url": "https://github.com/NixOS/nixpkgs/archive/a3d847c3bd3a3b75b3057d7b3730d3308dd8fd59.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
     }
 }
diff --git a/secrets/.keep-folder b/secrets/.keep-folder