about summary refs log tree commit diff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/nixfiles/acme.nix32
-rw-r--r--modules/nixfiles/alacritty.nix119
-rw-r--r--modules/nixfiles/alertmanager.nix81
-rw-r--r--modules/nixfiles/aria2.nix48
-rw-r--r--modules/nixfiles/aspell.nix23
-rw-r--r--modules/nixfiles/bat.nix39
-rw-r--r--modules/nixfiles/beets.nix73
-rw-r--r--modules/nixfiles/bluetooth.nix34
-rw-r--r--modules/nixfiles/broot.nix18
-rw-r--r--modules/nixfiles/chromium.nix42
-rw-r--r--modules/nixfiles/common/console.nix6
-rw-r--r--modules/nixfiles/common/default.nix19
-rw-r--r--modules/nixfiles/common/documentation.nix48
-rw-r--r--modules/nixfiles/common/environment.nix7
-rw-r--r--modules/nixfiles/common/home-manager.nix23
-rw-r--r--modules/nixfiles/common/kernel.nix32
-rw-r--r--modules/nixfiles/common/locale.nix18
-rw-r--r--modules/nixfiles/common/networking.nix66
-rw-r--r--modules/nixfiles/common/nix.nix173
-rw-r--r--modules/nixfiles/common/secrets.nix47
-rw-r--r--modules/nixfiles/common/security.nix19
-rw-r--r--modules/nixfiles/common/services.nix9
-rw-r--r--modules/nixfiles/common/shell/default.nix125
-rw-r--r--modules/nixfiles/common/shell/functions.bash73
-rw-r--r--modules/nixfiles/common/systemd.nix22
-rw-r--r--modules/nixfiles/common/tmp.nix18
-rw-r--r--modules/nixfiles/common/users.nix21
-rw-r--r--modules/nixfiles/common/xdg.nix86
-rw-r--r--modules/nixfiles/curl.nix38
-rw-r--r--modules/nixfiles/default.nix80
-rw-r--r--modules/nixfiles/direnv.nix24
-rw-r--r--modules/nixfiles/docker.nix34
-rw-r--r--modules/nixfiles/dwm.nix159
-rw-r--r--modules/nixfiles/emacs.nix69
-rw-r--r--modules/nixfiles/endlessh-go.nix57
-rw-r--r--modules/nixfiles/endlessh.nix45
-rw-r--r--modules/nixfiles/fail2ban.nix32
-rw-r--r--modules/nixfiles/firefox/default.nix52
-rw-r--r--modules/nixfiles/firefox/profile.nix479
-rw-r--r--modules/nixfiles/firefox/userChrome.css156
-rw-r--r--modules/nixfiles/firefox/userContent.css210
-rw-r--r--modules/nixfiles/flatpak.nix13
-rw-r--r--modules/nixfiles/fonts.nix106
-rw-r--r--modules/nixfiles/games/default.nix47
-rw-r--r--modules/nixfiles/games/gamemode.nix13
-rw-r--r--modules/nixfiles/games/gog.nix19
-rw-r--r--modules/nixfiles/games/lutris.nix37
-rw-r--r--modules/nixfiles/games/mangohud.nix13
-rw-r--r--modules/nixfiles/games/minecraft.nix15
-rw-r--r--modules/nixfiles/games/steam-run.nix59
-rw-r--r--modules/nixfiles/games/steam.nix21
-rw-r--r--modules/nixfiles/git.nix125
-rw-r--r--modules/nixfiles/gnome.nix65
-rw-r--r--modules/nixfiles/gnupg.nix91
-rw-r--r--modules/nixfiles/gotify.nix66
-rw-r--r--modules/nixfiles/grafana.nix84
-rw-r--r--modules/nixfiles/htop.nix57
-rw-r--r--modules/nixfiles/hydra.nix56
-rw-r--r--modules/nixfiles/ipfs.nix168
-rw-r--r--modules/nixfiles/kde.nix27
-rw-r--r--modules/nixfiles/libvirtd.nix40
-rw-r--r--modules/nixfiles/lidarr.nix27
-rw-r--r--modules/nixfiles/loki.nix113
-rw-r--r--modules/nixfiles/lxc.nix16
-rw-r--r--modules/nixfiles/matrix/default.nix1
-rw-r--r--modules/nixfiles/matrix/dendrite.nix148
-rw-r--r--modules/nixfiles/matrix/element.nix59
-rw-r--r--modules/nixfiles/matrix/synapse.nix90
-rw-r--r--modules/nixfiles/monitoring.nix114
-rw-r--r--modules/nixfiles/mpd.nix212
-rw-r--r--modules/nixfiles/mpv.nix133
-rw-r--r--modules/nixfiles/nextcloud.nix133
-rw-r--r--modules/nixfiles/nginx.nix90
-rw-r--r--modules/nixfiles/nmap.nix42
-rw-r--r--modules/nixfiles/node-exporter.nix35
-rw-r--r--modules/nixfiles/nsd.nix157
-rw-r--r--modules/nixfiles/openssh.nix52
-rw-r--r--modules/nixfiles/password-store.nix33
-rw-r--r--modules/nixfiles/podman.nix35
-rw-r--r--modules/nixfiles/postgresql.nix53
-rw-r--r--modules/nixfiles/profiles/common.nix103
-rw-r--r--modules/nixfiles/profiles/default.nix5
-rw-r--r--modules/nixfiles/profiles/dev/common.nix290
-rw-r--r--modules/nixfiles/profiles/dev/containers/default.nix73
-rw-r--r--modules/nixfiles/profiles/dev/default.nix1
-rw-r--r--modules/nixfiles/profiles/dev/ghci.conf35
-rw-r--r--modules/nixfiles/profiles/dev/pystartup.py127
-rw-r--r--modules/nixfiles/profiles/dev/sql/default.nix94
-rw-r--r--modules/nixfiles/profiles/headful.nix69
-rw-r--r--modules/nixfiles/profiles/headless.nix67
-rw-r--r--modules/nixfiles/prometheus.nix60
-rw-r--r--modules/nixfiles/promtail.nix53
-rw-r--r--modules/nixfiles/psd.nix60
-rw-r--r--modules/nixfiles/qutebrowser.nix547
-rw-r--r--modules/nixfiles/radarr.nix27
-rw-r--r--modules/nixfiles/radicale.nix48
-rw-r--r--modules/nixfiles/rss-bridge.nix33
-rw-r--r--modules/nixfiles/rtorrent.nix246
-rw-r--r--modules/nixfiles/searx.nix81
-rw-r--r--modules/nixfiles/shadowsocks.nix119
-rw-r--r--modules/nixfiles/soju.nix83
-rw-r--r--modules/nixfiles/sonarr.nix27
-rw-r--r--modules/nixfiles/sound.nix21
-rw-r--r--modules/nixfiles/subversion.nix53
-rw-r--r--modules/nixfiles/syncthing.nix161
-rw-r--r--modules/nixfiles/throttled.nix117
-rw-r--r--modules/nixfiles/tmux.nix60
-rw-r--r--modules/nixfiles/unbound.nix208
-rw-r--r--modules/nixfiles/vaultwarden.nix117
-rw-r--r--modules/nixfiles/vim/default.nix56
-rw-r--r--modules/nixfiles/vim/rc.vim252
-rw-r--r--modules/nixfiles/vscode.nix172
-rw-r--r--modules/nixfiles/wget.nix36
-rw-r--r--modules/nixfiles/wireguard.nix208
-rw-r--r--modules/nixfiles/x11.nix110
-rw-r--r--modules/nixfiles/xmonad.nix28
-rw-r--r--modules/nixfiles/zathura.nix123
117 files changed, 9391 insertions, 0 deletions
diff --git a/modules/nixfiles/acme.nix b/modules/nixfiles/acme.nix
new file mode 100644
index 0000000..196a6a5
--- /dev/null
+++ b/modules/nixfiles/acme.nix
@@ -0,0 +1,32 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.acme;
+in {
+  imports = [
+    (mkAliasOptionModule ["certs"] ["security" "acme" "certs"])
+  ];
+
+  options.nixfiles.modules.acme = {
+    enable = mkEnableOption "Whether to enable ACME.";
+
+    email = mkOption {
+      description = "Email for notifications.";
+      type = with types; str;
+      default = "admin+acme@${my.domain.shire}";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    security.acme = {
+      acceptTerms = true;
+      defaults = {
+        inherit (cfg) email;
+        validMinDays = 60;
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/alacritty.nix b/modules/nixfiles/alacritty.nix
new file mode 100644
index 0000000..8b3e646
--- /dev/null
+++ b/modules/nixfiles/alacritty.nix
@@ -0,0 +1,119 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.alacritty;
+in {
+  options.nixfiles.modules.alacritty.enable =
+    mkEnableOption "Whether to enable Alacritty terminal emulator.";
+
+  config = mkIf cfg.enable {
+    hm.programs.alacritty = {
+      enable = true;
+      settings = with config.nixfiles.modules; {
+        window = {
+          padding = with config.fontScheme.monospaceFont; {
+            x = size;
+            y = size;
+          };
+          dynamic_padding = false;
+          decorations =
+            if (kde.enable || gnome.enable)
+            then "full"
+            else "none";
+        };
+        font = with config.fontScheme.monospaceFont; {
+          normal = {
+            inherit family;
+            style = "Regular";
+          };
+          bold = {
+            inherit family;
+            style = "Bold";
+          };
+          italic = {
+            inherit family;
+            style = "Italic";
+          };
+          bold_italic = {
+            inherit family;
+            style = "Bold Italic";
+          };
+          inherit size;
+        };
+        colors = with profiles.common.colourScheme; {
+          primary = {inherit background foreground;};
+          cursor = {
+            text = "CellBackground";
+            cursor = "CellForeground";
+          };
+          vi_mode_cursor = {
+            text = "CellBackground";
+            cursor = "CellForeground";
+          };
+          search = {
+            matches = {
+              foreground = white;
+              background = red;
+            };
+            focused_match = {
+              foreground = red;
+              background = black;
+            };
+            bar = {
+              foreground = black;
+              background = white;
+            };
+          };
+          hints = {
+            start = {
+              foreground = black;
+              background = yellow;
+            };
+            end = {
+              foreground = yellow;
+              background = black;
+            };
+            line_indicator = {
+              foreground = null;
+              background = null;
+            };
+            selection = {
+              text = "CellBackground";
+              background = "CellForeground";
+            };
+            normal = {
+              inherit black red green yellow blue magenta cyan white;
+            };
+            bright = {
+              inherit
+                brightBlack
+                brightRed
+                brightGreen
+                brightYellow
+                brightBlue
+                brightMagenta
+                brightCyan
+                brightWhite
+                ;
+            };
+          };
+        };
+        bell = {
+          duration = 0;
+          command = null; # TODO notify-send?
+        };
+        cursor = {
+          style = {
+            shape = "Block";
+            blinking = "Off";
+          };
+          vi_mode_style = "Block";
+        };
+        selection.save_to_clipboard = true; # TODO autocutsel?
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/alertmanager.nix b/modules/nixfiles/alertmanager.nix
new file mode 100644
index 0000000..e067cd1
--- /dev/null
+++ b/modules/nixfiles/alertmanager.nix
@@ -0,0 +1,81 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.alertmanager;
+in {
+  options.nixfiles.modules.alertmanager = {
+    enable = mkEnableOption "Whether to enable Alertmanager.";
+
+    port = mkOption {
+      description = "Port.";
+      type = with types; port;
+      default = 30112;
+    };
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; nullOr str;
+      default = config.nixfiles.modules.monitoring.domain;
+    };
+
+    path = mkOption {
+      description = "Path.";
+      type = with types; str;
+      default = "/alertmanager";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    nixfiles.modules.nginx = with cfg; {
+      enable = true;
+      virtualHosts.${cfg.domain}.locations.${path} = {
+        proxyPass = "http://127.0.0.1:${toString port}${path}";
+        extraConfig = ''
+          if ($internal != 1) {
+            return 403;
+          }
+        '';
+      };
+    };
+
+    services = let
+      acme = config.nixfiles.modules.acme.enable;
+    in {
+      prometheus.alertmanager = {
+        enable = true;
+
+        listenAddress = "127.0.0.1";
+        inherit (cfg) port;
+
+        extraFlags = [
+          "--web.external-url=http${
+            optionalString acme "s"
+          }://${cfg.domain}${cfg.path}"
+        ];
+
+        # TODO Make an option.
+        configuration = {
+          global = {
+            smtp_from = "alertmanager@${my.domain.shire}";
+            smtp_smarthost = "${my.domain.shire}:584";
+          };
+
+          route = {
+            receiver = my.username;
+            group_by = ["alertname"];
+          };
+
+          receivers = [
+            {
+              name = my.username;
+              email_configs = [{to = "${my.username}+alert@${my.domain.shire}";}];
+            }
+          ];
+        };
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/aria2.nix b/modules/nixfiles/aria2.nix
new file mode 100644
index 0000000..87f2109
--- /dev/null
+++ b/modules/nixfiles/aria2.nix
@@ -0,0 +1,48 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.aria2;
+in {
+  options.nixfiles.modules.aria2.enable =
+    mkEnableOption "Whether to enable aria2.";
+
+  config = mkIf cfg.enable {
+    hm = {
+      programs.aria2 = {
+        enable = true;
+
+        settings = {
+          bt-max-peers = 128;
+          bt-save-metadata = true;
+          continue = true;
+          enable-dht = true;
+          enable-peer-exchange = true;
+          enable-rpc = false;
+          follow-torrent = true;
+          log-level = "info";
+          max-connection-per-server = 16;
+          max-overall-upload-limit = "1K";
+          max-tries = 5;
+          max-upload-limit = "1K";
+          seed-ratio = 0.1;
+          seed-time = 0.1;
+          stream-piece-selector = "default";
+          timeout = 60;
+        };
+      };
+
+      xdg.desktopEntries.aria2c = {
+        name = "aria2";
+        genericName = "Download Manager";
+        exec = "${pkgs.aria2}/bin/aria2c";
+        terminal = true;
+        categories = ["Application" "Network"];
+        mimeType = ["application/x-bittorrent" "x-scheme-handler/magnet"];
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/aspell.nix b/modules/nixfiles/aspell.nix
new file mode 100644
index 0000000..7669eb9
--- /dev/null
+++ b/modules/nixfiles/aspell.nix
@@ -0,0 +1,23 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.aspell;
+in {
+  options.nixfiles.modules.aspell.enable =
+    mkEnableOption "Whether to enable GNU Aspell.";
+
+  config = mkIf cfg.enable {
+    hm.home = {
+      file.".aspell.conf".text = ''
+        personal /dev/null
+        repl /dev/null
+      '';
+
+      packages = with pkgs; [(aspellWithDicts (p: with p; [en ru]))];
+    };
+  };
+}
diff --git a/modules/nixfiles/bat.nix b/modules/nixfiles/bat.nix
new file mode 100644
index 0000000..d4bb1d6
--- /dev/null
+++ b/modules/nixfiles/bat.nix
@@ -0,0 +1,39 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.bat;
+in {
+  options.nixfiles.modules.bat.enable = mkEnableOption "Whether to enable Bat.";
+
+  config = mkIf cfg.enable {
+    hm.programs = {
+      bat = {
+        enable = true;
+        config = {
+          style = "plain";
+          tabs = "4";
+          theme = "base16";
+          wrap = "never";
+        };
+      };
+
+      bash = {
+        shellAliases = let
+          bat = "${pkgs.bat}/bin/bat";
+        in {
+          bay = "${bat} --language=yaml --tabs 2";
+          baj = "${bat} --language=json --tabs 2";
+        };
+
+        initExtra = mkAfter ''
+          _complete_alias bay _bat bat
+          _complete_alias baj _bat bat
+        '';
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/beets.nix b/modules/nixfiles/beets.nix
new file mode 100644
index 0000000..de4cd73
--- /dev/null
+++ b/modules/nixfiles/beets.nix
@@ -0,0 +1,73 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.beets;
+in {
+  options.nixfiles.modules.beets.enable =
+    mkEnableOption "Whether to enable beets.";
+
+  config = mkIf cfg.enable {
+    hm = let
+      beetsdir = "${config.hm.xdg.dataHome}/beets";
+    in {
+      home.sessionVariables.BEETSDIR = beetsdir;
+
+      programs = {
+        beets = {
+          enable = true;
+
+          settings = {
+            library = "${beetsdir}/library.db";
+            directory = config.userDirs.music;
+            plugins = "badfiles edit fetchart info mbsync scrub";
+            original_date = true;
+            import = {
+              write = true;
+              copy = true;
+              move = false;
+              bell = true;
+              from_scratch = true;
+            };
+            match = {
+              preferred = {
+                countries = [
+                  "JP"
+                  "KR"
+                  "TW"
+                  "HK"
+                  "CN"
+                  "RU"
+                  "NL"
+                  "DE"
+                  "AT"
+                  "GB|UK"
+                  "CA"
+                  "AU"
+                  "NZ"
+                  "US"
+                ];
+                original_year = true;
+              };
+            };
+            edit = {
+              albumfields = "album artist albumartist";
+              itemfields = "track title album artist albumartist day month year genre";
+            };
+            fetchart = {
+              auto = true;
+              cautious = true;
+              cover_names = "cover Cover folder Folder art Art album Album front Front";
+              sources = "filesystem coverart itunes amazon albumart wikipedia";
+            };
+            scrub.auto = true;
+          };
+        };
+
+        bash.shellAliases.beet = "${config.hm.programs.beets.package}/bin/beet --config ${config.dirs.config}/beets/config.yaml";
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/bluetooth.nix b/modules/nixfiles/bluetooth.nix
new file mode 100644
index 0000000..04e6b8d
--- /dev/null
+++ b/modules/nixfiles/bluetooth.nix
@@ -0,0 +1,34 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.bluetooth;
+in {
+  options.nixfiles.modules.bluetooth.enable =
+    mkEnableOption "Whether to enable Bluetooth support.";
+
+  config = mkIf cfg.enable {
+    hardware.bluetooth = {
+      enable = true;
+      package = pkgs.bluezFull;
+      settings.General.FastConnectable = true;
+    };
+
+    environment = {
+      etc."bluetooth/input.conf".text = generators.toINI {} {
+        General = {
+          IdleTimeout = 15;
+          UserspaceHID = true;
+        };
+      };
+
+      systemPackages = with pkgs;
+      with config.nixfiles.modules;
+        optional gnome.enable gnome.gnome-bluetooth
+        ++ optional kde.enable plasma5Packages.bluedevil;
+    };
+  };
+}
diff --git a/modules/nixfiles/broot.nix b/modules/nixfiles/broot.nix
new file mode 100644
index 0000000..19cd1d6
--- /dev/null
+++ b/modules/nixfiles/broot.nix
@@ -0,0 +1,18 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.broot;
+in {
+  options.nixfiles.modules.broot.enable =
+    mkEnableOption "Whether to enable broot.";
+
+  config = mkIf cfg.enable {
+    hm.programs.broot = {
+      enable = true;
+      modal = true;
+    };
+  };
+}
diff --git a/modules/nixfiles/chromium.nix b/modules/nixfiles/chromium.nix
new file mode 100644
index 0000000..1ec761c
--- /dev/null
+++ b/modules/nixfiles/chromium.nix
@@ -0,0 +1,42 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.chromium;
+in {
+  options.nixfiles.modules.chromium.enable =
+    mkEnableOption "Whether to enable Chromium.";
+
+  config = mkIf cfg.enable {
+    hm = {
+      # home.sessionVariables.BROWSER = mkOverride 300 "chromium";
+
+      programs.chromium = {
+        enable = true;
+
+        package = pkgs.chromium;
+
+        extensions =
+          [
+            {id = "cjpalhdlnbpafiamejdnhcphjbkeiagm";} # UBlock Origin
+            {id = "clngdbkpkpeebahjckkjfobafhncgmne";} # Stylus
+            {id = "cnojnbdhbhnkbcieeekonklommdnndci";} # Search By Image
+            {id = "doojmbjmlfjjnbmnoijecmcbfeoakpjm";} # NoScript
+            {id = "eimadpbcbfnmbkopoojfekhnkhdbieeh";} # Dark Reader
+            {id = "jinjaccalgkegednnccohejagnlnfdag";} # Violentmonkey
+            {id = "nibjojkomfdiaoajekhjakgkdhaomnch";} # IPFS Companion
+            {id = "nngceckbapebfimnlniiiahkandclblb";} # Bitwarden
+            {id = "pmcmeagblkinmogikoikkdjiligflglb";} # Privacy Redirect
+          ]
+          ++ optional config.nixfiles.modules.kde.enable {
+            id = "cimiefiiaegbelhefglklhhakcgmhkai"; # KDE Plasma Integration
+          };
+      };
+    };
+
+    services.psd.enable = true;
+  };
+}
diff --git a/modules/nixfiles/common/console.nix b/modules/nixfiles/common/console.nix
new file mode 100644
index 0000000..3c73695
--- /dev/null
+++ b/modules/nixfiles/common/console.nix
@@ -0,0 +1,6 @@
+{config, ...}: {
+  console = {
+    earlySetup = true;
+    useXkbConfig = config.services.xserver.enable;
+  };
+}
diff --git a/modules/nixfiles/common/default.nix b/modules/nixfiles/common/default.nix
new file mode 100644
index 0000000..d73ac53
--- /dev/null
+++ b/modules/nixfiles/common/default.nix
@@ -0,0 +1,19 @@
+_: {
+  imports = [
+    ./documentation.nix
+    ./environment.nix
+    ./home-manager.nix
+    ./kernel.nix
+    ./locale.nix
+    ./networking.nix
+    ./nix.nix
+    ./secrets.nix
+    ./security.nix
+    ./services.nix
+    ./shell
+    ./systemd.nix
+    ./tmp.nix
+    ./users.nix
+    ./xdg.nix
+  ];
+}
diff --git a/modules/nixfiles/common/documentation.nix b/modules/nixfiles/common/documentation.nix
new file mode 100644
index 0000000..344d59d
--- /dev/null
+++ b/modules/nixfiles/common/documentation.nix
@@ -0,0 +1,48 @@
+{
+  config,
+  lib,
+  pkgs,
+  this,
+  ...
+}:
+with lib; {
+  config = mkMerge [
+    (mkIf this.isHeadful {
+      documentation = {
+        enable = true;
+
+        dev.enable = true;
+        doc.enable = false;
+        info.enable = false;
+        nixos.enable = true;
+
+        man = {
+          enable = true;
+          generateCaches = true;
+          man-db = {
+            enable = true;
+            manualPages =
+              (pkgs.buildEnv {
+                name = "man-paths";
+                paths = with config;
+                  environment.systemPackages ++ hm.home.packages;
+                pathsToLink = ["/share/man"];
+                extraOutputsToInstall = ["man"];
+                ignoreCollisions = true;
+              })
+              .overrideAttrs (_: _: {__contentAddressed = true;});
+          };
+        };
+      };
+
+      environment.sessionVariables = {
+        MANOPT = "--no-hyphenation";
+        MANPAGER = "${pkgs.less}/bin/less -+F";
+      };
+    })
+    (mkIf this.isHeadless {
+      hm.manual.manpages.enable = false;
+      documentation.enable = false;
+    })
+  ];
+}
diff --git a/modules/nixfiles/common/environment.nix b/modules/nixfiles/common/environment.nix
new file mode 100644
index 0000000..9998441
--- /dev/null
+++ b/modules/nixfiles/common/environment.nix
@@ -0,0 +1,7 @@
+{lib, ...}:
+with lib; {
+  environment = {
+    localBinInPath = true;
+    defaultPackages = mkForce [];
+  };
+}
diff --git a/modules/nixfiles/common/home-manager.nix b/modules/nixfiles/common/home-manager.nix
new file mode 100644
index 0000000..e4c5d3b
--- /dev/null
+++ b/modules/nixfiles/common/home-manager.nix
@@ -0,0 +1,23 @@
+{
+  config,
+  inputs,
+  lib,
+  ...
+}:
+with lib; {
+  imports = [
+    inputs.home-manager.nixosModules.home-manager
+    (mkAliasOptionModule ["hm"] ["home-manager" "users" my.username])
+  ];
+
+  hm.home = {inherit (config.system) stateVersion;};
+
+  home-manager = {
+    backupFileExtension = "bak";
+    useUserPackages = true;
+    useGlobalPkgs = true;
+    verbose = true;
+  };
+
+  system.extraDependencies = [inputs.home-manager];
+}
diff --git a/modules/nixfiles/common/kernel.nix b/modules/nixfiles/common/kernel.nix
new file mode 100644
index 0000000..f6c096b
--- /dev/null
+++ b/modules/nixfiles/common/kernel.nix
@@ -0,0 +1,32 @@
+{lib, ...}:
+with lib; {
+  boot = {
+    kernelParams = ["hibernate=no"];
+
+    kernel.sysctl = {
+      "fs.file-max" = pow 2 17;
+      "fs.inotify.max_user_watches" = pow 2 19;
+      "fs.suid_dumpable" = 0;
+      "kernel.core_uses_pid" = 1;
+      "kernel.exec-shield" = 1;
+      "kernel.kptr_restrict" = 1;
+      "kernel.maps_protect" = 1;
+      "kernel.msgmax" = pow 2 16;
+      "kernel.msgmnb" = pow 2 16;
+      "kernel.pid_max" = pow 2 16;
+      "kernel.randomize_va_space" = 2;
+      "kernel.shmall" = pow 2 28;
+      "kernel.shmmax" = pow 2 28;
+      "kernel.sysrq" = 0;
+      "vm.dirty_background_bytes" = pow 2 22;
+      "vm.dirty_background_ratio" = 5;
+      "vm.dirty_bytes" = pow 2 22;
+      "vm.dirty_ratio" = 30;
+      "vm.min_free_kbytes" = pow 2 16;
+      "vm.mmap_min_addr" = pow 2 12;
+      "vm.overcommit_memory" = mkDefault 0;
+      "vm.overcommit_ratio" = mkDefault 50;
+      "vm.vfs_cache_pressure" = 50;
+    };
+  };
+}
diff --git a/modules/nixfiles/common/locale.nix b/modules/nixfiles/common/locale.nix
new file mode 100644
index 0000000..34a738b
--- /dev/null
+++ b/modules/nixfiles/common/locale.nix
@@ -0,0 +1,18 @@
+{lib, ...}:
+with lib; {
+  i18n.defaultLocale = mkDefault "en_GB.UTF-8";
+
+  time.timeZone = mkDefault "Europe/Moscow";
+
+  # TODO Fcitx or UIM as a Japanese IME.
+  services.xserver = {
+    layout = comcat ["us" "ru"];
+    xkbVariant = comcat ["" "phonetic"];
+    xkbOptions = comcat [
+      "terminate:ctrl_alt_bksp"
+      "caps:escape"
+      "compose:menu"
+      "grp:win_space_toggle"
+    ];
+  };
+}
diff --git a/modules/nixfiles/common/networking.nix b/modules/nixfiles/common/networking.nix
new file mode 100644
index 0000000..0ff7e3d
--- /dev/null
+++ b/modules/nixfiles/common/networking.nix
@@ -0,0 +1,66 @@
+{
+  config,
+  lib,
+  pkgs,
+  this,
+  ...
+}:
+with lib; {
+  hm.home.file.".digrc".text = ''
+    +answer
+    +multiline
+    +recurse
+  '';
+
+  networking = {
+    hostName = this.hostname;
+    hostId = substring 0 8 (builtins.hashString "md5" this.hostname);
+    domain = my.domain.shire;
+
+    usePredictableInterfaceNames = false;
+
+    useDHCP = false;
+
+    nameservers = dns.const.quad9.default;
+
+    hosts = {
+      "127.0.0.2" = mkForce [];
+      "::1" = mkForce [];
+    };
+
+    firewall = {
+      enable = true;
+
+      logRefusedConnections = false;
+      logRefusedPackets = false;
+
+      rejectPackets = false;
+
+      allowPing = config.nixfiles.modules.profiles.headless.enable;
+    };
+  };
+
+  environment = {
+    systemPackages = with pkgs; [dnsutils ldns myip rsync];
+
+    shellAliases = listToAttrs (map
+      ({
+        name,
+        value,
+      }:
+        nameValuePair name "${pkgs.iproute2}/bin/${value}") [
+        {
+          name = "bridge";
+          value = "bridge -color=always";
+        }
+        {
+          name = "ip";
+          value = "ip -color=always";
+        }
+        {
+          name = "tc";
+          value = "tc -color=always";
+        }
+      ]);
+  };
+}
diff --git a/modules/nixfiles/common/nix.nix b/modules/nixfiles/common/nix.nix
new file mode 100644
index 0000000..cc050f8
--- /dev/null
+++ b/modules/nixfiles/common/nix.nix
@@ -0,0 +1,173 @@
+{
+  config,
+  inputs,
+  lib,
+  pkgs,
+  this,
+  ...
+}:
+with lib; let
+  nixfilesSrc = "${config.my.home}/src/nixfiles";
+in {
+  _module.args = let
+    importNixpkgs = nixpkgs:
+      import nixpkgs {
+        inherit (config.nixpkgs) localSystem crossSystem config;
+      };
+  in rec {
+    pkgsMaster = importNixpkgs inputs.nixpkgs-master;
+    pkgsStable = importNixpkgs inputs.nixpkgs-stable;
+    pkgsRev = rev: sha256:
+      importNixpkgs (pkgs.fetchFromGitHub {
+        owner = "NixOS";
+        repo = "nixpkgs";
+        inherit rev sha256;
+      });
+    pkgsPR = pr: pkgsRev "refs/pull/${toString pr}/head";
+    pkgsLocal = importNixpkgs "${config.my.home}/src/nixpkgs";
+  };
+
+  nix = let
+    filteredInputs = filterAttrs (n: _: n != "self") inputs;
+  in {
+    # https://github.com/NixOS/nix/blob/master/src/libutil/experimental-features.cc
+    extraOptions = ''
+      extra-experimental-features = ca-derivations
+      extra-experimental-features = flakes
+      extra-experimental-features = nix-command
+      extra-experimental-features = recursive-nix
+      flake-registry = ${inputs.flake-registry}/flake-registry.json
+      keep-derivations = true
+      keep-outputs = true
+      warn-dirty = false
+    '';
+
+    nixPath =
+      mapAttrsToList (n: v: "${n}=${v}") filteredInputs
+      ++ ["nixfiles=${nixfilesSrc}"];
+
+    registry =
+      mapAttrs (_: flake: {inherit flake;}) filteredInputs
+      // {
+        nixfiles.flake = inputs.self;
+      };
+
+    settings = {
+      trusted-users = ["root" "@wheel"];
+
+      substituters = [
+        "https://azahi.cachix.org"
+        "https://cachix.cachix.org"
+        "https://mic92.cachix.org"
+        "https://nix-community.cachix.org"
+        "https://pre-commit-hooks.cachix.org"
+      ];
+      trusted-public-keys = [
+        "azahi.cachix.org-1:2bayb+iWYMAVw3ZdEpVg+NPOHCXncw7WMQ0ElX1GO3s="
+        "cachix.cachix.org-1:eWNHQldwUO7G2VkjpnjDbWwy4KQ/HNxht7H4SSoMckM="
+        "mic92.cachix.org-1:gi8IhgiT3CYZnJsaW7fxznzTkMUOn1RY4GmXdT/nXYQ="
+        "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
+        "pre-commit-hooks.cachix.org-1:Pkk3Panw5AW24TOv6kz3PvLhlH8puAsJTBbOPmBo7Rc="
+      ];
+    };
+  };
+
+  nixpkgs = {
+    overlays = with inputs; [
+      self.overlays.default
+      (_: super:
+        {
+          nix-bash-completions =
+            super.nix-bash-completions.overrideAttrs
+            (_: _: {
+              postPatch = ''
+                substituteInPlace _nix --replace 'nix nixos-option' 'nixos-option'
+              '';
+            });
+          nix-index = super.nix-index.override {nix = config.nix.package;};
+          logcli = super.grafana-loki.overrideAttrs (_: _: {
+            subPackages = ["cmd/logcli"];
+            preFixup = "";
+            doCheck = false;
+          });
+          helm = super.kubernetes-helm-wrapped.override {
+            plugins = with super.kubernetes-helmPlugins; [
+              helm-diff
+              helm-secrets
+            ];
+          };
+          pgcli = super.pgcli.overrideAttrs (_: _: {
+            # https://github.com/NixOS/nixpkgs/pull/184533
+            postPatch = ''
+              substituteInPlace setup.py \
+                --replace "pgspecial>=1.13.1,<2.0.0" "pgspecial>=1.13.1"
+            '';
+          });
+        }
+        // (with super; let
+          np = nodePackages;
+        in {
+          dockerfile-language-server = np.dockerfile-language-server-nodejs;
+          editorconfig = editorconfig-core-c;
+          inherit (np) bash-language-server;
+          inherit (np) vim-language-server;
+          inherit (np) yaml-language-server;
+          json-language-server = np.vscode-json-languageserver;
+          k3d = kube3d;
+          lua-language-server = sumneko-lua-language-server;
+          nix-language-server = rnix-lsp;
+          telepresence = telepresence2;
+          tor-browser = tor-browser-bundle-bin;
+        }))
+      agenix.overlay
+      emacs-overlay.overlay
+      nur.overlay
+      xmonad-ng.overlay
+    ];
+
+    config.allowUnfree = true;
+  };
+
+  environment = {
+    sessionVariables.NIX_SHELL_PRESERVE_PROMPT = "1";
+
+    etc = {
+      nixpkgs.source = inputs.nixpkgs;
+
+      gc-roots.text =
+        concatMapStrings (x: x + "\n")
+        (with inputs; [nixpkgs nixpkgs-master nixpkgs-stable]);
+    };
+
+    systemPackages = with pkgs;
+      optionals config.profile.headful [
+        (pkgs.nixfiles.override {
+          nix = config.nix.package;
+          inherit nixfilesSrc;
+        })
+        nix-top
+        nix-tree
+      ];
+  };
+
+  hm.home.file.".nix-defexpr/default.nix".text =
+    optionalString this.isHeadful
+    (
+      let
+        hostname = strings.escapeNixIdentifier this.hostname;
+      in ''
+        let
+          self = builtins.getFlake "nixfiles";
+          configurations = self.nixosConfigurations;
+          local = configurations.${hostname};
+        in rec {
+          inherit self;
+          inherit (self) inputs lib;
+          inherit (lib) my;
+          this = my.configurations.${hostname};
+          inherit (local) config;
+          inherit (local.config.system.build) toplevel vm vmWithBootLoader manual;
+        } // configurations // local._module.args
+      ''
+    );
+}
diff --git a/modules/nixfiles/common/secrets.nix b/modules/nixfiles/common/secrets.nix
new file mode 100644
index 0000000..9e59716
--- /dev/null
+++ b/modules/nixfiles/common/secrets.nix
@@ -0,0 +1,47 @@
+{
+  config,
+  inputs,
+  lib,
+  pkgs,
+  this,
+  ...
+}:
+with lib; {
+  imports = [
+    inputs.agenix.nixosModule
+    (mkAliasOptionModule ["secrets"] ["age" "secrets"])
+  ];
+
+  config = {
+    age = {
+      identityPaths =
+        if this.isHeadful
+        then ["${config.my.home}/.ssh/id_${my.ssh.type}"]
+        else
+          map (attr: attr.path) (filter (attr: attr.type == my.ssh.type)
+            config.services.openssh.hostKeys);
+
+      # This can be used to auto-add all secrets, thus eleminating the need to
+      # specify path to each envrypted file. The drawback is that this will
+      # expose *all* secrets to all machines and try to decrypt them all even on
+      # machines where the secret will not be used.
+      #
+      # secrets =
+      #   let
+      #     secretsSourceDir = "${inputs.self}/age";
+      #   in
+      #   mapAttrs'
+      #     (name: _:
+      #       nameValuePair name {
+      #         file = "${secretsSourceDir}/${name}";
+      #         owner = mkDefault my.username;
+      #         group = mkDefault config.my.group;
+      #       })
+      #     (builtins.readDir secretsSourceDir);
+    };
+
+    environment.systemPackages = with pkgs; [agenix];
+
+    system.extraDependencies = [inputs.agenix];
+  };
+}
diff --git a/modules/nixfiles/common/security.nix b/modules/nixfiles/common/security.nix
new file mode 100644
index 0000000..d47edc9
--- /dev/null
+++ b/modules/nixfiles/common/security.nix
@@ -0,0 +1,19 @@
+_: {
+  security = {
+    sudo = {
+      enable = true;
+      execWheelOnly = true;
+      wheelNeedsPassword = false;
+      extraConfig = ''
+        Defaults env_keep+="SSH_CONNECTION SSH_CLIENT SSH_TTY"
+      '';
+    };
+
+    polkit.extraConfig = ''
+      polkit.addRule(function (action, subject) {
+        if (subject.isInGroup('wheel'))
+          return polkit.Result.YES;
+      });
+    '';
+  };
+}
diff --git a/modules/nixfiles/common/services.nix b/modules/nixfiles/common/services.nix
new file mode 100644
index 0000000..376c87d
--- /dev/null
+++ b/modules/nixfiles/common/services.nix
@@ -0,0 +1,9 @@
+_: {
+  services = {
+    earlyoom.enable = true;
+    haveged.enable = true;
+    irqbalance.enable = true;
+  };
+
+  hardware.ksm.enable = true;
+}
diff --git a/modules/nixfiles/common/shell/default.nix b/modules/nixfiles/common/shell/default.nix
new file mode 100644
index 0000000..7174443
--- /dev/null
+++ b/modules/nixfiles/common/shell/default.nix
@@ -0,0 +1,125 @@
+{
+  lib,
+  pkgs,
+  this,
+  ...
+}:
+with lib; {
+  hm = {
+    programs = {
+      bash = {
+        enable = true;
+
+        shellOptions = [
+          "autocd"
+          "cdspell"
+          "checkjobs"
+          "checkwinsize"
+          "dirspell"
+          "extglob"
+          "globstar"
+          "histappend"
+          "histreedit"
+          "histverify"
+        ];
+
+        profileExtra = ''
+          export _PROFILE_SOURCED=1
+        '';
+
+        initExtra = ''
+          set -o notify
+
+          ${readFile ./functions.bash}
+
+          GRC_ALIASES=true
+          source ${pkgs.grc}/etc/profile.d/grc.sh
+
+          if [ -z $_PROFILE_SOURCED ] && [ -f $HOME/.profile ]; then
+            source $HOME/.profile
+          fi
+        '';
+
+        historyControl = ["ignoredups" "ignorespace"];
+      };
+
+      command-not-found.enable = false;
+
+      dircolors.enable = true;
+    };
+
+    home.packages = with pkgs; [grc];
+  };
+
+  programs.command-not-found.enable = false;
+
+  environment = {
+    shellAliases =
+      listToAttrs
+      (map
+        ({
+          name,
+          value,
+        }:
+          nameValuePair name (with pkgs; let
+            pkg =
+              if this.isHeadful
+              then
+                (coreutils.overrideAttrs (_: super: {
+                  patches =
+                    super.patches
+                    ++ [
+                      (fetchpatch {
+                        url = "https://raw.githubusercontent.com/jarun/advcpmv/master/advcpmv-0.9-9.1.patch";
+                        sha256 = "sha256-d+SRT/R4xmfHLAdOr7m4R3WFiW64P5ZH6iqDvErYCyg=";
+                      })
+                    ];
+                }))
+              else coreutils;
+          in "${pkg}/bin/coreutils --coreutils-prog=${value}"))
+        (
+          let
+            mkAlias = {
+              name ? head command,
+              command,
+            }: {
+              inherit name;
+              value = concatStringsSep " " command;
+            };
+
+            progressBar = optionalString this.isHeadful "--progress-bar";
+          in [
+            (mkAlias {
+              command = ["cp" "--interactive" "--recursive" progressBar];
+            })
+            (mkAlias {command = ["mv" "--interactive" progressBar];})
+            (mkAlias {command = ["rm" "--interactive=once"];})
+            (mkAlias {command = ["ln" "--interactive"];})
+            (mkAlias {command = ["mkdir" "--parents"];})
+            (mkAlias {command = ["rmdir" "--parents"];})
+            (mkAlias {
+              name = "lower";
+              command = ["tr" "'[:upper:]'" "'[:lower:]'"];
+            })
+            (mkAlias {
+              name = "upper";
+              command = ["tr" "'[:lower:]'" "'[:upper:]'"];
+            })
+            (mkAlias {
+              name = "disk";
+              command = [
+                "df"
+                "--human-readable"
+                "--exclude-type=tmpfs"
+                "--exclude-type=devtmpfs"
+                "2>/dev/null"
+              ];
+            })
+          ]
+        ))
+      // genAttrs ["grep" "egrep" "fgrep"]
+      (name: "${pkgs.gnugrep}/bin/${name} --color=always");
+
+    systemPackages = with pkgs; [bash-completion bc gawk hr moreutils pv];
+  };
+}
diff --git a/modules/nixfiles/common/shell/functions.bash b/modules/nixfiles/common/shell/functions.bash
new file mode 100644
index 0000000..c18104f
--- /dev/null
+++ b/modules/nixfiles/common/shell/functions.bash
@@ -0,0 +1,73 @@
+_complete_alias() {
+    local alias_name=$1
+    local base_function=$2
+    local function_name=_alias_$alias_name
+    shift 2
+    eval "$function_name() {
+        COMP_WORDS=( ${*@Q} \"\${COMP_WORDS[@]:1}\" )
+        (( COMP_CWORD += $# - 1 ))
+        _completion_loader $1
+        $base_function
+    }"
+    complete -F "$function_name" "$alias_name"
+}
+
+function where() {
+    local s
+    s="$(type -P "$1")"
+    realpath "$s"
+}
+_complete_alias where _complete complete
+
+function what() {
+    local s
+    s="$(where "$1")"
+    printf "%s\n" "${s%/*/*}"
+}
+_complete_alias what _complete complete
+
+function cat() {
+    if (($# == 1)) && [[ -d $1 ]]; then
+        ll "$1"
+    else
+        command cat "$@"
+    fi
+}
+
+function cd() {
+    builtin cd "$@" &&
+        if ((${#FUNCNAME[@]} == 1)); then
+            ls
+        fi
+}
+
+function mkcd() {
+    mkdir -p "$1" && builtin cd "$1"
+}
+
+function mvcd() {
+    mv -i -- "$PWD" "$1" && builtin cd .
+}
+
+function bak() {
+    local f
+    for f; do
+        cp -ai -- "$f" "$f.bak"
+    done
+}
+
+function ubak() {
+    local f
+    for f; do
+        [[ $f == *.bak ]] || f="$f.bak"
+        mv -i -- "$f" "${f%.bak}"
+    done
+}
+
+function dec2hex() {
+    printf "0x%X\n" "$1"
+}
+
+function hex2dec() {
+    printf "%d\n" "0x$1"
+}
diff --git a/modules/nixfiles/common/systemd.nix b/modules/nixfiles/common/systemd.nix
new file mode 100644
index 0000000..5c7282d
--- /dev/null
+++ b/modules/nixfiles/common/systemd.nix
@@ -0,0 +1,22 @@
+{pkgs, ...}: {
+  hm.systemd.user.startServices = "sd-switch";
+
+  services.journald.extraConfig = ''
+    SystemMaxUse=5G
+  '';
+
+  systemd = let
+    extraConfig = ''
+      DefaultTimeoutStartSec=30s
+      DefaultTimeoutStopSec=15s
+    '';
+  in {
+    inherit extraConfig;
+    user = {inherit extraConfig;};
+  };
+
+  environment.sessionVariables = {
+    SYSTEMD_PAGER = "${pkgs.less}/bin/less";
+    SYSTEMD_LESS = "FRSXMK";
+  };
+}
diff --git a/modules/nixfiles/common/tmp.nix b/modules/nixfiles/common/tmp.nix
new file mode 100644
index 0000000..3fbf253
--- /dev/null
+++ b/modules/nixfiles/common/tmp.nix
@@ -0,0 +1,18 @@
+_: {
+  systemd.mounts = [
+    {
+      what = "tmpfs";
+      where = "/tmp";
+      type = "tmpfs";
+      mountConfig.Options = [
+        "huge=within_size"
+        "mode=1777"
+        "noatime"
+        "nodev"
+        "nosuid"
+        "rw"
+        "size=25%"
+      ];
+    }
+  ];
+}
diff --git a/modules/nixfiles/common/users.nix b/modules/nixfiles/common/users.nix
new file mode 100644
index 0000000..0878db6
--- /dev/null
+++ b/modules/nixfiles/common/users.nix
@@ -0,0 +1,21 @@
+{lib, ...}:
+with lib; {
+  imports = [(mkAliasOptionModule ["my"] ["users" "users" my.username])];
+
+  users = {
+    mutableUsers = false;
+
+    users = {
+      root.hashedPassword = "[REDACTED]";
+
+      ${my.username} = {
+        isNormalUser = true;
+        uid = 1000;
+        description = my.fullname;
+        inherit (my) hashedPassword;
+        openssh.authorizedKeys.keys = [my.ssh.key];
+        extraGroups = ["wheel"];
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/common/xdg.nix b/modules/nixfiles/common/xdg.nix
new file mode 100644
index 0000000..c05f5e3
--- /dev/null
+++ b/modules/nixfiles/common/xdg.nix
@@ -0,0 +1,86 @@
+{
+  config,
+  lib,
+  this,
+  ...
+}:
+with lib; {
+  imports = let
+    withBase = s: ["home-manager" "users" my.username "xdg" s];
+  in [
+    (mkAliasOptionModule ["dirs" "cache"] (withBase "cacheHome"))
+    (mkAliasOptionModule ["dirs" "config"] (withBase "configHome"))
+    (mkAliasOptionModule ["dirs" "data"] (withBase "dataHome"))
+    (mkAliasOptionModule ["dirs" "state"] (withBase "stateHome"))
+    (mkAliasOptionModule ["userDirs"] (withBase "userDirs"))
+  ];
+
+  hm = {
+    xdg = mkMerge [
+      {
+        enable = true;
+
+        userDirs = let
+          inherit (config.my) home;
+          tmp = home + "/tmp";
+        in {
+          enable = true;
+
+          createDirectories = this.isHeadful;
+
+          desktop = tmp;
+          documents = "${home}/doc";
+          download = tmp;
+          music = tmp;
+          pictures = tmp;
+          videos = tmp;
+          templates = tmp;
+          publicShare = "${home}/share";
+        };
+      }
+      (mkIf this.isHeadful {
+        mimeApps = let
+          images = [
+            "image/bmp"
+            "image/gif"
+            "image/jpeg"
+            "image/jpg"
+            "image/png"
+            "image/svg+xml"
+            "image/tiff"
+            "image/webp"
+          ];
+          media = [
+            "audio/aac"
+            "audio/flac"
+            "audio/mp3"
+            "audio/ogg"
+            "audio/wav"
+            "audio/webm"
+            "video/mkv"
+            "video/mp4"
+            "video/ogg"
+            "video/webm"
+            "video/x-matroska"
+          ];
+        in {
+          enable = true;
+
+          defaultApplications =
+            mkMerge
+            (mapAttrsToList (n: ms: genAttrs ms (_: ["${n}.desktop"])) {
+              aria2 = ["application/x-bittorrent" "x-scheme-handler/magnet"];
+              emacsclient = ["x-scheme-handler/mailto"];
+              firefox = [
+                "text/html"
+                "x-scheme-handler/http"
+                "x-scheme-handler/https"
+              ];
+              gwenview = images;
+              mpv = media;
+            });
+        };
+      })
+    ];
+  };
+}
diff --git a/modules/nixfiles/curl.nix b/modules/nixfiles/curl.nix
new file mode 100644
index 0000000..e7bee31
--- /dev/null
+++ b/modules/nixfiles/curl.nix
@@ -0,0 +1,38 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.curl;
+in {
+  options.nixfiles.modules.curl.enable =
+    mkEnableOption "Wether to enable cURL.";
+
+  config = mkIf cfg.enable {
+    hm.home.file.".curlrc".text = ''
+      connect-timeout = 60
+      progress-bar
+      referer = ";auto"
+      remote-time
+      show-error
+    '';
+
+    environment.systemPackages = with pkgs; [
+      curl
+      (writeShellScriptBin "0x0" ''
+        url="https://0x0.st"
+        form="file=@"
+
+        if [ -t 0 ] && [ -n "$1" ]; then
+            form="$form$1"
+        else
+            form="$form-"
+        fi
+
+        ${curl}/bin/curl --form "$form" "$url"
+      '')
+    ];
+  };
+}
diff --git a/modules/nixfiles/default.nix b/modules/nixfiles/default.nix
new file mode 100644
index 0000000..8d26e7f
--- /dev/null
+++ b/modules/nixfiles/default.nix
@@ -0,0 +1,80 @@
+{...}: {
+  imports = [
+    ./acme.nix
+    ./alacritty.nix
+    ./alertmanager.nix
+    ./aria2.nix
+    ./aspell.nix
+    ./bat.nix
+    ./beets.nix
+    ./bluetooth.nix
+    ./broot.nix
+    ./chromium.nix
+    ./common
+    ./curl.nix
+    ./direnv.nix
+    ./docker.nix
+    ./dwm.nix
+    ./emacs.nix
+    ./endlessh-go.nix
+    ./endlessh.nix
+    ./fail2ban.nix
+    ./firefox
+    ./flatpak.nix
+    ./fonts.nix
+    ./games
+    ./git.nix
+    ./gnome.nix
+    ./gnupg.nix
+    ./gotify.nix
+    ./grafana.nix
+    ./htop.nix
+    ./hydra.nix
+    ./ipfs.nix
+    ./kde.nix
+    ./libvirtd.nix
+    ./lidarr.nix
+    ./loki.nix
+    ./lxc.nix
+    ./matrix
+    ./monitoring.nix
+    ./mpd.nix
+    ./mpv.nix
+    ./nextcloud.nix
+    ./nginx.nix
+    ./nmap.nix
+    ./node-exporter.nix
+    ./nsd.nix
+    ./openssh.nix
+    ./password-store.nix
+    ./podman.nix
+    ./postgresql.nix
+    ./profiles
+    ./prometheus.nix
+    ./promtail.nix
+    ./psd.nix
+    ./qutebrowser.nix
+    ./radarr.nix
+    ./radicale.nix
+    ./rss-bridge.nix
+    ./rtorrent.nix
+    ./searx.nix
+    ./shadowsocks.nix
+    ./soju.nix
+    ./sonarr.nix
+    ./sound.nix
+    ./subversion.nix
+    ./syncthing.nix
+    ./throttled.nix
+    ./tmux.nix
+    ./unbound.nix
+    ./vaultwarden.nix
+    ./vim
+    ./vscode.nix
+    ./wget.nix
+    ./wireguard.nix
+    ./x11.nix
+    ./xmonad.nix
+    ./zathura.nix
+  ];
+}
diff --git a/modules/nixfiles/direnv.nix b/modules/nixfiles/direnv.nix
new file mode 100644
index 0000000..aea7dd8
--- /dev/null
+++ b/modules/nixfiles/direnv.nix
@@ -0,0 +1,24 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.direnv;
+in {
+  options.nixfiles.modules.direnv.enable =
+    mkEnableOption "Whether to enable direnv and Lorri.";
+
+  config = mkIf cfg.enable {
+    hm = {
+      programs.direnv = {
+        enable = true;
+        nix-direnv.enable = true;
+      };
+
+      home.sessionVariables.DIRENV_LOG_FORMAT = "";
+    };
+
+    services.lorri.enable = true;
+  };
+}
diff --git a/modules/nixfiles/docker.nix b/modules/nixfiles/docker.nix
new file mode 100644
index 0000000..d2e53d6
--- /dev/null
+++ b/modules/nixfiles/docker.nix
@@ -0,0 +1,34 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.docker;
+in {
+  options.nixfiles.modules.docker.enable =
+    mkEnableOption "Whether to enable Docker.";
+
+  config = mkIf cfg.enable {
+    secrets.containers-auth = {
+      file = "${inputs.self}/secrets/containers-auth";
+      path = "${config.my.home}/.docker/config.json";
+      owner = my.username;
+    };
+
+    virtualisation.docker.enable = true;
+
+    environment.systemPackages = with pkgs; [docker-compose];
+
+    my.extraGroups = ["docker"];
+
+    hm.programs.bash = {
+      shellAliases.d = "${pkgs.docker}/bin/docker";
+
+      initExtra = mkAfter ''
+        _complete_alias d _docker docker
+      '';
+    };
+  };
+}
diff --git a/modules/nixfiles/dwm.nix b/modules/nixfiles/dwm.nix
new file mode 100644
index 0000000..86d15fb
--- /dev/null
+++ b/modules/nixfiles/dwm.nix
@@ -0,0 +1,159 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.dwm;
+in {
+  options.nixfiles.modules.dwm.enable = mkEnableOption "Whether to enable dwm.";
+
+  config = mkIf cfg.enable {
+    nixfiles.modules.x11.enable = true;
+
+    hm.xsession = {
+      enable = true;
+
+      windowManager.command = let
+        pkg = pkgs.dwm.override {
+          conf = let
+            font = with config.fontScheme.monospaceFont; "${family}:size=${toString size}";
+            colour = config.colourScheme;
+          in ''
+            static const unsigned int borderpx = 1;
+            static const unsigned int snap = 32;
+            static const int showbar = 1;
+            static const int topbar = 1;
+
+            static const char *fonts[] = {
+              "${font}"
+            };
+
+            static const char *colors[][3] = {
+              [SchemeNorm] = {
+                "${colour.white}",
+                "${colour.black}",
+                "${colour.black}",
+              },
+              [SchemeSel] = {
+                "${colour.black}",
+                "${colour.white}",
+                "${colour.white}",
+              },
+            };
+
+            static const char *tags[] = {
+              "1",
+              "2",
+              "3",
+              "4",
+              "5",
+              "6",
+              "7",
+              "8",
+              "9"
+            };
+
+            static const Rule rules[] = {
+              { "Emacs", NULL, NULL, 1 << 0, 0, -1 },
+            };
+
+            static const float mfact = 0.666;
+            static const int nmaster = 1;
+            static const int resizehints = 0;
+            static const int lockfullscreen = 1;
+
+            static const Layout layouts[] = {
+              { "[]=", tile },
+              { "><>", NULL },
+              { "[M]", monocle },
+            };
+
+            #define MODKEY Mod4Mask
+            #define TAGKEYS(KEY,TAG) \
+              { MODKEY,                       KEY, view,       { .ui = 1 << TAG } }, \
+              { MODKEY|ControlMask,           KEY, toggleview, { .ui = 1 << TAG } }, \
+              { MODKEY|ShiftMask,             KEY, tag,        { .ui = 1 << TAG } }, \
+              { MODKEY|ControlMask|ShiftMask, KEY, toggletag,  { .ui = 1 << TAG } },
+
+            static char dmenumon[2] = "0";
+            static const char *dmenucmd[] = {
+              "${pkgs.dmenu}/bin/dmenu_run",
+              "-m", dmenumon,
+              "-fn", "${font}",
+              "-nb", "${colour.black}",
+              "-nf", "${colour.white}",
+              "-sb", "${colour.white}",
+              "-sf", "${colour.black}",
+              NULL,
+            };
+            static const char *termcmd[] = {
+              "${pkgs.alacritty}/bin/alacritty",
+              NULL,
+            };
+
+            static const Key keys[] = {
+              { MODKEY,           XK_x,      spawn,          {.v = dmenucmd } },
+              { MODKEY,           XK_Return, spawn,          {.v = termcmd } },
+              { MODKEY,           XK_b,      togglebar,      {0} },
+              { MODKEY,           XK_j,      focusstack,     {.i = +1 } },
+              { MODKEY,           XK_k,      focusstack,     {.i = -1 } },
+              { MODKEY|ShiftMask, XK_k,      incnmaster,     {.i = +1 } },
+              { MODKEY|ShiftMask, XK_j,      incnmaster,     {.i = -1 } },
+              { MODKEY,           XK_comma,  setmfact,       {.f = -0.05} },
+              { MODKEY,           XK_period, setmfact,       {.f = +0.05} },
+              { MODKEY,           XK_p,      zoom,           {0} },
+              { MODKEY,           XK_Tab,    view,           {0} },
+              { MODKEY,           XK_d,      killclient,     {0} },
+              { MODKEY,           XK_t,      setlayout,      {.v = &layouts[0]} },
+              { MODKEY,           XK_m,      setlayout,      {.v = &layouts[1]} },
+              { MODKEY,           XK_f,      setlayout,      {.v = &layouts[2]} },
+              { MODKEY,           XK_o,      togglefloating, {0} },
+              { MODKEY,           XK_0,      view,           {.ui = ~0 } },
+              { MODKEY|ShiftMask, XK_0,      tag,            {.ui = ~0 } },
+              { MODKEY,           XK_h,      focusmon,       {.i = -1 } },
+              { MODKEY,           XK_l,      focusmon,       {.i = +1 } },
+              { MODKEY|ShiftMask, XK_h,      tagmon,         {.i = -1 } },
+              { MODKEY|ShiftMask, XK_l,      tagmon,         {.i = +1 } },
+              TAGKEYS(            XK_1,                      0)
+              TAGKEYS(            XK_2,                      1)
+              TAGKEYS(            XK_3,                      2)
+              TAGKEYS(            XK_4,                      3)
+              TAGKEYS(            XK_5,                      4)
+              TAGKEYS(            XK_6,                      5)
+              TAGKEYS(            XK_7,                      6)
+              TAGKEYS(            XK_8,                      7)
+              TAGKEYS(            XK_9,                      8)
+              { MODKEY|ShiftMask, XK_q,      quit,           {0} },
+            };
+
+            static const Button buttons[] = {
+              { ClkLtSymbol,   0,      Button1, setlayout,      {0} },
+              { ClkLtSymbol,   0,      Button3, setlayout,      {.v = &layouts[2]} },
+              { ClkWinTitle,   0,      Button2, zoom,           {0} },
+              { ClkStatusText, 0,      Button2, spawn,          {.v = termcmd } },
+              { ClkClientWin,  MODKEY, Button1, movemouse,      {0} },
+              { ClkClientWin,  MODKEY, Button2, togglefloating, {0} },
+              { ClkClientWin,  MODKEY, Button3, resizemouse,    {0} },
+              { ClkTagBar,     0,      Button1, view,           {0} },
+              { ClkTagBar,     0,      Button3, toggleview,     {0} },
+              { ClkTagBar,     MODKEY, Button1, tag,            {0} },
+              { ClkTagBar,     MODKEY, Button3, toggletag,      {0} },
+            };
+          '';
+        };
+      in "${pkg}/bin/dwm";
+    };
+
+    hm.services.dwm-status = {
+      enable = true;
+      # package = pkgs.dwm-status.override {
+      #   enableAlsaUtils = false;
+      # };
+      order = ["audio" "backlight" "battery" "cpu_load" "network" "time"];
+    };
+
+    services.xserver.displayManager.startx.enable = true;
+  };
+}
diff --git a/modules/nixfiles/emacs.nix b/modules/nixfiles/emacs.nix
new file mode 100644
index 0000000..230b965
--- /dev/null
+++ b/modules/nixfiles/emacs.nix
@@ -0,0 +1,69 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.emacs;
+in {
+  options.nixfiles.modules.emacs.enable =
+    mkEnableOption "Whether to enable the GNU Emacs.";
+
+  # TODO Nixify.
+  config = mkIf cfg.enable {
+    hm = {
+      home = {
+        packages = with pkgs; [
+          cmigemo # :lang japanese
+          gcc # :lang (org +roam2)
+          gnuplot # :lang (org +gnuplot)
+          gnutls # :app irc
+          graphviz # :lang (org +roam2)
+          grip # :lang (markdown +grip)
+          maim # :lang (org +dragndrop)
+          pandoc # :lang org markdown latex
+          plantuml # :lang plantuml
+          pre-commit # :tools magit
+          sqlite # :lang (org +roam2)
+          texlive.combined.scheme-full # :lang org tex
+          xclip # :os (tty +osc)
+        ];
+
+        activation = {
+          symlinkMigemoFiles = ''
+            target="${config.dirs.data}/migemo"
+            [[ -L "$target" ]] && rm "$target"
+            ln -s ${pkgs.cmigemo}/share/migemo "$target"
+          '';
+          symlinkSkkFiles = ''
+            target="${config.dirs.data}/skk"
+            [[ -L "$target" ]] && rm "$target"
+            ln -s ${pkgs.skk-dicts}/share "$target"
+          '';
+          symlinkPlantumlFiles = ''
+            target="${config.dirs.data}/plantuml"
+            [[ -L "$target" ]] && rm "$target"
+            ln -s ${pkgs.plantuml}/lib "$target"
+          '';
+        };
+      };
+
+      programs.emacs = {
+        enable = true;
+        package = pkgs.emacs28.override {nativeComp = true;};
+        extraPackages = p:
+          with p; [
+            vterm # :term vterm
+          ];
+      };
+
+      services.emacs = {
+        enable = true;
+        client.enable = true;
+      };
+    };
+
+    fonts.fonts = with pkgs; [emacs-all-the-icons-fonts];
+  };
+}
diff --git a/modules/nixfiles/endlessh-go.nix b/modules/nixfiles/endlessh-go.nix
new file mode 100644
index 0000000..8ab71ee
--- /dev/null
+++ b/modules/nixfiles/endlessh-go.nix
@@ -0,0 +1,57 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.endlessh-go;
+in {
+  options.nixfiles.modules.endlessh-go = {
+    enable = mkEnableOption "Whether to enable endlessh-go.";
+
+    prometheusPort = mkOption {
+      description = "Prometheus port.";
+      type = with types; port;
+      default = 9119;
+    };
+  };
+
+  config = let
+    port = 22;
+  in
+    mkIf cfg.enable {
+      assertions = [
+        {
+          assertion = !(any (x: x == port) config.services.openssh.ports);
+          message = "Port ${toString port} is already occupied by OpenSSH";
+        }
+      ];
+
+      systemd.services.endlessh-go = {
+        description = "Endlessh SSH Tarpit";
+        requires = ["network-online.target"];
+        serviceConfig = {
+          Restart = "always";
+          ExecStart = concatStringsSep " " [
+            "${pkgs.endlessh-go}/bin/endlessh-go"
+            "-conn_type=tcp4"
+            "-host=0.0.0.0"
+            "-port=${toString port}"
+            "-enable_prometheus"
+            "-prometheus_port=${toString cfg.prometheusPort}"
+            "-geoip_supplier=ip-api"
+            "-logtostderr"
+            "-v=1"
+          ];
+          KillSignal = "SIGTERM";
+          AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+          DynamicUser = true;
+          StateDirectory = "endlessh-go";
+        };
+        wantedBy = ["multi-user.target"];
+      };
+
+      networking.firewall.allowedTCPPorts = [port];
+    };
+}
diff --git a/modules/nixfiles/endlessh.nix b/modules/nixfiles/endlessh.nix
new file mode 100644
index 0000000..2871683
--- /dev/null
+++ b/modules/nixfiles/endlessh.nix
@@ -0,0 +1,45 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.endlessh;
+in {
+  options.nixfiles.modules.endlessh.enable =
+    mkEnableOption "Whether to enable endlessh.";
+
+  config = let
+    port = 22;
+  in
+    mkIf cfg.enable {
+      assertions = [
+        {
+          assertion = !(any (x: x == port) config.services.openssh.ports);
+          message = "Port ${toString port} is already occupied by OpenSSH";
+        }
+      ];
+
+      systemd.services.endlessh = {
+        description = "Endlessh SSH Tarpit";
+        requires = ["network-online.target"];
+        serviceConfig = {
+          Restart = "always";
+          ExecStart = concatStringsSep " " [
+            "${pkgs.endlessh}/bin/endlessh"
+            "-v"
+            "-4"
+            "-p ${toString port}"
+          ];
+          KillSignal = "SIGTERM";
+          AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+          DynamicUser = true;
+          StateDirectory = "endlessh";
+        };
+        wantedBy = ["multi-user.target"];
+      };
+
+      networking.firewall.allowedTCPPorts = [port];
+    };
+}
diff --git a/modules/nixfiles/fail2ban.nix b/modules/nixfiles/fail2ban.nix
new file mode 100644
index 0000000..56670e7
--- /dev/null
+++ b/modules/nixfiles/fail2ban.nix
@@ -0,0 +1,32 @@
+{
+  config,
+  lib,
+  this,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.fail2ban;
+in {
+  options.nixfiles.modules.fail2ban.enable =
+    mkEnableOption "Whether to enable fail2ban service.";
+
+  config = mkIf cfg.enable {
+    services.fail2ban = {
+      enable = true;
+
+      bantime-increment = {
+        enable = true;
+        maxtime = "24h";
+        rndtime = "8m";
+      };
+
+      ignoreIP =
+        optionals (hasAttr "wireguard" this)
+        (with config.nixfiles.modules.wireguard; [ipv4.subnet ipv6.subnet]);
+
+      jails.DEFAULT = ''
+        blocktype = DROP
+      '';
+    };
+  };
+}
diff --git a/modules/nixfiles/firefox/default.nix b/modules/nixfiles/firefox/default.nix
new file mode 100644
index 0000000..d7afdae
--- /dev/null
+++ b/modules/nixfiles/firefox/default.nix
@@ -0,0 +1,52 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.firefox;
+in {
+  options.nixfiles.modules.firefox.enable = mkEnableOption "";
+
+  config = mkIf cfg.enable {
+    hm = {
+      home = {
+        # sessionVariables.BROWSER = mkOverride 200 "firefox";
+
+        packages = with pkgs; [
+          (writeShellScriptBin "firefox-vanilla" ''
+            ${config.hm.programs.firefox.package}/bin/firefox -p vanilla $@
+          '')
+          profile-cleaner
+        ];
+      };
+
+      programs.firefox = {
+        enable = true;
+
+        package = pkgs.firefox.override {
+          cfg = with config.nixfiles.modules; {
+            enablePlasmaBrowserIntegration = kde.enable;
+            enableGnomeExtensions = gnome.enable;
+          };
+        };
+
+        profiles.default = (import ./profile.nix) config.nixfiles.modules lib;
+
+        extensions = with pkgs.nur.repos.rycee.firefox-addons;
+          [
+            bitwarden
+            darkreader
+            ipfs-companion
+            noscript
+            privacy-redirect
+            stylus
+            ublock-origin
+            violentmonkey
+          ]
+          ++ optional config.nixfiles.modules.kde.enable plasma-integration;
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/firefox/profile.nix b/modules/nixfiles/firefox/profile.nix
new file mode 100644
index 0000000..3382b85
--- /dev/null
+++ b/modules/nixfiles/firefox/profile.nix
@@ -0,0 +1,479 @@
+modules: lib:
+with lib; let
+  mkCssWithRoot = css:
+    mkMerge [
+      (with modules.profiles.common.colourScheme; ''
+        :root {
+            --black: ${black};
+            --red: ${red};
+            --green: ${green};
+            --yellow: ${yellow};
+            --blue: ${blue};
+            --magenta: ${magenta};
+            --cyan: ${cyan};
+            --white: ${white};
+            --bright-black: ${brightBlack};
+            --bright-red: ${brightRed};
+            --bright-green: ${brightGreen};
+            --bright-yellow: ${brightYellow};
+            --bright-blue: ${brightBlue};
+            --bright-magenta: ${brightMagenta};
+            --bright-cyan: ${brightCyan};
+            --bright-white: ${brightWhite};
+            --background: ${background};
+            --foreground: ${foreground};
+      '')
+      (with modules.fonts.fontScheme; ''
+            --sans-serif-font-family: "${sansSerifFont.family}", "${sansSerifFontFallback.family}", sans-serif;
+            --sans-serif-font-size: ${toString sansSerifFont.size};
+            --serif-font-family: "${serifFont.family}", "${serifFontFallback.family}", serif;
+            --serif-font-size: ${toString serifFont.size};
+            --monospace-font-family: "${monospaceFont.family}", "${monospaceFontFallback.family}", monospace;
+            --monospace-font-size: ${toString monospaceFont.size};
+        }
+      '')
+      (builtins.readFile css)
+    ];
+in {
+  id = 0;
+
+  isDefault = true;
+
+  userChrome = mkCssWithRoot ./userChrome.css;
+
+  userContent = mkCssWithRoot ./userContent.css;
+
+  settings = {
+    # Updates
+    #
+    "app.update.auto" = false;
+    "browser.search.update" = false;
+    "extensions.update.enabled" = false;
+    "extensions.update.autoUpdateDefault" = false;
+    #
+    "extensions.getAddons.cache.enabled" = false;
+    "extensions.getAddons.showPane" = false;
+
+    # Telemetry
+    #
+    # https://bugzilla.mozilla.org/1195552
+    "datareporting.healthreport.service.enabled" = false;
+    "datareporting.healthreport.uploadEnabled" = false;
+    "datareporting.policy.dataSubmissionEnabled" = false;
+    # https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/internals/preferences.html
+    # https://medium.com/georg-fritzsche/data-preference-changes-in-firefox-58-2d5df9c428b5
+    "toolkit.telemetry.unified" = false;
+    "toolkit.telemetry.server" = "data:,";
+    "toolkit.telemetry.archive.enabled" = false;
+    "toolkit.telemetry.newProfilePing.enabled" = false;
+    "toolkit.telemetry.shutdownPingSender.enabled" = false;
+    "toolkit.telemetry.firstShutdownPing.enabled" = false;
+    "toolkit.telemetry.updatePing.enabled" = false;
+    "toolkit.telemetry.bhrPing.enabled" = false; # Background Hang Reporter
+    # https://blog.mozilla.org/data/2018/08/20/effectively-measuring-search-in-firefox
+    "toolkit.telemetry.coverage.opt-out" = true;
+    "toolkit.coverage.opt-out" = true;
+    "toolkit.coverage.endpoint.base" = "";
+
+    # Studies
+    #
+    # https://support.mozilla.org/en-US/kb/shield/
+    "app.shield.optoutstudies.enabled" = false;
+    # https://mozilla.github.io/normandy/
+    "app.normandy.enabled" = false;
+    "app.normandy.api_url" = "";
+
+    # Crash reports
+    #
+    "browser.tabs.crashReporting.sendReport" = false;
+    "breakpad.reportURL" = "";
+
+    # Captive Portal detection
+    #
+    # https://www.eff.org/deeplinks/2017/08/how-captive-portals-interfere-wireless-security-and-privacy
+    "captivedetect.canonicalURL" = "";
+    "network.captive-portal-service.enabled" = false;
+    # https://bugzilla.mozilla.org/1460537
+    "network.connectivity-service.enabled" = false;
+
+    # Safe browsing
+    #
+    # https://feeding.cloud.geek.nz/posts/how-safe-browsing-works-in-firefox/
+    # https://wiki.mozilla.org/Security/Safe_Browsing
+    # https://support.mozilla.org/kb/how-does-phishing-and-malware-protection-work
+    "browser.safebrowsing.malware.enabled" = false;
+    "browser.safebrowsing.passwords.enabled" = false;
+    "browser.safebrowsing.phishing.enabled" = false;
+    "browser.safebrowsing.downloads.enabled" = false;
+    "browser.safebrowsing.downloads.remote.enabled" = false;
+    "browser.safebrowsing.downloads.remote.url" = "";
+    "browser.safebrowsing.downloads.remote.block_potentially_unwanted" = false;
+    "browser.safebrowsing.downloads.remote.block_uncommon" = false;
+
+    # Implicit outbound
+    #
+    # https://developer.mozilla.org/docs/Web/HTTP/Link_prefetching_FAQ
+    "network.prefetch-next" = false;
+    # https://developer.mozilla.org/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control
+    "network.dns.disablePrefetch" = true;
+    "network.dns.disablePrefetchFromHTTPS" = true;
+    #
+    "network.predictor.enabled" = false;
+    "network.predictor.enable-prefetch" = false;
+    # https://news.slashdot.org/story/15/08/14/2321202/how-to-quash-firefoxs-silent-requests
+    "network.http.speculative-parallel-limit" = 0;
+    # https://www.bleepingcomputer.com/news/software/major-browsers-to-prevent-disabling-of-click-tracking-privacy-risk/
+    "browser.send_pings" = false;
+    # https://trac.torproject.org/projects/tor/wiki/doc/TorifyHOWTO/WebBrowsers
+    "network.proxy.socks_remote_dns" = true;
+    # https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/26424
+    "network.file.disable_unc_paths" = true;
+
+    # Proxy
+    #
+    # https://blog.mozilla.org/security/2021/10/25/securing-the-proxy-api-for-firefox-add-ons/
+    "network.proxy.failover_direct" = false;
+    # https://bugzilla.mozilla.org/buglist.cgi?bug_id=1732792,1733994,1733481
+    "network.proxy.allow_bypass" = false;
+
+    # DNS-over-HTTPS (DoH)
+    #
+    # https://hacks.mozilla.org/2018/05/a-cartoon-intro-to-dns-over-https/
+    # https://wiki.mozilla.org/Security/DOH-resolver-policy
+    # https://blog.mozilla.org/mozilla/news/firefox-by-default-dns-over-https-rollout-in-canada/
+    # https://www.eff.org/deeplinks/2020/12/dns-doh-and-odoh-oh-my-year-review-2020 ***/
+    "network.trr.mode" = 5;
+
+    # Location bar & search
+    #
+    "browser.fixup.alternate.enabled" = false;
+    #
+    "browser.urlbar.trimURLs" = false;
+    #
+    "browser.search.suggest.enabled" = false;
+    "browser.urlbar.suggest.searches" = false;
+    # https://bugzilla.mozilla.org/1348275
+    "browser.urlbar.speculativeConnect.enabled" = false;
+    # https://bugzilla.mozilla.org/1642623
+    "browser.urlbar.dnsResolveSingleWordsAfterSearch" = 0;
+    # https://blog.mozilla.org/data/2021/09/15/data-and-firefox-suggest/
+    "browser.urlbar.suggest.quicksuggest.nonsponsored" = false;
+    "browser.urlbar.suggest.quicksuggest.sponsored" = false;
+
+    # Search & form history
+    # https://blog.mindedsecurity.com/2011/10/autocompleteagain.html
+    # https://bugzilla.mozilla.org/381681
+    "browser.formfill.enable" = false;
+    # https://wiki.mozilla.org/Firefox/Features/Form_Autofill
+    "extensions.formautofill.available" = "off";
+    "extensions.formautofill.addresses.enabled" = false;
+    "extensions.formautofill.creditCards.available" = false;
+    "extensions.formautofill.creditCards.enabled" = false;
+    "extensions.formautofill.heuristics.enabled" = false;
+
+    # Passwords
+    #
+    "signon.rememberSignons" = false;
+
+    # Disk avoidance
+    #
+    # Disable caching to disk.
+    # This is a possible performance hit. Could be removed in favor of PSD?
+    "browser.cache.disk.enable" = false;
+    #
+    "browser.privatebrowsing.forceMediaMemoryCache" = true;
+    "media.memory_cache_max_size" = 65536;
+    # Extra session data
+    "browser.sessionstore.privacy_level" = 2;
+    # Reduce writes
+    "browser.sessionstore.interval" = 30000;
+
+    # SSL/TLS
+    #
+    # Block connections (SSL_ERROR_UNSAFE_NEGOTIATION) to servers that don't support RFC 5746
+    # https://wiki.mozilla.org/Security:Renegotiation
+    # https://datatracker.ietf.org/doc/html/rfc5746
+    # https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-3555
+    # https://www.ssllabs.com/ssl-pulse
+    "security.ssl.require_safe_negotiation" = true;
+    # Disable TLS1.3 0-RTT
+    # https://github.com/tlswg/tls13-spec/issues/1001
+    # https://www.rfc-editor.org/rfc/rfc9001.html#name-replay-attacks-with-0-rtt
+    # https://blog.cloudflare.com/tls-1-3-overview-and-q-and-a/
+    "security.tls.enable_0rtt_data" = false;
+
+    # OCSP (Online Certificate Status Protocol)
+    #
+    # https://en.wikipedia.org/wiki/Ocsp
+    # https://scotthelme.co.uk/revocation-is-broken/
+    # https://blog.mozilla.org/security/2013/07/29/ocsp-stapling-in-firefox/
+    # https://www.imperialviolet.org/2014/04/19/revchecking.html
+    # "security.OCSP.require" = true;
+
+    # HPKP (HTTP Public Key Pinning)
+    #
+    # https://blog.mozilla.org/security/2016/10/18/phasing-out-sha-1-on-the-public-web/
+    "security.pki.sha1_enforcement_level" = 1;
+    # https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/16206
+    "security.cert_pinning.enforcement_level" = 2;
+    # https://bugzilla.mozilla.org/buglist.cgi?bug_id=1429800,1670985
+    # https://blog.mozilla.org/security/tag/crlite/
+    "security.remote_settings.crlite_filters.enabled" = true;
+    "security.pki.crlite_mode" = 2;
+
+    # Mixed content
+    #
+    "security.mixed_content.block_display_content" = true;
+    # Force HTTPS-only mode
+    # Exceptions are managed on the settings page
+    "dom.security.https_only_mode" = true;
+    "dom.security.https_only_mode_pbm" = true;
+    # https://bugzilla.mozilla.org/buglist.cgi?bug_id=1642387,1660945
+    "dom.security.https_only_mode_send_http_background_request" = false;
+
+    # UI
+    #
+    # Dispaly warning on the padlock for "broken security"
+    # https://wiki.mozilla.org/Security:Renegotiation
+    # https://bugzilla.mozilla.org/1353705
+    "security.ssl.treat_unsafe_negotiation_as_broken" = true;
+    # https://github.com/pyllyukko/user.js/issues/210
+    "browser.ssl_override_behavior" = 1;
+    # Display advanced information on Insecure Connection warning pages
+    "browser.xul.error_pages.expert_bad_cert" = true;
+    # Display "Not Secure" text on HTTP sites
+    "security.insecure_connection_text.enabled" = true;
+    #
+    "browser.privatebrowsing.infoEnabled" = false;
+    "browser.privatebrowsing.infoTitleEnabled" = false;
+    "browser.privatebrowsing.promoEnabled" = false;
+    "browser.privatebrowsing.promoTitleEnabled" = false;
+    "browser.privatebrowsing.vpnpromourl" = "";
+
+    # UX
+    #
+    "browser.urlbar.decodeURLsOnCopy" = true;
+    #
+    "browser.tabs.closeWindowWithLastTab" = true;
+    #
+    "general.autoScroll" = true;
+    "general.smoothScroll" = true;
+
+    # Headers & referers
+    #
+    # "network.http.referer.XOriginPolicy" = 2;
+    # "network.http.referer.XOriginTrimmingPolicy" = 2;
+
+    # Containers
+    #
+    # https://wiki.mozilla.org/Security/Contextual_Identity_Project/Containers
+    # https://addons.mozilla.org/firefox/addon/temporary-containers/
+    # https://medium.com/@stoically/enhance-your-privacy-in-firefox-with-temporary-containers-33925cd6cd21
+    # https://github.com/stoically/temporary-containers/wiki
+    "privacy.userContext.enabled" = true;
+    "privacy.userContext.ui.enabled" = true;
+
+    # WebRTC
+    #
+    # Disable WebRTC
+    # https://browserleaks.com/webrtc
+    # https://groups.google.com/g/discuss-webrtc/c/6stQXi72BEU/m/2FwZd24UAQAJ
+    # https://datatracker.ietf.org/doc/html/draft-ietf-mmusic-mdns-ice-candidates#section-3.1.1
+    # "media.peerconnection.enabled" = false;
+
+    # DRM
+    #
+    # https://www.eff.org/deeplinks/2017/10/drms-dead-canary-how-we-just-lost-web-what-we-learned-it-and-what-we-need-do-next
+    "media.eme.enabled" = false;
+    # https://bugzilla.mozilla.org/show_bug.cgi?id=1451762#c55
+    "browser.eme.ui.enabled" = false;
+
+    # GMP (Gecko Media Plugins)
+    #
+    "media.gmp-provider.enabled" = false;
+
+    # Disable autoplay of HTML5 media
+    "media.autoplay.enabled" = false;
+    "media.autoplay.default" = 5;
+    "media.autoplay.blocking_policy" = 2;
+
+    # DOM (Document Object Model)
+    #
+    # Disable "Confirm you want to leave" dialog on page close
+    # https://developer.mozilla.org/docs/Web/Events/beforeunload
+    "dom.disable_beforeunload" = true;
+    # Prevent scripts from moving and resizing open windows
+    "dom.disable_window_move_resize" = true;
+    # Block popup windows
+    "dom.disable_open_during_load" = true;
+    # Limit events that can cause a popup
+    "dom.popup_allowed_events" = "click dblclick mousedown pointerdown";
+
+    # PDFjs
+    #
+    # "pdfjs.disabled" = true;
+    "pdfjs.enableScripting" = false;
+
+    # Developer Tools
+    #
+    "devtools.accessibility.enabled" = false;
+    "devtools.application.enabled" = false;
+    "devtools.chrome.enabled" = true;
+    "devtools.dom.enabled" = true;
+    "devtools.enabled" = true;
+    "devtools.inspector.enabled" = true;
+    "devtools.jsonview.enabled" = true;
+    "devtools.memory.enabled" = true;
+    "devtools.netmonitor.enabled" = true;
+    "devtools.performance.enabled" = false;
+    "devtools.storage.enabled" = true;
+    "devtools.styleeditor.enabled" = true;
+    # This will prevent working with XUL
+    # https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/16222
+    "devtools.debugger.enabled" = false;
+    "devtools.debugger.remote-enabled" = false;
+
+    # Downloads
+    #
+    "browser.download.useDownloadDir" = false;
+    "browser.download.alwaysOpenPanel" = false;
+    "browser.download.manager.addToRecentDocs" = false;
+    "browser.download.autohideButton" = false;
+
+    # Extensions
+    #
+    # https://mike.kaply.com/2012/02/21/understanding-add-on-scopes/
+    # https://archive.is/DYjAM
+    "extensions.enabledScopes" = 5;
+    "extensions.autoDisableScopes" = 15;
+    # https://bugzilla.mozilla.org/buglist.cgi?bug_id=1659530,1681331
+    "extensions.postDownloadThirdPartyPrompt" = false;
+    # Disable recommendations in about:addons panes
+    "extensions.htmlaboutaddons.recommendations.enabled" = false;
+    # Mozilla's trash
+    "extensions.pocket.enabled" = false;
+    "extensions.screenshots.disabled" = true;
+    "idenitity.fxaccounts.enabled" = false;
+    "reader.parse-on-load.enabled" = false;
+
+    # ETP (Encahnced Tracking Protection)
+    #
+    # https://blog.mozilla.org/security/2021/02/23/total-cookie-protection/
+    "browser.contentblocking.category" = "strict";
+    "privacy.partition.serviceWorkers" = true;
+
+    # Sanitize
+    #
+    "privacy.sanitize.sanitizeOnShutdown" = true;
+    "privacy.sanitize.timeSpan" = 0;
+    "privacy.clearOnShutdown.cache" = true;
+    "privacy.clearOnShutdown.cookies" = false;
+    "privacy.clearOnShutdown.downloads" = true;
+    "privacy.clearOnShutdown.formdata" = true;
+    "privacy.clearOnShutdown.history" = false;
+    "privacy.clearOnShutdown.offlineApps" = false;
+    "privacy.clearOnShutdown.sessions" = false;
+    "privacy.clearOnShutdown.siteSettings" = false;
+    "privacy.cpd.cache" = true;
+    "privacy.cpd.cookies" = false;
+    "privacy.cpd.downloads" = true;
+    "privacy.cpd.formdata" = true;
+    "privacy.cpd.history" = false;
+    "privacy.cpd.offlineApps" = false;
+    "privacy.cpd.passwords" = false;
+    "privacy.cpd.sessions" = false;
+    "privacy.cpd.siteSettings" = false;
+
+    # Fingerprinting
+    #
+    # https://bugzilla.mozilla.org/418986
+    # "privacy.resistFingerprinting" = true;
+    # https://bugzilla.mozilla.org/1448423
+    # "browser.startup.blankWindow" = false;
+
+    # Rice
+    #
+    "toolkit.legacyUserProfileCustomizations.stylesheets" = true;
+    #
+    "browser.startup.homepage" = "about:blank"; # TODO Custom?
+    "browser.startup.homepage_welcome_url" = "";
+    "browser.startup.homepage_welcome_url.additional" = "";
+    #
+    "browser.toolbars.bookmarks.visibility" = "never";
+    #
+    "browser.tabs.inTitlebar" = 1;
+    #
+    "browser.newtabpage.enabled" = false;
+    "browser.newtabpage.enhanced" = false;
+    "browser.newtabpage.activity-stream.default.sites" = "";
+    "browser.newtabpage.activity-stream.asrouter.disable-captive-portal-vpn-promo" =
+      true;
+    "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons" = false;
+    "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features" =
+      false;
+    "browser.newtabpage.activity-stream.discoverystream.enabled" = false;
+    "browser.newtabpage.activity-stream.feeds.discoverystreamfeed" = false;
+    "browser.newtabpage.activity-stream.feeds.places" = false;
+    "browser.newtabpage.activity-stream.feeds.sections" = false;
+    "browser.newtabpage.activity-stream.feeds.snippets" = false;
+    "browser.newtabpage.activity-stream.feeds.telemetry" = false;
+    "browser.newtabpage.activity-stream.feeds.topsites" = false;
+    "browser.newtabpage.activity-stream.showSearch" = false;
+    "browser.newtabpage.activity-stream.showSponsored" = false;
+    "browser.newtabpage.activity-stream.showSponsoredTopSites" = false;
+    "browser.newtabpage.activity-stream.telemetry" = false;
+    #
+    "layout.css.color-mix.enabled" = true;
+    #
+    "svg.context-properties.content.enabled" = true;
+
+    # Annoyances
+    #
+    "browser.aboutConfig.showWarning" = false;
+    "browser.disableResetPrompt" = true;
+    "browser.newtabpage.introShown" = true;
+    "browser.onboarding.enabled" = false;
+    "browser.shell.checkDefaultBrowser" = false;
+    "browser.tabs.warnOnClose" = false;
+    "browser.tabs.warnOnCloseOtherTabs" = false;
+    "browser.tabs.warnOnOpen" = false;
+    "browser.tabs.warnOnQuitShortcut" = false;
+    "full-screen-api.warning.delay" = 0;
+    "full-screen-api.warning.timeout" = 0;
+    "permissions.default.shortcuts" = 2;
+    "security.dialog_enable_delay" = 0;
+
+    # Geo
+    #
+    # Disable OS provider
+    "geo.provider.use_gpsd" = false;
+    # Use the Mozilla provider. Defaults to Google otherwise
+    "geo.provider.network.url" = "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%";
+
+    # Unsorted
+    #
+    "accessibility.force_disabled" = 1;
+    "beacon.enabled" = false;
+    "browser.helperApps.deleteTempFileOnExit" = true;
+    "browser.pagethumbnails.capturing_disabled" = true;
+    "browser.region.network.url" = "";
+    "browser.region.update.enabled" = false;
+    "browser.selfsupport.url" = "";
+    "browser.uitour.enabled" = false;
+    "browser.uitour.url" = "";
+    "intl.accept_languages" = "en-US, en";
+    "layout.spellcheckDefault" = 2;
+    "middlemouse.contentLoadURL" = false;
+    "permissions.delegation.enabled" = false;
+    "permissions.manager.defaultsUrl" = "";
+    "webchannel.allowObject.urlWhitelist" = "";
+    "network.manage-offline-status" = false;
+    "xpinstall.signatures.required" = false;
+
+    # Toolbar
+    #
+    "browser.uiCustomization.state" = ''
+      {"placements":{"widget-overflow-fixed-list":["ublock0_raymondhill_net-browser-action","_73a6fe31-595d-460b-a920-fcc0f8843232_-browser-action","_446900e4-71c2-419f-a6a7-df9c091e268b_-browser-action","_2e5ff8c8-32fe-46d0-9fc8-6b8986621f3c_-browser-action","_b7f9d2cd-d772-4302-8c3f-eb941af36f76_-browser-action","ipfs-firefox-addon_lidel_org-browser-action","addon_darkreader_org-browser-action","_7a7a4a92-a2a0-41d1-9fd7-1e92480d612d_-browser-action","_aecec67f-0d10-4fa7-b7c7-609a2db280cf_-browser-action"],"nav-bar":["back-button","forward-button","urlbar-container","save-to-pocket-button"],"toolbar-menubar":["menubar-items"],"TabsToolbar":["tabbrowser-tabs","new-tab-button","alltabs-button"],"PersonalToolbar":["personal-bookmarks"]},"seen":["addon_darkreader_org-browser-action","ipfs-firefox-addon_lidel_org-browser-action","plasma-browser-integration_kde_org-browser-action","ublock0_raymondhill_net-browser-action","_2e5ff8c8-32fe-46d0-9fc8-6b8986621f3c_-browser-action","_446900e4-71c2-419f-a6a7-df9c091e268b_-browser-action","_73a6fe31-595d-460b-a920-fcc0f8843232_-browser-action","_7a7a4a92-a2a0-41d1-9fd7-1e92480d612d_-browser-action","_aecec67f-0d10-4fa7-b7c7-609a2db280cf_-browser-action","_b7f9d2cd-d772-4302-8c3f-eb941af36f76_-browser-action","developer-button"],"dirtyAreaCache":["nav-bar","widget-overflow-fixed-list","toolbar-menubar","TabsToolbar","PersonalToolbar"],"currentVersion":17,"newElementCount":6}
+    '';
+  };
+}
diff --git a/modules/nixfiles/firefox/userChrome.css b/modules/nixfiles/firefox/userChrome.css
new file mode 100644
index 0000000..4d83391
--- /dev/null
+++ b/modules/nixfiles/firefox/userChrome.css
@@ -0,0 +1,156 @@
+@-moz-document url(chrome://browser/content/browser.xul), url(chrome://browser/content/browser.xhtml)
+{
+    :root {
+        --toolbarbutton-border-radius: 0 !important;
+        --tab-border-radius: 0 !important;
+        --tab-block-margin: 0 !important;
+        --arrowpanel-border-radius: 0 !important;
+    }
+
+    #PersonalToolbar toolbarbutton:not(:hover),
+    #bookmarks-toolbar-button:not(:hover) {
+        filter: grayscale(1) !important;
+    }
+
+    .titlebar-spacer {
+        display: none !important;
+    }
+
+    .tabbrowser-tab::after,
+    .tabbrowser-tab::before {
+        border: none !important;
+    }
+
+    #urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity,
+    #urlbar[pageproxystate="valid"] > #identity-box.chromeUI,
+    #urlbar[pageproxystate="valid"] > #identity-box.extensionPage,
+    #urlbar-display-box {
+        border: none !important;
+    }
+
+    .tab-close-button {
+        display: none !important;
+    }
+
+    #tabbrowser-tabs:not([movingtab])
+        > #tabbrowser-arrowscrollbox
+        > .tabbrowser-tab
+        > .tab-stack
+        > .tab-background[multiselected="true"],
+    #tabbrowser-tabs:not([movingtab])
+        > #tabbrowser-arrowscrollbox
+        > .tabbrowser-tab
+        > .tab-stack
+        > .tab-background[selected="true"] {
+        background-image: none !important;
+    }
+
+    #nav-bar:not([tabs-hidden="true"]) {
+        box-shadow: none;
+    }
+
+    #tabbrowser-tabs[haspinnedtabs]:not([positionpinnedtabs])
+        > #tabbrowser-arrowscrollbox
+        > .tabbrowser-tab[first-visible-unpinned-tab] {
+        margin-inline-start: 0 !important;
+    }
+
+    .tab-background {
+        border-right: 0px solid var(--black) !important;
+        margin-left: -4px !important;
+    }
+
+    .tabbrowser-tab:is([visuallyselected="true"], [multiselected])
+        > .tab-stack
+        > .tab-background {
+        box-shadow: none !important;
+    }
+
+    .tabbrowser-tab[last-visible-tab="true"] {
+        padding-inline-end: 0 !important;
+    }
+
+    #tabs-newtab-button {
+        padding-left: 0 !important;
+    }
+
+    #urlbar-input-container {
+        border: 3px solid var(--black) !important;
+    }
+
+    #urlbar[focused="true"] > #urlbar-background {
+        box-shadow: none !important;
+    }
+
+    #navigator-toolbox {
+        border: none !important;
+    }
+
+    .bookmark-item .toolbarbutton-icon {
+        display: none;
+    }
+
+    toolbarbutton.bookmark-item:not(.subviewbutton) {
+        min-width: 1.6em;
+    }
+
+    #back-button,
+    #forward-button,
+    #context-bookmarklink,
+    #context-inspect-a11y,
+    #context-navigation,
+    #context-openlinkinusercontext-menu,
+    #context-pocket,
+    #context-print-selection,
+    #context-savelink,
+    #context-savelinktopocket,
+    #context-savepage,
+    #context-searchselect,
+    #context-selectall,
+    #context-sendimage,
+    #context-sendlinktodevice,
+    #context-sendlinktodevice,
+    #context-sendpagetodevice,
+    #context-viewsource,
+    #context_bookmarkTab,
+    #context_closeTabOptions,
+    #context_moveTabOptions,
+    #context_reopenInContainer,
+    #context_selectAllTabs,
+    #context_sendTabToDevice {
+        display: none !important;
+    }
+
+    .identity-color-blue {
+        --identity-tab-color: var(--blue) !important;
+        --identity-icon-color: var(--blue) !important;
+    }
+    .identity-color-turquoise {
+        --identity-tab-color: var(--cyan) !important;
+        --identity-icon-color: var(--cyan) !important;
+    }
+    .identity-color-green {
+        --identity-tab-color: var(--green) !important;
+        --identity-icon-color: var(--green) !important;
+    }
+    .identity-color-yellow {
+        --identity-tab-color: var(--yellow) !important;
+        --identity-icon-color: var(--yellow) !important;
+    }
+    .identity-color-orange {
+        --identity-tab-color: var(--brightRed) !important;
+        --identity-icon-color: var(--brightRed) !important;
+    }
+    .identity-color-red {
+        --identity-tab-color: var(--red) !important;
+        --identity-icon-color: var(--red) !important;
+    }
+    .identity-color-pink {
+        --identity-tab-color: var(--brightMagenta) !important;
+        --identity-icon-color: var(--brightMagenta) !important;
+    }
+    .identity-color-purple {
+        --identity-tab-color: var(--magenta) !important;
+        --identity-icon-color: var(--magenta) !important;
+    }
+}
diff --git a/modules/nixfiles/firefox/userContent.css b/modules/nixfiles/firefox/userContent.css
new file mode 100644
index 0000000..8426dc3
--- /dev/null
+++ b/modules/nixfiles/firefox/userContent.css
@@ -0,0 +1,210 @@
+@-moz-document url(about:blank), url(about:home), url(about:newtab), url(about:privatebrowsing) {
+    html,
+    body {
+        background: var(--background) !important;
+    }
+
+    body {
+        display: none !important;
+    }
+}
+
+@-moz-document media-document(all) {
+    body {
+        background-image: none !important;
+        background-color: var(--background) !important;
+    }
+}
+
+@-moz-document url-prefix(https://gitlab.com/)
+{
+    code {
+        font-family: var(--monospace-font-family) !important;
+        font-size: var(--monospace-font-size) !important;
+    }
+}
+
+@-moz-document url-prefix(https://github.com/), url-prefix(https://gist.github.com/)
+{
+    .blob-num,
+    .blob-code-inner {
+        font-family: var(--monospace-font-family) !important;
+        font-size: var(--monospace-font-size) !important;
+    }
+}
+
+@-moz-document regexp("https:\/\/.*\.stackexchange\.com\/.*"), url-prefix(https://askubuntu.com/), url-prefix(https://serverfault.com/), url-prefix(https://stackoverflow.com/), url-prefix(https://superuser.com/)
+{
+    #footer,
+    #left-sidebar,
+    #noscript-warning,
+    #notify-container,
+    #post-form,
+    #sidebar,
+    .ai-start,
+    .bottom-notice,
+    .comment-user,
+    .d-flex.g4,
+    .d-flex.s-btn-group,
+    .js-add-link.comments-link,
+    .js-dismissable-hero,
+    .js-post-issue,
+    .js-show-link.comments-link,
+    .post-taglist,
+    .s-notice,
+    .s-topbar,
+    .site-header,
+    a.ws-nowrap {
+        display: none !important;
+    }
+
+    #question-header .question-hyperlink {
+        font-family: var(--sans-serif-font-family);
+    }
+
+    #mainbar {
+        width: 100% !important;
+    }
+
+    #content {
+        margin-top: -50px !important;
+        border-width: 0 0 0 0 !important;
+    }
+}
+
+@-moz-document regexp("https:\/\/habr\.com\/(ru|en)\/(article|company\/.*\/blog|post)\/.*") {
+    .Vue-Toastification__container,
+    .tm-article-presenter__meta,
+    .tm-article-snippet__labels,
+    .tm-article-sticky-panel,
+    .tm-base-layout__header,
+    .tm-block.tm-block_spacing-bottom,
+    .tm-block_spacing-around,
+    .tm-comment-footer,
+    .tm-comment__header,
+    .tm-footer,
+    .tm-footer-menu,
+    .tm-header,
+    .tm-notice,
+    .tm-page__header,
+    .tm-page__sidebar,
+    .tm-user-info,
+    vue-portal-target {
+        display: none !important;
+    }
+
+    * {
+        transition: none !important;
+    }
+
+    body {
+        font-family: var(--sans-serif-font-family) !important;
+        font-size: var(--sans-serif-font-size) !important;
+    }
+
+    #app {
+        height: auto !important;
+    }
+
+    .tm-article-presenter__footer,
+    .tm-page__main_has-sidebar {
+        margin-left: auto !important;
+        margin-right: auto !important;
+        max-width: 100% !important;
+    }
+
+    .tm-page {
+        padding: 0 !important;
+    }
+
+    .tm-comment__buttons {
+        margin: 0 !important;
+    }
+
+    code {
+        font-family: var(--monospace-font-family) !important;
+        font-size: var(--monospace-font-size) !important;
+    }
+}
+
+@-moz-document url-prefix(https://jisho.org)
+{
+    header,
+    footer {
+        display: none !important;
+    }
+}
+
+@-moz-document url-prefix(https://search.nixos.org)
+{
+    .search-sidebar,
+    footer.container {
+        display: none !important;
+    }
+}
+
+@-moz-document url-prefix(https://gog.com)
+{
+    .galaxy-section-wrapper {
+        display: none !important;
+    }
+}
+
+@-moz-document url-prefix(https://steamdb.info)
+{
+    #steamdb-extension-protip {
+        display: none !important;
+    }
+}
+
+@-moz-document regexp("https:\/\/\.*\.hh\.ru/.*") {
+    .HH-Supernova-Footer,
+    .index-dashboard-applicant__banners,
+    .notification-manager,
+    .resume-sidebar-background,
+    .supernova-overlay {
+        display: none !important;
+    }
+}
+
+@-moz-document url-prefix(https://utaten.com/lyric)
+{
+    :root {
+        --kana-font-size: 20px;
+        --furigana-font-size: 14px;
+    }
+
+    body {
+        font-family: var(--sans-serif-font-family) !important;
+        font-size: var(--kana-font-size) !important;
+    }
+
+    .rb {
+        font-size: var(--kana-font-size) !important;
+    }
+
+    .rt {
+        font-size: var(--furigana-font-size) !important;
+    }
+
+    #footer__area,
+    #reviews,
+    #sidebar,
+    .btn_sbs,
+    .gaugeWrap,
+    .lyricData,
+    .movie_contents,
+    .newLyricWorkFooter,
+    .path::before,
+    .recommendAdTag,
+    .shareArea,
+    .sideBySideBanner,
+    .topBanner,
+    header {
+        display: none !important;
+    }
+
+    #contents {
+        width: 100% !important;
+    }
+}
diff --git a/modules/nixfiles/flatpak.nix b/modules/nixfiles/flatpak.nix
new file mode 100644
index 0000000..0dec763
--- /dev/null
+++ b/modules/nixfiles/flatpak.nix
@@ -0,0 +1,13 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.flatpak;
+in {
+  options.nixfiles.modules.flatpak.enable =
+    mkEnableOption "Whether to enable Flatpak.";
+
+  config = mkIf cfg.enable {services.flatpak.enable = true;};
+}
diff --git a/modules/nixfiles/fonts.nix b/modules/nixfiles/fonts.nix
new file mode 100644
index 0000000..29e2acb
--- /dev/null
+++ b/modules/nixfiles/fonts.nix
@@ -0,0 +1,106 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.fonts;
+in {
+  imports = [
+    (mkAliasOptionModule ["fontScheme"] [
+      "nixfiles"
+      "modules"
+      "fonts"
+      "fontScheme"
+    ])
+  ];
+
+  options.nixfiles.modules.fonts = {
+    enable = mkEnableOption "Whether to enable fonts and their configurations.";
+
+    fontScheme = let
+      mkFont = {
+        family,
+        style,
+        size,
+      }: {
+        family = mkOption {
+          description = "Family of the font.";
+          type = types.str;
+          default = family;
+        };
+        style = mkOption {
+          description = "Style of the font.";
+          type = types.str;
+          default = style;
+        };
+        size = mkOption {
+          description = "Size of the font.";
+          type = types.int;
+          default = size;
+        };
+      };
+    in {
+      serifFont = mkFont {
+        family = "Iosevka Etoile";
+        style = "Regular";
+        size = 16;
+      };
+
+      serifFontFallback = mkFont {
+        family = "Sarasa Gothic J";
+        style = "Regular";
+        size = 16;
+      };
+
+      sansSerifFont = mkFont {
+        family = "Iosevka Aile";
+        style = "Regular";
+        size = 16;
+      };
+
+      sansSerifFontFallback = mkFont {
+        family = "Sarasa Gothic J";
+        style = "Regular";
+        size = 16;
+      };
+
+      monospaceFont = mkFont {
+        family = "Iosevka";
+        style = "Regular";
+        size = 16;
+      };
+
+      monospaceFontFallback = mkFont {
+        family = "Sarasa Mono J";
+        style = "Regular";
+        size = 16;
+      };
+    };
+  };
+
+  config = mkMerge [
+    (mkIf cfg.enable {
+      hm.fonts.fontconfig.enable = true;
+
+      fonts = {
+        fonts = with pkgs; [iosevka sarasa-gothic];
+
+        fontconfig = {
+          enable = true;
+
+          defaultFonts = {
+            monospace = ["Iosevka" "Sarasa Mono" "DejaVu Sans Mono" "Noto Sans Mono"];
+            sansSerif = ["Iosevka Aile" "Sarasa Gothic" "DejaVu Sans" "Noto Sans"];
+            serif = ["Iosevka Etoile" "Sarasa Gothic" "DejaVu Serif" "Noto Serif"];
+          };
+        };
+      };
+    })
+    (mkIf (!cfg.enable) {
+      hm.fonts.fontconfig.enable = mkForce false;
+      fonts.fontconfig.enable = mkForce false;
+    })
+  ];
+}
diff --git a/modules/nixfiles/games/default.nix b/modules/nixfiles/games/default.nix
new file mode 100644
index 0000000..532fc57
--- /dev/null
+++ b/modules/nixfiles/games/default.nix
@@ -0,0 +1,47 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.games;
+in {
+  imports = [
+    ./gamemode.nix
+    ./gog.nix
+    ./lutris.nix
+    ./mangohud.nix
+    ./minecraft.nix
+    ./steam-run.nix
+    ./steam.nix
+  ];
+
+  options.nixfiles.modules.games.enable32BitSupport =
+    mkEnableOption "Whether to enable support for games.";
+
+  config = mkIf cfg.enable32BitSupport {
+    services = {
+      jack.alsa.support32Bit = config.services.jack.alsa.enable;
+
+      pipewire.alsa.support32Bit = config.services.pipewire.alsa.enable;
+
+      xserver.inputClassSections = [
+        ''
+          Identifier "ds-touchpad"
+          Driver "libinput"
+          MatchProduct "Wireless Controller Touchpad"
+          Option "Ignore" "true"
+        ''
+      ];
+    };
+
+    hardware = {
+      opengl = mkIf config.hardware.opengl.enable {
+        extraPackages32 = config.hardware.opengl.extraPackages;
+        driSupport32Bit = config.hardware.opengl.driSupport;
+      };
+
+      pulseaudio.support32Bit = config.hardware.pulseaudio.enable;
+    };
+  };
+}
diff --git a/modules/nixfiles/games/gamemode.nix b/modules/nixfiles/games/gamemode.nix
new file mode 100644
index 0000000..a74b651
--- /dev/null
+++ b/modules/nixfiles/games/gamemode.nix
@@ -0,0 +1,13 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.games.gamemode;
+in {
+  options.nixfiles.modules.games.gamemode.enable =
+    mkEnableOption "Whether to enable GameMode.";
+
+  config = mkIf cfg.enable {programs.gamemode.enable = true;};
+}
diff --git a/modules/nixfiles/games/gog.nix b/modules/nixfiles/games/gog.nix
new file mode 100644
index 0000000..f1188a2
--- /dev/null
+++ b/modules/nixfiles/games/gog.nix
@@ -0,0 +1,19 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.games.gog;
+in {
+  options.nixfiles.modules.games.gog.enable =
+    mkEnableOption
+    "Whether to enable GOG clients and the ability to run GOG games.";
+
+  config = mkIf cfg.enable {
+    nixfiles.modules.games.steam-run.enable = true;
+
+    hm.home.packages = with pkgs; [lgogdownloader];
+  };
+}
diff --git a/modules/nixfiles/games/lutris.nix b/modules/nixfiles/games/lutris.nix
new file mode 100644
index 0000000..ec1eaa2
--- /dev/null
+++ b/modules/nixfiles/games/lutris.nix
@@ -0,0 +1,37 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.games.lutris;
+in {
+  options.nixfiles.modules.games.lutris.enable =
+    mkEnableOption "Whether to enable Lutris.";
+
+  config = mkIf cfg.enable {
+    nixfiles.modules.games = {
+      steam-run.enable = true;
+      gamemode.enable = true;
+    };
+
+    hm.home.packages = with pkgs; [
+      (lutris.override {
+        lutris-unwrapped = lutris-unwrapped.override {
+          wine = buildFHSUserEnv {
+            # We don't really need Wine because Lutris downloads required
+            # runtime files for us. This feature is more robust because you can
+            # juggle different versions without manually rebuilding anything
+            # because nixpkgs cache was pruned.
+            name = "empty";
+          };
+        };
+        steamSupport = false;
+        extraPkgs = _: [
+          driversi686Linux.mesa # Battle.net
+        ];
+      })
+    ];
+  };
+}
diff --git a/modules/nixfiles/games/mangohud.nix b/modules/nixfiles/games/mangohud.nix
new file mode 100644
index 0000000..21d5fd1
--- /dev/null
+++ b/modules/nixfiles/games/mangohud.nix
@@ -0,0 +1,13 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.games.mangohud;
+in {
+  options.nixfiles.modules.games.mangohud.enable =
+    mkEnableOption "Whether to enable MangoHud.";
+
+  config = mkIf cfg.enable {hm.programs.mangohud.enable = true;};
+}
diff --git a/modules/nixfiles/games/minecraft.nix b/modules/nixfiles/games/minecraft.nix
new file mode 100644
index 0000000..70a98ce
--- /dev/null
+++ b/modules/nixfiles/games/minecraft.nix
@@ -0,0 +1,15 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.games.minecraft;
+in {
+  options.nixfiles.modules.games.minecraft.enable =
+    mkEnableOption "Whether to enable Minecraft.";
+
+  config =
+    mkIf cfg.enable {hm.home.packages = with pkgs; [jre nixfiles.UltimMC];};
+}
diff --git a/modules/nixfiles/games/steam-run.nix b/modules/nixfiles/games/steam-run.nix
new file mode 100644
index 0000000..983cc77
--- /dev/null
+++ b/modules/nixfiles/games/steam-run.nix
@@ -0,0 +1,59 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.games.steam-run;
+in {
+  options.nixfiles.modules.games.steam-run = {
+    enable = mkEnableOption "Whether to enable native Steam runtime.";
+
+    quirks = {
+      mountandblade = mkEnableOption ''Fix "Mount & Blade: Warband" issues.'';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    nixfiles.modules = {
+      games = {
+        enable32BitSupport = true;
+        gamemode.enable = true;
+      };
+    };
+
+    hm.home.packages = with pkgs; [
+      (steam.override {
+        extraLibraries = _:
+          with cfg.quirks;
+            optionals mountandblade [
+              (glew.overrideAttrs (_: super: let
+                opname = super.pname;
+              in rec {
+                pname = "${opname}-mountandblade";
+                inherit (super) version;
+                src = fetchurl {
+                  url = "mirror://sourceforge/${opname}/${opname}-${version}.tgz";
+                  sha256 = "sha256-BN6R5+Z2MDm8EZQAlc2cf4gLq6ghlqd2X3J6wFqZPJU=";
+                };
+              }))
+              (fmodex.overrideAttrs (_: super: let
+                opname = super.pname;
+              in rec {
+                pname = "${opname}-mountandblade";
+                inherit (super) version;
+                installPhase = let
+                  libPath =
+                    makeLibraryPath [alsa-lib libpulseaudio stdenv.cc.cc];
+                in ''
+                  install -Dm755 api/lib/libfmodex64-${version}.so $out/lib/libfmodex64.so
+                  patchelf --set-rpath ${libPath} $out/lib/libfmodex64.so
+                '';
+              }))
+            ];
+      })
+      .run
+    ];
+  };
+}
diff --git a/modules/nixfiles/games/steam.nix b/modules/nixfiles/games/steam.nix
new file mode 100644
index 0000000..c1d471e
--- /dev/null
+++ b/modules/nixfiles/games/steam.nix
@@ -0,0 +1,21 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.games.steam;
+in {
+  options.nixfiles.modules.games.steam.enable =
+    mkEnableOption "Whether to enable Steam runtime.";
+
+  config = mkIf cfg.enable {
+    nixfiles.modules.games = {
+      enable32BitSupport = true;
+      gamemode.enable = true;
+    };
+
+    hm.home.packages = with pkgs; [steam];
+  };
+}
diff --git a/modules/nixfiles/git.nix b/modules/nixfiles/git.nix
new file mode 100644
index 0000000..9008c2a
--- /dev/null
+++ b/modules/nixfiles/git.nix
@@ -0,0 +1,125 @@
+{
+  config,
+  lib,
+  inputs,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.git;
+in {
+  options.nixfiles.modules.git.enable =
+    mkEnableOption "Whether to enable Git version control system.";
+
+  config = mkIf cfg.enable {
+    secrets = {
+      glab-cli-config = {
+        file = "${inputs.self}/secrets/glab-cli-config";
+        path = "${config.dirs.config}/glab-cli/config.yml";
+        owner = my.username;
+      };
+      gh-hosts = {
+        file = "${inputs.self}/secrets/gh-hosts";
+        path = "${config.dirs.config}/gh/hosts.yml";
+        owner = my.username;
+      };
+      hut = {
+        file = "${inputs.self}/secrets/hut";
+        path = "${config.dirs.config}/hut/config";
+        owner = my.username;
+      };
+    };
+
+    hm = {
+      home.packages = with pkgs; [glab hut];
+
+      programs = {
+        git = {
+          enable = true;
+
+          package = pkgs.git.override {
+            sendEmailSupport = true;
+            withSsh = true;
+          };
+
+          userName = my.fullname;
+          userEmail = my.email;
+          signing = {
+            inherit (my.pgp) key;
+            signByDefault = true;
+          };
+
+          extraConfig =
+            {
+              advice.detachedHead = false;
+              color.ui = true;
+              core.whitespace = "trailing-space";
+              diff = {
+                mnemonicPrefix = true;
+                renames = "copies";
+                submodule = "log";
+              };
+              init.defaultBranch = "master";
+              status.submoduleSummary = true;
+              github.user = my.username;
+              gitlab.user = my.username;
+            }
+            // mapAttrs'
+            (n: v: nameValuePair ''url "git@${v}:"'' {insteadOf = "${n}:";}) {
+              "bitbucket" = "bitbucket.com";
+              "codeberg" = "codeberg.org";
+              "github" = "github.com";
+              "gitlab" = "gitlab.com";
+              "sourcehut" = "git.sr.ht";
+            };
+
+          aliases = let
+            git = "${config.hm.programs.git.package}/bin/git";
+            curl = "${pkgs.curl}/bin/curl";
+          in {
+            fuck = "!${git} reset --hard && ${git} clean -fdx";
+            gud = ''commit -m "git gud"'';
+            wtc = "!${curl} -sq whatthecommit.com/index.txt | ${git} commit -F -";
+          };
+
+          # All helper tool/editor generated files should go here. This must be
+          # kept relatively clean and void of any tooling/project-specific
+          # residual files.
+          ignores = [
+            "*~"
+            ".ccls-cache/"
+            ".clangd/"
+            ".dir-locals.el"
+            ".gdb_history"
+            ".netrwhist"
+            "[._]*.s[a-v][a-z]"
+            "[._]*.sw[a-p]"
+            "[._]s[a-rt-v][a-z]"
+            "[._]ss[a-gi-z]"
+            "[._]sw[a-p]"
+            "\\#*\\#"
+            "compile_commands.json"
+            "cscope.*"
+            "vgcore.*"
+          ];
+        };
+
+        gh = {
+          enable = true;
+          settings.git_protocol = "ssh";
+        };
+
+        bash = {
+          shellAliases.gl = "${pkgs.glab}/bin/glab";
+          initExtra = mkAfter "_complete_alias gl __start_glab glab";
+        };
+      };
+
+      xdg.configFile."glab-cli/aliases.yml".text = generators.toYAML {} {
+        ci = "pipeline ci";
+        co = "mr checkout";
+        li = "ci lint";
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/gnome.nix b/modules/nixfiles/gnome.nix
new file mode 100644
index 0000000..2d0f6f6
--- /dev/null
+++ b/modules/nixfiles/gnome.nix
@@ -0,0 +1,65 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.gnome;
+in {
+  options.nixfiles.modules.gnome.enable =
+    mkEnableOption "Whether to enable the GNOME desktop environment.";
+
+  config = mkIf cfg.enable {
+    nixfiles.modules = {
+      gnupg.pinentry = mkForce "gnome";
+      sound.enable = true;
+      x11.enable = true;
+    };
+
+    services = {
+      xserver = {
+        enable = true;
+        desktopManager.gnome.enable = true;
+        displayManager.gdm = {
+          enable = true;
+          wayland = false;
+        };
+      };
+
+      gnome = {
+        core-os-services.enable = true;
+        core-shell.enable = true;
+        core-utilities.enable = false;
+        core-developer-tools.enable = false;
+        games.enable = false;
+
+        chrome-gnome-shell.enable = false;
+        gnome-initial-setup.enable = false;
+        gnome-online-accounts.enable = false;
+        gnome-remote-desktop.enable = false;
+        gnome-settings-daemon.enable = true;
+        gnome-user-share.enable = false;
+        rygel.enable = false;
+        tracker-miners.enable = false;
+        tracker.enable = false;
+      };
+
+      dleyna-renderer.enable = false;
+      dleyna-server.enable = false;
+    };
+
+    environment = {
+      gnome = {
+        excludePackages = with pkgs.gnome; [
+          geary
+          gnome-disk-utility
+          seahorse
+          sushi
+        ];
+      };
+
+      systemPackages = with pkgs; [pinentry-gnome];
+    };
+  };
+}
diff --git a/modules/nixfiles/gnupg.nix b/modules/nixfiles/gnupg.nix
new file mode 100644
index 0000000..16b8264
--- /dev/null
+++ b/modules/nixfiles/gnupg.nix
@@ -0,0 +1,91 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.gnupg;
+in {
+  options.nixfiles.modules.gnupg = {
+    enable = mkEnableOption "Whether to enable GnuPG.";
+
+    pinentry = mkOption {
+      description = "Name of a pinentry implementation.";
+      type = types.str;
+      default = with config.nixfiles.modules;
+        if kde.enable
+        then "qt"
+        else if gnome.enable
+        then "gnome"
+        else "curses";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    hm = {
+      programs.gpg = {
+        enable = true;
+
+        settings =
+          {
+            display-charset = "utf-8";
+            enable-progress-filter = true;
+            fixed-list-mode = true;
+            keyid-format = "0xlong";
+            no-comments = true;
+            no-emit-version = true;
+            no-greeting = true;
+            with-fingerprint = true;
+            throw-keyids = false;
+
+            use-agent = true;
+
+            armor = true;
+
+            no-random-seed-file = true;
+
+            list-options = "show-uid-validity";
+            verify-options = "show-uid-validity";
+          }
+          // (let
+            cipherAlgos = ["AES256" "AES192" "AES"];
+            compressionAlgos = ["ZLIB" "BZIP2" "ZIP" "Uncompressed"];
+            digestAlgos = ["SHA512" "SHA384" "SHA256" "SHA224"];
+
+            cs = concatStringsSep " ";
+          in {
+            default-preference-list =
+              cs (digestAlgos ++ cipherAlgos ++ compressionAlgos);
+
+            personal-cipher-preferences = cs cipherAlgos;
+            personal-compress-preferences = cs compressionAlgos;
+            personal-digest-preferences = cs digestAlgos;
+
+            s2k-cipher-algo = head cipherAlgos;
+            s2k-digest-algo = head digestAlgos;
+
+            digest-algo = head digestAlgos;
+            cert-digest-algo = head digestAlgos;
+          });
+      };
+
+      services.gpg-agent = {
+        enable = true;
+
+        enableSshSupport = true;
+        enableScDaemon = false;
+
+        defaultCacheTtl = 999999;
+        defaultCacheTtlSsh = 999999;
+        maxCacheTtl = 999999;
+        maxCacheTtlSsh = 999999;
+
+        grabKeyboardAndMouse = true;
+
+        sshKeys = [my.pgp.grip];
+
+        pinentryFlavor = cfg.pinentry;
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/gotify.nix b/modules/nixfiles/gotify.nix
new file mode 100644
index 0000000..b2bf3ae
--- /dev/null
+++ b/modules/nixfiles/gotify.nix
@@ -0,0 +1,66 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.gotify;
+in {
+  options.nixfiles.modules.gotify = {
+    enable = mkEnableOption "Whether to enable Gotify.";
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = "gotify.${config.networking.domain}";
+    };
+  };
+
+  config = let
+    db = "gotify";
+  in
+    mkIf cfg.enable {
+      nixfiles.modules = {
+        nginx = {
+          enable = true;
+          virtualHosts.${cfg.domain} = {
+            locations."/" = {
+              proxyPass = "http://127.0.0.1:${toString config.services.gotify.port}";
+              proxyWebsockets = true;
+            };
+          };
+        };
+        postgresql.enable = true;
+      };
+
+      services = {
+        gotify = {
+          enable = true;
+          port = 7665;
+        };
+
+        postgresql = {
+          ensureDatabases = [db];
+          ensureUsers = [
+            {
+              name = db;
+              ensurePermissions."DATABASE \"${db}\"" = "ALL PRIVILEGES";
+            }
+          ];
+        };
+      };
+
+      systemd.services.gotify-server = {
+        after = ["network-online.target" "postgresql.service"];
+        environment = {
+          GOTIFY_DATABASE_DIALECT = "postgres";
+          GOTIFY_DATABASE_CONNECTION = concatStringsSep " " [
+            "host=/run/postgresql"
+            "user=${db}"
+            "dbname=${db}"
+            "sslmode=disable"
+          ];
+        };
+      };
+    };
+}
diff --git a/modules/nixfiles/grafana.nix b/modules/nixfiles/grafana.nix
new file mode 100644
index 0000000..4340f04
--- /dev/null
+++ b/modules/nixfiles/grafana.nix
@@ -0,0 +1,84 @@
+{
+  config,
+  inputs,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.grafana;
+in {
+  options.nixfiles.modules.grafana = {
+    enable = mkEnableOption "Whether to enable Grafana.";
+
+    port = mkOption {
+      description = "Port.";
+      type = with types; port;
+      default = 30101;
+    };
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; nullOr str;
+      default = null;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    secrets = {
+      grafana-admin-password = {
+        file = "${inputs.self}/secrets/grafana-admin-password";
+        owner = "grafana";
+        group = "grafana";
+      };
+      grafana-key = {
+        file = "${inputs.self}/secrets/grafana-key";
+        owner = "grafana";
+        group = "grafana";
+      };
+    };
+
+    nixfiles.modules = {
+      nginx = {
+        enable = true;
+        virtualHosts.${cfg.domain}.locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}";
+      };
+      postgresql.enable = true;
+    };
+
+    services = let
+      db = "grafana";
+    in {
+      grafana = {
+        enable = true;
+
+        inherit (cfg) domain port;
+        protocol = "http";
+        addr = "127.0.0.1";
+
+        analytics.reporting.enable = false;
+
+        database = {
+          type = "postgres";
+          host = "/run/postgresql";
+          name = db;
+          user = db;
+        };
+
+        security = with config.secrets; {
+          secretKeyFile = grafana-key.path;
+          adminPasswordFile = grafana-admin-password.path;
+        };
+      };
+
+      postgresql = {
+        ensureDatabases = [db];
+        ensureUsers = [
+          {
+            name = db;
+            ensurePermissions."DATABASE \"${db}\"" = "ALL PRIVILEGES";
+          }
+        ];
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/htop.nix b/modules/nixfiles/htop.nix
new file mode 100644
index 0000000..9cb3e60
--- /dev/null
+++ b/modules/nixfiles/htop.nix
@@ -0,0 +1,57 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.htop;
+in {
+  options.nixfiles.modules.htop.enable =
+    mkEnableOption "Whether to enable htop.";
+
+  config = mkIf cfg.enable {
+    hm.programs.htop = {
+      enable = true;
+
+      settings = with config.hm.lib.htop; {
+        fields = with fields; [
+          PID
+          USER
+          PRIORITY
+          NICE
+          M_SIZE
+          M_RESIDENT
+          M_SHARE
+          STATE
+          PERCENT_CPU
+          PERCENT_MEM
+          TIME
+          COMM
+        ];
+        account_guest_in_cpu_meter = 1;
+        detailed_cpu_time = 0;
+        enable_mouse = 0;
+        find_comm_in_cmdline = 1;
+        header_margin = 1;
+        hide_function_bar = 1;
+        hide_kernel_threads = 1;
+        hide_userland_threads = 1;
+        highlight_base_name = 1;
+        highlight_changes = 0;
+        highlight_changes_delay_secs = 1;
+        highlight_deleted_exe = 1;
+        highlight_megabytes = 1;
+        highlight_threads = 1;
+        shadow_other_users = 1;
+        show_cpu_frequency = 1;
+        show_cpu_usage = 1;
+        show_program_path = 0;
+        show_thread_names = 0;
+        strip_exe_from_cmdline = 1;
+        tree_view = 1;
+        tree_view_always_by_pid = 1;
+        update_process_names = 1;
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/hydra.nix b/modules/nixfiles/hydra.nix
new file mode 100644
index 0000000..4161eb3
--- /dev/null
+++ b/modules/nixfiles/hydra.nix
@@ -0,0 +1,56 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.hydra;
+in {
+  options.nixfiles.modules.hydra = {
+    enable = mkEnableOption "Whether to enable Nginx.";
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = "hydra.${config.networking.domain}";
+    };
+
+    port = mkOption {
+      description = "Port.";
+      type = with types; port;
+      default = 7754;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    nixfiles.modules = {
+      nginx = {
+        enable = true;
+        virtualHosts.${cfg.domain}.locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}";
+      };
+      postgresql.enable = true;
+    };
+
+    services = let
+      db = "hydra";
+    in {
+      hydra = {
+        enable = true;
+        listenHost = "127.0.0.1";
+        inherit (cfg) port;
+        dbi = "dbi:Pg:dbname=${db};user=${db}";
+        hydraURL = cfg.domain;
+      };
+
+      postgresql = {
+        ensureDatabases = [db];
+        ensureUsers = [
+          {
+            name = db;
+            ensurePermissions."DATABASE \"${db}\"" = "ALL PRIVILEGES";
+          }
+        ];
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/ipfs.nix b/modules/nixfiles/ipfs.nix
new file mode 100644
index 0000000..501a07c
--- /dev/null
+++ b/modules/nixfiles/ipfs.nix
@@ -0,0 +1,168 @@
+{
+  config,
+  lib,
+  this,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.ipfs;
+
+  swarmDefaultPort = 4001;
+  apiDefaultPort = 5001;
+  gatewayDefaultPort = 6001;
+in {
+  options.nixfiles.modules.ipfs = {
+    enable = mkEnableOption "Whether to enable IPFS daemon.";
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = "ipfs.${config.networking.fqdn}";
+    };
+
+    swarmPort = mkOption {
+      description = "Swarm port.";
+      type = with types; port;
+      default =
+        if this.isHeadless
+        then swarmDefaultPort + 990
+        else swarmDefaultPort;
+    };
+
+    apiPort = mkOption {
+      description = "API port.";
+      type = with types; port;
+      default =
+        if this.isHeadless
+        then apiDefaultPort + 990
+        else apiDefaultPort;
+    };
+
+    gatewayPort = mkOption {
+      description = "Gateway port.";
+      type = with types; port;
+      default =
+        if this.isHeadless
+        then gatewayDefaultPort + 990
+        else gatewayDefaultPort;
+    };
+  };
+
+  config = mkIf cfg.enable (mkMerge [
+    {
+      services.ipfs = {
+        enable = true;
+
+        user = my.username;
+        inherit (config.my) group;
+
+        dataDir = "${config.my.home}/.ipfs";
+
+        swarmAddress = let
+          port = toString cfg.swarmPort;
+        in
+          if this.isHeadless
+          then [
+            "/ip4/127.0.0.1/tcp/${port}"
+            "/ip4/127.0.0.1/udp/${port}/quic"
+          ]
+          else [
+            "/ip4/0.0.0.0/tcp/${port}"
+            "/ip6/::/tcp/${port}"
+            "/ip4/0.0.0.0/udp/${port}/quic"
+            "/ip6/::/udp/${port}/quic"
+          ];
+        apiAddress = "/ip4/127.0.0.1/tcp/${toString cfg.apiPort}";
+        gatewayAddress = "/ip4/127.0.0.1/tcp/${toString cfg.gatewayPort}";
+
+        autoMigrate = true;
+        autoMount = true;
+        emptyRepo = true;
+        enableGC = true;
+
+        extraConfig = mkMerge [
+          (let
+            filterAddresses =
+              [
+                "/ip4/100.64.0.0/ipcidr/10"
+                "/ip4/169.254.0.0/ipcidr/16"
+                "/ip4/172.16.0.0/ipcidr/12"
+                "/ip4/192.0.0.0/ipcidr/24"
+                "/ip4/192.0.2.0/ipcidr/24"
+                "/ip4/192.168.0.0/ipcidr/16"
+                "/ip4/198.18.0.0/ipcidr/15"
+                "/ip4/198.51.100.0/ipcidr/24"
+                "/ip4/203.0.113.0/ipcidr/24"
+                "/ip4/240.0.0.0/ipcidr/4"
+                "/ip6/100::/ipcidr/64"
+                "/ip6/2001:2::/ipcidr/48"
+                "/ip6/2001:db8::/ipcidr/32"
+                "/ip6/fe80::/ipcidr/10"
+              ]
+              ++ optionals (!hasAttr "wireguard" this) [
+                "/ip4/10.0.0.0/ipcidr/8"
+                "/ip6/fc00::/ipcidr/7"
+              ];
+          in {
+            Addresses = with config.services.ipfs; {
+              # https://github.com/NixOS/nixpkgs/pull/165259
+              # I think this shit broke inheritance... Gotta test more and make
+              # a PR I guess.
+              API = apiAddress;
+              Gateway = gatewayAddress;
+              Swarm = swarmAddress;
+
+              NoAnnounce = filterAddresses;
+            };
+            Swarm.AddrFilters = filterAddresses;
+            API.HTTPHeaders.Access-Control-Allow-Methods = ["GET" "POST" "PUT"];
+          })
+          (mkIf this.isHeadful {
+            API.HTTPHeaders.Access-Control-Allow-Origin = ["*"];
+          })
+          (mkIf this.isHeadless {
+            API.HTTPHeaders.Access-Control-Allow-Origin = ["https://${cfg.domain}" "https://api.${cfg.domain}"];
+          })
+        ];
+      };
+
+      networking.firewall = rec {
+        allowedTCPPorts = [swarmDefaultPort];
+        allowedUDPPorts = allowedTCPPorts;
+      };
+    }
+    (mkIf this.isHeadless {
+      nixfiles.modules.nginx = {
+        enable = true;
+        virtualHosts = {
+          ${cfg.domain}.locations."/".proxyPass = "http://127.0.0.1:${toString cfg.gatewayPort}";
+          "swarm.${cfg.domain}" = {
+            serverName = cfg.domain;
+            listen = [
+              {
+                addr = "0.0.0.0";
+                port = swarmDefaultPort;
+              }
+              {
+                addr = "[::0]";
+                port = swarmDefaultPort;
+              }
+            ];
+            locations."/".proxyPass = "http://127.0.0.1:${toString cfg.swarmPort}";
+          };
+          "api.${cfg.domain}" = {
+            # TODO Redirect "/" to "/webui" but keep other endpoints.
+            locations."/" = {
+              proxyPass = "http://127.0.0.1:${toString cfg.apiPort}";
+              extraConfig = ''
+                if ($internal != 1) {
+                  return 403;
+                }
+              '';
+            };
+          };
+        };
+      };
+    })
+  ]);
+}
diff --git a/modules/nixfiles/kde.nix b/modules/nixfiles/kde.nix
new file mode 100644
index 0000000..571cb6d
--- /dev/null
+++ b/modules/nixfiles/kde.nix
@@ -0,0 +1,27 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.kde;
+in {
+  options.nixfiles.modules.kde.enable =
+    mkEnableOption "Whether to enable KDE Plasma 5.";
+
+  config = mkIf cfg.enable {
+    nixfiles.modules = {
+      gnupg.pinentry = mkForce "qt";
+      sound.enable = true;
+      x11.enable = true;
+    };
+
+    services.xserver = {
+      desktopManager.plasma5.enable = true;
+      displayManager.sddm.enable = true;
+    };
+
+    environment.systemPackages = with pkgs; [pinentry-qt];
+  };
+}
diff --git a/modules/nixfiles/libvirtd.nix b/modules/nixfiles/libvirtd.nix
new file mode 100644
index 0000000..5ce37f0
--- /dev/null
+++ b/modules/nixfiles/libvirtd.nix
@@ -0,0 +1,40 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.libvirtd;
+in {
+  options.nixfiles.modules.libvirtd.enable =
+    mkEnableOption "Wether to enable libvirtd.";
+
+  config = mkIf cfg.enable {
+    virtualisation.libvirtd = {
+      enable = true;
+
+      onBoot = "ignore";
+      onShutdown = "shutdown";
+
+      qemu = {
+        package = pkgs.qemu_kvm;
+        runAsRoot = false;
+
+        ovmf = {
+          enable = true;
+          packages = [pkgs.OVMFFull.fd];
+        };
+
+        swtpm = {
+          enable = false;
+          package = pkgs.swtpm-tpm2;
+        };
+      };
+    };
+
+    environment.systemPackages = with pkgs; [virt-manager qemu-utils];
+
+    my.extraGroups = ["libvirtd"];
+  };
+}
diff --git a/modules/nixfiles/lidarr.nix b/modules/nixfiles/lidarr.nix
new file mode 100644
index 0000000..54d1fe5
--- /dev/null
+++ b/modules/nixfiles/lidarr.nix
@@ -0,0 +1,27 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.lidarr;
+in {
+  options.nixfiles.modules.lidarr = {
+    enable = mkEnableOption "Whether to enable Lidarr.";
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = "lidarr.${config.networking.fqdn}";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    nixfiles.modules.nginx = {
+      enable = true;
+      virtualHosts.${cfg.domain}.locations."/".proxyPass = "http://127.0.0.1:8686";
+    };
+
+    services.lidarr.enable = true;
+  };
+}
diff --git a/modules/nixfiles/loki.nix b/modules/nixfiles/loki.nix
new file mode 100644
index 0000000..27217bd
--- /dev/null
+++ b/modules/nixfiles/loki.nix
@@ -0,0 +1,113 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.loki;
+in {
+  options.nixfiles.modules.loki = {
+    # TODO Figure out why this shit refuses to work with my configuraiton.
+    enable = mkEnableOption "Whether to enable Loki.";
+
+    port = mkOption {
+      description = "Port.";
+      type = with types; port;
+      default = 30171;
+    };
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = config.nixfiles.modules.monitoring.domain;
+    };
+
+    path = mkOption {
+      description = "Path.";
+      type = with types; str;
+      default = "/loki";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    nixfiles.modules.nginx = with cfg; {
+      enable = true;
+      virtualHosts.${domain}.locations.${path} = {
+        proxyPass = "http://127.0.0.1:${toString port}";
+        extraConfig = ''
+          if ($internal != 1) {
+            return 403;
+          }
+        '';
+      };
+    };
+
+    services.loki = {
+      enable = true;
+
+      configuration = rec {
+        auth_enabled = false;
+
+        server = rec {
+          http_listen_address = "127.0.0.1";
+          http_listen_port = cfg.port;
+          http_path_prefix = cfg.path;
+
+          grpc_listen_address = "127.0.0.1";
+          grpc_listen_port = http_listen_port + 1;
+
+          log_level = "warn";
+        };
+
+        common = rec {
+          path_prefix = "/var/lib/loki";
+          storage.filesystem = {
+            chunks_directory = "${path_prefix}/chunker";
+            rules_directory = "${path_prefix}/ruler";
+          };
+          replication_factor = 1;
+          instance_interface_names = ["lo"];
+          ring = {
+            instance_addr = "127.0.0.1";
+            kvstore.store = "inmemory";
+          };
+        };
+
+        ruler = {
+          rule_path = "${common.path_prefix}/ruler";
+          storage = {
+            type = "local";
+            local.directory =
+              pkgs.writeTextDir "ruler/ruler.yml"
+              (generators.toJSON {} {groups = [{name = "default";}];});
+          };
+        };
+
+        schema_config.configs = [
+          {
+            from = "2020-01-01";
+            store = "boltdb-shipper";
+            object_store = "filesystem";
+            schema = "v11";
+            index = {
+              prefix = "index_";
+              period = "24h";
+            };
+            chunks = {
+              prefix = "chunks_";
+              period = "24h";
+            };
+          }
+        ];
+
+        analytics.reporting_enabled = false;
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d /var/lib/loki 0700 loki loki - -"
+      "d /var/lib/loki/ruler 0700 loki loki - -"
+    ];
+  };
+}
diff --git a/modules/nixfiles/lxc.nix b/modules/nixfiles/lxc.nix
new file mode 100644
index 0000000..ea4d8a6
--- /dev/null
+++ b/modules/nixfiles/lxc.nix
@@ -0,0 +1,16 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.lxc;
+in {
+  options.nixfiles.modules.lxc.enable =
+    mkEnableOption "Whether to enable LXC/LXD.";
+
+  config = mkIf cfg.enable {
+    virtualisation.lxd.enable = true;
+    my.extraGroups = "lxd";
+  };
+}
diff --git a/modules/nixfiles/matrix/default.nix b/modules/nixfiles/matrix/default.nix
new file mode 100644
index 0000000..bd221c4
--- /dev/null
+++ b/modules/nixfiles/matrix/default.nix
@@ -0,0 +1 @@
+_: {imports = [./dendrite.nix ./element.nix ./synapse.nix];}
diff --git a/modules/nixfiles/matrix/dendrite.nix b/modules/nixfiles/matrix/dendrite.nix
new file mode 100644
index 0000000..4e40e97
--- /dev/null
+++ b/modules/nixfiles/matrix/dendrite.nix
@@ -0,0 +1,148 @@
+{
+  config,
+  lib,
+  inputs,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.matrix.dendrite;
+in {
+  options.nixfiles.modules.matrix.dendrite = {
+    enable = mkEnableOption "Whether to enable Dendrite Matrix server.";
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = config.networking.domain;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    secrets.dendrite-private-key = {
+      file = "${inputs.self}/secrets/dendrite-private-key";
+      mode = "0444"; # User is dynamic.
+    };
+
+    nixfiles.modules = {
+      nginx = {
+        enable = true;
+        virtualHosts.${cfg.domain}.locations = {
+          "/_matrix".proxyPass = "http://127.0.0.1:${toString config.services.dendrite.httpPort}";
+          "= /.well-known/matrix/server" = {
+            extraConfig = ''
+              add_header Content-Type application/json;
+            '';
+            return = "200 '${
+              generators.toJSON {} {"m.server" = "${cfg.domain}:443";}
+            }'";
+          };
+          "= /.well-known/matrix/client" = {
+            extraConfig = ''
+              add_header Content-Type application/json;
+              add_header Access-Control-Allow-Origin *;
+            '';
+            return = "200 '${
+              generators.toJSON {} {
+                "m.homeserver".base_url = "https://${cfg.domain}";
+              }
+            }'";
+          };
+        };
+      };
+      postgresql.enable = true;
+    };
+
+    services = let
+      name = "dendrite";
+      prefix = "${name}-";
+      databaseNames = [
+        "account"
+        "appservice"
+        "device"
+        "federation"
+        "key"
+        "media"
+        "mscs"
+        "room"
+        "sync"
+      ];
+      databaseList = forEach databaseNames (x: concatStrings [prefix x]);
+      databaseAttr = genAttrs databaseNames (x: concatStrings [prefix x]);
+
+      mkDatabaseConnection = database: "postgres://${name}@/${database}?sslmode=disable";
+    in {
+      dendrite = {
+        enable = true;
+        httpPort = 8008;
+        settings = {
+          global = {
+            server_name = cfg.domain;
+
+            private_key = config.secrets.dendrite-private-key.path;
+
+            disable_federation = false;
+          };
+
+          app_service_api.database.connection_string =
+            mkDatabaseConnection databaseAttr.appservice;
+
+          client_api = {
+            registration_disabled = true;
+            turn = {}; # TODO
+          };
+
+          federation_api.database.connection_string =
+            mkDatabaseConnection databaseAttr.federation;
+
+          key_server.database.connection_string =
+            mkDatabaseConnection databaseAttr.key;
+
+          media_api = {
+            database.connection_string =
+              mkDatabaseConnection databaseAttr.media;
+            base_path = "./media";
+          };
+
+          mscs.database.connection_string =
+            mkDatabaseConnection databaseAttr.mscs;
+
+          room_server.database.connection_string =
+            mkDatabaseConnection databaseAttr.room;
+
+          sync_api.database.connection_string =
+            mkDatabaseConnection databaseAttr.sync;
+
+          user_api = {
+            account_database.connection_string =
+              mkDatabaseConnection databaseAttr.account;
+            device_database.connection_string =
+              mkDatabaseConnection databaseAttr.device;
+          };
+
+          metrics.enabled = false; # TODO
+        };
+      };
+
+      postgresql = {
+        ensureDatabases = databaseList;
+        ensureUsers =
+          map (x: {
+            inherit name;
+            ensurePermissions."DATABASE \"${x}\"" = "ALL PRIVILEGES";
+          })
+          databaseList;
+      };
+    };
+
+    systemd.services.dendrite.serviceConfig.ExecStart =
+      mkForce
+      (concatStringsSep " " [
+        "${pkgs.dendrite}/bin/dendrite-monolith-server"
+        "--config /run/dendrite/dendrite.yaml"
+        "--http-bind-address 127.0.0.1:${
+          toString config.services.dendrite.httpPort
+        }"
+      ]);
+  };
+}
diff --git a/modules/nixfiles/matrix/element.nix b/modules/nixfiles/matrix/element.nix
new file mode 100644
index 0000000..79c9f97
--- /dev/null
+++ b/modules/nixfiles/matrix/element.nix
@@ -0,0 +1,59 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.matrix.element;
+in {
+  options.nixfiles.modules.matrix.element = {
+    enable = mkEnableOption "Whether to enable Element Matrix web interface.";
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; nullOr str;
+      default = "element.${config.networking.domain}";
+    };
+
+    homeserver = mkOption {
+      description = "Default Matrix homeserver.";
+      type = with types; str;
+      default = my.domain.azahi;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = with config.nixfiles.modules.matrix;
+          (synapse.enable || dendrite.enable) && !(!synapse.enable && !dendrite.enable);
+        message = "Synapse or Dendrite must be enabled";
+      }
+    ];
+
+    nixfiles.modules.nginx = with cfg; {
+      enable = true;
+      virtualHosts.${domain}.locations."/".root = pkgs.element-web.override {
+        conf = {
+          default_server_config."m.homeserver" = {
+            base_url = "https://${homeserver}";
+            server_name = homeserver;
+          };
+          disable_custom_urls = true;
+          disable_guests = true;
+          disable_login_language_selector = true;
+          disable_3pid_login = true;
+          brand = homeserver;
+          branding.authFooterLinks = [
+            {
+              text = "Hosted on NixOS";
+              url = "https://nixos.org";
+            }
+          ];
+          default_theme = "dark";
+        };
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/matrix/synapse.nix b/modules/nixfiles/matrix/synapse.nix
new file mode 100644
index 0000000..cde5f8e
--- /dev/null
+++ b/modules/nixfiles/matrix/synapse.nix
@@ -0,0 +1,90 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.matrix.synapse;
+in {
+  options.nixfiles.modules.matrix.synapse = {
+    enable = mkEnableOption "Whether to enable Synapse Matrix server.";
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = config.networking.domain;
+    };
+  };
+
+  config = let
+    bind_address = "127.0.0.1";
+    port = 8448;
+  in
+    mkIf cfg.enable {
+      nixfiles.modules = {
+        nginx = {
+          enable = true;
+          virtualHosts.${cfg.domain}.locations = {
+            "~ ^(/_matrix|/_synapse/client)".proxyPass = "http://${bind_address}:${toString port}";
+            "= /.well-known/matrix/server" = {
+              extraConfig = ''
+                add_header Content-Type application/json;
+              '';
+              return = "200 '${
+                generators.toJSON {} {"m.server" = "${cfg.domain}:443";}
+              }'";
+            };
+            "= /.well-known/matrix/client" = {
+              extraConfig = ''
+                add_header Content-Type application/json;
+                add_header Access-Control-Allow-Origin *;
+              '';
+              return = "200 '${
+                generators.toJSON {} {
+                  "m.homeserver".base_url = "https://${cfg.domain}";
+                }
+              }'";
+            };
+          };
+        };
+        postgresql.enable = true;
+      };
+
+      services = let
+        db = "synapse";
+      in {
+        matrix-synapse = {
+          enable = true;
+          server_name = config.networking.domain;
+
+          database_type = "psycopg2";
+          database_name = db;
+          database_user = db;
+
+          listeners = [
+            {
+              inherit bind_address port;
+              type = "http";
+              tls = false;
+              x_forwarded = true;
+              resources = [
+                {
+                  names = ["client" "federation"];
+                  compress = false;
+                }
+              ];
+            }
+          ];
+        };
+
+        postgresql.initialScript = pkgs.writeText "init-matrix-synapse.sql" ''
+          CREATE USER "${db}";
+          CREATE DATABASE "${db}" WITH OWNER "${db}"
+            TEMPLATE template0
+            LC_COLLATE = "C"
+            LC_CTYPE = "C";
+        '';
+      };
+    };
+}
diff --git a/modules/nixfiles/monitoring.nix b/modules/nixfiles/monitoring.nix
new file mode 100644
index 0000000..6db74d4
--- /dev/null
+++ b/modules/nixfiles/monitoring.nix
@@ -0,0 +1,114 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.monitoring;
+in {
+  options.nixfiles.modules.monitoring = {
+    enable = mkEnableOption ''
+      Whether to enable custom monitoring stack.
+
+      Currently this configures and enables Grafana, Loki, Prometheus and
+      Alertmanager.
+    '';
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; nullOr str;
+      default = "monitoring.${config.networking.domain}";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    nixfiles.modules = {
+      grafana = {
+        enable = true;
+        inherit (cfg) domain;
+      };
+      loki = {
+        enable = true;
+        inherit (cfg) domain;
+      };
+      prometheus = {
+        enable = true;
+        inherit (cfg) domain;
+      };
+      alertmanager = {
+        enable = true;
+        inherit (cfg) domain;
+      };
+    };
+
+    services = {
+      grafana.provision = {
+        enable = true;
+        datasources = with config.nixfiles.modules; [
+          {
+            name = "Prometheus";
+            type = "prometheus";
+            access = "proxy";
+            url = with prometheus; "https://${domain}${path}";
+            isDefault = true;
+          }
+          {
+            name = "Loki";
+            type = "loki";
+            access = "proxy";
+            url = with loki; "https://${domain}${path}";
+          }
+        ];
+        # TODO Move dashboards to this repository.
+        dashboards = [
+          {
+            name = "system";
+            options.path = pkgs.fetchurl {
+              url = "https://gist.githubusercontent.com/azahi/b8951223e6850d88159b0c34749a20aa/raw/00c6928374b5d231dc3afe617165550868b8c233/System-1645616603173.json";
+              sha256 = "sha256-vGlCOHT5Rp1K88Z8lkLGkvTDeFpgi967CSOb/797vwY=";
+            };
+          }
+          {
+            name = "endlessh-go";
+            options.path = pkgs.fetchurl {
+              url = "https://gist.githubusercontent.com/azahi/fd917e917fb53deacf3c6d7366bae2b2/raw/796530bd899e9328e423d18a462faf3c7d01c823/endlessh.json";
+              sha256 = "sha256-/wJDiFlrHEa1f82pDFtG/T2GRKGlAur0dlQ8eeVJ3m4=";
+            };
+          }
+          {
+            name = "unbound";
+            options.path = pkgs.fetchurl {
+              url = "https://gist.githubusercontent.com/azahi/73fc27e22acc9a01898b2d3e21150aac/raw/37d03bc51253583d9265d6f2189242003092261e/Unbound-1645616052811.json";
+              sha256 = "sha256-PXDCCa5qOg/Hj9CBId0OVUYmPlOjS05NxhDQ6zWxGp0=";
+            };
+          }
+          {
+            name = "nginx";
+            options.path = pkgs.fetchurl {
+              url = "https://raw.githubusercontent.com/nginxinc/nginx-prometheus-exporter/452953738bfcb8aac02ce6e6ac32f53479cad2aa/grafana/dashboard.json";
+              sha256 = "sha256-HRDWsSQeLzybaJ1g5xZqtmhf8JPtGhv/6TD1aFsgJIw=";
+            };
+          }
+          {
+            name = "postgersql";
+            options.path = pkgs.fetchurl {
+              url = "https://raw.githubusercontent.com/lstn/misc-grafana-dashboards/051873e103119ff0edf9886dcdc0c6c0a3ee6702/dashboards/postgresql-database.json";
+              sha256 = "sha256-awlj3BM3GaBBG/zJvxsVBRzoM8i6aY5dV34++DZFQHs=";
+            };
+          }
+        ];
+      };
+
+      loki.configuration.ruler.alertmanager_url = with config.nixfiles.modules.alertmanager; "https://${domain}${path}";
+
+      prometheus.alertmanagers = [
+        {
+          scheme = "https";
+          path_prefix = config.nixfiles.modules.alertmanager.path;
+          static_configs = [{targets = [cfg.domain];}];
+        }
+      ];
+    };
+  };
+}
diff --git a/modules/nixfiles/mpd.nix b/modules/nixfiles/mpd.nix
new file mode 100644
index 0000000..b2962ab
--- /dev/null
+++ b/modules/nixfiles/mpd.nix
@@ -0,0 +1,212 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.mpd;
+in {
+  options.nixfiles.modules.mpd.enable =
+    mkEnableOption "Wether to enable MPD and its clients.";
+
+  config = mkIf cfg.enable {
+    hm = {
+      home.packages = with pkgs; [mpc_cli];
+
+      services.mpd = {
+        enable = true;
+
+        extraConfig = ''
+          restore_paused "yes"
+
+          auto_update "no"
+
+          replaygain "album"
+          replaygain_preamp "0"
+          replaygain_limit "yes"
+
+          volume_normalization "no"
+
+          zeroconf_enabled "no"
+
+          audio_output {
+              type "pipewire"
+              name "piepwire"
+          }
+        '';
+      };
+
+      programs.ncmpcpp = {
+        enable = true;
+
+        settings = rec {
+          ncmpcpp_directory = "${config.hm.xdg.dataHome}/ncmpcpp";
+          lyrics_directory = "${ncmpcpp_directory}/lyrics";
+
+          playlist_disable_highlight_delay = 1;
+          message_delay_time = 1;
+
+          song_window_title_format = "{%a - }{%t}|{%f}";
+          song_list_format = "{$6%t}|{$2%f}$1 $R{$8%b}$1 {$5%a}";
+          song_columns_list_format = "(20)[red]{a} (30)[cyan]{b} (50)[blue]{t|f}";
+          song_status_format = "{{$8%a$9{ $b-$/b $6%b$9 {(%y)} } - } '{%t}}|{%f}'";
+          song_library_format = "{%n - }{%t}|{%f}";
+
+          now_playing_prefix = "$b";
+          now_playing_suffix = "$/b";
+
+          selected_item_prefix = "$0";
+          selected_item_suffix = "$9";
+          modified_item_prefix = "$3> $9";
+
+          browser_playlist_prefix = "$2playlist$9 ";
+          browser_sort_format = "{%a - }{%t}|{%f} {(%l)}";
+
+          playlist_show_mpd_host = false;
+          playlist_show_remaining_time = false;
+          playlist_shorten_total_times = false;
+          playlist_separate_albums = false;
+
+          playlist_display_mode = "classic";
+          browser_display_mode = "classic";
+          search_engine_display_mode = "classic";
+          playlist_editor_display_mode = "classic";
+
+          incremental_seeking = true;
+          seek_time = 1;
+
+          volume_change_step = 5;
+
+          autocenter_mode = true;
+          centered_cursor = true;
+
+          progressbar_look = "=*-";
+
+          default_place_to_search_in = "database";
+          search_engine_default_search_mode = 1;
+          data_fetching_delay = false;
+          media_library_primary_tag = "album_artist";
+          browser_sort_mode = "name";
+          default_find_mode = "wrapped";
+          default_tag_editor_pattern = "%n - %t";
+          empty_tag_marker = "<blank>";
+          tags_separator = " | ";
+          tag_editor_extended_numeration = true;
+          media_library_sort_by_mtime = false;
+          regular_expressions = "none";
+          block_search_constraints_change_if_items_found = true;
+
+          ignore_leading_the = true;
+
+          enable_window_title = false;
+
+          header_visibility = false;
+          statusbar_visibility = false;
+          titles_visibility = false;
+
+          display_volume_level = false;
+          display_bitrate = false;
+          display_remaining_time = false;
+
+          cyclic_scrolling = true;
+          lines_scrolled = 1;
+
+          follow_now_playing_lyrics = false;
+          fetch_lyrics_for_current_song_in_background = false;
+          store_lyrics_in_song_dir = false;
+
+          generate_win32_compatible_filenames = false;
+          allow_for_physical_item_deletion = false;
+          show_hidden_files_in_local_browser = false;
+
+          screen_switcher_mode = "playlist, browser";
+          startup_screen = "playlist";
+          startup_slave_screen = "";
+          startup_slave_screen_focus = false;
+          locked_screen_width_part = 50;
+          ask_for_locked_screen_width_part = true;
+
+          jump_to_now_playing_song_at_start = false;
+
+          ask_before_clearing_playlists = false;
+
+          clock_display_seconds = false;
+
+          mouse_support = false;
+
+          external_editor = "${config.programs.vim.package}/bin/vim";
+          use_console_editor = true;
+
+          colors_enabled = true;
+          discard_colors_if_item_is_selected = true;
+
+          empty_tag_color = "cyan";
+          header_window_color = "cyan";
+          volume_color = "cyan";
+          state_line_color = "cyan";
+          state_flags_color = "green";
+          main_window_color = "blue";
+          color1 = "cyan";
+          color2 = "red";
+          progressbar_color = "cyan";
+          progressbar_elapsed_color = "white";
+          statusbar_color = "yellow";
+          window_border_color = "green";
+          active_window_border = "red";
+        };
+
+        bindings = [
+          {
+            key = "j";
+            command = "scroll_down";
+          }
+          {
+            key = "k";
+            command = "scroll_up";
+          }
+          {
+            key = "J";
+            command = ["select_item" "scroll_down"];
+          }
+          {
+            key = "K";
+            command = ["select_item" "scroll_up"];
+          }
+          {
+            key = "h";
+            command = ["previous_column" "master_screen"];
+          }
+          {
+            key = "l";
+            command = ["next_column" "slave_screen"];
+          }
+          {
+            key = "g";
+            command = "move_home";
+          }
+          {
+            key = "G";
+            command = "move_end";
+          }
+          {
+            key = "d";
+            command = [
+              "delete_playlist_items"
+              "delete_browser_items"
+              "delete_stored_playlist"
+            ];
+          }
+          {
+            key = "L";
+            command = "show_lyrics";
+          }
+          {
+            key = "H";
+            command = "toggle_lyrics_fetcher";
+          }
+        ];
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/mpv.nix b/modules/nixfiles/mpv.nix
new file mode 100644
index 0000000..68628c5
--- /dev/null
+++ b/modules/nixfiles/mpv.nix
@@ -0,0 +1,133 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.mpv;
+in {
+  options.nixfiles.modules.mpv.enable = mkEnableOption "Whether to enable MPV.";
+
+  config = mkIf cfg.enable {
+    hm.programs = {
+      mpv = {
+        enable = true;
+
+        package = with pkgs;
+          mpv-with-scripts.override {
+            scripts = with mpvScripts; [autoload mpv-autosub sponsorblock];
+          };
+
+        bindings = {
+          "RIGHT" = "seek 10";
+          "LEFT" = "seek -10";
+          "UP" = "seek 60";
+          "DOWN" = "seek -60";
+
+          "Shift+RIGHT" = "no-osd seek 1 exact";
+          "Shift+LEFT" = "no-osd seek -1 exact";
+          "Shift+UP" = "no-osd seek 5 exact";
+          "Shift+DOWN" = "no-osd seek -5 exact";
+
+          "Alt+k" = "add sub-scale +0.1";
+          "Alt+j" = "add sub-scale -0.1";
+
+          "B" = ''cycle-values background "#000000" "#ffffff"'';
+        };
+
+        profiles = {
+          "protocol.http".force-window = "immediate";
+          "protocol.https".profile = "protocol.http";
+        };
+
+        config = let
+          lang = comcat [
+            "Japanese"
+            "japanese"
+            "jp"
+            "jpn"
+            "jaJP"
+            "ja-JP"
+            "English"
+            "english"
+            "en"
+            "eng"
+            "enUS"
+            "en-US"
+            "Russian"
+            "russian"
+            "ru"
+            "rus"
+            "ruRU"
+            "ru-RU"
+          ];
+        in {
+          audio-display = "no";
+          autofit-larger = "100%x95%";
+          cursor-autohide = 1000;
+          force-seekable = "no";
+          fullscreen = true;
+          load-unsafe-playlists = true;
+          msg-color = true;
+          msg-module = true;
+          prefetch-playlist = true;
+          save-position-on-quit = false;
+          screenshot-format = "png";
+          screenshot-template = "%F [%p]";
+          stop-screensaver = true;
+          term-osd-bar = true;
+          use-filedir-conf = true;
+
+          osd-bar-align-y = 0;
+          osd-bar-h = 2;
+          osd-bar-w = 60;
+          osd-border-color = "#FF262626";
+          osd-border-size = 2.5;
+          osd-color = "#FFFFFFFF";
+          osd-duration = 2500;
+          osd-font-size = 40;
+          osd-fractions = true;
+          osd-level = 1;
+          osd-shadow-color = "#33000000";
+
+          osc = false;
+
+          sub-auto = "fuzzy";
+          sub-file-paths-append = "srt";
+          sub-ass-force-margins = true;
+          sub-ass-force-style = "kerning=yes";
+          sub-fix-timing = true;
+          sub-use-margins = true;
+          sub-font-size = 40;
+          sub-color = "#FFFFFFFF";
+          sub-border-color = "#FF262626";
+          sub-border-size = 2.5;
+          sub-shadow-offset = 1;
+          sub-shadow-color = "#33000000";
+          sub-spacing = 0.5;
+          blend-subtitles = true;
+
+          audio-file-auto = "fuzzy";
+          volume = 100;
+          volume-max = 200;
+
+          alang = lang;
+          slang = lang;
+
+          ytdl = true;
+          ytdl-raw-options = ''sub-lang="${lang}",write-sub='';
+          ytdl-format = "(bestvideo[height<=?1080][fps<=?60][protocol!=http_dash_segments])+(bestaudio[acodec=opus]/bestaudio)/best";
+        };
+      };
+
+      bash = {
+        shellAliases.cam = "${config.hm.programs.mpv.package}/bin/mpv av://v4l2:/dev/video0";
+
+        initExtra = mkAfter ''
+          _complete_alias cam _mpv mpv
+        '';
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/nextcloud.nix b/modules/nixfiles/nextcloud.nix
new file mode 100644
index 0000000..d63f824
--- /dev/null
+++ b/modules/nixfiles/nextcloud.nix
@@ -0,0 +1,133 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.nextcloud;
+in {
+  options.nixfiles.modules.nextcloud = {
+    enable = mkEnableOption "Whether to enable Nextcloud.";
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = "nextcloud.${config.networking.domain}";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    nixfiles.modules = {
+      nginx = {
+        enable = true;
+        virtualHosts.${cfg.domain} = {};
+      };
+      postgresql.enable = true;
+    };
+
+    services = let
+      db = "nextcloud";
+    in {
+      nextcloud = mkMerge [
+        {
+          enable = true;
+          package = pkgs.nextcloud23;
+
+          hostName = cfg.domain;
+
+          appstoreEnable = false;
+
+          config = {
+            adminpassFile = null; # This needs to be set as secret.
+
+            dbtype = "pgsql";
+            dbhost = "/run/postgresql";
+            dbuser = db;
+            dbname = db;
+
+            defaultPhoneRegion = "RU";
+          };
+
+          extraApps = let
+            mkNextcloudApp = {
+              name,
+              version,
+              sha256,
+            }:
+              pkgs.fetchNextcloudApp {
+                inherit name version sha256;
+                url = "https://github.com/nextcloud/${name}/archive/refs/tags/v${version}.tar.gz";
+              };
+          in {
+            contacts = mkNextcloudApp {
+              name = "contacts";
+              version = "4.0.1";
+              sha256 = "sha256-dXKsG8KmlUojeY5dUn/XsMD3KaSh4QcZFOGDdcqlSvE=";
+            };
+            calendar = mkNextcloudApp {
+              name = "calendar";
+              version = "3.0.5";
+              sha256 = "sha256-aKUKm7fWJQxOWwma56Tv+GGIo+p0n30Nhoyt4XoxsjI=";
+            };
+            files_rightclick = mkNextcloudApp {
+              name = "files_rightclick";
+              version = "23.0.1";
+              sha256 = "sha256-VYODzkvvGrtpyRoug/8UPKhAgfCx1ltP1JdGPiB/lts=";
+            };
+            unsplash = mkNextcloudApp {
+              name = "unsplash";
+              version = "1.2.4";
+              sha256 = "sha256-KGSkBOrNu0nK0YvAPYaxEL/kZNoJQD1oBV2aUBxh6cI=";
+            };
+            previewgenerator = mkNextcloudApp {
+              name = "previewgenerator";
+              version = "3.4.1";
+              sha256 = "sha256-IUdj0xWt5zHxQoiMv1bYyYTzekuOFrsRIe530QOwC/w=";
+            };
+            bruteforcesettings = mkNextcloudApp {
+              name = "bruteforcesettings";
+              version = "2.3.0";
+              sha256 = "sha256-J7ujmiPaw8GI7vDfVPXEum2XAMWvahciP8C6iXgckdE=";
+            };
+          };
+        }
+        (mkIf config.nixfiles.modules.acme.enable {
+          https = true;
+          config.overwriteProtocol = "https";
+        })
+      ];
+
+      postgresql = {
+        ensureDatabases = [db];
+        ensureUsers = [
+          {
+            name = db;
+            ensurePermissions."DATABASE \"${db}\"" = "ALL PRIVILEGES";
+          }
+        ];
+      };
+    };
+
+    systemd = {
+      services = {
+        nextcloud-setup.after = ["network-online.target" "postgresql.service"];
+
+        nextcloud-preview-generate-cron.serviceConfig = {
+          Type = "oneshot";
+          User = "nextcloud";
+          ExecStart = "${config.services.nextcloud.occ}/bin/nextcloud-occ preview:pre-generate";
+        };
+      };
+
+      timers.nextcloud-preview-generate = {
+        wantedBy = ["timers.target"];
+        timerConfig = {
+          OnBootSec = "15m";
+          OnUnitActiveSec = "15m";
+          Unit = "nextcloud-preview-generate-cron.service";
+        };
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/nginx.nix b/modules/nixfiles/nginx.nix
new file mode 100644
index 0000000..35f5098
--- /dev/null
+++ b/modules/nixfiles/nginx.nix
@@ -0,0 +1,90 @@
+{
+  config,
+  lib,
+  pkgs,
+  this,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.nginx;
+in {
+  options.nixfiles.modules.nginx = {
+    enable = mkEnableOption "Whether to enable Nginx.";
+
+    virtualHosts = mkOption {
+      description = "Attrset of virtual hosts.";
+      # Not sure how to "inherit" the type from the original Nixpkgs option.
+      # Just make sure it's compatible with service.nginx.virtualHosts, ok?
+      type = with types; anything;
+      default = null;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services = {
+      nginx = {
+        enable = true;
+        enableReload = true;
+
+        package = pkgs.nginxMainline;
+
+        statusPage = true;
+
+        recommendedGzipSettings = true;
+        recommendedOptimisation = true;
+        recommendedProxySettings = true;
+        recommendedTlsSettings = true;
+
+        commonHttpConfig = concatStrings [
+          ''
+            add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive";
+          ''
+          (optionalString (hasAttr "wireguard" this)
+            (with config.nixfiles.modules.wireguard; ''
+              geo $internal {
+                default 0;
+                127.0.0.1/32 1;
+                ${ipv4.subnet} 1;
+                ${ipv6.subnet} 1;
+              }
+            ''))
+        ];
+
+        virtualHosts =
+          {
+            default = {
+              default = true;
+              rejectSSL = true;
+              locations."/".return = "444";
+            };
+          }
+          // (mkIf (cfg.virtualHosts != null) (mapAttrs (_: attr:
+            mkMerge [
+              attr
+              (mkIf config.nixfiles.modules.acme.enable {
+                enableACME = true;
+                forceSSL = true;
+              })
+            ])
+          cfg.virtualHosts));
+      };
+
+      fail2ban.jails = {
+        nginx-http-auth = ''
+          enabled = true
+        '';
+        nginx-botsearch = ''
+          enabled = true
+        '';
+      };
+
+      prometheus.exporters.nginx = {
+        enable = true;
+        listenAddress = mkDefault this.wireguard.ipv4.address;
+        port = mkDefault 9113;
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = [80 443];
+  };
+}
diff --git a/modules/nixfiles/nmap.nix b/modules/nixfiles/nmap.nix
new file mode 100644
index 0000000..945cd59
--- /dev/null
+++ b/modules/nixfiles/nmap.nix
@@ -0,0 +1,42 @@
+{
+  config,
+  lib,
+  pkgs,
+  inputs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.nmap;
+in {
+  options.nixfiles.modules.nmap.enable =
+    mkEnableOption "Whether to enable Nmap.";
+
+  config = mkIf cfg.enable {
+    hm.home = {
+      file = {
+        ".nmap/scripts/vulners/vulners.nse".source = "${inputs.nmap-vulners}/vulners.nse";
+        ".nmap/scripts/vulscan/vulscan.nse".source = "${inputs.nmap-vulscan}/vulscan.nse";
+      };
+      packages = with pkgs; [nmap nmap-formatter];
+      activation.regenerateNmapScriptDatabase = with pkgs; ''
+        # declare -a vulscandbs=(
+        #   "cve"
+        #   "exploitdb"
+        #   "openvas"
+        #   "osvdb"
+        #   "scipvuldb"
+        #   "securityfocus"
+        #   "securitytracker"
+        #   "xforce"
+        # )
+        # for i in "''${vulscandbs[@]}"; do
+        #   ${curl}/bin/curl \
+        #     -o "$HOME/.nmap/scripts/vulscan/$i.csv" \
+        #     "https://www.computec.ch/projekte/vulscan/download/$i.csv"
+        # done
+
+        ${nmap}/bin/nmap --script-updatedb
+      '';
+    };
+  };
+}
diff --git a/modules/nixfiles/node-exporter.nix b/modules/nixfiles/node-exporter.nix
new file mode 100644
index 0000000..794c309
--- /dev/null
+++ b/modules/nixfiles/node-exporter.nix
@@ -0,0 +1,35 @@
+{
+  config,
+  lib,
+  this,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.node-exporter;
+in {
+  options.nixfiles.modules.node-exporter.enable =
+    mkEnableOption "Whether to enable Prometheus Node Exporter.";
+
+  config = mkIf cfg.enable {
+    services.prometheus.exporters.node = {
+      enable = true;
+      listenAddress = mkDefault this.wireguard.ipv4.address;
+      port = 9100;
+      enabledCollectors = [
+        "buddyinfo"
+        "ethtool"
+        "interrupts"
+        "ksmd"
+        "lnstat"
+        "logind"
+        "mountstats"
+        "network_route"
+        "processes"
+        "qdisc"
+        "systemd"
+        "tcpstat"
+        "zoneinfo"
+      ];
+    };
+  };
+}
diff --git a/modules/nixfiles/nsd.nix b/modules/nixfiles/nsd.nix
new file mode 100644
index 0000000..f328b5c
--- /dev/null
+++ b/modules/nixfiles/nsd.nix
@@ -0,0 +1,157 @@
+{
+  config,
+  inputs,
+  lib,
+  this,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.nsd;
+in {
+  options.nixfiles.modules.nsd = {
+    enable = mkEnableOption "Whether to enable NSD.";
+
+    fqdn = mkOption {
+      description = "FQDN of this nameserver.";
+      type = with types; str;
+      default = "ns.${config.networking.domain}";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services = {
+      nsd = {
+        enable = true;
+        interfaces = with this; [ipv4.address ipv6.address];
+        ipTransparent = true;
+        ratelimit.enable = true;
+
+        # TODO DNSSEC.
+        zones = let
+          dns = inputs.dns-nix.lib;
+        in
+          with dns.combinators; let
+            ips = hostname:
+              with my.configurations.${hostname}; {
+                A = [(a ipv4.address)];
+                AAAA = [(aaaa ipv6.address)];
+              };
+
+            # TODO Try moving DKIM keys somewhere in "my" or secrets maybe?
+            mkEmailEntries = {
+              domain ? my.domain.shire,
+              dkimKey ? null,
+            }: {
+              MX = [(mx.mx 10 "${domain}.")];
+              TXT = [(spf.strict ["a" "mx"])];
+              DMARC = [
+                {
+                  p = "quarantine";
+                  sp = "quarantine";
+                  rua = ["mailto:admin+rua@${domain}"];
+                  ruf = ["mailto:admin+ruf@${domain}"];
+                }
+              ];
+              DKIM = optional (dkimKey != null) {
+                selector = "mail";
+                p = dkimKey;
+              };
+            };
+
+            mkZone = {
+              domain,
+              sldIps ? (ips "manwe"),
+              extra ? {},
+            }: {
+              ${domain}.data = dns.toString domain ({
+                  TTL = 60 * 60;
+
+                  SOA = {
+                    nameServer = "${cfg.fqdn}.";
+                    adminEmail = "admin+dns@${my.domain.shire}";
+                    serial = 2022081122;
+                  };
+
+                  NS = with my.domain; ["ns1.${shire}" "ns2.${shire}"];
+
+                  CAA = letsEncrypt "admin+caa@${my.domain.shire}";
+                }
+                // sldIps
+                // extra);
+            };
+          in
+            mkMerge [
+              (mkZone {
+                domain = my.domain.shire;
+                extra =
+                  (mkEmailEntries {
+                    dkimKey = "[DKIM]";
+                  })
+                  // {
+                    subdomains = rec {
+                      manwe = ips "manwe";
+                      "*.manwe" = manwe;
+                      varda = ips "varda";
+                      "*.varda" = varda;
+                      yavanna = ips "yavanna";
+                      "*.yavanna" = yavanna;
+
+                      ns1 = manwe;
+                      # ns2 = varda;
+
+                      flood = yavanna;
+                      gotify = manwe;
+                      monitoring = manwe;
+                      radicale = varda;
+                      rss-bridge = varda;
+                      vaultwarden = varda;
+                    };
+                  };
+              })
+              (mkZone {
+                domain = my.domain.azahi;
+                extra =
+                  (mkEmailEntries {
+                    dkimKey = "[DKIM]";
+                  })
+                  // {
+                    subdomains = {
+                      github.CNAME = ["github.com/${my.username}"];
+                      gitlab.CNAME = ["gitlab.com/${my.username}"];
+                    };
+                  };
+              })
+              (mkZone {
+                domain = my.domain.gondor;
+                extra =
+                  (mkEmailEntries {
+                    dkimKey = "[DKIM]";
+                  })
+                  // {
+                    subdomains.frodo = ips "manwe";
+                  };
+              })
+              (mkZone {
+                domain = my.domain.rohan;
+                extra =
+                  (mkEmailEntries {
+                    dkimKey = "[DKIM]";
+                  })
+                  // {
+                    subdomains.frodo = ips "manwe";
+                  };
+              })
+            ];
+      };
+
+      fail2ban.jails.nsd = ''
+        enabled = true
+      '';
+    };
+
+    networking.firewall = rec {
+      allowedTCPPorts = [53];
+      allowedUDPPorts = allowedTCPPorts;
+    };
+  };
+}
diff --git a/modules/nixfiles/openssh.nix b/modules/nixfiles/openssh.nix
new file mode 100644
index 0000000..2f1559e
--- /dev/null
+++ b/modules/nixfiles/openssh.nix
@@ -0,0 +1,52 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.openssh;
+in {
+  options.nixfiles.modules.openssh = {
+    client.enable = mkEnableOption "Whether to enable OpenSSH client.";
+    server.enable = mkEnableOption "Whether to enable OpenSSH server.";
+  };
+
+  config = mkMerge [
+    (mkIf cfg.client.enable {
+      hm = {
+        home.packages = with pkgs; [mosh sshfs];
+
+        programs.ssh = {
+          enable = true;
+          controlMaster = "auto";
+          controlPersist = "24H";
+          hashKnownHosts = true;
+          serverAliveCountMax = 30;
+          serverAliveInterval = 60;
+        };
+      };
+    })
+    (mkIf cfg.server.enable {
+      programs.mosh.enable = true;
+
+      services = let
+        port = 22022;
+      in {
+        openssh = {
+          enable = true;
+          ports = [port];
+          logLevel = "VERBOSE";
+          permitRootLogin = "no";
+          passwordAuthentication = false;
+        };
+
+        fail2ban.jails.sshd = ''
+          enabled = true
+          mode = aggressive
+          port = ${toString port}
+        '';
+      };
+    })
+  ];
+}
diff --git a/modules/nixfiles/password-store.nix b/modules/nixfiles/password-store.nix
new file mode 100644
index 0000000..19e2f2a
--- /dev/null
+++ b/modules/nixfiles/password-store.nix
@@ -0,0 +1,33 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.password-store;
+in {
+  options.nixfiles.modules.password-store.enable =
+    mkEnableOption "Whether to enable pass.";
+
+  config = mkIf cfg.enable {
+    hm.programs = {
+      password-store = {
+        enable = true;
+
+        package = pkgs.pass.withExtensions (p: with p; [pass-otp]);
+
+        settings.PASSWORD_STORE_DIR = "${config.my.home}/.password-store";
+      };
+
+      # A dirty little hack to make completions for "otp" to work.
+      bash.initExtra = let
+        completions = "${config.hm.programs.password-store.package}/share/bash-completion/completions";
+      in
+        mkAfter ''
+          source ${completions}/pass-otp
+          source ${completions}/pass
+        '';
+    };
+  };
+}
diff --git a/modules/nixfiles/podman.nix b/modules/nixfiles/podman.nix
new file mode 100644
index 0000000..6c8b7e5
--- /dev/null
+++ b/modules/nixfiles/podman.nix
@@ -0,0 +1,35 @@
+{
+  config,
+  lib,
+  inputs,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.podman;
+in {
+  options.nixfiles.modules.podman.enable =
+    mkEnableOption "Whether to enable Podman.";
+
+  config = mkIf cfg.enable {
+    secrets.containers-auth = {
+      file = "${inputs.self}/secrets/containers-auth";
+      path = "${config.dirs.config}/containers/auth.json";
+      owner = my.username;
+    };
+
+    virtualisation.podman.enable = true;
+
+    environment.systemPackages = with pkgs; [podman-compose];
+
+    my.extraGroups = ["podman"];
+
+    hm.programs.bash = {
+      shellAliases.p = "${pkgs.podman}/bin/podman";
+
+      initExtra = mkAfter ''
+        _complete_alias p __start_podman podman
+      '';
+    };
+  };
+}
diff --git a/modules/nixfiles/postgresql.nix b/modules/nixfiles/postgresql.nix
new file mode 100644
index 0000000..23623af
--- /dev/null
+++ b/modules/nixfiles/postgresql.nix
@@ -0,0 +1,53 @@
+{
+  config,
+  lib,
+  pkgs,
+  this,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.postgresql;
+in {
+  options.nixfiles.modules.postgresql.enable =
+    mkEnableOption "Whether to enable PostgeSQL.";
+
+  config = mkIf cfg.enable {
+    hm = {
+      home.sessionVariables.PSQLRC = config.hm.xdg.configFile."psqlrc".target;
+
+      xdg.configFile."psqlrc".text = ''
+        \set QUIET 1
+
+        \timing
+        \x auto
+        \pset null '[NULL]'
+        \set PROMPT1 '%[%033[1m%]%M %n@%/%R%[%033[0m%]% λ '
+        \set PROMPT2 '    … > '
+        \set VERBOSITY verbose
+        \set HISTCONTROL ignoredups
+        \set HISTFILE /dev/null
+
+        \unset QUIET
+      '';
+    };
+
+    services = {
+      postgresql = {
+        enable = true;
+        package = pkgs.postgresql_14;
+        # TODO Test if this is still required.
+        authentication = ''
+          local all all trust
+          host all all 127.0.0.1/32 trust
+          host all all ::1/128 trust
+        '';
+      };
+
+      prometheus.exporters.postgres = {
+        enable = true;
+        listenAddress = mkDefault this.wireguard.ipv4.address;
+        port = mkDefault 9187;
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/profiles/common.nix b/modules/nixfiles/profiles/common.nix
new file mode 100644
index 0000000..dd287dc
--- /dev/null
+++ b/modules/nixfiles/profiles/common.nix
@@ -0,0 +1,103 @@
+{
+  config,
+  lib,
+  pkgs,
+  this,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.profiles.common;
+in {
+  imports = [
+    (mkAliasOptionModule ["colourScheme"] [
+      "nixfiles"
+      "modules"
+      "profiles"
+      "common"
+      "colourScheme"
+    ])
+  ];
+
+  options.nixfiles.modules.profiles.common = {
+    enable = mkEnableOption "The most common profiles of all profiles.";
+
+    colourScheme = let
+      mkColour = default:
+        mkOption {
+          type = types.str;
+          inherit default;
+          description = "Color in a standard hexademical notation.";
+          example = "#000000";
+        };
+    in rec {
+      black = mkColour "#161719";
+      red = mkColour "#cc6666";
+      green = mkColour "#b5bd68";
+      yellow = mkColour "#f0c674";
+      blue = mkColour "#81a2be";
+      magenta = mkColour "#b294bb";
+      cyan = mkColour "#8abeb7";
+      white = mkColour "#c5c8c6";
+
+      brightBlack = mkColour "#969896";
+      brightRed = mkColour "#cc6666";
+      brightGreen = mkColour "#b5bd68";
+      brightYellow = mkColour "#f0c674";
+      brightBlue = mkColour "#81a2be";
+      brightMagenta = mkColour "#b294bb";
+      brightCyan = mkColour "#8abeb7";
+      brightWhite = mkColour "#ffffff";
+
+      background = black;
+      foreground = white;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = !(with this; isHeadless && isHeadful);
+        message = ''
+          The configuration cannot be both "headful" and "headless" at the same
+          time.
+        '';
+      }
+    ];
+
+    profile = with this; {
+      headless = isHeadless;
+      headful = isHeadful;
+    };
+
+    nixfiles.modules = {
+      htop.enable = true;
+      tmux.enable = true;
+      vim.enable = true;
+    };
+
+    hm.home.language = {
+      collate = "C";
+      messages = "C";
+    };
+
+    programs.less = {
+      enable = true;
+      envVariables.LESSHISTFILE = "-";
+    };
+
+    environment.systemPackages = with pkgs; [
+      cryptsetup
+      ddrescue
+      file
+      git
+      gnupg
+      lshw
+      lsof
+      pciutils
+      psmisc
+      tree
+      usbutils
+      util-linux
+    ];
+  };
+}
diff --git a/modules/nixfiles/profiles/default.nix b/modules/nixfiles/profiles/default.nix
new file mode 100644
index 0000000..3df88f8
--- /dev/null
+++ b/modules/nixfiles/profiles/default.nix
@@ -0,0 +1,5 @@
+{lib, ...}: {
+  imports = [./common.nix ./dev ./headful.nix ./headless.nix];
+
+  config.nixfiles.modules.profiles.common.enable = lib.mkDefault true;
+}
diff --git a/modules/nixfiles/profiles/dev/common.nix b/modules/nixfiles/profiles/dev/common.nix
new file mode 100644
index 0000000..d6c44ea
--- /dev/null
+++ b/modules/nixfiles/profiles/dev/common.nix
@@ -0,0 +1,290 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.profiles.dev.common;
+in {
+  options.nixfiles.modules.profiles.dev.common.enable = mkEnableOption "";
+
+  config = mkIf cfg.enable {
+    nixfiles.modules = {
+      bat.enable = true;
+      curl.enable = true;
+      direnv.enable = true;
+      git.enable = true;
+      gnupg.enable = true;
+      nmap.enable = true;
+      wget.enable = true;
+    };
+
+    hm.home = {
+      file = {
+        ".editorconfig".text = ''
+          root = true
+
+          [*]
+          charset = utf-8
+          end_of_line = lf
+          indent_size = 4
+          indent_style = space
+          insert_final_newline = true
+          max_line_length = 80
+          trim_trailing_whitespace = true
+
+          [*.nix]
+          indent_size = 2
+          indent_style = space
+
+          [*.{S,s,asm}]
+          indent_size = 4
+          indent_style = tab
+
+          [*.{C,H,c,c++,cc,cpp,cxx,h,h++,hh,hpp,hxx}]
+          indent_size = 4
+          indent_style = tab
+
+          [*.{bash,sh}]
+          indent_size = 4
+          indent_style = tab
+
+          [*.{cl,clj,el,l,lisp,lsp,rkt,scm,ss}]
+          indent_size = unset
+          indent_style = unset
+
+          [*.go]
+          indent_size = 4
+          indent_style = tab
+
+          [*.{py,pyx}]
+          indent_size = 4
+          indent_style = space
+
+          [*.{hs,lhs}]
+          indent_size = 2
+          indent_style = space
+
+          [*.{html,xhtml,xml}]
+          indent_size = 4
+          indent_style = tab
+
+          [*.json]
+          indent_size = 2
+          indent_style = space
+
+          [*.{yaml,yml}]
+          indent_size = 2
+          indent_style = space
+
+          [*.{toml,tml}]
+          indent_size = 4
+          indent_style = space
+
+          [*.{py,pyx}]
+          indent_size = 4
+          indent_style = space
+          max_line_length = 72
+
+          [*.zig]
+          indent_size = 4
+          indent_style = tab
+
+          [configure.ac]
+          indent_size = 4
+          indent_style = tab
+
+          [{Makefile*,*.mk}]
+          indent_size = 4
+          indent_style = tab
+
+          [{CMakeLists.txt,*.cmake}]
+          indent_size = 8
+          indent_style = tab
+
+          [*.tex]
+          indent_size = 4
+          indent_style = tab
+
+          [*.{md,adoc,rtf,txt}]
+          indent_size = 4
+          indent_style = tab
+        '';
+
+        ".ghc/ghci.conf".source = ./ghci.conf;
+
+        ".stack/config.yaml".text = generators.toYAML {} {
+          templates.params = rec {
+            author-name = my.fullname;
+            author-email = my.email;
+            copyright = "Copyright (c) ${author-name} <${author-email}>";
+            github-username = my.username;
+          };
+        };
+
+        ".clang-format".text = generators.toYAML {} {
+          AccessModifierOffset = -4;
+          AlignAfterOpenBracket = "Align";
+          AlignConsecutiveAssignments = "Consecutive";
+          AlignConsecutiveBitFields = "Consecutive";
+          AlignConsecutiveDeclarations = "Consecutive";
+          AlignConsecutiveMacros = "Consecutive";
+          AlignEscapedNewlines = "Right";
+          AlignOperands = "Align";
+          AlignTrailingComments = false;
+          AllowAllArgumentsOnNextLine = false;
+          AllowAllConstructorInitializersOnNextLine = true;
+          AllowAllParametersOfDeclarationOnNextLine = true;
+          AllowShortBlocksOnASingleLine = "Never";
+          AllowShortCaseLabelsOnASingleLine = false;
+          AllowShortEnumsOnASingleLine = false;
+          AllowShortFunctionsOnASingleLine = "None";
+          AllowShortIfStatementsOnASingleLine = "Never";
+          AllowShortLambdasOnASingleLine = "Inline";
+          AllowShortLoopsOnASingleLine = false;
+          AlwaysBreakAfterDefinitionReturnType = "All";
+          AlwaysBreakAfterReturnType = "AllDefinitions";
+          AlwaysBreakBeforeMultilineStrings = false;
+          AlwaysBreakTemplateDeclarations = "Yes";
+          BinPackArguments = false;
+          BinPackParameters = false;
+          BreakBeforeBinaryOperators = "None";
+          BreakBeforeBraces = "Allman";
+          BreakBeforeTernaryOperators = true;
+          BreakConstructorInitializers = "BeforeComma";
+          BreakInheritanceList = "BeforeComma";
+          BreakStringLiterals = true;
+          ColumnLimit = 80;
+          CommentPragmas = "^ IWYU pragma:";
+          CompactNamespaces = false;
+          ConstructorInitializerAllOnOneLineOrOnePerLine = false;
+          ConstructorInitializerIndentWidth = 4;
+          ContinuationIndentWidth = 4;
+          Cpp11BracedListStyle = true;
+          DeriveLineEnding = false;
+          DerivePointerAlignment = false;
+          DisableFormat = false;
+          ExperimentalAutoDetectBinPacking = false;
+          FixNamespaceComments = true;
+          IncludeBlocks = "Regroup";
+          IndentCaseBlocks = false;
+          IndentCaseLabels = false;
+          IndentExternBlock = "NoIndent";
+          IndentGotoLabels = false;
+          IndentPPDirectives = "None";
+          IndentWidth = 4;
+          IndentWrappedFunctionNames = false;
+          KeepEmptyLinesAtTheStartOfBlocks = false;
+          Language = "Cpp";
+          MaxEmptyLinesToKeep = 1;
+          NamespaceIndentation = "None";
+          PointerAlignment = "Left";
+          ReflowComments = false;
+          SortIncludes = "CaseSensitive";
+          SortUsingDeclarations = true;
+          SpaceAfterCStyleCast = false;
+          SpaceAfterLogicalNot = false;
+          SpaceAfterTemplateKeyword = true;
+          SpaceBeforeAssignmentOperators = true;
+          SpaceBeforeCpp11BracedList = false;
+          SpaceBeforeCtorInitializerColon = true;
+          SpaceBeforeInheritanceColon = true;
+          SpaceBeforeParens = "ControlStatements";
+          SpaceBeforeRangeBasedForLoopColon = true;
+          SpaceInEmptyParentheses = false;
+          SpacesBeforeTrailingComments = 1;
+          SpacesInAngles = false;
+          SpacesInCStyleCastParentheses = false;
+          SpacesInContainerLiterals = false;
+          SpacesInParentheses = false;
+          SpacesInSquareBrackets = false;
+          Standard = "Latest";
+          TabWidth = 4;
+          UseTab = "Always";
+        };
+
+        ".gdbinit".text = ''
+          set confirm off
+          set verbose off
+          set editing off
+
+          set history expansion on
+
+          set height 0
+          set width  0
+
+          handle SIGALRM nostop print nopass
+          handle SIGBUS    stop print nopass
+          handle SIGPIPE nostop print nopass
+          handle SIGSEGV   stop print nopass
+
+          set print address on
+          set print elements 0
+          set print object on
+          set print pretty on
+          set print repeats 0
+          set print static-members on
+          set print vtbl on
+
+          set output-radix 10
+
+          set demangle-style gnu-v3
+
+          set disassembly-flavor intel
+
+          alias iv=info variables
+
+          alias da=disassemble
+
+          define fs
+              finish
+              step
+          end
+
+          define btc
+              backtrace
+              continue
+          end
+        '';
+      };
+
+      sessionVariables = {
+        CARGO_HOME = "${config.dirs.data}/cargo";
+        GOPATH = "${config.dirs.data}/go";
+        PYTHONSTARTUP = ./pystartup.py;
+      };
+
+      # TODO Probably should scrap most of these in favor of per-project
+      # shell.nix or flake.nix.
+      packages = with pkgs; [
+        alejandra
+        bash-language-server
+        cloc
+        dockerfile-language-server
+        editorconfig
+        fd
+        fzf
+        htmlq
+        jc
+        jq
+        json-language-server
+        logcli
+        nix-language-server
+        nixfmt
+        nixpkgs-fmt
+        ripgrep
+        ripgrep-all
+        shellcheck
+        shfmt
+        treefmt
+        wrk
+        yaml-language-server
+        yamllint
+        yq
+      ];
+    };
+
+    my.extraGroups = ["kvm"];
+  };
+}
diff --git a/modules/nixfiles/profiles/dev/containers/default.nix b/modules/nixfiles/profiles/dev/containers/default.nix
new file mode 100644
index 0000000..d0e7ed7
--- /dev/null
+++ b/modules/nixfiles/profiles/dev/containers/default.nix
@@ -0,0 +1,73 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.profiles.dev.containers;
+in {
+  options.nixfiles.modules.profiles.dev.containers.enable =
+    mkEnableOption "Wether to enable tools for working with Kubernetes.";
+
+  config = mkIf cfg.enable {
+    nixfiles.modules = {
+      profiles.dev.common.enable = true;
+      podman.enable = true;
+    };
+
+    hm = {
+      home = {
+        sessionVariables = {
+          MINIKUBE_IN_STYLE = "false";
+          WERF_DEV = "true";
+          WERF_INSECURE_REGISTRY = "true";
+          WERF_LOG_DEBUG = "true";
+          WERF_LOG_PRETTY = "false";
+          WERF_LOG_VERBOSE = "true";
+          WERF_SYNCHRONIZATION = ":local";
+        };
+
+        file.".minikube/config/config.json".text = generators.toJSON {} {
+          config.Rootless = true;
+          driver = "podman";
+          container-runtime = "cri-o";
+        };
+
+        packages = with pkgs; [
+          buildah
+          chart-testing
+          cmctl
+          helm
+          kubectl
+          kubectx
+          kubescape
+          kubespy
+          minikube
+          skaffold
+          skopeo
+          stern
+          telepresence
+          werf
+        ];
+      };
+
+      programs.bash = {
+        shellAliases = with pkgs; {
+          b = "${buildah}/bin/buildah";
+          h = "${helm}/bin/helm";
+          k = "${kubectl}/bin/kubectl";
+          kns = "${kubectx}/bin/kubens";
+          ktx = "${kubectx}/bin/kubectx";
+        };
+        initExtra = mkAfter ''
+          _complete_alias b _buildah buildah
+          _complete_alias h __start_helm helm
+          _complete_alias k __start_kubectl kubectl
+          _complete_alias kns _kube_namespaces kubens
+          _complete_alias ktx _kube_contexts kubectx
+        '';
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/profiles/dev/default.nix b/modules/nixfiles/profiles/dev/default.nix
new file mode 100644
index 0000000..1e0bd01
--- /dev/null
+++ b/modules/nixfiles/profiles/dev/default.nix
@@ -0,0 +1 @@
+_: {imports = [./common.nix ./containers ./sql];}
diff --git a/modules/nixfiles/profiles/dev/ghci.conf b/modules/nixfiles/profiles/dev/ghci.conf
new file mode 100644
index 0000000..d672167
--- /dev/null
+++ b/modules/nixfiles/profiles/dev/ghci.conf
@@ -0,0 +1,35 @@
+:set -XBinaryLiterals
+:set -XFlexibleContexts
+:set -XNoMonomorphismRestriction
+
+:seti -XConstraintKinds
+:seti -XDataKinds
+:seti -XDeriveFunctor
+:seti -XFlexibleInstances
+:seti -XFunctionalDependencies
+:seti -XGADTs
+:seti -XLambdaCase
+:seti -XMagicHash
+:seti -XMultiParamTypeClasses
+:seti -XMultiWayIf
+:seti -XOverloadedLabels
+:seti -XPackageImports
+:seti -XPolyKinds
+:seti -XRankNTypes
+:seti -XScopedTypeVariables
+:seti -XStandaloneDeriving
+:seti -XTupleSections
+:seti -XTypeFamilies
+:seti -XTypeOperators
+:seti -XUndecidableInstances
+
+:set +c
+:set +m
+:set +r
+:set +s
+:set +t
+
+:set prompt      "\ESC[1;34m>\ESC[m\STX "
+:set prompt-cont "\ESC[1;94m|\ESC[m\STX "
+
+:def hoogle \x -> pure (":!hoogle --color --count=10 \"" ++ x ++ "\"")
diff --git a/modules/nixfiles/profiles/dev/pystartup.py b/modules/nixfiles/profiles/dev/pystartup.py
new file mode 100644
index 0000000..1a78b55
--- /dev/null
+++ b/modules/nixfiles/profiles/dev/pystartup.py
@@ -0,0 +1,127 @@
+import atexit
+import os
+import readline
+import rlcompleter
+import sys
+from code import InteractiveConsole
+from tempfile import mkstemp
+
+readline.parse_and_bind("tab: complete")
+
+
+class TermColors(dict):
+    color_templates = (
+        ("Normal", "0"),
+        ("Black", "0;30"),
+        ("Red", "0;31"),
+        ("Green", "0;32"),
+        ("Brown", "0;33"),
+        ("Blue", "0;34"),
+        ("Purple", "0;35"),
+        ("Cyan", "0;36"),
+        ("LightGray", "0;37"),
+        ("DarkGray", "1;30"),
+        ("LightRed", "1;31"),
+        ("LightGreen", "1;32"),
+        ("Yellow", "1;33"),
+        ("LightBlue", "1;34"),
+        ("LightPurple", "1;35"),
+        ("LightCyan", "1;36"),
+        ("White", "1;37"),
+    )
+    color_base = "\001\033[%sm\002"
+
+    def __init__(self):
+        self.update(
+            dict([(k, self.color_base % v) for k, v in self.color_templates])
+        )
+
+
+class Completer(object):
+    def save_history(self):
+        import readline
+
+        readline.write_history_file(self.python_histfile)
+
+    def __init__(self):
+        self.python_dir = os.path.expanduser(
+            "%s/python" % os.environ["XDG_DATA_HOME"]
+        )
+
+        if not os.path.exists(self.python_dir):
+            os.mkdir(self.python_dir)
+
+        self.python_histfile = os.path.expanduser(
+            "%s/history" % self.python_dir
+        )
+
+        if os.path.exists(self.python_histfile):
+            readline.read_history_file(self.python_histfile)
+
+        readline.set_history_length(1000)
+        atexit.register(self.save_history)
+
+
+def DisplayHook(value):
+    if value is not None:
+        try:
+            import __builtin__
+
+            __builtin__._ = value
+        except ImportError:
+            __builtins__._ = value
+
+        import pprint
+
+        pprint.pprint(value)
+        del pprint
+
+
+class EditableBufferInteractiveConsole(InteractiveConsole):
+    def __init__(self, *args, **kwargs):
+        self.last_buffer = []
+        InteractiveConsole.__init__(self, *args, **kwargs)
+
+    def runsource(self, source, *args):
+        self.last_buffer = [source.encode("utf-8")]
+        return InteractiveConsole.runsource(self, source, *args)
+
+    def raw_input(self, *args):
+        line = InteractiveConsole.raw_input(self, *args)
+
+        if line == EDIT_CMD:
+            tmp_fd, tmp_file = mkstemp(".py")
+
+            os.write(tmp_fd, b"\n".join(self.last_buffer))
+            os.close(tmp_fd)
+
+            os.system("%s %s" % (EDITOR, tmp_file))
+
+            line = open(tmp_file).read()
+
+            os.unlink(tmp_file)
+            tmp_file = ""
+
+            lines = line.split("\n")
+
+            for i in range(len(lines) - 1):
+                self.push(lines[i])
+
+            line = lines[-1]
+        return line
+
+
+TC = TermColors()
+ps1 = "%sλ%s %s>%s "
+sys.ps1 = ps1 % (TC["Blue"], TC["Normal"], TC["White"], TC["Normal"])
+ps2 = "    %s…%s %s>%s "
+sys.ps2 = ps2 % (TC["Blue"], TC["Normal"], TC["White"], TC["Normal"])
+sys.displayhook = DisplayHook
+
+C = Completer()
+EDITOR = os.environ.get("EDITOR", "vim")
+EDIT_CMD = ":e"
+C = EditableBufferInteractiveConsole(locals=locals())
+C.interact(banner="")
+
+sys.exit()
diff --git a/modules/nixfiles/profiles/dev/sql/default.nix b/modules/nixfiles/profiles/dev/sql/default.nix
new file mode 100644
index 0000000..deb3738
--- /dev/null
+++ b/modules/nixfiles/profiles/dev/sql/default.nix
@@ -0,0 +1,94 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.profiles.dev.sql;
+in {
+  options.nixfiles.modules.profiles.dev.sql.enable =
+    mkEnableOption "Whether to enable SQL database management tools.";
+
+  config = mkIf cfg.enable {
+    hm = {
+      home.packages = with pkgs; [pgcli litecli];
+
+      xdg = let
+        mainSection = {
+          destructive_warning = "True";
+          enable_pager = "True";
+          keyword_casing = "auto";
+          less_chatty = "True";
+          log_file = "/dev/null";
+          log_level = "CRITICAL";
+          multi_line = "False";
+          syntax_style = "default";
+          table_format = "fancy_grid";
+        };
+
+        colorsSection = with config.nixfiles.modules.profiles.common.colourScheme; {
+          "arg-toolbar" = "noinherit bold";
+          "arg-toolbar.text" = "nobold";
+          "bottom-toolbar" = "bg:${black} ${white}";
+          "bottom-toolbar.off" = "bg:${black} ${brightBlack}";
+          "bottom-toolbar.on" = "bg:${black} ${brightWhite}";
+          "bottom-toolbar.transaction.failed" = "bg:${black} ${red} bold";
+          "bottom-toolbar.transaction.valid" = "bg:${black} ${green} bold";
+          "completion-menu.completion" = "bg:${black} ${white}";
+          "completion-menu.completion.current" = "bg:${white} ${black}";
+          "completion-menu.meta.completion" = "bg:${black} ${yellow}";
+          "completion-menu.meta.completion.current" = "bg:${yellow} ${black}";
+          "completion-menu.multi-column-meta" = "bg:${yellow} ${black}";
+          "scrollbar" = "bg:${black}";
+          "scrollbar.arrow" = "bg:${black}";
+          "search" = "bg:${magenta} ${brightWhite}";
+          "search-toolbar" = "noinherit bold";
+          "search-toolbar.text" = "nobold";
+          "search.current" = "bg:${green} ${brightWhite}";
+          "selected" = "bg:${blue} ${brightWhite}";
+          "system-toolbar" = "noinherit bold";
+        };
+
+        mkCliConfig = {
+          name,
+          custom,
+        }: {
+          "${name}/config" = {
+            text = generators.toINI {} {
+              main = mainSection // custom;
+              colors = mapAttrs (_: v: "'${v}'") colorsSection;
+            };
+          };
+        };
+      in {
+        configFile = mkMerge (map mkCliConfig [
+          {
+            name = "pgcli";
+            custom = {
+              auto_expand = "True";
+              casing_file = "/dev/null";
+              expand = "True";
+              history_file = "/dev/null";
+              keyring = "False";
+              multi_line_mode = "psql";
+              on_error = "STOP";
+              prompt = "'\\u@\\h:\\d> '";
+              vi = "True";
+            };
+          }
+          {
+            name = "litecli";
+            custom = {
+              audit_log = "/dev/null";
+              key_bindings = "vi";
+              prompt = "'\\d> '";
+              prompt_continuation = "'-> '";
+              auto_vertical_output = "True";
+            };
+          }
+        ]);
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/profiles/headful.nix b/modules/nixfiles/profiles/headful.nix
new file mode 100644
index 0000000..fa2f0d7
--- /dev/null
+++ b/modules/nixfiles/profiles/headful.nix
@@ -0,0 +1,69 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.profiles.headful;
+in {
+  imports = [
+    (mkAliasOptionModule ["profile" "headful"] [
+      "nixfiles"
+      "modules"
+      "profiles"
+      "headful"
+      "enable"
+    ])
+  ];
+
+  options.nixfiles.modules.profiles.headful.enable =
+    mkEnableOption "Wether to enable headful profile.";
+
+  config = mkIf cfg.enable {
+    nixfiles.modules = {
+      alacritty.enable = true;
+      aria2.enable = true;
+      aspell.enable = true;
+      emacs.enable = true;
+      firefox.enable = true;
+      mpv.enable = true;
+      openssh.client.enable = true;
+      password-store.enable = true;
+      sound.enable = true;
+      x11.enable = true;
+
+      dwm.enable = mkDefault false;
+      kde.enable = mkDefault true;
+      xmonad.enable = mkDefault false;
+    };
+
+    hm.home.packages = with pkgs; [convmv dos2unix];
+
+    hardware.opengl = {
+      enable = true;
+      driSupport = true;
+    };
+
+    programs = {
+      iftop.enable = true;
+      mtr.enable = true;
+      traceroute.enable = true;
+
+      bash.shellAliases.open = "${pkgs.xdg-utils}/bin/xdg-open";
+    };
+
+    services.upower.enable = true;
+
+    environment.systemPackages = with pkgs; [
+      arping
+      ethtool
+      inetutils
+      nethogs
+      socat
+      tcpdump
+    ];
+
+    my.extraGroups = ["audio" "video" "input"];
+  };
+}
diff --git a/modules/nixfiles/profiles/headless.nix b/modules/nixfiles/profiles/headless.nix
new file mode 100644
index 0000000..9737344
--- /dev/null
+++ b/modules/nixfiles/profiles/headless.nix
@@ -0,0 +1,67 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.profiles.headless;
+in {
+  imports = [
+    (mkAliasOptionModule ["profile" "headless"] [
+      "nixfiles"
+      "modules"
+      "profiles"
+      "headless"
+      "enable"
+    ])
+  ];
+
+  options.nixfiles.modules.profiles.headless.enable =
+    mkEnableOption "Whether to enable headless profile.";
+
+  config = mkIf cfg.enable {
+    nixfiles.modules = {
+      openssh.server.enable = true;
+      endlessh-go.enable = true;
+
+      fail2ban.enable = true;
+
+      node-exporter.enable = true;
+      promtail.enable = true;
+    };
+
+    hm.home.file = {
+      ".hushlogin".text = "";
+      ".bash_history".source =
+        config.hm.lib.file.mkOutOfStoreSymlink "/dev/null";
+    };
+
+    boot.kernelPackages = pkgs.linuxPackages_5_15_hardened;
+
+    nix = {
+      gc = {
+        automatic = true;
+        dates = "weekly";
+        options = "--delete-older-than 30d";
+      };
+
+      optimise = {
+        automatic = true;
+        dates = ["weekly"];
+      };
+    };
+
+    i18n = {
+      # TODO Convert everything including PostgreSQL databases to the "en_US"
+      # locale or probably even the "C" one.
+      defaultLocale = mkForce "C";
+      supportedLocales = mkForce ["en_US.UTF-8/UTF-8" "en_GB.UTF-8/UTF-8"];
+    };
+    security.polkit.enable = false;
+    services.udisks2.enable = false;
+    xdg.sounds.enable = false;
+
+    environment.systemPackages = with pkgs; [alacritty.terminfo];
+  };
+}
diff --git a/modules/nixfiles/prometheus.nix b/modules/nixfiles/prometheus.nix
new file mode 100644
index 0000000..b67dd2e
--- /dev/null
+++ b/modules/nixfiles/prometheus.nix
@@ -0,0 +1,60 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.prometheus;
+in {
+  options.nixfiles.modules.prometheus = {
+    enable = mkEnableOption "Whether to enable Prometheus.";
+
+    port = mkOption {
+      description = "Port.";
+      type = with types; port;
+      default = 30111;
+    };
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = config.nixfiles.modules.monitoring.domain;
+    };
+
+    path = mkOption {
+      description = "Path.";
+      type = with types; str;
+      default = "/prometheus";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    nixfiles.modules.nginx = with cfg; {
+      enable = true;
+      virtualHosts.${domain}.locations.${path} = {
+        proxyPass = with cfg; "http://127.0.0.1:${toString port}";
+        extraConfig = ''
+          if ($internal != 1) {
+            return 403;
+          }
+        '';
+      };
+    };
+
+    services.prometheus = with cfg; {
+      enable = true;
+
+      listenAddress = "127.0.0.1";
+      inherit port;
+
+      extraFlags = [
+        "--web.external-url=http${
+          optionalString config.nixfiles.modules.acme.enable "s"
+        }://${domain}${path}"
+        "--storage.tsdb.retention.size=50GB"
+        "--storage.tsdb.retention.time=1y"
+        "--storage.tsdb.wal-compression"
+      ];
+    };
+  };
+}
diff --git a/modules/nixfiles/promtail.nix b/modules/nixfiles/promtail.nix
new file mode 100644
index 0000000..ba4e635
--- /dev/null
+++ b/modules/nixfiles/promtail.nix
@@ -0,0 +1,53 @@
+{
+  config,
+  lib,
+  this,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.promtail;
+in {
+  options.nixfiles.modules.promtail = {
+    enable = mkEnableOption "Whether to enable Promtail.";
+
+    loki = {
+      url = mkOption {
+        description = "Address of a listening Loki service.";
+        type = with types; str;
+        default = with config.nixfiles.modules.loki; "https://${domain}${path}";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.promtail = {
+      enable = true;
+
+      configuration = {
+        server = rec {
+          http_listen_address = this.wireguard.ipv4.address;
+          http_listen_port = 30181;
+
+          grpc_listen_address = this.wireguard.ipv4.address;
+          grpc_listen_port = http_listen_port + 1;
+
+          log_level = "warn";
+        };
+
+        clients = [{url = "${cfg.loki.url}/loki/api/v1/push";}];
+
+        positions.filename = "/tmp/positions.yaml";
+
+        scrape_configs = [
+          {
+            job_name = "journal";
+            journal = {
+              max_age = "24h";
+              labels.job = "systemd-journal";
+            };
+          }
+        ];
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/psd.nix b/modules/nixfiles/psd.nix
new file mode 100644
index 0000000..290c067
--- /dev/null
+++ b/modules/nixfiles/psd.nix
@@ -0,0 +1,60 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.psd;
+in {
+  options.nixfiles.modules.psd.enable =
+    mkEnableOption "Whether to enable Profile Sync Daemon.";
+
+  config = mkIf cfg.enable {
+    hm.home = {
+      file."${config.hm.xdg.configHome}/psd/psd.conf".text = ''
+        USE_OVERLAYFS="yes"
+      '';
+
+      packages = with pkgs; [profile-sync-daemon];
+    };
+
+    systemd.user = {
+      services = {
+        psd = {
+          unitConfig = {
+            Description = "Profile-sync-daemon";
+            Wants = ["psd-resync.service"];
+            RequiresMountsFor = "/home/";
+            After = ["local-fs.target"];
+          };
+          serviceConfig = {
+            RemainAfterExit = true;
+            ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon startup";
+            ExecStop = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon unsync";
+          };
+          wantedBy = ["graphical.target"];
+        };
+
+        psd-resync = {
+          unitConfig = {
+            Description = "Profile-sync-daemon resync";
+            After = ["psd.service"];
+            Wants = ["psd-resync.timer"];
+            BindsTo = ["psd.service"];
+          };
+          serviceConfig.ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon resync";
+          wantedBy = ["graphical.target"];
+        };
+      };
+
+      timers.psd-resync = {
+        unitConfig = {
+          Description = "Profile-sync-daemon resync timer";
+          BindsTo = ["psd.service"];
+        };
+        timerConfig.OnUnitActiveSec = "1h";
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/qutebrowser.nix b/modules/nixfiles/qutebrowser.nix
new file mode 100644
index 0000000..5af7e1f
--- /dev/null
+++ b/modules/nixfiles/qutebrowser.nix
@@ -0,0 +1,547 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.qutebrowser;
+in {
+  options.nixfiles.modules.qutebrowser.enable =
+    mkEnableOption "Whether to enable qutebrowser.";
+
+  config = mkIf cfg.enable {
+    hm = {
+      # home.sessionVariables.BROWSER = mkOverride 400 "qutebrowser";
+
+      programs.qutebrowser = with config.nixfiles.modules; {
+        enable = true;
+
+        package = pkgs.qutebrowser.override {
+          withMediaPlayback = false;
+          withPdfReader = false;
+        };
+
+        keyBindings.normal = mkIf mpv.enable {
+          "z" = let
+            mpv = "${config.hm.programs.mpv.package}/bin/mpv";
+          in "hint links spawn --detach ${mpv} {hint-url}";
+        };
+
+        searchEngines = rec {
+          aliexpress = "https://www.aliexpress.com/wholesale?SearchText={}";
+          ansible = "https://galaxy.ansible.com/search?keywords={}";
+          arch = "https://wiki.archlinux.org/?search={}";
+          crates = "https://crates.io/search?q={}";
+          crawl = "http://crawl.chaosforge.org/index.php?search={}";
+          discogs = "https://www.discogs.com/search/?q={}";
+          dockerdocs = "https://docs.docker.com/search/?q={}";
+          dockerhub = "https://hub.docker.com/search?q={}";
+          doublegis = "https://2gis.ru/search/{}";
+          duckduckgo = "https://duckduckgo.com/?q={}'";
+          dwarffortress = "https://dwarffortresswiki.org/index.php?search={}";
+          ebay = "https://www.ebay.com/sch/i.html?_nkw={}";
+          ecosia = "https://www.ecosia.org/search?q={}";
+          factorio = "https://wiki.factorio.com/index.php?search={}";
+          genius = "https://genius.com/search?q={}";
+          github = "https://github.com/search?q={}";
+          godocs = "https://godocs.io/?q={}";
+          gogdb = "https://www.gogdb.org/products?search={}";
+          google = "https://www.google.com/search?q={}";
+          google-images = "https://www.google.com/search?q={}&tbm=isch";
+          gopkgs = "https://pkg.go.dev/search?q={}";
+          habr = "https://habr.com/ru/search/?q={}";
+          hackage = "https://hackage.haskell.org/packages/search?terms={}";
+          hackernews = "https://hn.algolia.com/?q={}";
+          headhunter = "https://hh.ru/search/vacancy?st=searchVacancy&text={}";
+          hoogle = "https://hoogle.haskell.org/?hoogle={}";
+          jisho = "https://jisho.org/search/{}";
+          kotobank = "https://kotobank.jp/gs/?q={}";
+          kubernetes = "https://kubernetes.io/search/?q={}";
+          lastfm = "https://www.last.fm/search?q={}";
+          lobsters = "https://lobste.rs/search?q=test{}";
+          mdn = "https://developer.mozilla.org/en-US/search?q={}";
+          melpa = "https://melpa.org/#/?q={}";
+          moddb = "https://www.moddb.com/search?q={}";
+          musicbrainz = "https://musicbrainz.org/search?query={}";
+          nix-issues = "https://github.com/NixOS/nix/issues?q={}";
+          nix-prs = "https://github.com/NixOS/nix/pulls?q={}";
+          nixos-flakes = "https://search.nixos.org/flakes?query={}";
+          nixos-options = "https://search.nixos.org/options?query={}";
+          nixos-packages = "https://search.nixos.org/packages?query={}";
+          nixos-wiki = "https://nixos.wiki/index.php?search={}";
+          nixpkgs-issues = "https://github.com/NixOS/nixpkgs/issues?q={}";
+          nixpkgs-prs = "https://github.com/NixOS/nixpkgs/pulls?q={}";
+          openstreetmap = "https://www.openstreetmap.org/search?query={}";
+          ozon = "https://www.ozon.ru/search/?text={}";
+          protondb = "https://www.protondb.com/search?q={}";
+          pypi = "https://pypi.org/search/?q={}";
+          pythondocs = "https://docs.python.org/3/search.html?q={}";
+          rateyourmusic = "https://rateyourmusic.com/search?searchterm={}";
+          riichi = "https://riichi.wiki/index.php?search={}";
+          rustdoc = "https://doc.rust-lang.org/std/?search={}";
+          searx = "https://searx.tiekoetter.com/search?q={}";
+          slashdot = "https://slashdot.org/index2.pl?fhfilter={}";
+          sourcehut = "https://sr.ht/projects?search={}";
+          steam = "https://store.steampowered.com/search/?term={}";
+          steamdb = "https://steamdb.info/search/?a=app&q={}";
+          ubuntu = "https://wiki.ubuntu.com/Home?action=fullsearch&value={}";
+          wikipedia-en = "https://en.wikipedia.org/w/index.php?search={}";
+          wikipedia-ru = "https://ru.wikipedia.org/w/index.php?search={}";
+          wikipedia-ja = "https://ja.wikipedia.org/w/index.php?search={}";
+          wolphramalpha = "https://www.wolframalpha.com/input/?i={}";
+          yahoo = "https://yahoo.com/search/?text={}";
+          yahoo-images = "https://yahoo.com/images/search?text={}";
+          yahoo-market = "https://market.yahoo.com/search?text={}";
+          youtube = "https://yewtu.be/search?q={}";
+
+          aw = arch;
+          d = duckduckgo;
+          do = dockerhub;
+          docker = dockerhub;
+          dod = dockerdocs;
+          g = google;
+          gh = github;
+          h = hoogle;
+          k = kubernetes;
+          mb = musicbrainz;
+          n = nixos-options;
+          nw = nixos-wiki;
+          py = pypi;
+          pyd = pythondocs;
+          rym = rateyourmusic;
+          s = searx;
+          sh = sourcehut;
+          sr = sourcehut;
+          w = wikipedia-en;
+          wen = wikipedia-en;
+          wja = wikipedia-ja;
+          wru = wikipedia-ru;
+          y = yahoo;
+          yt = youtube;
+        };
+
+        settings = {
+          changelog_after_upgrade = "never";
+
+          content = {
+            autoplay = false;
+            cookies.accept = "all";
+            default_encoding = "utf-8";
+            desktop_capture = "ask";
+            dns_prefetch = false;
+            geolocation = false;
+            headers.do_not_track = true;
+            javascript.enabled = true;
+            prefers_reduced_motion = true;
+            webgl = true;
+
+            blocking = {
+              enabled = true;
+              method = "adblock";
+              adblock.lists = [
+                "https://easylist.to/easylist/easylist.txt"
+                "https://easylist.to/easylist/easyprivacy.txt"
+                "https://easylist.to/easylist/fanboy-social.txt"
+                "https://secure.fanboy.co.nz/fanboy-annoyance.txt"
+                "https://secure.fanboy.co.nz/fanboy-cookiemonster.txt"
+              ];
+            };
+          };
+
+          completion = {
+            height = "50%";
+            show = "auto";
+            shrink = true;
+            timestamp_format = "%y-%m-%d";
+            min_chars = 3;
+            open_categories = ["bookmarks" "quickmarks" "history"];
+
+            scrollbar = {
+              width = 0;
+              padding = 0;
+            };
+          };
+
+          downloads = {
+            location = {
+              directory = config.userDirs.download;
+              prompt = true;
+            };
+            remove_finished = 0;
+          };
+
+          editor.command = [
+            (
+              if alacritty.enable
+              then "${pkgs.alacritty}/bin/alacritty"
+              else "${pkgs.xterm}/bin/xterm"
+            )
+            "-e"
+            "${config.programs.vim.package}/bin/vim"
+            "-f"
+            "{}"
+          ];
+
+          hints = {
+            auto_follow = "unique-match";
+            auto_follow_timeout = 0;
+            border = "0px";
+            min_chars = 1;
+            scatter = false;
+            uppercase = false;
+          };
+
+          hints.radius = 0;
+          keyhint.radius = 0;
+          prompt.radius = 0;
+
+          scrolling = {
+            bar = "never";
+            smooth = false;
+          };
+
+          spellcheck.languages = ["en-GB" "en-US" "ru-RU"];
+
+          statusbar.position = "bottom";
+
+          tabs = {
+            position = "top";
+
+            title = {
+              alignment = "left";
+              format = "{audio}{index} : {current_title}";
+              format_pinned = "{audio}{index}";
+            };
+
+            min_width = -1;
+            max_width = -1;
+
+            indicator.width = 0;
+
+            pinned = {
+              shrink = true;
+              frozen = false;
+            };
+
+            close_mouse_button = "middle";
+            mousewheel_switching = false;
+
+            background = true;
+            select_on_remove = "next";
+            new_position = {
+              related = "next";
+              unrelated = "last";
+            };
+
+            favicons = {
+              show = "pinned";
+              scale = 0.75;
+            };
+          };
+
+          url = rec {
+            default_page = "about:blank";
+            start_pages = [default_page];
+          };
+
+          window = {
+            hide_decoration = false; # TODO Test in a WM.
+            title_format = "{perc}{current_title}{title_sep}qutebrowser";
+          };
+
+          colors = with config.colourScheme; {
+            completion = rec {
+              fg = white;
+              match.fg = red;
+              odd.bg = black;
+              even.bg = odd.bg;
+              category = {
+                fg = white;
+                bg = black;
+                border = {
+                  top = black;
+                  bottom = black;
+                };
+              };
+              item.selected = {
+                fg = black;
+                bg = white;
+                border = {
+                  top = white;
+                  bottom = white;
+                };
+              };
+              scrollbar = {
+                fg = white;
+                bg = black;
+              };
+            };
+            contextmenu = {
+              menu = {
+                fg = white;
+                bg = black;
+              };
+              selected = {
+                fg = black;
+                bg = white;
+              };
+              disabled = {
+                fg = brightBlack;
+                bg = black;
+              };
+            };
+            downloads = {
+              bar.bg = black;
+              start = {
+                fg = green;
+                bg = black;
+              };
+              stop = {
+                fg = yellow;
+                bg = black;
+              };
+              error = {
+                fg = red;
+                bg = black;
+              };
+              system = {
+                fg = "none";
+                bg = "none";
+              };
+            };
+            hints = {
+              fg = white;
+              match.fg = red;
+              bg = black;
+            };
+            keyhint = {
+              fg = white;
+              suffix.fg = red;
+              bg = black;
+            };
+            messages = {
+              error = rec {
+                bg = black;
+                fg = red;
+                border = bg;
+              };
+              info = rec {
+                fg = blue;
+                bg = black;
+                border = bg;
+              };
+              warning = rec {
+                fg = yellow;
+                bg = black;
+                border = bg;
+              };
+            };
+            prompts = rec {
+              fg = white;
+              bg = black;
+              selected = {
+                fg = black;
+                bg = white;
+              };
+              border = bg;
+            };
+            statusbar = {
+              normal = {
+                bg = black;
+                fg = white;
+              };
+              command = {
+                bg = black;
+                fg = white;
+              };
+              insert = {
+                bg = green;
+                fg = black;
+              };
+              passthrough = {
+                bg = blue;
+                fg = black;
+              };
+              private = {
+                bg = magenta;
+                fg = black;
+              };
+              url = {
+                fg = blue;
+                hover.fg = brightBlue;
+                success = {
+                  http.fg = brightGreen;
+                  https.fg = brightGreen;
+                };
+                warn.fg = brightYellow;
+                error.fg = brightRed;
+              };
+            };
+            tabs = rec {
+              bar.bg = black;
+              even = {
+                bg = black;
+                fg = white;
+              };
+              odd = with even; {inherit bg fg;};
+              selected = rec {
+                even = {
+                  bg = white;
+                  fg = black;
+                };
+                odd = with even; {inherit bg fg;};
+              };
+              pinned = rec {
+                even = {
+                  bg = brightBlack;
+                  fg = brightWhite;
+                };
+                odd = with even; {inherit bg fg;};
+              };
+              indicator = {
+                start = green;
+                stop = yellow;
+                error = red;
+                system = "none";
+              };
+            };
+            webpage = {
+              bg = "white";
+              darkmode = {
+                enabled = false;
+                algorithm = "lightness-cielab";
+                contrast = 0.0;
+                grayscale = {
+                  all = false;
+                  images = 0.0;
+                };
+                policy = {
+                  images = "smart";
+                  page = "smart";
+                };
+                threshold = {
+                  background = 0;
+                  text = 256;
+                };
+              };
+              preferred_color_scheme = "auto";
+            };
+          };
+
+          fonts =
+            (with config.fontScheme.monospaceFont; {
+              default_family = family;
+              default_size = (toString size) + "pt";
+            })
+            // {
+              web = with config.fontScheme; {
+                family = rec {
+                  standard = sans_serif;
+                  fixed = monospaceFont.family;
+                  serif = serifFont.family;
+                  sans_serif = sansSerifFont.family;
+                  cursive = null;
+                  fantasy = null;
+                };
+                size = rec {
+                  default = sansSerifFont.size;
+                  default_fixed = monospaceFont.size;
+                  minimum = 0;
+                  minimum_logical = default / 2;
+                };
+              };
+            }
+            // (listToAttrs
+              (map (name: nameValuePair name "default_size default_family") [
+                "completion.category"
+                "completion.entry"
+                "contextmenu"
+                "debug_console"
+                "downloads"
+                "hints"
+                "keyhint"
+                "messages.error"
+                "messages.info"
+                "messages.warning"
+                "prompts"
+                "statusbar"
+              ]));
+
+          qt = mkIf kde.enable {
+            force_platform = null;
+            force_platformtheme = "KDE";
+          };
+        };
+
+        extraConfig =
+          (let
+            mkPaddingDictionary = {
+              name,
+              bottom,
+              left,
+              right,
+              top,
+            }: let
+              n = "c.${name}.padding";
+              b = "'bottom': ${toString bottom}";
+              l = "'left': ${toString left}";
+              r = "'right': ${toString right}";
+              t = "'top': ${toString top}";
+            in "${n} = {${b}, ${l}, ${r}, ${t}}";
+
+            final = map mkPaddingDictionary [
+              {
+                name = "hints";
+                bottom = 3;
+                left = 3;
+                right = 3;
+                top = 3;
+              }
+              {
+                name = "statusbar";
+                bottom = 1;
+                left = 0;
+                right = 3;
+                top = 1;
+              }
+              {
+                name = "tabs";
+                bottom = 1;
+                left = 6;
+                right = 6;
+                top = 1;
+              }
+            ];
+          in
+            concatStringsSep "\n" final + "\n")
+          + (let
+            allowSetting = setting: url: "config.set('content.${setting}', True, '${url}')";
+
+            allowMediaCaptureSetting = url: [
+              (allowSetting "desktop_capture" url)
+              (allowSetting "media.audio_video_capture" url)
+            ];
+            allowedMediaCapture = flatten (map allowMediaCaptureSetting [
+              "https://discord.com"
+              "https://meet.google.com"
+              "https://web.skype.com"
+            ]);
+
+            allowNotificationsSetting = allowSetting "notifications.enabled";
+            allowedNotifications = map allowNotificationsSetting [
+              "https://discord.com"
+              "https://web.skype.com"
+              "https://web.telegram.org"
+              "https://web.whatsapp.com"
+            ];
+
+            final = allowedMediaCapture ++ allowedNotifications;
+          in
+            concatStringsSep "\n" final + "\n");
+      };
+    };
+
+    services.psd.enable = true;
+  };
+}
diff --git a/modules/nixfiles/radarr.nix b/modules/nixfiles/radarr.nix
new file mode 100644
index 0000000..f57efc9
--- /dev/null
+++ b/modules/nixfiles/radarr.nix
@@ -0,0 +1,27 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.radarr;
+in {
+  options.nixfiles.modules.radarr = {
+    enable = mkEnableOption "Whether to enable Radarr.";
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = "radarr.${config.networking.fqdn}";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    nixfiles.modules.nginx = {
+      enable = true;
+      virtualHosts.${cfg.domain}.locations."/".proxyPass = "http://127.0.0.1:7878";
+    };
+
+    services.radarr.enable = true;
+  };
+}
diff --git a/modules/nixfiles/radicale.nix b/modules/nixfiles/radicale.nix
new file mode 100644
index 0000000..8286be1
--- /dev/null
+++ b/modules/nixfiles/radicale.nix
@@ -0,0 +1,48 @@
+{
+  config,
+  inputs,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.radicale;
+in {
+  options.nixfiles.modules.radicale = {
+    enable = mkEnableOption "Whether to enable Radicale.";
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = "radicale.${config.networking.domain}";
+    };
+  };
+
+  config = let
+    port = 5232;
+  in
+    mkIf cfg.enable {
+      secrets.radicale-htpasswd = {
+        file = "${inputs.self}/secrets/radicale-htpasswd";
+        owner = "radicale";
+        group = "radicale";
+      };
+
+      nixfiles.modules.nginx = {
+        enable = true;
+        virtualHosts.${cfg.domain}.locations."/".proxyPass = "http://127.0.0.1:${toString port}";
+      };
+
+      services.radicale = {
+        enable = true;
+        settings = {
+          server.hosts = ["127.0.0.1:${toString port}"];
+          web.type = "none";
+          auth = {
+            type = "htpasswd";
+            htpasswd_filename = config.secrets.radicale-htpasswd.path;
+            htpasswd_encryption = "bcrypt";
+          };
+        };
+      };
+    };
+}
diff --git a/modules/nixfiles/rss-bridge.nix b/modules/nixfiles/rss-bridge.nix
new file mode 100644
index 0000000..fe3a638
--- /dev/null
+++ b/modules/nixfiles/rss-bridge.nix
@@ -0,0 +1,33 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.rss-bridge;
+in {
+  options.nixfiles.modules.rss-bridge = {
+    enable = mkEnableOption "Whether to enable rss-bridge.";
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = "rss-bridge.${config.networking.domain}";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    nixfiles.modules.nginx = {
+      enable = true;
+      virtualHosts.${cfg.domain} = {};
+    };
+
+    services = {
+      rss-bridge = {
+        enable = true;
+        virtualHost = cfg.domain;
+        whitelist = ["LWNprev" "Phoronix"]; # TODO Expand.
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/rtorrent.nix b/modules/nixfiles/rtorrent.nix
new file mode 100644
index 0000000..2f1708c
--- /dev/null
+++ b/modules/nixfiles/rtorrent.nix
@@ -0,0 +1,246 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.rtorrent;
+in {
+  options.nixfiles.modules.rtorrent = {
+    enable = mkEnableOption "Whether to enable rTorrent.";
+
+    flood = {
+      enable = mkEnableOption "Whether to enable Flood.";
+
+      domain = mkOption {
+        description = "Domain name sans protocol scheme.";
+        type = with types; str;
+        default = "flood.${config.networking.domain}";
+      };
+    };
+  };
+
+  config = let
+    user = "rtorrent";
+    group = "rtorrent";
+    baseDir = "/var/lib/rtorrent";
+    rpcSocket = "/run/rtorrent/rpc.socket";
+  in
+    mkIf cfg.enable (mkMerge [
+      (let
+        port = 50000;
+      in {
+        systemd = {
+          services.rtorrent = {
+            description = "rTorrent";
+            after = ["network.target" "local-fs.target"];
+            serviceConfig = let
+              leechDir = "${baseDir}/leech";
+              seedDir = "${baseDir}/seed";
+              sessionDir = "${baseDir}/session";
+              logDir = "${baseDir}/log";
+            in {
+              User = user;
+              Group = group;
+              Type = "simple";
+              KillMode = "process";
+              KillSignal = "SIGHUP";
+              ExecStartPre = concatStringsSep " " [
+                "${pkgs.coreutils-full}/bin/mkdir -p"
+                leechDir
+                seedDir
+                sessionDir
+                logDir
+              ];
+              ExecStart = let
+                configFile = let
+                  moveCompleted = let
+                    pkg = pkgs.writeShellApplication {
+                      name = "move-completed";
+                      runtimeInputs = with pkgs; [
+                        coreutils-full
+                        gnused
+                        findutils
+                      ];
+                      text = ''
+                        set -x
+
+                        leech_path="$1"
+                        seed_path="$2"
+                        # seed_path="$(echo "$2" | sed 's@+@ @g;s@%@\\x@g' | xargs -0 printf '%b')"
+
+                        mkdir -p "$seed_path"
+                        mv -u "$leech_path" "$seed_path"
+                      '';
+                    };
+                  in "${pkg}/bin/move-completed";
+                in
+                  pkgs.writeText "rtorrent.rc" ''
+                    method.insert = cfg.leech,     private|const|string, (cat, "${leechDir}")
+                    method.insert = cfg.seed,      private|const|string, (cat, "${seedDir}")
+                    method.insert = cfg.session,   private|const|string, (cat, "${sessionDir}")
+                    method.insert = cfg.log,       private|const|string, (cat, "${logDir}")
+                    method.insert = cfg.rpcsocket, private|const|string, (cat, "${rpcSocket}")
+
+                    directory.default.set = (cat, (cfg.leech))
+                    session.path.set = (cat, (cfg.session))
+
+                    network.port_range.set = ${toString port}-${toString port}
+                    network.port_random.set = no
+
+                    dht.mode.set = disable
+                    protocol.pex.set = no
+
+                    trackers.use_udp.set = no
+
+                    protocol.encryption.set = allow_incoming,try_outgoing,enable_retry
+
+                    pieces.memory.max.set = 2048M
+                    pieces.preload.type.set = 2
+
+                    network.max_open_files.set   = 1024
+                    network.max_open_sockets.set = 1024
+
+                    network.http.max_open.set = 128
+
+                    throttle.global_down.max_rate.set_kb = 0
+                    throttle.global_up.max_rate.set_kb   = 0
+
+                    encoding.add = UTF-8
+                    system.umask.set = 0027
+                    system.cwd.set = (directory.default)
+
+                    network.scgi.open_local = (cat, (cfg.rpcsocket))
+                    schedule = scgi_group, 0, 0, "\
+                      execute.nothrow=chown, \":${group}\", (cfg.rpcsocket)\
+                    "
+                    schedule = scgi_permission, 0, 0, "\
+                      execute.nothrow=chmod, \"g+w,o=\", (cfg.rpcsocket)\
+                    "
+
+                    method.insert = d.move_completed, simple, "\
+                      d.directory.set=$argument.1=;\
+                      execute=${moveCompleted}, $argument.0=, $argument.1=;\
+                      d.save_full_session=\
+                    "
+                    method.insert = d.leech_path, simple, "\
+                      if=(d.is_multi_file),\
+                      (cat, (d.directory), /),\
+                      (cat, (d.directory), /, (d.name))\
+                    "
+                    method.insert = d.seed_path, simple, "\
+                      cat=$cfg.seed=, /, $d.custom1=\
+                    "
+                    method.set_key = event.download.finished, move_complete, "\
+                      d.move_completed=$d.leech_path=, $d.seed_path=\
+                    "
+
+                    log.open_file = "log", (cat, (cfg.log), "/", "default.log")
+                    log.add_output = "debug", "log"
+                    log.execute = (cat, (cfg.log), "/", "execute.log")
+                  '';
+              in
+                concatStringsSep " " [
+                  "${pkgs.rtorrent}/bin/rtorrent"
+                  "-n"
+                  "-o system.daemon.set=true"
+                  "-o import=${configFile}"
+                ];
+              Restart = "on-failure";
+              RestartSec = 3;
+              RuntimeDirectory = "rtorrent";
+              RuntimeDirectoryMode = 755;
+            };
+            wantedBy = ["multi-user.target"];
+          };
+
+          tmpfiles.rules = ["d '${baseDir}' 0750 ${user} ${group} -"];
+        };
+
+        users = {
+          users.${user} = {
+            inherit group;
+            shell = pkgs.bashInteractive;
+            home = baseDir;
+            description = "rTorrent";
+            isSystemUser = true;
+          };
+          groups.${group} = {};
+        };
+        my.extraGroups = [group];
+
+        networking.firewall.allowedTCPPorts = [port];
+
+        boot.kernel.sysctl = {
+          "net.core.rmem_max" = mkOverride 500 (pow 2 24);
+          "net.core.wmem_max" = mkOverride 500 (pow 2 24);
+          "net.ipv4.tcp_fin_timeout" = mkOverride 500 30;
+          "net.ipv4.tcp_rmem" = mkOverride 500 (mkTcpMem 12 23 24);
+          "net.ipv4.tcp_slow_start_after_idle" = 0;
+          "net.ipv4.tcp_tw_recycle" = mkOverride 500 1;
+          "net.ipv4.tcp_tw_reuse" = mkOverride 500 1;
+          "net.ipv4.tcp_wmem" = mkOverride 500 (mkTcpMem 12 23 24);
+        };
+      })
+      (let
+        port = 50001;
+        pkg = pkgs.nodePackages.flood;
+      in
+        mkIf cfg.flood.enable {
+          nixfiles.modules.nginx = {
+            enable = true;
+            virtualHosts.${cfg.flood.domain} = {
+              root = "${pkg}/lib/node_modules/flood/dist/assets";
+              locations = {
+                "/" = {
+                  tryFiles = "$uri /index.html";
+                  extraConfig = ''
+                    if ($internal != 1) {
+                      return 403;
+                    }
+                  '';
+                };
+                "/api" = {
+                  proxyPass = "http://127.0.0.1:${toString port}";
+                  extraConfig = ''
+                    proxy_buffering off;
+                    proxy_cache off;
+
+                    if ($internal != 1) {
+                      return 403;
+                    }
+                  '';
+                };
+              };
+            };
+          };
+
+          systemd.services.flood = {
+            description = "Flood";
+            after = ["network.target" "rtorrent.service"];
+            path = with pkgs; [mediainfo];
+            serviceConfig = {
+              User = user;
+              Group = group;
+              Type = "simple";
+              KillMode = "process";
+              ExecStart = concatStringsSep " " [
+                "${pkg}/bin/flood"
+                "--allowedpath=${baseDir}"
+                "--baseuri=/"
+                "--rundir=${baseDir}/flood"
+                "--host=127.0.0.1"
+                "--port=${toString port}"
+                "--rtsocket=${rpcSocket}"
+                "--ssl=false"
+                "--auth=none"
+              ];
+              Restart = "on-failure";
+              RestartSec = 3;
+            };
+            wantedBy = ["multi-user.target"];
+          };
+        })
+    ]);
+}
diff --git a/modules/nixfiles/searx.nix b/modules/nixfiles/searx.nix
new file mode 100644
index 0000000..a5bb005
--- /dev/null
+++ b/modules/nixfiles/searx.nix
@@ -0,0 +1,81 @@
+{
+  config,
+  inputs,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.searx;
+in {
+  options.nixfiles.modules.searx = {
+    enable = mkEnableOption "Whether to enable SearX.";
+
+    port = mkOption {
+      description = "Port.";
+      type = with types; port;
+      default = 61001;
+    };
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; nullOr str;
+      default = "searx.${config.networking.domain}";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    secrets.searx-environment = {
+      file = "${inputs.self}/secrets/searx-environment";
+      owner = "searx";
+      group = "searx";
+    };
+
+    nixfiles.modules.nginx = {
+      enable = true;
+      virtualHosts.${cfg.domain}.locations."/" = {
+        proxyPass = "http://127.0.0.1:${toString cfg.port}";
+        extraConfig = ''
+          if ($internal != 1) {
+            return 403;
+          }
+        '';
+      };
+    };
+
+    services = {
+      searx = {
+        enable = true;
+
+        settings = {
+          general = {
+            instance_name = cfg.domain;
+            contact_url = "mailto:admin+searx@${config.networking.domain}";
+            git_url = false;
+            git_branch = false;
+            docs_url = false;
+            wiki_url = false;
+            twitter_url = false;
+          };
+          server = {
+            bind_address = "127.0.0.1";
+            inherit (cfg) port;
+            secret_key = "@SECRET_KEY@";
+            base_url = false;
+            image_proxy = false;
+            default_http_headers = {
+              Referrer-Policy = "no-referrer";
+              X-Content-Type-Options = "nosniff";
+              X-Download-Options = "noopen";
+              X-Robots-Tag = "noindex, nofollow, nosnippet, noarchive";
+            };
+          };
+          search = {
+            safe_search = 0;
+            autocomplete = "";
+          };
+        };
+        environmentFile = config.secrets.searx-environment.path;
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/shadowsocks.nix b/modules/nixfiles/shadowsocks.nix
new file mode 100644
index 0000000..c496caa
--- /dev/null
+++ b/modules/nixfiles/shadowsocks.nix
@@ -0,0 +1,119 @@
+{
+  config,
+  inputs,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.shadowsocks;
+in {
+  options.nixfiles.modules.shadowsocks = {
+    enable = mkEnableOption "Whether to enable Shadowsocks.";
+
+    port = mkOption {
+      type = with types; port;
+      default = 8388;
+      description = "Port.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    secrets.shadowsocks-password = {
+      file = "${inputs.self}/secrets/shadowsocks-password";
+      mode = "0444"; # User is dynamic.
+    };
+
+    services = {
+      shadowsocks = {
+        enable = true;
+        passwordFile = config.secrets.shadowsocks-password.path;
+        localAddress = ["0.0.0.0"];
+        mode = "tcp_only";
+      };
+
+      fail2ban.jails.shadowsocks-libev = ''
+        enabled = true
+        filter = shadowsocks-libev
+        port = ${toString cfg.port}
+      '';
+    };
+
+    systemd.services.shadowsocks-libev.path = with pkgs;
+      mkForce [
+        (writeShellApplication {
+          name = "ss-server";
+          runtimeInputs = [shadowsocks-libev];
+          text = let
+            # https://github.com/shadowsocks/shadowsocks-libev/blob/master/acl/server_block_local.acl
+            aclFile = writeText "outbound_block_list.acl" ''
+              [outbound_block_list]
+              0.0.0.0/8
+              10.0.0.0/8
+              100.64.0.0/10
+              127.0.0.0/8
+              169.254.0.0/16
+              172.16.0.0/12
+              192.0.0.0/24
+              192.0.2.0/24
+              192.88.99.0/24
+              192.168.0.0/16
+              198.18.0.0/15
+              198.51.100.0/24
+              203.0.113.0/24
+              224.0.0.0/4
+              240.0.0.0/4
+              255.255.255.255/32
+              ::1/128
+              ::ffff:127.0.0.1/104
+              fc00::/7
+              fe80::/10
+            '';
+          in ''
+            ss-server --acl ${aclFile} "$@"
+          '';
+        })
+        coreutils-full
+        jq
+      ];
+
+    environment.etc = mkIf config.nixfiles.modules.fail2ban.enable {
+      "fail2ban/filter.d/shadowsocks-libev.conf".text = ''
+        [Definition]
+        failregex = ^.*failed to handshake with <ADDR>: authentication error$
+        ignoreregex =
+        journalmatch = _SYSTEMD_UNIT=shadowsocks-libev.service
+      '';
+    };
+
+    networking.firewall = {
+      allowedTCPPorts = [cfg.port];
+      extraCommands = ''
+        iptables -A nixos-fw -p tcp --syn --dport ${
+          toString cfg.port
+        } -m connlimit --connlimit-above 32 -j nixos-fw-refuse
+      '';
+    };
+
+    boot.kernel.sysctl = {
+      "net.core.rmem_max" = mkOverride 100 (pow 2 26);
+      "net.core.wmem_max" = mkOverride 100 (pow 2 26);
+      "net.core.netdev_max_backlog" = pow 2 18;
+      "net.core.somaxconn" = pow 2 12;
+      "net.ipv4.tcp_syncookies" = 1;
+      "net.ipv4.tcp_tw_reuse" = mkOverride 100 1;
+      "net.ipv4.tcp_tw_recycle" = mkOverride 100 0;
+      "net.ipv4.tcp_fin_timeout" = mkOverride 100 30;
+      "net.ipv4.tcp_keepalive_time" = 60 * 20;
+      "net.ipv4.ip_local_port_range" = "10000 65000";
+      "net.ipv4.tcp_max_syn_backlog" = pow 2 13;
+      "net.ipv4.tcp_max_tw_buckets" = pow 2 12;
+      "net.ipv4.tcp_fastopen" = 3;
+      "net.ipv4.tcp_mem" = mkOverride 100 (mkTcpMem 15 16 17);
+      "net.ipv4.tcp_rmem" = mkOverride 100 (mkTcpMem 12 16 26);
+      "net.ipv4.tcp_wmem" = mkOverride 100 (mkTcpMem 12 16 26);
+      "net.ipv4.tcp_mtu_probing" = 1;
+      "net.ipv4.tcp_congestion_control" = "hybla";
+    };
+  };
+}
diff --git a/modules/nixfiles/soju.nix b/modules/nixfiles/soju.nix
new file mode 100644
index 0000000..b586cbf
--- /dev/null
+++ b/modules/nixfiles/soju.nix
@@ -0,0 +1,83 @@
+{
+  config,
+  lib,
+  pkgs,
+  this,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.soju;
+in {
+  options.nixfiles.modules.soju = {
+    enable = mkEnableOption "Whether to enable soju.";
+
+    protocol = mkOption {
+      description = "Port.";
+      type = with types; enum ["ircs" "irc+insecure"];
+      default = "irc+insecure";
+    };
+
+    address = mkOption {
+      description = "Address.";
+      type = with types; str;
+      default = this.wireguard.ipv4.address;
+    };
+
+    port = mkOption {
+      description = "Port.";
+      type = with types; port;
+      default = 6667;
+    };
+
+    domain = mkOption {
+      description = "Domain.";
+      type = with types; str;
+      default = config.networking.fqdn;
+    };
+  };
+
+  config = let
+    db = "soju";
+  in
+    mkIf cfg.enable {
+      services.postgresql = {
+        ensureDatabases = [db];
+        ensureUsers = [
+          {
+            name = db;
+            ensurePermissions."DATABASE \"${db}\"" = "ALL PRIVILEGES";
+          }
+        ];
+      };
+
+      systemd.services.soju = {
+        description = "soju IRC bouncer";
+        wantedBy = ["multi-user.target"];
+        after = ["network-online.target" "postgresql.service"];
+        serviceConfig = {
+          Restart = "always";
+          ExecStart = let
+            configFile = pkgs.writeText "soju.conf" ''
+              listen ${cfg.protocol}://${cfg.address}:${toString cfg.port}
+              db postgres "${
+                concatStringsSep " " [
+                  "host=/run/postgresql"
+                  "user=${db}"
+                  "dbname=${db}"
+                  "sslmode=disable"
+                ]
+              }"
+              hostname ${cfg.domain}
+              title ${cfg.domain}
+            '';
+          in
+            concatStringsSep " " [
+              "${pkgs.soju}/bin/soju"
+              "-config ${configFile}"
+            ];
+          DynamicUser = true;
+          StateDirectory = "soju";
+        };
+      };
+    };
+}
diff --git a/modules/nixfiles/sonarr.nix b/modules/nixfiles/sonarr.nix
new file mode 100644
index 0000000..a2003f0
--- /dev/null
+++ b/modules/nixfiles/sonarr.nix
@@ -0,0 +1,27 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.sonarr;
+in {
+  options.nixfiles.modules.sonarr = {
+    enable = mkEnableOption "Whether to enable Sonarr.";
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = "sonarr.${config.networking.fqdn}";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    nixfiles.modules.nginx = {
+      enable = true;
+      virtualHosts.${cfg.domain}.locations."/".proxyPass = "http://127.0.0.1:8989";
+    };
+
+    services.sonarr.enable = true;
+  };
+}
diff --git a/modules/nixfiles/sound.nix b/modules/nixfiles/sound.nix
new file mode 100644
index 0000000..b352f3e
--- /dev/null
+++ b/modules/nixfiles/sound.nix
@@ -0,0 +1,21 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.sound;
+in {
+  options.nixfiles.modules.sound.enable =
+    mkEnableOption "Whether to enable sound support.";
+
+  config = mkIf cfg.enable {
+    services.pipewire = {
+      enable = true;
+
+      alsa.enable = false;
+      jack.enable = false;
+      pulse.enable = true;
+    };
+  };
+}
diff --git a/modules/nixfiles/subversion.nix b/modules/nixfiles/subversion.nix
new file mode 100644
index 0000000..1cfdca6
--- /dev/null
+++ b/modules/nixfiles/subversion.nix
@@ -0,0 +1,53 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.subversion;
+in {
+  options.nixfiles.modules.subversion.enable =
+    mkEnableOption "Whether to enable Subversion version control system.";
+
+  config = mkIf cfg.enable {
+    nixfiles.modules.gnupg.enable = true;
+
+    hm.home = {
+      file = {
+        ".subversion/config".text = generators.toINI {} {
+          auth = {
+            password-stores = "gpg-agent";
+            ssl-client-cert-file-prompt = "no";
+            store-passwords = "yes";
+            store-auth-creds = "yes";
+          };
+          helpers = {
+            editor-cmd = "${config.programs.vim.package}/bin/vim";
+            diff-cmd = "${pkgs.colordiff}/bin/colordiff";
+          };
+          miscellany = {
+            global-ignores = with config.hm.programs.git;
+              optionalString (ignores != []) (concatStringsSep " " ignores);
+            diff-ignore-content-type = "no";
+          };
+          working-copy = {
+            exclusive-locking-clients = "svn";
+            exclusive-locking = true;
+            busy-timeout = 10000;
+          };
+        };
+
+        ".subversion/servers".text = generators.toINI {} {
+          global = {
+            store-auth-creds = "yes";
+            store-passwords = "yes";
+            store-plaintext-passwords = "yes";
+          };
+        };
+      };
+
+      packages = with pkgs; [(subversionClient.override {saslSupport = true;})];
+    };
+  };
+}
diff --git a/modules/nixfiles/syncthing.nix b/modules/nixfiles/syncthing.nix
new file mode 100644
index 0000000..5a973cc
--- /dev/null
+++ b/modules/nixfiles/syncthing.nix
@@ -0,0 +1,161 @@
+{
+  config,
+  lib,
+  pkgs,
+  this,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.syncthing;
+in {
+  options.nixfiles.modules.syncthing = {
+    enable = mkEnableOption "Whether to enable Syncthing.";
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = "syncthing.${config.networking.fqdn}";
+    };
+
+    # TODO Make this simpler.
+    cert = mkOption {
+      description = "Path to the cert file.";
+      type = with types; nullOr string;
+      default = null;
+    };
+
+    # TODO Make this simpler.
+    key = mkOption {
+      description = "Path to the key file.";
+      type = with types; nullOr string;
+      default = null;
+    };
+  };
+
+  config = mkIf cfg.enable (mkMerge [
+    {
+      assertions = [
+        {
+          assertion = cfg.cert != null;
+          message = "Cert file needs to be specified.";
+        }
+        {
+          assertion = cfg.key != null;
+          message = "Key file needs to be specified.";
+        }
+      ];
+
+      services.syncthing = {
+        enable = true;
+
+        user = my.username;
+        inherit (config.my) group;
+
+        dataDir = config.my.home;
+
+        guiAddress = "127.0.0.1:8384";
+
+        inherit (cfg) key cert;
+
+        overrideDevices = true;
+        devices = mapAttrs (name: attr:
+          mkIf (attr.syncthing.id != null && hasAttr "wireguard" attr) {
+            inherit (attr.syncthing) id;
+            addresses = ["tcp://${name}.${config.networking.domain}:22000"];
+            introducer = this.isHeadless;
+          })
+        my.configurations;
+
+        overrideFolders = true;
+        folders = let
+          filterDevices = f:
+            attrNames (filterAttrs (_: attr:
+              (attr.hostname != this.hostname)
+              && (attr.syncthing.id != null)
+              && f attr)
+            my.configurations);
+          all = filterDevices (_: true);
+          notHeadless = filterDevices (attr: !attr.isHeadless);
+          notOther = filterDevices (attr: !attr.isOther);
+
+          simple = {
+            type = "simple";
+            params.keep = "5";
+          };
+          trashcan = {
+            type = "trashcan";
+            params.cleanoutDays = "30";
+          };
+          void = {
+            type = "external";
+            params.versionPath = with pkgs;
+              writeShellScriptBin "backup" ''
+                ${coreutils-full}/bin/rm -rf $1/$2
+              '';
+          };
+        in
+          with config.hm.xdg.userDirs; {
+            share = {
+              path = publicShare;
+              devices = notHeadless;
+              versioning = void;
+            };
+            pass = {
+              path =
+                config.hm.programs.password-store.settings.PASSWORD_STORE_DIR;
+              devices = all;
+              versioning = trashcan;
+            };
+            org = {
+              path = "${documents}/org";
+              devices = all;
+              versioning = simple;
+            };
+            roam = {
+              path = "${documents}/roam";
+              devices = notOther;
+              versioning = simple;
+            };
+            elfeed = {
+              path = "${config.my.home}/.elfeed";
+              devices = notOther;
+              versioning = trashcan;
+            };
+            vidya = {
+              path = "${documents}/vidya";
+              devices = notOther;
+              versioning = void;
+            };
+          };
+
+        extraOptions = {
+          gui = {
+            insecureAdminAccess = true;
+            insecureSkipHostcheck = this.isHeadless;
+          };
+          options = {
+            # Only local discovery is used over VPN.
+            globalAnnounceEnabled = false;
+            relaysEnabled = false;
+            urAccepted = -1;
+          };
+        };
+      };
+
+      systemd.services.syncthing.environment.STNODEFAULTFOLDER = "yes";
+    }
+    (mkIf this.isHeadless {
+      nixfiles.modules.nginx = {
+        enable = true;
+        virtualHosts.${cfg.domain}.locations."/" = {
+          proxyPass = "http://${config.services.syncthing.guiAddress}";
+          extraConfig = ''
+            if ($internal != 1) {
+              return 403;
+            }
+          '';
+        };
+      };
+    })
+  ]);
+}
diff --git a/modules/nixfiles/throttled.nix b/modules/nixfiles/throttled.nix
new file mode 100644
index 0000000..b7c7d8a
--- /dev/null
+++ b/modules/nixfiles/throttled.nix
@@ -0,0 +1,117 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.throttled;
+in {
+  options.nixfiles.modules.throttled.enable =
+    mkEnableOption "Whether to enable Throttled.";
+
+  config = mkIf cfg.enable {
+    environment.etc."throttled.conf".text = ''
+      [GENERAL]
+      # Enable or disable the script execution
+      Enabled: True
+      # SYSFS path for checking if the system is running on AC power
+      Sysfs_Power_Path: /sys/class/power_supply/AC*/online
+      # Auto reload config on changes
+      Autoreload: True
+
+      ## Settings to apply while connected to Battery power
+      [BATTERY]
+      # Update the registers every this many seconds
+      Update_Rate_s: 30
+      # Max package power for time window #1
+      PL1_Tdp_W: 29
+      # Time window #1 duration
+      PL1_Duration_s: 28
+      # Max package power for time window #2
+      PL2_Tdp_W: 44
+      # Time window #2 duration
+      PL2_Duration_S: 0.002
+      # Max allowed temperature before throttling
+      Trip_Temp_C: 85
+      # Set cTDP to normal=0, down=1 or up=2 (EXPERIMENTAL)
+      cTDP: 0
+      # Disable BDPROCHOT (EXPERIMENTAL)
+      Disable_BDPROCHOT: False
+
+      ## Settings to apply while connected to AC power
+      [AC]
+      # Update the registers every this many seconds
+      Update_Rate_s: 5
+      # Max package power for time window #1
+      PL1_Tdp_W: 44
+      # Time window #1 duration
+      PL1_Duration_s: 28
+      # Max package power for time window #2
+      PL2_Tdp_W: 44
+      # Time window #2 duration
+      PL2_Duration_S: 0.002
+      # Max allowed temperature before throttling
+      Trip_Temp_C: 95
+      # Set HWP energy performance hints to 'performance' on high load (EXPERIMENTAL)
+      # Uncomment only if you really want to use it
+      # HWP_Mode: False
+      # Set cTDP to normal=0, down=1 or up=2 (EXPERIMENTAL)
+      cTDP: 0
+      # Disable BDPROCHOT (EXPERIMENTAL)
+      Disable_BDPROCHOT: False
+
+      # All voltage values are expressed in mV and *MUST* be negative (i.e. undervolt)!
+      [UNDERVOLT.BATTERY]
+      # CPU core voltage offset (mV)
+      CORE: 0
+      # Integrated GPU voltage offset (mV)
+      GPU: 0
+      # CPU cache voltage offset (mV)
+      CACHE: 0
+      # System Agent voltage offset (mV)
+      UNCORE: 0
+      # Analog I/O voltage offset (mV)
+      ANALOGIO: 0
+
+      # All voltage values are expressed in mV and *MUST* be negative (i.e. undervolt)!
+      [UNDERVOLT.AC]
+      # CPU core voltage offset (mV)
+      CORE: 0
+      # Integrated GPU voltage offset (mV)
+      GPU: 0
+      # CPU cache voltage offset (mV)
+      CACHE: 0
+      # System Agent voltage offset (mV)
+      UNCORE: 0
+      # Analog I/O voltage offset (mV)
+      ANALOGIO: 0
+
+      # [ICCMAX.AC]
+      # # CPU core max current (A)
+      # CORE:
+      # # Integrated GPU max current (A)
+      # GPU:
+      # # CPU cache max current (A)
+      # CACHE:
+
+      # [ICCMAX.BATTERY]
+      # # CPU core max current (A)
+      # CORE:
+      # # Integrated GPU max current (A)
+      # GPU:
+      # # CPU cache max current (A)
+      # CACHE:
+    '';
+
+    systemd.services.throttled = {
+      description = "Stop Intel throttling";
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pkgs.throttled}/opt/throttled/throttled.py";
+      };
+      environment.PYTHONUNBUFFERED = "1";
+      wantedBy = ["multi-user.target"];
+    };
+  };
+}
diff --git a/modules/nixfiles/tmux.nix b/modules/nixfiles/tmux.nix
new file mode 100644
index 0000000..c90b0fc
--- /dev/null
+++ b/modules/nixfiles/tmux.nix
@@ -0,0 +1,60 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.tmux;
+in {
+  options.nixfiles.modules.tmux.enable =
+    mkEnableOption "Whether to enable tmux.";
+
+  config = mkIf cfg.enable {
+    hm.programs.tmux = {
+      enable = true;
+
+      aggressiveResize = true;
+      baseIndex = 1;
+      clock24 = true;
+      disableConfirmationPrompt = true;
+      escapeTime = 0;
+      historyLimit = 50000;
+      newSession = true;
+      resizeAmount = 10;
+      terminal = "screen-256color";
+
+      extraConfig = ''
+        set -g set-titles on
+
+        set -g status-left  ""
+        set -g status-right ""
+
+        set -g detach-on-destroy off
+
+        set -g status-keys emacs
+        set -g mode-keys   vi
+
+        bind h select-pane -L
+        bind j select-pane -D
+        bind k select-pane -U
+        bind l select-pane -R
+
+        bind -r H resize-pane -L 10
+        bind -r J resize-pane -D 10
+        bind -r K resize-pane -U 10
+        bind -r L resize-pane -R 10
+
+        bind < swap-pane -D
+        bind > swap-pane -U
+
+        bind , swap-window -t -1
+        bind . swap-window -t +1
+
+        bind Tab last-window
+
+        bind _ split-window -v
+        bind | split-window -h
+      '';
+    };
+  };
+}
diff --git a/modules/nixfiles/unbound.nix b/modules/nixfiles/unbound.nix
new file mode 100644
index 0000000..af983a8
--- /dev/null
+++ b/modules/nixfiles/unbound.nix
@@ -0,0 +1,208 @@
+{
+  config,
+  lib,
+  pkgs,
+  this,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.unbound;
+in {
+  options.nixfiles.modules.unbound = {
+    enable = mkEnableOption ''
+      Whether to enable Unbound.
+
+      This service is configured to be used in conjunction with a Wireguard
+      server located on the same host to enable internal name resolution with a
+      forwarding fallback via Quad9.
+
+      Features included: Internal name resolution, Quad9 as a recursive
+      resolver, DNSSEC validation, Redis cache and adblocking via pgl.yoyo.org
+      lists.
+    '';
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = config.networking.domain;
+    };
+  };
+
+  config = let
+    adblock-conf = "${config.services.unbound.stateDir}/adblock.conf";
+  in
+    mkIf cfg.enable {
+      services = {
+        unbound = {
+          enable = true;
+
+          package = pkgs.unbound-with-systemd.override {
+            withRedis = true;
+            withTFO = true;
+          };
+
+          settings = {
+            server = {
+              interface = with this.wireguard; [
+                "127.0.0.1"
+                "::1"
+                ipv4.address
+                ipv6.address
+              ];
+
+              local-zone =
+                concatLists
+                (mapAttrsToList (h: _: [''"${h}.${cfg.domain}" redirect''])
+                  my.configurations);
+              local-data = concatLists (mapAttrsToList (hostname: let
+                domain = "${hostname}.${cfg.domain}";
+              in
+                attr: (optionals (hasAttr "wireguard" attr) (with attr.wireguard;
+                  [
+                    ''"${domain} 604800 IN A ${ipv4.address}"''
+                    ''"${domain} 604800 IN AAAA ${ipv6.address}"''
+                    ''"${domain}. A ${ipv4.address}"''
+                    ''"${domain}. AAAA ${ipv6.address}"''
+                  ]
+                  ++ concatMap (domain: [
+                    ''"${domain}. A ${ipv4.address}"''
+                    ''"${domain}. AAAA ${ipv6.address}"''
+                  ])
+                  attr.domains)))
+              my.configurations);
+              local-data-ptr = concatLists (mapAttrsToList (hostname: let
+                domain = "${hostname}.${cfg.domain}";
+              in
+                attr: (optionals (hasAttr "wireguard" attr) (with attr.wireguard;
+                  [
+                    ''"${ipv4.address} ${domain}"''
+                    ''"${ipv6.address} ${domain}"''
+                  ]
+                  ++ concatMap (domain: [
+                    ''"${ipv4.address} ${domain}"''
+                    ''"${ipv6.address} ${domain}"''
+                  ])
+                  attr.domains)))
+              my.configurations);
+
+              access-control = with config.nixfiles.modules.wireguard; [
+                "0.0.0.0/0 refuse"
+                "::/0 refuse"
+                "127.0.0.0/8 allow"
+                "::1/128 allow"
+                "${ipv4.subnet} allow"
+                "${ipv6.subnet} allow"
+              ];
+
+              private-domain = cfg.domain;
+              private-address = with config.nixfiles.modules.wireguard; [
+                ipv4.subnet
+                ipv6.subnet
+              ];
+
+              domain-insecure = cfg.domain;
+
+              prefetch = true;
+              prefetch-key = true;
+
+              hide-identity = true;
+              hide-version = true;
+
+              extended-statistics = true;
+
+              include = ''"${adblock-conf}"'';
+            };
+
+            forward-zone = [
+              {
+                name = ".";
+                forward-tls-upstream = true;
+                forward-addr = let
+                  mkDnsOverTls = ips: auth:
+                    map (ip: concatStrings [ip "@" auth]) ips;
+                in
+                  mkDnsOverTls dns.const.quad9.default "853#dns.quad9.net";
+              }
+            ];
+
+            cachedb = with config.services.redis.servers.unbound; {
+              backend = "redis";
+              redis-server-host = bind;
+              redis-server-port = port;
+            };
+          };
+
+          localControlSocketPath = "/run/unbound/unbound.socket";
+        };
+
+        redis = {
+          servers.unbound = {
+            enable = true;
+            bind = "127.0.0.1";
+            port = 6379;
+          };
+          vmOverCommit = mkForce true;
+        };
+
+        prometheus.exporters = {
+          unbound = {
+            enable = true;
+            listenAddress = mkDefault this.wireguard.ipv4.address;
+            port = 9167;
+            fetchType = "uds";
+            controlInterface = config.services.unbound.localControlSocketPath;
+            inherit (config.services.unbound) group user;
+          };
+
+          redis = {
+            enable = true;
+            listenAddress = mkDefault this.wireguard.ipv4.address;
+            port = mkDefault 9121;
+            extraFlags = with config.services.redis.servers.unbound; [
+              "--redis.addr=redis://${bind}:${toString port}"
+              "--redis.user=${user}"
+            ];
+          };
+        };
+      };
+
+      systemd = {
+        services = {
+          unbound.after = ["unbound-adblock-update.service"];
+
+          unbound-adblock-update = {
+            serviceConfig = with config.services.unbound; {
+              Type = "oneshot";
+              User = user;
+              Group = group;
+              ExecStart = let
+                pkg = with pkgs;
+                  writeShellApplication {
+                    name = "unbound-adblock-update";
+                    runtimeInputs = [curl package];
+                    text = ''
+                      curl \
+                        "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=unbound&showintro=0&mimetype=plaintext" \
+                        >${adblock-conf}
+
+                      if [[ -f "${localControlSocketPath}" ]]; then
+                         unbound-control reload
+                      fi
+                    '';
+                  };
+              in "${pkg}/bin/unbound-adblock-update";
+            };
+          };
+        };
+
+        timers.unbound-adblock-update = {
+          requires = ["network-online.target"];
+          timerConfig = {
+            OnUnitActiveSec = "1d";
+            Unit = "unbound-adblock-update.service";
+          };
+          wantedBy = ["timers.target"];
+        };
+      };
+    };
+}
diff --git a/modules/nixfiles/vaultwarden.nix b/modules/nixfiles/vaultwarden.nix
new file mode 100644
index 0000000..4f5bcb4
--- /dev/null
+++ b/modules/nixfiles/vaultwarden.nix
@@ -0,0 +1,117 @@
+{
+  config,
+  inputs,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.vaultwarden;
+in {
+  options.nixfiles.modules.vaultwarden = {
+    enable = mkEnableOption "Whether to enable Vaultwarden.";
+
+    domain = mkOption {
+      description = "Domain name sans protocol scheme.";
+      type = with types; str;
+      default = "vaultwarden.${config.networking.domain}";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    secrets.vaultwarden-environment = {
+      file = "${inputs.self}/secrets/vaultwarden-environment";
+      owner = "vaultwarden";
+      group = "vaultwarden";
+    };
+
+    nixfiles.modules = {
+      nginx = {
+        enable = true;
+        virtualHosts.${cfg.domain} = with config.services.vaultwarden.config; {
+          locations."/" = {
+            proxyPass = "http://[${ROCKET_ADDRESS}]:${toString ROCKET_PORT}";
+            proxyWebsockets = true;
+          };
+          locations."/notifications/hub" = {
+            proxyPass = "http://[${WEBSOCKET_ADDRESS}]:${toString WEBSOCKET_PORT}";
+            proxyWebsockets = true;
+          };
+          locations."/notifications/hub/negotiate" = {
+            proxyPass = "http://[${ROCKET_ADDRESS}]:${toString ROCKET_PORT}";
+            proxyWebsockets = true;
+          };
+        };
+      };
+      postgresql.enable = true;
+    };
+
+    services = let
+      db = "vaultwarden";
+    in {
+      vaultwarden = {
+        enable = true;
+        config = {
+          TZ = config.time.timeZone;
+
+          WEB_VAULT_ENABLED = true;
+
+          DOMAIN = optionalString (cfg.domain != null) "http://${cfg.domain}";
+
+          SIGNUPS_ALLOWED = false;
+          INVITATIONS_ALLOWED = true;
+
+          ROCKET_ADDRESS = "::1";
+          ROCKET_PORT = 8812;
+
+          WEBSOCKET_ENABLED = true;
+          WEBSOCKET_ADDRESS = "::1";
+          WEBSOCKET_PORT = 8813;
+
+          LOG_LEVEL = "error";
+
+          DATABASE_URL = "postgresql://${db}@/${db}";
+        };
+        dbBackend = "postgresql";
+        environmentFile = config.secrets.vaultwarden-environment.path;
+      };
+
+      postgresql = {
+        ensureDatabases = [db];
+        ensureUsers = [
+          {
+            name = db;
+            ensurePermissions."DATABASE \"${db}\"" = "ALL PRIVILEGES";
+          }
+        ];
+      };
+
+      fail2ban.jails = mkIf config.nixfiles.modules.fail2ban.enable {
+        vaultwarden = ''
+          enabled = true
+          filter = vaultwarden
+          port = http,https
+        '';
+        vaultwarden-admin = ''
+          enabled = true
+          filter = vaultwarden-admin
+          port = http,https
+        '';
+      };
+    };
+
+    environment.etc = mkIf config.nixfiles.modules.fail2ban.enable {
+      "fail2ban/filter.d/vaultwarden.conf".text = ''
+        [Definition]
+        failregex = ^.*Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
+        ignoreregex =
+        journalmatch = _SYSTEMD_UNIT=vaultwarden.service
+      '';
+      "fail2ban/filter.d/vaultwarden-admin.conf".text = ''
+        [Definition]
+        failregex = ^.*Invalid admin token\. IP: <ADDR>.*$
+        ignoreregex =
+        journalmatch = _SYSTEMD_UNIT=vaultwarden.service
+      '';
+    };
+  };
+}
diff --git a/modules/nixfiles/vim/default.nix b/modules/nixfiles/vim/default.nix
new file mode 100644
index 0000000..cef4423
--- /dev/null
+++ b/modules/nixfiles/vim/default.nix
@@ -0,0 +1,56 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.vim;
+in {
+  options.nixfiles.modules.vim.enable =
+    mkEnableOption "Whether to enable Vim text editor.";
+
+  config = mkIf cfg.enable {
+    programs.vim.package = with pkgs;
+      ((vim_configurable.override {features = "normal";}
+          // listToAttrs
+          (map (name: nameValuePair name false) [
+            "cscopeSupport"
+            "darwinSupport"
+            "guiSupport"
+            "luaSupport"
+            "multibyteSupport"
+            "netbeansSupport"
+            "nlsSupport"
+            "perlSupport"
+            "pythonSupport"
+            "rubySupport"
+            "tclSupport"
+            "ximSupport"
+          ]))
+        .overrideAttrs (_: super: {
+          configureFlags =
+            super.configureFlags
+            ++ ["--enable-gpm=no" "--enable-gui=no"];
+        }))
+      .customize {
+        name = "vim";
+        vimrcConfig = {
+          customRC = readFile ./rc.vim;
+          packages.myVimPackage.start = with vimPlugins; [
+            editorconfig-vim
+            vim-eunuch
+            vim-nix
+            vim-sleuth
+            vim-surround
+            vim-unimpaired
+          ];
+        };
+      };
+
+    environment = {
+      systemPackages = [config.programs.vim.package];
+      variables.EDITOR = mkOverride 100 "vim";
+    };
+  };
+}
diff --git a/modules/nixfiles/vim/rc.vim b/modules/nixfiles/vim/rc.vim
new file mode 100644
index 0000000..c53b2d2
--- /dev/null
+++ b/modules/nixfiles/vim/rc.vim
@@ -0,0 +1,252 @@
+set nocompatible
+
+let $VIMFILES = expand('<sfile>:p:h')
+
+let g:skip_defaults_vim = 1
+
+let g:netrw_dirhistmax = 0
+
+set autoread
+set backspace=indent,eol,start
+set clipboard=unnamed,unnamedplus
+set diffopt+=iwhite
+set hidden
+set history=256
+set lazyredraw
+set mouse=
+set path+=**
+set tabpagemax=50
+set viminfo=
+
+set cmdheight=1
+set display+=lastline
+set fillchars=vert:\ "
+set laststatus=2
+set modeline
+set noshowmode
+set ruler
+set shortmess+=I
+set textwidth=0
+set title
+
+set sessionoptions-=options
+set viewoptions-=options
+
+set noerrorbells
+set novisualbell
+
+set splitbelow
+set splitright
+
+set complete=
+set complete+=.
+set complete+=b
+set complete+=t
+set completeopt=
+set completeopt+=menu
+set completeopt+=longest
+
+set gdefault
+set hlsearch
+set incsearch
+set iskeyword+=-
+set magic
+
+set foldmethod=marker
+set nofoldenable
+
+set shortmess=
+set shortmess+=I
+set shortmess+=T
+set shortmess+=a
+set shortmess+=c
+set shortmess+=t
+
+set nolist
+set nowrap
+
+set scrolloff=10
+set sidescrolloff=10
+
+set number
+if v:version >= 700
+    set numberwidth=3
+endif
+
+set wildignorecase
+set wildmenu
+set wildignore=
+
+set nobackup
+set noswapfile
+set noundofile
+set nowritebackup
+
+set smartcase
+set ignorecase
+
+set autoindent
+set breakindent
+set smartindent
+
+set expandtab
+set shiftround
+set shiftwidth=4
+set smarttab
+set softtabstop=4
+set tabstop=4
+
+autocmd BufEnter *.* :set colorcolumn=
+
+if &t_Co == 8 && $TERM !~# '^Eterm'
+    set t_Co=16
+endif
+
+if &listchars ==# 'eol:$'
+    set listchars=tab:>\ ,trail:-,extends:>,precedes:<,nbsp:+
+endif
+
+if v:version > 703 || v:version == 703 && has("patch541")
+    set formatoptions+=j
+endif
+
+if has('path_extra')
+    setglobal tags-=./tags tags-=./tags; tags^=./tags;
+endif
+
+if !has('nvim') && &ttimeoutlen == -1
+    set ttimeout
+    set ttimeoutlen=100
+endif
+
+try
+    set encoding=utf-8
+    scriptencoding utf-8
+catch
+endtry
+
+try
+    if &fileencodings !~? "utf-8"
+        let g:added_fenc_utf8 = 1
+        set fileencodings+=utf-8
+    endif
+catch
+endtry
+
+if has('autocmd')
+    filetype plugin indent on
+
+    if exists("+omnifunc")
+        autocmd Filetype *
+                    \ if &omnifunc == "" |
+                    \ setlocal omnifunc=syntaxcomplete#Complete |
+                    \ endif
+    endif
+
+    autocmd BufEnter * set noreadonly
+endif
+
+if has('syntax') && !exists('g:syntax_on')
+    syntax enable
+endif
+
+if !exists('g:loaded_matchit') && findfile('plugin/matchit.vim', &rtp) ==# ''
+    runtime! macros/matchit.vim
+endif
+
+nnoremap        <SPACE>     <nop>
+let mapleader=" "
+
+nnoremap        :W          :w
+nnoremap        :W!         :w!
+nnoremap        :Q          :q
+nnoremap        :Q!         :q!
+
+nnoremap <Expr> j           v:count ? 'j' : 'gj'
+nnoremap <Expr> k           v:count ? 'k' : 'gk'
+
+nnoremap        J           gt
+nnoremap        K           gT
+
+nnoremap        <C-a>       ^h
+vnoremap        <C-a>       ^h
+nnoremap        H           ^h
+vnoremap        H           ^h
+
+nnoremap        <C-e>       $
+vnoremap        <C-e>       $
+nnoremap        L           $
+vnoremap        L           $
+
+nnoremap        N           Nzzzv
+nnoremap        n           nzzzv
+
+inoremap        <C-u>       <C-g>u<C-u>
+inoremap        <C-w>       <C-g>u<C-w>
+
+vnoremap        <           <gv
+vnoremap        >           >gv
+vnoremap        <Tab>       >gv
+vnoremap        <S-Tab>     <gv
+nnoremap        <Tab>       >>_
+nnoremap        <S-Tab>     <<_
+
+nnoremap        ]b          :<C-u>bnext<CR>
+nnoremap        [b          :<C-u>bprevious<CR>
+
+nnoremap        <C-h>       <C-w>h
+nnoremap        <C-j>       <C-w>j
+nnoremap        <C-k>       <C-w>k
+nnoremap        <C-l>       <C-w>l
+
+nnoremap        *           /\<<C-r>=expand('<cword>')<CR>\><CR>
+nnoremap        #           ?\<<C-r>=expand('<cword>')<CR>\><CR>
+
+nnoremap        <C-L>       :<C-u>nohlsearch<C-r>=has('diff')?'<Bar>diffupdate':''<CR><CR><C-l>
+
+cnoremap        ;/          <C-r>=expand('%:p:h').'/'<CR>
+cnoremap        ;;          <C-r>=expand('%:t')<CR>
+cnoremap        ;.          <C-r>=expand('%:p:r')<CR>
+
+nnoremap        <Leader>.   :<C-u>lcd %:p:h<CR>
+
+nnoremap        Q           @q
+
+nnoremap        <Leader>c   ^v$h
+nnoremap        <Leader>v   ggVG
+
+nnoremap        <Leader>y   "+y
+nnoremap        <Leader>Y   "+Y
+
+nnoremap        <Leader>p   "+p
+nnoremap        <Leader>P   "+P
+
+inoremap        <C-v>       <C-c>"+pi
+cnoremap        <C-v>       <C-r>+
+
+nmap            <Leader>w   :<C-u>w!<CR>
+nmap            <Leader>wq  :<C-u>wq!<CR>
+
+nnoremap        ZX          :<C-u>qa!<CR>
+
+nnoremap        <Leader>q   :<C-u>q<CR>
+
+command         WS          w !sudo tee "%" >/dev/null
+
+function! SwitchCase()
+    normal! ~
+    if strlen(getline('.')) != virtcol('.')
+        normal! h
+    endif
+endfunction
+nnoremap        ~           :<C-u>call SwitchCase()<CR>
+
+function! s:GM()
+    execute 'normal! ^'
+    let first_col = virtcol('.')
+    execute 'normal! g_'
+    let last_col  = virtcol('.')
+    execute 'normal! ' . (first_col + last_col) / 2 . '|'
+endfunction
+nnoremap        gm          :<C-u>call <SID>GM()<CR>
+onoremap        gm          :<C-u>call <SID>GM()<CR>
diff --git a/modules/nixfiles/vscode.nix b/modules/nixfiles/vscode.nix
new file mode 100644
index 0000000..b41d037
--- /dev/null
+++ b/modules/nixfiles/vscode.nix
@@ -0,0 +1,172 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.vscode;
+in {
+  options.nixfiles.modules.vscode = {
+    enable = mkEnableOption "Whether to enable VSCode.";
+
+    package = with pkgs;
+      mkOption {
+        type = types.enum [vscodium vscode vscode-fhs];
+        default = vscodium;
+        description = "Which package to use as a VSCode implementation.";
+      };
+
+    vim.enable = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Whether to enable Vim emulation.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    hm.programs.vscode = with config.nixfiles;
+    with modules;
+    with profiles; {
+      enable = true;
+
+      inherit (cfg) package;
+
+      extensions = with pkgs;
+      with vscode-extensions;
+        [editorconfig.editorconfig file-icons.file-icons redhat.vscode-yaml]
+        ++ optional cfg.vim.enable vscodevim.vim
+        ++ vscode-utils.extensionsFromVscodeMarketplace [
+          {
+            name = "vscode-xml";
+            publisher = "redhat";
+            version = "0.20.0";
+            sha256 = "sha256-GKBrf9s8n7Wv14RSfwyDma1dM0fGMvRkU/7v2DAcB9A=";
+          }
+        ];
+
+      userSettings = let
+        font = config.fontScheme.monospaceFont;
+        fontFamily = font.family;
+        fontSize = font.size;
+      in {
+        editor =
+          {
+            inherit fontFamily fontSize;
+            inlayHints = {inherit fontFamily fontSize;};
+            codeLens = false;
+            cursorStyle = "block";
+            detectIndentation = true;
+            minimap.enabled = false;
+            renderWhitespace = "trailing";
+            rulers = [80 120];
+            smoothScrolling = false;
+            tabCompletion = true;
+          }
+          // (let
+            surround = 10;
+          in {
+            cursorSurroundingLines = surround;
+            scrollBeyondLastColumn = surround;
+          });
+
+        keyboard.dispatch = "keyCode";
+
+        diffEditor.codeLens = false;
+
+        files = {
+          autoSave = "off";
+          enableTrash = false;
+        };
+
+        workbench = {
+          activityBar.visible = false;
+          editor.highlightModifiedTabs = true;
+          enableExperiments = false;
+          settings.enableNaturalLanguageSearch = false;
+          startupEditor = "none";
+          tips.enabled = false;
+          tree.indent = 4;
+          welcomePage = {
+            walkthroughs.openOnInstall = false;
+            preferReducedMotion = true;
+          };
+        };
+
+        debug.console = {inherit fontFamily fontSize;};
+
+        scm = {
+          inputFontFamily = fontFamily;
+          inputFontSize = fontSize;
+        };
+
+        extensions = {
+          autoCheckUpdates = false;
+          autoUpdate = false;
+          ignoreRecommendations = true;
+        };
+
+        terminal = {
+          external.linuxExec =
+            if alacritty.enable
+            then "${pkgs.alacritty}/bin/alacritty"
+            else "${pkgs.xterm}/bin/xterm}";
+
+          integrated = {
+            inherit fontFamily fontSize;
+            enableBell = true;
+          };
+        };
+
+        update = {
+          mode = "none";
+          showReleaseNotes = false;
+        };
+
+        telemetry = {
+          enableCrashReporter = false;
+          enableTelemetry = false;
+        };
+
+        security.workspace.trust.enabled = false;
+
+        git.allowForcePush = true;
+
+        vim = let
+          applyInputMethod = {
+            "ibus" = let
+              bin = "${pkgs.ibus}/bin/ibus";
+            in {
+              enable = true;
+              defaultIM = "xkb:us::eng";
+              obtainIMCmd = "${bin} engine";
+              switchIMCmd = "${bin} engine {im}";
+            };
+            "fcitx" = let
+              bin = "${pkgs.fcitx}/bin/fcitx-remote";
+            in {
+              enable = true;
+              defaultIM = "1";
+              obtainIMCmd = bin;
+              switchIMCmd = "${bin} -t {im}";
+            };
+          };
+        in
+          mkIf cfg.vim.enable rec {
+            easymotion = true;
+            easymotionMarkerFontFamily = fontFamily;
+            easymotionMarkerFontSize = fontSize;
+
+            leader = " ";
+
+            useSystemClipboard = true;
+
+            autoSwitchInputMethod = let
+              inputMethod = config.i18n.inputMethod.enabled;
+            in
+              mkIf (inputMethod != null) applyInputMethod.${inputMethod};
+          };
+      };
+    };
+  };
+}
diff --git a/modules/nixfiles/wget.nix b/modules/nixfiles/wget.nix
new file mode 100644
index 0000000..9e4db27
--- /dev/null
+++ b/modules/nixfiles/wget.nix
@@ -0,0 +1,36 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.wget;
+in {
+  options.nixfiles.modules.wget.enable =
+    mkEnableOption "Whether to enable wget.";
+
+  config = mkIf cfg.enable {
+    hm = {
+      home.file.".wgetrc".text = ''
+        adjust_extension = on
+        dirstruct = off
+        follow_ftp = on
+        passive_ftp = off
+        progress = bar
+        quota = inf
+        reclevel = 5
+        recursive = off
+        robots = off
+        timestamping = off
+        tries = 5
+        wait = 0
+        waitretry = 10
+      '';
+
+      programs.bash.shellAliases.wget = "${pkgs.wget}/bin/wget --hsts-file=${config.hm.xdg.cacheHome}/wget-hsts";
+    };
+
+    environment.systemPackages = with pkgs; [wget];
+  };
+}
diff --git a/modules/nixfiles/wireguard.nix b/modules/nixfiles/wireguard.nix
new file mode 100644
index 0000000..1da3e74
--- /dev/null
+++ b/modules/nixfiles/wireguard.nix
@@ -0,0 +1,208 @@
+{
+  config,
+  lib,
+  pkgs,
+  this,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.wireguard;
+in {
+  options.nixfiles.modules.wireguard = {
+    # TODO Make this simpler.
+    privateKeyFile = mkOption {
+      description = "Path to the private key file.";
+      type = with types; nullOr string;
+      default = null;
+    };
+
+    client = {
+      enable = mkEnableOption "Whether to enable WireGuard client.";
+
+      enableTrafficRouting = mkOption {
+        description = "Whether to enable traffic routing through the sever.";
+        type = with types; bool;
+        default = !this.isHeadless;
+      };
+    };
+
+    server = {
+      enable = mkEnableOption ''
+        Whether to enable WireGuard server. This module requires
+        <option>networking.nat.externalInterface</option> to be set.
+      '';
+
+      ipv4.address = mkOption {
+        description = "IPv4 address to bind to.";
+        type = with types; str;
+        default = my.configurations.manwe.wireguard.ipv4.address;
+      };
+
+      ipv6.address = mkOption {
+        description = "IPv4 address to bind to.";
+        type = with types; str;
+        default = my.configurations.manwe.wireguard.ipv6.address;
+      };
+
+      address = mkOption {
+        description = "Endpoint address to use";
+        type = with types; str;
+        default = my.configurations.manwe.ipv4.address;
+      };
+
+      port = mkOption {
+        description = "Endpoint port to use.";
+        type = with types; int;
+        default = 6969;
+      };
+
+      publicKey = mkOption {
+        description = "Server's public key.";
+        type = with types; str;
+        default = my.configurations.manwe.wireguard.publicKey;
+      };
+
+      peers = mkOption {
+        description = "List of peers.";
+        type = with types; listOf attrs;
+        default = mapAttrsToList (_: attr:
+          with attr; {
+            inherit (wireguard) publicKey;
+            allowedIPs = with wireguard; [
+              "${ipv4.address}/32"
+              "${ipv6.address}/128"
+            ];
+          }) (filterAttrs (_: attr:
+          attr.hostname != this.hostname && hasAttr "wireguard" attr)
+        my.configurations);
+      };
+    };
+
+    interface = mkOption {
+      description = "Name of the interface to use WireGuard with.";
+      type = with types; str;
+      default = "wg69";
+    };
+
+    ipv4.subnet = mkOption {
+      description = "CIDR notation for the IPv4 subnet to use over WireGuard.";
+      type = with types; str;
+      default = "10.69.0.0/16";
+    };
+
+    ipv6.subnet = mkOption {
+      description = "CIDR notation for the IPv6 subnet to use over WireGuard.";
+      type = with types; str;
+      default = "fd69::/16";
+    };
+  };
+
+  config =
+    {
+      assertions = [
+        {
+          assertion = cfg.privateKeyFile != null;
+          message = "Key file must be specified.";
+        }
+        {
+          assertion = config.security.sudo.enable;
+          message = "Sudo is not enabled.";
+        }
+        {
+          assertion = any (x: x == "wheel") config.my.extraGroups;
+          message = ''User is not in the "wheel" group.'';
+        }
+      ];
+    }
+    // mkMerge [
+      (mkIf (cfg.client.enable || cfg.server.enable) {
+        networking.firewall.trustedInterfaces = [cfg.interface];
+      })
+      (mkIf cfg.client.enable {
+        networking.wg-quick.interfaces.${cfg.interface} = mkMerge [
+          (with this.wireguard; {
+            inherit (cfg) privateKeyFile;
+            address = ["${ipv4.address}/16" "${ipv6.address}/16"];
+          })
+          (with cfg.server; {
+            peers = [
+              {
+                inherit publicKey;
+                endpoint = "${address}:${toString port}";
+                allowedIPs =
+                  if cfg.client.enableTrafficRouting
+                  then [
+                    "0.0.0.0/0"
+                    "::/0"
+                  ]
+                  else [
+                    cfg.ipv4.subnet
+                    cfg.ipv6.subnet
+                  ];
+                persistentKeepalive = 25;
+              }
+            ];
+            dns = [
+              ipv4.address
+              ipv6.address
+            ]; # This assumes that the host has Unbound running.
+          })
+        ];
+
+        environment.systemPackages = with pkgs; [
+          (writeShellApplication {
+            name = "wg-toggle";
+            runtimeInputs = [iproute2 jq wireguard-tools];
+            text = ''
+              ip46() {
+                sudo ip -4 "$@"
+                sudo ip -6 "$@"
+              }
+
+              fwmark=$(sudo wg show ${cfg.interface} fwmark) || exit
+              if ip -j rule list lookup "$fwmark" | jq -e 'length > 0' >/dev/null; then
+                  ip46 rule del lookup main suppress_prefixlength 0
+                  ip46 rule del lookup "$fwmark"
+              else
+                  ip46 rule add not fwmark "$fwmark" lookup "$fwmark"
+                  ip46 rule add lookup main suppress_prefixlength 0
+              fi
+            '';
+          })
+        ];
+      })
+      (mkIf cfg.server.enable {
+        networking = {
+          wireguard = {
+            enable = true;
+            interfaces.${cfg.interface} = with cfg.server; {
+              inherit (cfg) privateKeyFile;
+              ips = ["${ipv4.address}/16" "${ipv6.address}/16"];
+              listenPort = port;
+              inherit peers;
+              allowedIPsAsRoutes = false;
+            };
+          };
+
+          nat = {
+            enable = true;
+            enableIPv6 = true;
+
+            internalInterfaces = [cfg.interface];
+            internalIPs = [cfg.ipv4.subnet];
+            internalIPv6s = [cfg.ipv6.subnet];
+          };
+
+          firewall.allowedUDPPorts = [cfg.server.port];
+        };
+
+        # # TODO Dashboard for this.
+        # services.prometheus.exporters.wireguard = {
+        #   enable = true;
+        #   listenAddress = mkDefault this.wireguard.ipv4.address;
+        #   withRemoteIp = true;
+        #   port = 9586;
+        # };
+      })
+    ];
+}
diff --git a/modules/nixfiles/x11.nix b/modules/nixfiles/x11.nix
new file mode 100644
index 0000000..ff0feaf
--- /dev/null
+++ b/modules/nixfiles/x11.nix
@@ -0,0 +1,110 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.x11;
+in {
+  options.nixfiles.modules.x11.enable = mkEnableOption "Whether to enable X11.";
+
+  config = mkIf cfg.enable {
+    nixfiles.modules.fonts.enable = true;
+
+    hm = {
+      home = {
+        packages = with pkgs;
+          [xclip xdotool]
+          ++ (with xorg; [
+            xdpyinfo
+            xdriinfo
+            xev
+            xfontsel
+            xkill
+            xlsatoms
+            xlsclients
+            xlsfonts
+            xprop
+            xrandr
+            xwininfo
+          ]);
+
+        sessionVariables = with config.dirs; {
+          XCOMPOSEFILE = "${cache}/XComposeFile";
+          XCOMPOSECACHE = "${cache}/XComposeCache";
+        };
+      };
+
+      xsession.scriptPath = ".xinitrc";
+
+      xresources.properties = with config.nixfiles.modules; let
+        font = with config.fontScheme.monospaceFont; "${family}:style=${style}:size=${toString size}";
+      in
+        {
+          "*.font" = font;
+
+          "Xft.antialias" = 1;
+          "Xft.autohint" = 0;
+          "Xft.dpi" = 96;
+          "Xft.hinting" = 1;
+          "Xft.hintstyle" = "hintslight";
+          "Xft.lcdfilter" = "lcddefault";
+          "Xft.rgba" = "rgb";
+        }
+        // (with profiles.common.colourScheme; {
+          "*.color0" = black;
+          "*.color8" = brightBlack;
+          "*.color1" = red;
+          "*.color9" = brightRed;
+          "*.color2" = green;
+          "*.color10" = brightGreen;
+          "*.color3" = yellow;
+          "*.color11" = brightYellow;
+          "*.color4" = blue;
+          "*.color12" = brightBlue;
+          "*.color5" = magenta;
+          "*.color13" = brightMagenta;
+          "*.color6" = cyan;
+          "*.color14" = brightCyan;
+          "*.color7" = white;
+          "*.color15" = brightWhite;
+
+          "*.background" = background;
+          "*.foreground" = foreground;
+        });
+    };
+
+    services.xserver = {
+      enable = true;
+
+      tty = mkDefault 1;
+
+      autoRepeatDelay = 200;
+      autoRepeatInterval = 25;
+
+      libinput.enable = true;
+
+      monitorSection = ''
+        Option "DPMS" "false"
+      '';
+
+      serverFlagsSection = ''
+        Option "BlankTime" "0"
+        Option "OffTime" "0"
+        Option "StandbyTime" "0"
+        Option "SuspendTime" "0"
+      '';
+
+      inputClassSections = [
+        ''
+          Identifier "Mouse"
+          MatchIsPointer "yes"
+          Option "AccelerationNumerator" "2"
+          Option "AccelerationDenominator" "1"
+          Option "AccelerationThreshold" "4"
+        ''
+      ];
+    };
+  };
+}
diff --git a/modules/nixfiles/xmonad.nix b/modules/nixfiles/xmonad.nix
new file mode 100644
index 0000000..566ec53
--- /dev/null
+++ b/modules/nixfiles/xmonad.nix
@@ -0,0 +1,28 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.xmonad;
+in {
+  options.nixfiles.modules.xmonad.enable =
+    mkEnableOption "Whether to enable XMonad.";
+
+  config = mkIf cfg.enable {
+    nixfiles.modules.x11.enable = true;
+
+    hm = {
+      xsession = {
+        enable = true;
+
+        scriptPath = ".xinitrc";
+
+        windowManager.command = "${pkgs.xmonad-ng}/bin/xmonad-ng";
+      };
+    };
+
+    services.xserver.displayManager.startx.enable = true;
+  };
+}
diff --git a/modules/nixfiles/zathura.nix b/modules/nixfiles/zathura.nix
new file mode 100644
index 0000000..d4e11e6
--- /dev/null
+++ b/modules/nixfiles/zathura.nix
@@ -0,0 +1,123 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.nixfiles.modules.zathura;
+in {
+  options.nixfiles.modules.zathura.enable =
+    mkEnableOption "Whether to enable zathura PDF reader.";
+
+  config = mkIf cfg.enable {
+    hm.programs.zathura = with config.nixfiles.modules; {
+      enable = true;
+
+      options =
+        (with profiles.common.colourScheme; {
+          default-fg = white;
+          default-bg = black;
+
+          statusbar-fg = black;
+          statusbar-bg = white;
+
+          inputbar-fg = black;
+          inputbar-bg = brightGreen;
+
+          notification-fg = black;
+          notification-bg = brightBlue;
+
+          notification-warning-fg = black;
+          notification-warning-bg = brightYellow;
+
+          notification-error-fg = black;
+          notification-error-bg = brightRed;
+
+          highlight-color = brightYellow;
+          highlight-active-color = yellow;
+
+          completion-fg = brightWhite;
+          completion-bg = brightBlack;
+
+          completion-highlight-fg = black;
+          completion-highlight-bg = brightRed;
+
+          completion-group-fg = black;
+          completion-group-bg = brightRed;
+
+          recolor-darkcolor = black;
+          recolor-lightcolor = white;
+        })
+        // {
+          recolor = true;
+          recolor-keephue = false;
+          recolor-reverse-video = false;
+
+          highlight-transparency = "0.3";
+
+          font = fonts.fontScheme.monospaceFont.family;
+
+          n-completion-items = 10;
+
+          guioptions = "";
+
+          statusbar-basename = true;
+          statusbar-home-tilde = true;
+
+          statusbar-h-padding = 0;
+          statusbar-v-padding = 0;
+
+          window-height = 800;
+          window-width = 600;
+
+          window-icon = "";
+
+          abort-clear-search = true;
+
+          incremental-search = true;
+
+          adjust-open = "best-fit";
+
+          advance-pages-per-row = false;
+
+          database = "sqlite";
+
+          dbus-service = false;
+
+          page-padding = 0;
+
+          pages-per-row = 1;
+
+          render-loading = false;
+
+          show-directories = true;
+          show-hidden = true;
+          show-recent = 10;
+
+          link-zoom = true;
+          link-hadjust = true;
+
+          window-title-basename = true;
+          window-title-home-tilde = true;
+          window-title-page = true;
+
+          zoom-center = false;
+          zoom-max = 1000;
+          zoom-min = 10;
+          zoom-step = 10;
+
+          scroll-hstep = -1;
+          scroll-step = 40;
+          scroll-full-overlap = 0;
+          scroll-wrap = true;
+          scroll-page-aware = false;
+
+          selection-clipboard =
+            if (kde.enable || gnome.enable)
+            then "clipboard"
+            else "primary";
+          selection-notification = false;
+        };
+    };
+  };
+}

Consider giving Nix/NixOS a try! <3