From c6c6c17dd3db0e10096f3da60e0bfe1ed2e91268 Mon Sep 17 00:00:00 2001
From: Julien Moutinho <julm@sourcephile.fr>
Date: Mon, 16 Mar 2020 00:52:52 +0100
Subject: [PATCH] nix: revamp the config paths

---
 .gitmodules                                   |   6 +-
 Makefile                                      |   4 +-
 base/apu2e4.nix                               |  77 +++++
 base/unbound.nix                              | 148 ++++++++++
 {config/dns => base/unbound}/named.root       |   0
 base/zfs.nix                                  |  33 +++
 defaults.nix                                  | 150 ++++++++++
 defaults/predictable-interface-names.nix      |  72 +++++
 {nixos/defaults => defaults}/readline/inputrc |   0
 nixos/modules.nix => modules.nix              |   0
 .../services/databases/openldap.nix           |   0
 .../services/mail/dovecot.nix                 |   0
 .../services/networking/domains.nix           |   0
 nixos/defaults.nix                            | 137 ---------
 .../defaults/predictable-interface-names.nix  |  72 -----
 servers.nix                                   |   4 +-
 servers/mermet.nix                            | 103 -------
 servers/mermet/configuration.nix              |  98 +++++++
 {config => servers/mermet}/gitolite           |   0
 servers/mermet/keys.nix                       |  32 ++-
 servers/mermet/production.nix                 |  14 +-
 servers/mermet/production/apu2e4.nix          | 109 ++-----
 servers/mermet/production/lesptts.nix         | 268 +++++++++---------
 servers/mermet/production/zfs.nix             | 150 ++++------
 servers/mermet/unbound.nix                    | 147 ----------
 25 files changed, 818 insertions(+), 806 deletions(-)
 create mode 100644 base/apu2e4.nix
 create mode 100644 base/unbound.nix
 rename {config/dns => base/unbound}/named.root (100%)
 create mode 100644 base/zfs.nix
 create mode 100644 defaults.nix
 create mode 100644 defaults/predictable-interface-names.nix
 rename {nixos/defaults => defaults}/readline/inputrc (100%)
 rename nixos/modules.nix => modules.nix (100%)
 rename {nixos/modules => modules}/services/databases/openldap.nix (100%)
 rename {nixos/modules => modules}/services/mail/dovecot.nix (100%)
 rename {nixos/modules => modules}/services/networking/domains.nix (100%)
 delete mode 100644 nixos/defaults.nix
 delete mode 100644 nixos/defaults/predictable-interface-names.nix
 delete mode 100644 servers/mermet.nix
 create mode 100644 servers/mermet/configuration.nix
 rename {config => servers/mermet}/gitolite (100%)
 delete mode 100644 servers/mermet/unbound.nix

diff --git a/.gitmodules b/.gitmodules
index b4caaa7..2d565a6 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
-[submodule "config/gitolite"]
-	path = config/gitolite
-	url = ./config/gitolite
+[submodule "servers/mermet/gitolite"]
+	path = servers/mermet/gitolite
+	url = ./servers/mermet/gitolite
diff --git a/Makefile b/Makefile
index 1722682..f247f78 100644
--- a/Makefile
+++ b/Makefile
@@ -2,8 +2,8 @@ all: init
 
 include .lib/nixops/Makefile.make
 
-.PHONY: config/dns/named.root
-config/dns/named.root:
+.PHONY: base/unbound/named.root
+base/unbound/named.root:
 	mkdir -p $(@D)
 	curl >$@ -L https://www.internic.net/domain/named.root
 
diff --git a/base/apu2e4.nix b/base/apu2e4.nix
new file mode 100644
index 0000000..c31216f
--- /dev/null
+++ b/base/apu2e4.nix
@@ -0,0 +1,77 @@
+{ pkgs, lib, config, ... }:
+{
+hardware.cpu.amd.updateMicrocode = true;
+nix = {
+  # Too CPU hungry for the APU2, for too little Mio saved
+  autoOptimiseStore = false;
+  maxJobs = 4;
+};
+powerManagement.cpuFreqGovernor = lib.mkDefault "ondemand";
+
+boot.kernel = {
+  sysctl = {
+    "vm.swappiness" = 10;
+    "vm.vfs_cache_pressure" = 50;
+  };
+};
+
+boot.loader = {
+  grub = {
+    enable = true;
+    version = 2;
+    copyKernels = true;
+    # efiSupport = true;
+    devices = [
+      # Completed in the importing configuration
+    ];
+    /*
+    mirroredBoots = [
+      { devices = [ "${disk_id}" ];
+        path    = "/boot${bootnum}";
+      }
+    ];
+    */
+  };
+  /*
+  efi = {
+    canTouchEfiVariables = true;
+    efiSysMountPoint = "/boot/efi";
+    efiInstallAsRemovable = false;
+  };
+  */
+};
+
+boot.initrd = {
+  availableKernelModules = [
+    "ahci"
+    "ehci_pci"
+    "sd_mod"
+    "uas"
+    # Ethernet driver
+    "igb"
+    # Made the AES modules available at initrd,
+    # to speedup the deciphering of the root.
+    "aes_x86_64"
+    "aesni_intel"
+    "cryptd"
+  ];
+  kernelModules = [ ];
+
+};
+boot.kernelModules = [ ];
+boot.extraModulePackages = [ ];
+boot.kernelParams = [
+  "gfxpayload=text"
+  #"console=tty0"
+  "console=ttyS0,115200n8"
+  # Use arc_summary to print stats
+  "zfs.zfs_arc_max=${toString (500 * 1024 * 1024)}" # bytes
+];
+
+environment = {
+  systemPackages = with pkgs; [
+    pciutils
+    flashrom
+  ];
+};
+}
diff --git a/base/unbound.nix b/base/unbound.nix
new file mode 100644
index 0000000..af5bb71
--- /dev/null
+++ b/base/unbound.nix
@@ -0,0 +1,148 @@
+{ pkgs, lib, config, ... }:
+let
+  inherit (config) users;
+  inherit (config.services) unbound;
+  stateDir = "/var/lib/unbound";
+in
+{
+networking.resolvconf.useLocalResolver = true;
+services.unbound = {
+  enable = true;
+  # DOC: https://calomel.org/unbound_dns.html
+  extraConfig = ''
+    remote-control:
+      control-enable: yes
+      control-interface: /run/unbound/unbound.socket
+
+    server:
+      log-queries: no
+      verbosity: 1
+
+      port: 53
+
+      # The file which contains the listing of primary root DNS servers.
+      # To be updated once every six months.
+      root-hints: /var/lib/unbound/named.root
+
+      # Do no answer id.server and hostname.bind queries.
+      hide-identity: yes
+      # Do not answer version.server and version.bind queries.
+      hide-version: yes
+
+      # Will trust glue only if it is within the servers authority.
+      # Harden against out of zone rrsets, to avoid spoofing attempts.
+      # Hardening queries multiple name servers for the same data to make
+      # spoofing significantly harder and does not mandate dnssec.
+      harden-glue: yes
+
+      # Require DNSSEC data for trust-anchored zones, if such data is absent, the
+      # zone becomes  bogus.  Harden against receiving dnssec-stripped data. If you
+      # turn it off, failing to validate dnskey data for a trustanchor will trigger
+      # insecure mode for that zone (like without a trustanchor).  Default on,
+      # which insists on dnssec data for trust-anchored zones.
+      harden-dnssec-stripped: yes
+
+      # Use 0x20-encoded random bits in the query to foil spoof attempts.
+      # http://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00
+      #
+      # When Unbound sends a query to a remote server it sends the hostname
+      # string in random upper and lower characters. The remote server must
+      # resolve the hostname as if all the characters were lower case. The remote
+      # server must then send the query back to Unbound in the same random upper
+      # and lower characters that Unbound sent. If the characters of the hostname
+      # in the response are in the same format as the query then the dns-0x20
+      # check is satisfied.
+      # Attackers hoping to poison a Unbound DNS cache must therefore guess the
+      # mixed-case encoding of the query and the timing of the return dns answer
+      # in addition to all other fields required in a DNS poisoning attack.
+      # dns-0x20 increases the difficulty of the attack significantly.
+      #
+      # It may result in maybe 0.4% of domains getting no answers
+      # due to no support on the authoritative server side
+      use-caps-for-id: yes
+
+      #cache-min-ttl: 3600
+      cache-max-ttl: 86400
+
+      # Perform prefetching of close to expired message cache entries.  If a client
+      # requests the dns lookup and the TTL of the cached hostname is going to
+      # expire in less than 10% of its TTL, unbound will (1st) return the IP of the
+      # host to the client and (2nd) pre-fetch the DNS request from the remote DNS server.
+      # This method has been shown to increase the amount of cached hits by
+      # local clients by 10% on average.
+      prefetch: yes
+
+      # Number of threads to create. 1 disables threading.
+      # This should equal the number of CPU cores in the machine.
+      num-threads: ${toString config.nix.maxJobs}
+
+      # The number of slabs to use for cache and must be a power of 2 times the
+      # number of num-threads set above. more slabs reduce lock contention,
+      # but fragment memory usage.
+      msg-cache-slabs: 8
+      rrset-cache-slabs: 8
+      infra-cache-slabs: 8
+      key-cache-slabs: 8
+
+      # Increase the memory size of the cache. Use roughly twice as much rrset cache
+      # memory as you use msg cache memory. Due to malloc overhead, the total memory
+      # usage is likely to rise to double (or 2.5x) the total cache memory.
+      rrset-cache-size: 32m
+      msg-cache-size: 16m
+
+      # buffer size for UDP port 53 incoming (SO_RCVBUF socket option). This sets
+      # the kernel buffer larger so that no messages are lost in spikes in the traffic.
+      so-rcvbuf: 1m
+
+      # Enforce privacy of these addresses. Strips them away from answers.
+      # It may cause DNSSEC validation to additionally mark it as bogus.
+      # Protects against 'DNS Rebinding' (uses browser as network proxy).
+      #  Only 'private-domain' and 'local-data' names are allowed
+      # to have these private addresses. No default.
+      private-address: 192.168.0.0/16
+      private-address: 172.16.0.0/12
+      private-address: 10.0.0.0/8
+
+      # Allow the domain (and its subdomains) to contain private addresses.
+      # local-data statements are allowed to contain private addresses too.
+      #private-domain: "home.lan"
+
+      # If nonzero, unwanted replies are not only reported in statistics, but also
+      # a running total is kept per thread. If it reaches the threshold, a warning
+      # is printed and a defensive action is taken, the cache is cleared to flush
+      # potential poison out of it.  A suggested value is 10000000, the default is
+      # 0 (turned off). calomel.org thinks 10K is a good value.
+      unwanted-reply-threshold: 10000
+
+      # IMPORTANT FOR TESTING: If you are testing and setup NSD or BIND on
+      # localhost you will want to allow the resolver to send queries to localhost.
+      # Make sure to set do-not-query-localhost: yes.
+      do-not-query-localhost: yes
+
+      # Should additional section of secure message also be kept clean of unsecure
+      # data. Useful to shield the users of this validator from potential bogus
+      # data in the additional section. All unsigned data in the additional section
+      # is removed from secure messages.
+      val-clean-additional: yes
+  '';
+};
+systemd.services.unbound = {
+  serviceConfig = {
+    RuntimeDirectory = "unbound";
+    RuntimeDirectoryMode = "0700";
+    # FIXME: upstream service shouldn't overwrite ExecStopPost
+    # so that postStop can be used.
+    ExecStopPost = lib.mkForce (pkgs.writeShellScript "unit-script-unbound-post-stop" ''
+      ${pkgs.utillinux}/bin/umount ${stateDir}/dev/random
+      ${pkgs.utillinux}/bin/umount ${stateDir}/run/unbound
+    '');
+  };
+  preStart = ''
+    install -m 444 -o unbound -g nogroup \
+     ${unbound/named.root} \
+     /var/lib/unbound/named.root
+    mkdir -p ${stateDir}/run/unbound
+    ${pkgs.utillinux}/bin/mount --bind -n /run/unbound ${stateDir}/run/unbound
+  '';
+};
+}
diff --git a/config/dns/named.root b/base/unbound/named.root
similarity index 100%
rename from config/dns/named.root
rename to base/unbound/named.root
diff --git a/base/zfs.nix b/base/zfs.nix
new file mode 100644
index 0000000..42a86f9
--- /dev/null
+++ b/base/zfs.nix
@@ -0,0 +1,33 @@
+{ pkgs, lib, config, ... }:
+{
+# The 32-bit host id of the machine, formatted as 8 hexadecimal characters.
+# You should try to make this id unique among your machines.
+# Manually generated with : head -c4 /dev/urandom | od -A none -t x4 | cut -d ' ' -f 2
+networking.hostId = "69c40b03";
+
+# none is the recommended elevator with ZFS (which has its own I/O scheduler)
+# and/or for SSD, whereas HDD could use mq-deadline.
+services.udev.extraRules = ''
+  # set none scheduler for non-rotating disks
+  ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="none"
+'';
+
+boot.supportedFilesystems = [ "zfs" ];
+
+# Ensure extra safeguards are active that zfs uses to protect zfs pools.
+boot.zfs.forceImportAll  = false;
+boot.zfs.forceImportRoot = false;
+
+boot.zfs.enableUnstable = true;
+boot.zfs.requestEncryptionCredentials = true;
+
+# Enables periodic scrubbing of ZFS pools.
+services.zfs.autoScrub.enable = true;
+
+environment = {
+  systemPackages = [
+    pkgs.mbuffer
+    pkgs.zfs
+  ];
+};
+}
diff --git a/defaults.nix b/defaults.nix
new file mode 100644
index 0000000..dca736e
--- /dev/null
+++ b/defaults.nix
@@ -0,0 +1,150 @@
+{ pkgs, lib, config, ... }:
+let inherit (lib) types;
+in
+{
+imports = [
+  ./modules.nix
+  defaults/predictable-interface-names.nix
+];
+
+nix = {
+  #binaryCaches = lib.mkForce [];
+  extraOptions = ''
+  '';
+  # Use gc.automatic to keep disk space under control.
+  gc = {
+    automatic = true;
+    dates = "weekly";
+    options = "--delete-older-than 30d";
+  };
+  nixPath = [
+    ("nixpkgs=" + toString pkgs.path)
+  ];
+};
+
+nixpkgs = {
+  config = {
+    allowUnfree = false;
+    /*
+    packageOverrides = pkgs: {
+      postfix = pkgs.postfix.override {
+        withLDAP = true;
+      };
+    };
+    */
+  };
+  overlays = import ./overlays.nix;
+};
+
+documentation.nixos = {
+  enable = false; # NOTE: useless on a server, and CPU intensive.
+};
+
+# Clean /tmp automatically on boot.
+boot.cleanTmpDir = true;
+
+time = {
+  timeZone = "Europe/Paris";
+};
+
+i18n = {
+  defaultLocale = "fr_FR.UTF-8";
+};
+
+console = {
+  font   = "Lat2-Terminus16";
+  keyMap = "fr";
+};
+
+# Always try to start all the units (default.target)
+# because systemd's emergency shell does not try to start sshd.
+# https://wiki.archlinux.org/index.php/systemd#Disable_emergency_mode_on_remote_machine
+systemd.enableEmergencyMode = false;
+
+# This is a remote headless server: always reboot on a kernel panic,
+# to not have to physically go power cycle the apu2e4.
+# Which happens if the wrong ZFS password is used
+# but the boot is manually forced to continue.
+# Using kernelParams instead of kernel.sysctl
+# sets this up as soon as the initrd.
+boot.kernelParams = [ "panic=10" ];
+
+services = {
+  openssh = {
+    enable = true;
+    passwordAuthentication = false;
+    extraConfig = ''
+    '';
+  };
+  journald = {
+    extraConfig = ''
+      SystemMaxUse=50M
+    '';
+  };
+};
+
+environment = {
+  #checkConfigurationOptions = false;
+  systemPackages = with pkgs; [
+    binutils
+    #dnsutils
+    dstat
+    htop
+    inetutils
+    iotop
+    lsof
+    mailutils
+    multitail
+    ncdu
+    pv
+    swaplist
+    tcpdump
+    tmux
+    tree
+    vim
+    which
+  ];
+
+  etc."inputrc".text = lib.readFile defaults/readline/inputrc;
+};
+
+programs = {
+  bash = {
+    interactiveShellInit = ''
+      bind '"\e[A":history-search-backward'
+      bind '"\e[B":history-search-forward'
+
+      # Ignore duplicate commands, ignore commands starting with a space
+      export HISTCONTROL=erasedups:ignorespace
+      export HISTSIZE=42000
+
+      # Append to the history instead of overwriting (good for multiple connections)
+      shopt -s histappend
+    '';
+    shellAliases = {
+      cl = "clear";
+      l  = "ls -alh";
+      ll = "ls -l";
+      ls = "ls --color=tty";
+      mem = "ps -e -orss=,user=,args= | sort -b -k1,1n";
+
+      s="sudo systemctl";
+      s-u="systemctl --user";
+
+      nixos-clean="sudo nix-collect-garbage -d";
+      nixos-history="sudo nix-env --list-generations --profile /nix/var/nix/profiles/system";
+      nixos-rollback="sudo nixos-rebuild switch --rollback";
+      nixos-update="sudo nix-channel --update";
+      nixos-upgrade="sudo nixos-rebuild switch";
+      nixos-upstream="sudo nix-channel --list";
+    };
+  };
+  gnupg = {
+    agent = {
+      pinentryFlavor = "curses";
+    };
+  };
+  mosh.enable = true;
+  mtr.enable = true;
+};
+}
diff --git a/defaults/predictable-interface-names.nix b/defaults/predictable-interface-names.nix
new file mode 100644
index 0000000..4db44c3
--- /dev/null
+++ b/defaults/predictable-interface-names.nix
@@ -0,0 +1,72 @@
+# Use predictable interface names in stage-1 and stage-2.
+# DOC: https://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/
+#
+# Tip: names that can be given using ID_NET_NAME_* envvars
+# can be checked before hand with:
+# udevadm test-builtin net_id /sys/class/net/*
+{ pkgs, lib, config, ... }:
+let
+  udevNetSetupLinkRules = pkgs.writeTextDir "etc/udev/rules.d/79-net-setup-link.rules" ''
+    SUBSYSTEM!="net", GOTO="net_setup_link_end"
+
+    IMPORT{builtin}="path_id"
+
+    ACTION!="add", GOTO="net_setup_link_end"
+
+    # Load net_setup_link to setup the ID_NET_NAME_* envvars
+    IMPORT{builtin}="net_setup_link"
+
+    # Rename eth* using the "path" name policy (eg. enp1s0),
+    # Note that in stage-1 the envvar ID_NET_NAME is not set,
+    # hence not usable as in ''${pkgs.systemd}/lib/udev/rules.d/80-net-setup-link.rules
+    # Because in stage-1 there is no /etc/systemd/network/*.link
+    # nor **/systemd/network/99-default.link
+    # to set NamePolicy= which is responsible to set ID_NET_NAME.
+    # Not sure if ATTR{type}=="1" and KERNEL=="eth*" are equivalent or not.
+    ATTR{type}=="1", KERNEL=="eth*", NAME="$env{ID_NET_NAME_PATH}"
+
+    LABEL="net_setup_link_end"
+  '';
+in
+{
+networking = {
+  # Currently no-op.
+  # false would set boot.kernelParams = [ "net.ifnames=0" ];
+  # to disable NamePolicy= in *.link.
+  usePredictableInterfaceNames = true;
+};
+
+boot.initrd = {
+  extraUdevRulesCommands = ''
+    # The name set here in stage-1 by 79-net-setup-link.rules
+    # will stay in stage-2 (at least until the device is removed/added).
+    cp -v ${udevNetSetupLinkRules}/etc/udev/rules.d/79-net-setup-link.rules $out/
+  '';
+};
+
+services.udev.packages = [
+  # Only useful here in stage-2 if the device is removed and re-added
+  # (eg. the network module is rmmod-ed then modprobe-d).
+  # The stage-1 (or initrd) is only a pivot_root after all,
+  # it does not reload the kernel, hence passing to stage-2
+  # does not trigger ACTION=="add" for the net devices.
+  udevNetSetupLinkRules
+];
+
+/* Useless block, only here for explanations.
+
+# NixOS put this .link only in the root filesystem, not in the initrd
+# hence it's only active in stage-2, not stage-1.
+# And even in stage-2, the 80-net-setup-link.rules has priority.
+# DOC: https://www.freedesktop.org/software/systemd/man/systemd.link.html
+environment.etc."systemd/network/79-net-setup.link".text = ''
+  [Match]
+  OriginalName=*
+
+  [Link]
+  #NamePolicy=keep kernel database onboard slot path
+  NamePolicy=mac
+  MACAddressPolicy=persistent
+'';
+*/
+}
diff --git a/nixos/defaults/readline/inputrc b/defaults/readline/inputrc
similarity index 100%
rename from nixos/defaults/readline/inputrc
rename to defaults/readline/inputrc
diff --git a/nixos/modules.nix b/modules.nix
similarity index 100%
rename from nixos/modules.nix
rename to modules.nix
diff --git a/nixos/modules/services/databases/openldap.nix b/modules/services/databases/openldap.nix
similarity index 100%
rename from nixos/modules/services/databases/openldap.nix
rename to modules/services/databases/openldap.nix
diff --git a/nixos/modules/services/mail/dovecot.nix b/modules/services/mail/dovecot.nix
similarity index 100%
rename from nixos/modules/services/mail/dovecot.nix
rename to modules/services/mail/dovecot.nix
diff --git a/nixos/modules/services/networking/domains.nix b/modules/services/networking/domains.nix
similarity index 100%
rename from nixos/modules/services/networking/domains.nix
rename to modules/services/networking/domains.nix
diff --git a/nixos/defaults.nix b/nixos/defaults.nix
deleted file mode 100644
index c41b2eb..0000000
--- a/nixos/defaults.nix
+++ /dev/null
@@ -1,137 +0,0 @@
-{ pkgs, lib, config, ... }:
-let inherit (lib) types;
-in
-{
-imports = [
-  ./modules.nix
-  ./defaults/predictable-interface-names.nix
-];
-config = {
-  nix = {
-    #binaryCaches = lib.mkForce [];
-    extraOptions = ''
-    '';
-    # Use gc.automatic to keep disk space under control.
-    gc = {
-      automatic = true;
-      dates = "weekly";
-      options = "--delete-older-than 30d";
-    };
-    nixPath = [
-      ("nixpkgs=" + toString pkgs.path)
-    ];
-  };
-
-  nixpkgs = {
-    config = {
-      allowUnfree = false;
-      /*
-      packageOverrides = pkgs: {
-        postfix = pkgs.postfix.override {
-          withLDAP = true;
-        };
-      };
-      */
-    };
-    #overlays = import ../overlays.nix;
-  };
-
-  documentation.nixos = {
-    enable = false; # NOTE: useless on a server, and CPU intensive.
-  };
-
-  # Clean /tmp automatically on boot.
-  boot.cleanTmpDir = true;
-
-  time = {
-    timeZone = "Europe/Paris";
-  };
-
-  i18n = {
-    defaultLocale = "fr_FR.UTF-8";
-  };
-
-  console = {
-    font   = "Lat2-Terminus16";
-    keyMap = "fr";
-  };
-
-  services = {
-    openssh = {
-      enable = true;
-      passwordAuthentication = false;
-      extraConfig = ''
-      '';
-    };
-    journald = {
-      extraConfig = ''
-        SystemMaxUse=50M
-      '';
-    };
-  };
-
-  environment = {
-    #checkConfigurationOptions = false;
-    systemPackages = with pkgs; [
-      binutils
-      #dnsutils
-      dstat
-      htop
-      inetutils
-      iotop
-      lsof
-      mailutils
-      multitail
-      ncdu
-      pv
-      swaplist
-      tcpdump
-      tmux
-      tree
-      vim
-      which
-    ];
-
-    etc."inputrc".text = lib.readFile defaults/readline/inputrc;
-  };
-
-  programs = {
-    bash = {
-      interactiveShellInit = ''
-        bind '"\e[A":history-search-backward'
-        bind '"\e[B":history-search-forward'
-
-        # Ignore duplicate commands, ignore commands starting with a space
-        export HISTCONTROL=erasedups:ignorespace
-        export HISTSIZE=42000
-
-        # Append to the history instead of overwriting (good for multiple connections)
-        shopt -s histappend
-      '';
-      shellAliases = {
-        cl = "clear";
-        l  = "ls -alh";
-        ll = "ls -l";
-        ls = "ls --color=tty";
-        mem = "ps -e -orss=,user=,args= | sort -b -k1,1n";
-
-        s="sudo systemctl";
-        s-u="systemctl --user";
-
-        nixos-clean="sudo nix-collect-garbage -d";
-        nixos-history="sudo nix-env --list-generations --profile /nix/var/nix/profiles/system";
-        nixos-rollback="sudo nixos-rebuild switch --rollback";
-        nixos-update="sudo nix-channel --update";
-        nixos-upgrade="sudo nixos-rebuild switch";
-        nixos-upstream="sudo nix-channel --list";
-      };
-    };
-    gnupg = {
-      agent = {
-        pinentryFlavor = "curses";
-      };
-    };
-    mtr.enable = true;
-  };
-};
-}
diff --git a/nixos/defaults/predictable-interface-names.nix b/nixos/defaults/predictable-interface-names.nix
deleted file mode 100644
index 23e58af..0000000
--- a/nixos/defaults/predictable-interface-names.nix
+++ /dev/null
@@ -1,72 +0,0 @@
-# Use predictable interface names in stage-1 and stage-2.
-# DOC: https://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/
-#
-# Tip: names that can be given using ID_NET_NAME_* envvars
-# can be checked before hand with:
-# udevadm test-builtin net_id /sys/class/net/*
-
-{ pkgs, lib, config, ... }:
-let udevNetSetupLinkRules = pkgs.writeTextDir "etc/udev/rules.d/79-net-setup-link.rules" ''
-    SUBSYSTEM!="net", GOTO="net_setup_link_end"
-
-    IMPORT{builtin}="path_id"
-
-    ACTION!="add", GOTO="net_setup_link_end"
-
-    # Load net_setup_link to setup the ID_NET_NAME_* envvars
-    IMPORT{builtin}="net_setup_link"
-
-    # Rename eth* using the "path" name policy (eg. enp1s0),
-    # Note that in stage-1 the envvar ID_NET_NAME is not set,
-    # hence not usable as in ''${pkgs.systemd}/lib/udev/rules.d/80-net-setup-link.rules
-    # Because in stage-1 there is no /etc/systemd/network/*.link
-    # nor **/systemd/network/99-default.link
-    # to set NamePolicy= which is responsible to set ID_NET_NAME.
-    # Not sure if ATTR{type}=="1" and KERNEL=="eth*" are equivalent or not.
-    ATTR{type}=="1", KERNEL=="eth*", NAME="$env{ID_NET_NAME_PATH}"
-
-    LABEL="net_setup_link_end"
-  '';
-in
-{
-  networking = {
-    # Currently no-op.
-    # false would set boot.kernelParams = [ "net.ifnames=0" ];
-    # to disable NamePolicy= in *.link.
-    usePredictableInterfaceNames = true;
-  };
-
-  boot.initrd = {
-    extraUdevRulesCommands = ''
-      # The name set here in stage-1 by 79-net-setup-link.rules
-      # will stay in stage-2 (at least until the device is removed/added).
-      cp -v ${udevNetSetupLinkRules}/etc/udev/rules.d/79-net-setup-link.rules $out/
-    '';
-  };
-
-  services.udev.packages = [
-    # Only useful here in stage-2 if the device is removed and re-added
-    # (eg. the network module is rmmod-ed then modprobe-d).
-    # The stage-1 (or initrd) is only a pivot_root after all,
-    # it does not reload the kernel, hence passing to stage-2
-    # does not trigger ACTION=="add" for the net devices.
-    udevNetSetupLinkRules
-  ];
-
-  /* Useless block, only here for explanations.
-
-  # NixOS put this .link only in the root filesystem, not in the initrd
-  # hence it's only active in stage-2, not stage-1.
-  # And even in stage-2, the 80-net-setup-link.rules has priority.
-  # DOC: https://www.freedesktop.org/software/systemd/man/systemd.link.html
-  environment.etc."systemd/network/79-net-setup.link".text = ''
-    [Match]
-    OriginalName=*
-
-    [Link]
-    #NamePolicy=keep kernel database onboard slot path
-    NamePolicy=mac
-    MACAddressPolicy=persistent
-  '';
-  */
-}
diff --git a/servers.nix b/servers.nix
index 4c89b2c..e95fa68 100644
--- a/servers.nix
+++ b/servers.nix
@@ -6,9 +6,9 @@
   };
 
   defaults = {
-    #imports = [ nixos/defaults.nix ];
+    #imports = [ ../defaults.nix ];
   };
 
   #friot = import servers/friot.nix;
-  mermet = import servers/mermet.nix;
+  mermet = import servers/mermet/configuration.nix;
 }
diff --git a/servers/mermet.nix b/servers/mermet.nix
deleted file mode 100644
index 1018f9b..0000000
--- a/servers/mermet.nix
+++ /dev/null
@@ -1,103 +0,0 @@
-# This is the root configuration of the target machine.
-# Usable by nixos-install and used by nixops.
-# It is NOT copied nor usable on the target machine,
-# only the resulting closure is copied to the target machine.
-{ pkgs, lib, config, options, ... }:
-let
-  inherit (builtins) readFile;
-  inherit (builtins.extraBuiltins) pass pass-chomp;
-in
-{
-  # This value determines the NixOS release with which your system is to be
-  # compatible, in order to avoid breaking some software such as database servers.
-  # You should change this only after NixOS release notes say you should.
-  system.stateVersion = "19.09"; # Did you read the comment?
-
-  nix = {
-    trustedUsers = [ "julm" ];
-  };
-
-  nixpkgs.overlays = import ../overlays.nix;
-
-  imports =
-    [ ../nixos/defaults.nix
-      mermet/unbound.nix
-      #mermet/nsd.nix
-      mermet/knot.nix
-      mermet/openldap.nix
-      mermet/gitolite.nix
-      mermet/nginx.nix
-      mermet/postfix.nix
-      mermet/dovecot.nix
-      mermet/redis.nix
-      mermet/rspamd.nix
-    ];
-
-  networking = rec {
-    hostName   = "mermet";
-    domainBase = "sourcephile";
-    domain     = "${domainBase}.fr";
-  };
-
-  /*
-  environment.etc."sudo.conf".text = ''
-    Debug sudo /var/log/sudo_debug.log all@debug
-    Debug sudoers.so /var/log/sudo_debug.log all@debug
-  '';
-  */
-
-  users = {
-    mutableUsers = false;
-    users = {
-      root = {
-        openssh.authorizedKeys.keys = [
-          (readFile ../../sec/ssh/julm.pub)
-          (readFile ../../sec/ssh/julm-mob.pub)
-        ];
-      };
-      julm = {
-        uid = 1000;
-        hashedPassword = pass-chomp "servers/mermet/login/julm/hashedPassword";
-        isNormalUser = true;
-        openssh.authorizedKeys.keys = [
-          (readFile ../../sec/ssh/julm.pub)
-          (readFile ../../sec/ssh/julm-mob.pub)
-          (readFile ../../sec/ssh/julm-mermet.pub)
-        ];
-      };
-    };
-    groups = {
-      wheel = {
-        members = [ "julm" ];
-      };
-      julm = {
-        members = [ "julm" ];
-        gid = 1000;
-      };
-    };
-  };
-
-  programs = {
-    mosh.enable = true;
-  };
-
-  systemd.coredump.enable = true;
-
-  environment = {
-    enableDebugInfo = true;
-    systemPackages = with pkgs; [
-      cryptsetup
-      direnv
-      file
-      fio
-      gdb
-      git
-      gptfdisk
-      #hey
-      lm_sensors
-      rsync
-      smartctl-tbw
-      socat
-    ];
-  };
-}
diff --git a/servers/mermet/configuration.nix b/servers/mermet/configuration.nix
new file mode 100644
index 0000000..c5af83f
--- /dev/null
+++ b/servers/mermet/configuration.nix
@@ -0,0 +1,98 @@
+# This is the root configuration of the target machine.
+# Usable by nixos-install and used by nixops.
+# It is NOT copied nor usable on the target machine,
+# only the resulting closure is copied to the target machine.
+{ pkgs, lib, config, options, ... }:
+let
+  inherit (builtins) readFile;
+  inherit (builtins.extraBuiltins) pass pass-chomp;
+in
+{
+# This value determines the NixOS release with which your system is to be
+# compatible, in order to avoid breaking some software such as database servers.
+# You should change this only after NixOS release notes say you should.
+system.stateVersion = "19.09"; # Did you read the comment?
+
+nix = {
+  trustedUsers = [ "julm" ];
+};
+
+imports = [
+  ../../defaults.nix
+  ../../base/unbound.nix
+  #./nsd.nix
+  ./knot.nix
+  ./openldap.nix
+  ./gitolite.nix
+  ./nginx.nix
+  ./postfix.nix
+  ./dovecot.nix
+  ./redis.nix
+  ./rspamd.nix
+];
+
+networking = rec {
+  hostName   = "mermet";
+  domainBase = "sourcephile";
+  domain     = "${domainBase}.fr";
+};
+
+/*
+environment.etc."sudo.conf".text = ''
+  Debug sudo /var/log/sudo_debug.log all@debug
+  Debug sudoers.so /var/log/sudo_debug.log all@debug
+'';
+*/
+
+users = {
+  mutableUsers = false;
+  users = {
+    root = {
+      openssh.authorizedKeys.keys = [
+        (readFile ../../../sec/ssh/julm.pub)
+        (readFile ../../../sec/ssh/julm-mob.pub)
+      ];
+    };
+    julm = {
+      uid = 1000;
+      hashedPassword = pass-chomp "servers/mermet/login/julm/hashedPassword";
+      isNormalUser = true;
+      openssh.authorizedKeys.keys = [
+        (readFile ../../../sec/ssh/julm.pub)
+        (readFile ../../../sec/ssh/julm-mob.pub)
+        (readFile ../../../sec/ssh/julm-mermet.pub)
+      ];
+    };
+  };
+  groups = {
+    wheel = {
+      members = [ "julm" ];
+    };
+    julm = {
+      members = [ "julm" ];
+      gid = 1000;
+    };
+  };
+};
+
+systemd.coredump.enable = true;
+
+environment = {
+  enableDebugInfo = true;
+  systemPackages = with pkgs; [
+    cryptsetup
+    direnv
+    file
+    fio
+    gdb
+    git
+    gptfdisk
+    #hey
+    home-manager
+    lm_sensors
+    rsync
+    smartctl-tbw
+    socat
+  ];
+};
+}
diff --git a/config/gitolite b/servers/mermet/gitolite
similarity index 100%
rename from config/gitolite
rename to servers/mermet/gitolite
diff --git a/servers/mermet/keys.nix b/servers/mermet/keys.nix
index 3686d0d..2fedeec 100644
--- a/servers/mermet/keys.nix
+++ b/servers/mermet/keys.nix
@@ -4,20 +4,22 @@ let
   inherit (builtins.extraBuiltins) pass;
 in
 {
-  deployment.keys = {
-    "sourcephile.fr.key.pem" = {
-      text        = pass "x509/sourcephile.fr/key.pem";
-      user        = "root";
-      group       = "root";
-      destDir     = "/run/keys/";
-      permissions = "0400"; # WARNING: not enforced when deployment.storeKeysOnMachine = true
-    };
-    "autogeree.net.key.pem" = {
-      text        = pass "x509/autogeree.net/key.pem";
-      user        = "root";
-      group       = "root";
-      destDir     = "/run/keys/";
-      permissions = "0400"; # WARNING: not enforced when deployment.storeKeysOnMachine = true
-    };
+deployment.keys = {
+  /*
+  "sourcephile.fr.key.pem" = {
+    text        = pass "x509/sourcephile.fr/key.pem";
+    user        = "root";
+    group       = "root";
+    destDir     = "/run/keys/";
+    permissions = "0400"; # WARNING: not enforced when deployment.storeKeysOnMachine = true
   };
+  "autogeree.net.key.pem" = {
+    text        = pass "x509/autogeree.net/key.pem";
+    user        = "root";
+    group       = "root";
+    destDir     = "/run/keys/";
+    permissions = "0400"; # WARNING: not enforced when deployment.storeKeysOnMachine = true
+  };
+  */
+};
 }
diff --git a/servers/mermet/production.nix b/servers/mermet/production.nix
index 03f4343..c7ed66f 100644
--- a/servers/mermet/production.nix
+++ b/servers/mermet/production.nix
@@ -4,13 +4,13 @@
 , ... }:
 let inherit (config) networking; in
 {
-imports =
-  [ ./keys.nix
-    production/apu2e4.nix
-    production/lesptts.nix
-    production/zfs.nix
-    production/shorewall.nix
-  ];
+imports = [
+  ./keys.nix
+  production/apu2e4.nix
+  production/lesptts.nix
+  production/zfs.nix
+  production/shorewall.nix
+];
 deployment = {
   targetEnv = "none";
   targetHost = (builtins.elemAt networking.interfaces.enp1s0.ipv4.addresses 0).address;
diff --git a/servers/mermet/production/apu2e4.nix b/servers/mermet/production/apu2e4.nix
index edb04a9..d317b15 100644
--- a/servers/mermet/production/apu2e4.nix
+++ b/servers/mermet/production/apu2e4.nix
@@ -1,97 +1,30 @@
 { pkgs, lib, config, ... }:
 {
-  hardware.cpu.amd.updateMicrocode = true;
-  nix = {
-    # Too CPU hungry for the APU2, for too little Mio saved
-    autoOptimiseStore = false;
-    maxJobs = 4;
-  };
-  powerManagement.cpuFreqGovernor = lib.mkDefault "ondemand";
+imports = [
+  ../../../base/apu2e4.nix
+];
 
-  boot.kernel = {
-    sysctl = {
-      "vm.swappiness" = 10;
-      "vm.vfs_cache_pressure" = 50;
-    };
-  };
+boot.loader.grub.devices = [
+  "/dev/disk/by-id/ata-Samsung_SSD_840_EVO_250GB_S1DBNSAF340110R"
+];
 
-  boot.loader = {
-    grub = {
-      enable = true;
-      version = 2;
-      copyKernels = true;
-      # efiSupport = true;
-      devices = [
-        "/dev/disk/by-id/ata-Samsung_SSD_840_EVO_250GB_S1DBNSAF340110R"
-      ];
-      /*
-      mirroredBoots = [
-        { devices = [ "${disk_id}" ];
-          path    = "/boot${bootnum}";
-        }
-      ];
-      */
-    };
-    /*
-    efi = {
-      canTouchEfiVariables = true;
-      efiSysMountPoint = "/boot/efi";
-      efiInstallAsRemovable = false;
-    };
-    */
+fileSystems."/boot" =
+  { device = "/dev/disk/by-uuid/dc3c5387-17d2-43b3-bfa2-bf73afacca07";
+    fsType = "ext2";
   };
 
-  boot.initrd = {
-    availableKernelModules = [
-      "ahci"
-      "ehci_pci"
-      "sd_mod"
-      "uas"
-      # Ethernet driver
-      "igb"
-      # Made the AES modules available at initrd,
-      # to speedup the deciphering of the root.
-      "aes_x86_64"
-      "aesni_intel"
-      "cryptd"
-    ];
-    kernelModules = [ ];
-
+fileSystems."/boot/efi" =
+  { device = "/dev/disk/by-uuid/62E6-E65F";
+    fsType = "vfat";
   };
-  boot.kernelModules = [ ];
-  boot.extraModulePackages = [ ];
-  boot.kernelParams = [
-    "gfxpayload=text"
-    #"console=tty0"
-    "console=ttyS0,115200n8"
-    # Use arc_summary to print stats
-    "zfs.zfs_arc_max=${toString (500 * 1024 * 1024)}" # bytes
-  ];
-
-  fileSystems."/boot" =
-    { device = "/dev/disk/by-uuid/dc3c5387-17d2-43b3-bfa2-bf73afacca07";
-      fsType = "ext2";
-    };
 
-  fileSystems."/boot/efi" =
-    { device = "/dev/disk/by-uuid/62E6-E65F";
-      fsType = "vfat";
-    };
-
-  swapDevices =
-    [ { device = "/dev/disk/by-partuuid/6b1eaa35-776b-4e60-b21e-7bcee535dd8b";
-        randomEncryption = {
-          enable = true;
-          cipher = "aes-xts-plain64";
-          source = "/dev/urandom";
-        };
-      }
-    ];
-
-  environment = {
-    systemPackages = with pkgs; [
-      pciutils
-      flashrom
-    ];
-  };
+swapDevices =
+  [ { device = "/dev/disk/by-partuuid/6b1eaa35-776b-4e60-b21e-7bcee535dd8b";
+      randomEncryption = {
+        enable = true;
+        cipher = "aes-xts-plain64";
+        source = "/dev/urandom";
+      };
+    }
+  ];
 }
diff --git a/servers/mermet/production/lesptts.nix b/servers/mermet/production/lesptts.nix
index aac5f86..9c41aa0 100644
--- a/servers/mermet/production/lesptts.nix
+++ b/servers/mermet/production/lesptts.nix
@@ -12,163 +12,151 @@ let
   lanIPv4Gateway = "192.168.1.1";
 in
 {
-  boot.initrd.network = {
-    enable = true;
-    ssh = {
-       enable = true;
-       # To prevent ssh from freaking out because a different host key is used,
-       # a different port for dropbear is useful
-       # (assuming the same host has also a normal sshd running)
-       port = 2222;
-       # The initrd needs a cleartext key and is built on the host,
-       # hence this key needs to be cleartext on the host.
-       # Moreover building the initrd means that the key will go into the Nix store,
-       # of the host, then of the target on deployment,
-       # because GRUB does not support boot.initrd.secrets
-       # (only systemd-boot does, but sticking to GRUB is more reassuring).
-       # In any case, the initrd is sent to a non-encrypted /boot partition
-       # to be able to start unattended, hence the key will be available
-       # to anyone who has physically access to the disk where /boot is.
-       # NOTE: dropbearkey -t ecdsa -f /tmp/dropbear-ecdsa.key
-       #hostECDSAKey = "../../../sec/tmp/dropbear-ecdsa.key";
-       hostECDSAKey = pass-to-file "servers/mermet/dropbear/ecdsa.key"
-                                   (../../../sec + "/tmp/dropbear-ecdsa.key");
+boot.initrd.network = {
+  enable = true;
+  ssh = {
+     enable = true;
+     # To prevent ssh from freaking out because a different host key is used,
+     # a different port for dropbear is useful
+     # (assuming the same host has also a normal sshd running)
+     port = 2222;
+     # The initrd needs a cleartext key and is built on the host,
+     # hence this key needs to be cleartext on the host.
+     # Moreover building the initrd means that the key will go into the Nix store,
+     # of the host, then of the target on deployment,
+     # because GRUB does not support boot.initrd.secrets
+     # (only systemd-boot does, but sticking to GRUB is more reassuring).
+     # In any case, the initrd is sent to a non-encrypted /boot partition
+     # to be able to start unattended, hence the key will be available
+     # to anyone who has physically access to the disk where /boot is.
+     # NOTE: dropbearkey -t ecdsa -f /tmp/dropbear-ecdsa.key
+     #hostECDSAKey = "../../../sec/tmp/dropbear-ecdsa.key";
+     hostECDSAKey = pass-to-file "servers/mermet/dropbear/ecdsa.key"
+                                 (../../../sec + "/tmp/dropbear-ecdsa.key");
 
-       # WARNING: dropbear does not support (and will ignore) ssh-ed25519 keys
-       authorizedKeys = users.users.root.openssh.authorizedKeys.keys;
-    };
-    # This will automatically load the zfs password prompt on login
-    # and kill the other prompt so boot can continue
-    # The pkill zfs kills the zfs load-key from the console
-    # allowing the boot to continue.
-    postCommands = ''
-      echo >>/root/.profile "zfs load-key -a && pkill zfs"
-    '';
+     # WARNING: dropbear does not support (and will ignore) ssh-ed25519 keys
+     authorizedKeys = users.users.root.openssh.authorizedKeys.keys;
   };
+  # This will automatically load the zfs password prompt on login
+  # and kill the other prompt so boot can continue
+  # The pkill zfs kills the zfs load-key from the console
+  # allowing the boot to continue.
+  postCommands = ''
+    echo >>/root/.profile "zfs load-key -a && pkill zfs"
+  '';
+};
 
-  /* WARNING: using ipconfig (the ip= kernel parameter) IS NOT RELIABLE:
-     a 91.216.110.35/32 becomes a 91.216.110.35/8
-  boot.kernelParams = map
-    (ip: "ip=${ip.clientIP}:${ip.serverIP}:${ip.gatewayIP}:${ip.netmask}:${ip.hostname}:${ip.device}:${ip.autoconf}")
-    [ { clientIP = netIPv4; serverIP = "";
-        gatewayIP = networking.defaultGateway.address;
-        netmask = "255.255.255.255";
-        hostname = ""; device = networking.defaultGateway.interface;
-        autoconf = "off";
-      }
-      { clientIP = lanIPv4; serverIP = "";
-        gatewayIP = "";
-        netmask = "255.255.255.0";
-        hostname = ""; device = "enp2s0";
-        autoconf = "off";
-      }
-    ];
-  */
-  /* DIY network config, but a right one */
-  boot.initrd.preLVMCommands = ''
-    set -x
+/* WARNING: using ipconfig (the ip= kernel parameter) IS NOT RELIABLE:
+   a 91.216.110.35/32 becomes a 91.216.110.35/8
+boot.kernelParams = map
+  (ip: "ip=${ip.clientIP}:${ip.serverIP}:${ip.gatewayIP}:${ip.netmask}:${ip.hostname}:${ip.device}:${ip.autoconf}")
+  [ { clientIP = netIPv4; serverIP = "";
+      gatewayIP = networking.defaultGateway.address;
+      netmask = "255.255.255.255";
+      hostname = ""; device = networking.defaultGateway.interface;
+      autoconf = "off";
+    }
+    { clientIP = lanIPv4; serverIP = "";
+      gatewayIP = "";
+      netmask = "255.255.255.0";
+      hostname = ""; device = "enp2s0";
+      autoconf = "off";
+    }
+  ];
+*/
+/* DIY network config, but a right one */
+boot.initrd.preLVMCommands = ''
+  set -x
 
-    # IPv4 net
-    ip link set enp1s0 up
-    ip address add ${netIPv4}/32 dev enp1s0
-    ip route add ${netIPv4Gateway} dev enp1s0
-    ip route add default via ${netIPv4Gateway} dev enp1s0
+  # IPv4 net
+  ip link set enp1s0 up
+  ip address add ${netIPv4}/32 dev enp1s0
+  ip route add ${netIPv4Gateway} dev enp1s0
+  ip route add default via ${netIPv4Gateway} dev enp1s0
 
-    # IPv4 lan
-    ip link set enp2s0 up
-    ip address add ${lanIPv4}/32 dev enp2s0
-    ip route add ${lanIPv4Gateway} dev enp2s0
-    ip route add ${lanNet} dev enp2s0 src ${lanIPv4} proto kernel
-      # NOTE: ${lanIPv4}/24 would not work with initrd's ip, hence ${lanNet}
+  # IPv4 lan
+  ip link set enp2s0 up
+  ip address add ${lanIPv4}/32 dev enp2s0
+  ip route add ${lanIPv4Gateway} dev enp2s0
+  ip route add ${lanNet} dev enp2s0 src ${lanIPv4} proto kernel
+    # NOTE: ${lanIPv4}/24 would not work with initrd's ip, hence ${lanNet}
 
-    # IPv6 net
-    #ip -6 address add ''${netIPv6} dev enp1s0
-    #ip -6 route add ''${netIPv6Gateway} dev enp1s0
-    #ip -6 route add default via ''${netIPv6Gateway} dev enp1s0
+  # IPv6 net
+  #ip -6 address add ''${netIPv6} dev enp1s0
+  #ip -6 route add ''${netIPv6Gateway} dev enp1s0
+  #ip -6 route add default via ''${netIPv6Gateway} dev enp1s0
 
-    ip -4 address
-    ip -4 route
-    #ip -6 address
-    #ip -6 route
+  ip -4 address
+  ip -4 route
+  #ip -6 address
+  #ip -6 route
 
-    set +x
+  set +x
 
-    # Since boot.initrd.network's preLVMCommands won't set hasNetwork=1
-    # we have to run the postCommands ourselves.
-    ${config.boot.initrd.network.postCommands}
-  '';
-  # Workaround https://github.com/NixOS/nixpkgs/issues/56822
-  #boot.initrd.kernelModules = [ "ipv6" ];
+  # Since boot.initrd.network's preLVMCommands won't set hasNetwork=1
+  # we have to run the postCommands ourselves.
+  ${config.boot.initrd.network.postCommands}
+'';
 
-  # This is a remote headless server: always try to start all the units (default.target)
-  # systemd's emergency shell does not try to start sshd, hence is no help.
-  # https://wiki.archlinux.org/index.php/systemd#Disable_emergency_mode_on_remote_machine
-  systemd.enableEmergencyMode = false;
+# Workaround https://github.com/NixOS/nixpkgs/issues/56822
+#boot.initrd.kernelModules = [ "ipv6" ];
 
-  # This is a remote headless server: always reboot on a kernel panic,
-  # to not have to physically go power cycle the apu2e4.
-  # Which happens if the wrong ZFS password is used
-  # but the boot is manually forced to continue.
-  # Using kernelParams instead of kernel.sysctl
-  # sets this up as soon as the initrd.
-  boot.kernelParams = [ "panic=10" ];
+# Useless without an out-of-band access, and unsecure
+# (though / may still be encrypted at this point).
+# boot.kernelParams = [ "boot.shell_on_fail" ];
 
-  # Useless without an out-of-band access, and unsecure
-  # (though / may still be encrypted at this point).
-  # boot.kernelParams = [ "boot.shell_on_fail" ];
+# Disable IPv6 entirely until it's available
+boot.kernel.sysctl = {
+  "net.ipv6.conf.enp1s0.disable_ipv6" = 1;
+};
 
-  # Disable IPv6 entirely until it's available
-  boot.kernel.sysctl = {
-    "net.ipv6.conf.enp1s0.disable_ipv6" = 1;
+#services.nsd.interfaces = [ netIPv4 ];
+services.knot.extraConfig = lib.mkBefore ''
+  server:
+    listen: ${netIPv4}@53
+    #listen: ::@53
+'';
+networking = {
+  useDHCP = false;
+  defaultGateway = {
+    address = netIPv4Gateway;
+    interface = "enp1s0";
   };
-
-  #services.nsd.interfaces = [ netIPv4 ];
-  services.knot.extraConfig = lib.mkBefore ''
-    server:
-      listen: ${netIPv4}@53
-      #listen: ::@53
-  '';
-  networking = {
+  /*
+  defaultGateway6 = {
+    address = netIPv6Gateway;
+    interface = "enp1s0";
+  };
+  */
+  #nameservers = [ ];
+  interfaces.enp1s0 = {
     useDHCP = false;
-    defaultGateway = {
-      address = netIPv4Gateway;
-      interface = "enp1s0";
-    };
+    ipv4.addresses = [ { address = netIPv4; prefixLength = 32; } ];
+    ipv4.routes    = [ { address = networking.defaultGateway.address; prefixLength = 32; } ];
+
     /*
-    defaultGateway6 = {
-      address = netIPv6Gateway;
-      interface = "enp1s0";
-    };
+    ipv6.addresses = [ { address = netIPv6; prefixLength = 64; }
+                       { address = "fe80::1"; prefixLength = 10; }
+                     ];
+    ipv6.routes    = [ { address = networking.defaultGateway6.address; prefixLength = 64; } ];
     */
-    #nameservers = [ ];
-    interfaces.enp1s0 = {
-      useDHCP = false;
-      ipv4.addresses = [ { address = netIPv4; prefixLength = 32; } ];
-      ipv4.routes    = [ { address = networking.defaultGateway.address; prefixLength = 32; } ];
-
-      /*
-      ipv6.addresses = [ { address = netIPv6; prefixLength = 64; }
-                         { address = "fe80::1"; prefixLength = 10; }
+  };
+  interfaces.enp2s0 = {
+    useDHCP = false;
+    ipv4.addresses = [ { address = lanIPv4; prefixLength = 24; } ];
+    /*
+    # FIXME: remove this /1 hack when the machine will be racked at PTT
+    ipv4.routes    = [ { address = "0.0.0.0";   prefixLength = 1; via = "192.168.1.1"; }
+                       { address = "128.0.0.0"; prefixLength = 1; via = "192.168.1.1"; }
                        ];
-      ipv6.routes    = [ { address = networking.defaultGateway6.address; prefixLength = 64; } ];
-      */
-    };
-    interfaces.enp2s0 = {
-      useDHCP = false;
-      ipv4.addresses = [ { address = lanIPv4; prefixLength = 24; } ];
-      /*
-      # FIXME: remove this /1 hack when the machine will be racked at PTT
-      ipv4.routes    = [ { address = "0.0.0.0";   prefixLength = 1; via = "192.168.1.1"; }
-                         { address = "128.0.0.0"; prefixLength = 1; via = "192.168.1.1"; }
-                         ];
-      */
-      /*
-      ipv6.addresses = [ { address = "fe80::1"; prefixLength = 10; } ];
-      ipv6.routes    = [ ];
-      */
-    };
-    interfaces.enp3s0 = {
-      useDHCP = false;
-    };
+    */
+    /*
+    ipv6.addresses = [ { address = "fe80::1"; prefixLength = 10; } ];
+    ipv6.routes    = [ ];
+    */
+  };
+  interfaces.enp3s0 = {
+    useDHCP = false;
   };
+};
 }
diff --git a/servers/mermet/production/zfs.nix b/servers/mermet/production/zfs.nix
index 64e6ad8..c4a7665 100644
--- a/servers/mermet/production/zfs.nix
+++ b/servers/mermet/production/zfs.nix
@@ -1,104 +1,74 @@
 { pkgs, lib, config, ... }:
-
 {
-  imports = [];
-
-  boot.supportedFilesystems = [ "zfs" ];
-
-  # The 32-bit host id of the machine, formatted as 8 hexadecimal characters.
-  # You should try to make this id unique among your machines.
-  # Manually generated with : head -c4 /dev/urandom | od -A none -t x4 | cut -d ' ' -f 2
-  networking.hostId = "69c40b03";
-
-  # none is the recommended elevator with ZFS (which has its own I/O scheduler)
-  # and/or for SSD, whereas HDD could use mq-deadline.
-  services.udev.extraRules = ''
-    # set none scheduler for non-rotating disks
-    ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="none"
-  '';
-
-  # Ensure extra safeguards are active that zfs uses to protect zfs pools.
-  boot.zfs.forceImportAll  = false;
-  boot.zfs.forceImportRoot = false;
-
-  boot.zfs.enableUnstable = true;
-  boot.zfs.requestEncryptionCredentials = true;
-
-  # Enables periodic scrubbing of ZFS pools.
-  services.zfs.autoScrub.enable = true;
-
-  environment = {
-    systemPackages = [
-      pkgs.mbuffer
-      pkgs.zfs
-    ];
+imports = [
+  ../../../base/zfs.nix
+];
+
+/*
+# Enable the (OpenSolaris-compatible) ZFS auto-snapshotting service.
+services.zfs.autoSnapshot = {
+  enable   = true;
+  frequent = ;
+  hourly   = ;
+  daily    = ;
+  weekly   = ;
+  monthly  = ;
+};
+*/
+
+/*
+fileSystems."/boot" =
+  { device = "bpool/boot";
+    fsType = "zfs";
   };
-
-  /*
-  # Enable the (OpenSolaris-compatible) ZFS auto-snapshotting service.
-  services.zfs.autoSnapshot = {
-    enable   = true;
-    frequent = ;
-    hourly   = ;
-    daily    = ;
-    weekly   = ;
-    monthly  = ;
+*/
+fileSystems."/" =
+  { device = "rpool/root";
+    fsType = "zfs";
   };
-  */
-
-  /*
-  fileSystems."/boot" =
-    { device = "bpool/boot";
-      fsType = "zfs";
-    };
-  */
-  fileSystems."/" =
-    { device = "rpool/root";
-      fsType = "zfs";
-    };
 
-  fileSystems."/home" =
-    { device = "rpool/home";
-      fsType = "zfs";
-    };
+fileSystems."/home" =
+  { device = "rpool/home";
+    fsType = "zfs";
+  };
 
-  fileSystems."/nix" =
-    { device = "rpool/nix";
-      fsType = "zfs";
-    };
+fileSystems."/nix" =
+  { device = "rpool/nix";
+    fsType = "zfs";
+  };
 
-  fileSystems."/var" =
-    { device = "rpool/var";
-      fsType = "zfs";
-    };
+fileSystems."/var" =
+  { device = "rpool/var";
+    fsType = "zfs";
+  };
 
-  fileSystems."/var/cache" =
-    { device = "rpool/var/cache";
-      fsType = "zfs";
-    };
+fileSystems."/var/cache" =
+  { device = "rpool/var/cache";
+    fsType = "zfs";
+  };
 
-  fileSystems."/var/log" =
-    { device = "rpool/var/log";
-      fsType = "zfs";
-    };
+fileSystems."/var/log" =
+  { device = "rpool/var/log";
+    fsType = "zfs";
+  };
 
-  fileSystems."/var/lib/dovecot" =
-    { device = "rpool/var/mail";
-      fsType = "zfs";
-    };
+fileSystems."/var/lib/dovecot" =
+  { device = "rpool/var/mail";
+    fsType = "zfs";
+  };
 
-  fileSystems."/var/lib/redis" =
-    { device = "rpool/var/redis";
-      fsType = "zfs";
-    };
+fileSystems."/var/lib/redis" =
+  { device = "rpool/var/redis";
+    fsType = "zfs";
+  };
 
-  fileSystems."/var/tmp" =
-    { device = "rpool/var/tmp";
-      fsType = "zfs";
-    };
+fileSystems."/var/tmp" =
+  { device = "rpool/var/tmp";
+    fsType = "zfs";
+  };
 
-  fileSystems."/var/www" =
-    { device = "rpool/var/www";
-      fsType = "zfs";
-    };
+fileSystems."/var/www" =
+  { device = "rpool/var/www";
+    fsType = "zfs";
+  };
 }
diff --git a/servers/mermet/unbound.nix b/servers/mermet/unbound.nix
deleted file mode 100644
index 6ce9a27..0000000
--- a/servers/mermet/unbound.nix
+++ /dev/null
@@ -1,147 +0,0 @@
-{ pkgs, lib, config, ... }:
-let
-  inherit (config) users;
-  stateDir = "/var/lib/unbound";
-in
-{
-  networking.resolvconf.useLocalResolver = true;
-  services.unbound = {
-    enable = true;
-    # DOC: https://calomel.org/unbound_dns.html
-    extraConfig = ''
-      remote-control:
-        control-enable: yes
-        control-interface: /run/unbound/unbound.socket
-
-      server:
-        log-queries: no
-        verbosity: 1
-
-        port: 53
-
-        # The file which contains the listing of primary root DNS servers.
-        # To be updated once every six months.
-        root-hints: /var/lib/unbound/named.root
-
-        # Do no answer id.server and hostname.bind queries.
-        hide-identity: yes
-        # Do not answer version.server and version.bind queries.
-        hide-version: yes
-
-        # Will trust glue only if it is within the servers authority.
-        # Harden against out of zone rrsets, to avoid spoofing attempts.
-        # Hardening queries multiple name servers for the same data to make
-        # spoofing significantly harder and does not mandate dnssec.
-        harden-glue: yes
-
-        # Require DNSSEC data for trust-anchored zones, if such data is absent, the
-        # zone becomes  bogus.  Harden against receiving dnssec-stripped data. If you
-        # turn it off, failing to validate dnskey data for a trustanchor will trigger
-        # insecure mode for that zone (like without a trustanchor).  Default on,
-        # which insists on dnssec data for trust-anchored zones.
-        harden-dnssec-stripped: yes
-
-        # Use 0x20-encoded random bits in the query to foil spoof attempts.
-        # http://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00
-        #
-        # When Unbound sends a query to a remote server it sends the hostname
-        # string in random upper and lower characters. The remote server must
-        # resolve the hostname as if all the characters were lower case. The remote
-        # server must then send the query back to Unbound in the same random upper
-        # and lower characters that Unbound sent. If the characters of the hostname
-        # in the response are in the same format as the query then the dns-0x20
-        # check is satisfied.
-        # Attackers hoping to poison a Unbound DNS cache must therefore guess the
-        # mixed-case encoding of the query and the timing of the return dns answer
-        # in addition to all other fields required in a DNS poisoning attack.
-        # dns-0x20 increases the difficulty of the attack significantly.
-        #
-        # It may result in maybe 0.4% of domains getting no answers
-        # due to no support on the authoritative server side
-        use-caps-for-id: yes
-
-        #cache-min-ttl: 3600
-        cache-max-ttl: 86400
-
-        # Perform prefetching of close to expired message cache entries.  If a client
-        # requests the dns lookup and the TTL of the cached hostname is going to
-        # expire in less than 10% of its TTL, unbound will (1st) return the IP of the
-        # host to the client and (2nd) pre-fetch the DNS request from the remote DNS server.
-        # This method has been shown to increase the amount of cached hits by
-        # local clients by 10% on average.
-        prefetch: yes
-
-        # Number of threads to create. 1 disables threading.
-        # This should equal the number of CPU cores in the machine.
-        num-threads: ${toString config.nix.maxJobs}
-
-        # The number of slabs to use for cache and must be a power of 2 times the
-        # number of num-threads set above. more slabs reduce lock contention,
-        # but fragment memory usage.
-        msg-cache-slabs: 8
-        rrset-cache-slabs: 8
-        infra-cache-slabs: 8
-        key-cache-slabs: 8
-
-        # Increase the memory size of the cache. Use roughly twice as much rrset cache
-        # memory as you use msg cache memory. Due to malloc overhead, the total memory
-        # usage is likely to rise to double (or 2.5x) the total cache memory.
-        rrset-cache-size: 32m
-        msg-cache-size: 16m
-
-        # buffer size for UDP port 53 incoming (SO_RCVBUF socket option). This sets
-        # the kernel buffer larger so that no messages are lost in spikes in the traffic.
-        so-rcvbuf: 1m
-
-        # Enforce privacy of these addresses. Strips them away from answers.
-        # It may cause DNSSEC validation to additionally mark it as bogus.
-        # Protects against 'DNS Rebinding' (uses browser as network proxy).
-        #  Only 'private-domain' and 'local-data' names are allowed
-        # to have these private addresses. No default.
-        private-address: 192.168.0.0/16
-        private-address: 172.16.0.0/12
-        private-address: 10.0.0.0/8
-
-        # Allow the domain (and its subdomains) to contain private addresses.
-        # local-data statements are allowed to contain private addresses too.
-        #private-domain: "home.lan"
-
-        # If nonzero, unwanted replies are not only reported in statistics, but also
-        # a running total is kept per thread. If it reaches the threshold, a warning
-        # is printed and a defensive action is taken, the cache is cleared to flush
-        # potential poison out of it.  A suggested value is 10000000, the default is
-        # 0 (turned off). calomel.org thinks 10K is a good value.
-        unwanted-reply-threshold: 10000
-
-        # IMPORTANT FOR TESTING: If you are testing and setup NSD or BIND on
-        # localhost you will want to allow the resolver to send queries to localhost.
-        # Make sure to set do-not-query-localhost: yes.
-        do-not-query-localhost: yes
-
-        # Should additional section of secure message also be kept clean of unsecure
-        # data. Useful to shield the users of this validator from potential bogus
-        # data in the additional section. All unsigned data in the additional section
-        # is removed from secure messages.
-        val-clean-additional: yes
-    '';
-  };
-  systemd.services.unbound = {
-    serviceConfig = {
-      RuntimeDirectory = "unbound";
-      RuntimeDirectoryMode = "0700";
-      # FIXME: upstream service shouldn't overwrite ExecStopPost
-      # so that postStop can be used.
-      ExecStopPost = lib.mkForce (pkgs.writeShellScript "unit-script-unbound-post-stop" ''
-        ${pkgs.utillinux}/bin/umount ${stateDir}/dev/random
-        ${pkgs.utillinux}/bin/umount ${stateDir}/run/unbound
-      '');
-    };
-    preStart = ''
-      install -m 444 -o unbound -g nogroup \
-       ${../../config/dns/named.root} \
-       /var/lib/unbound/named.root
-      mkdir -p ${stateDir}/run/unbound
-      ${pkgs.utillinux}/bin/mount --bind -n /run/unbound ${stateDir}/run/unbound
-    '';
-  };
-}
-- 
2.49.0