diff --git a/configuration/services/foundryvtt.nix b/configuration/services/foundryvtt.nix
index d573480..a4978fd 100644
--- a/configuration/services/foundryvtt.nix
+++ b/configuration/services/foundryvtt.nix
@@ -8,11 +8,11 @@
 in {
   imports = [flake-inputs.foundryvtt.nixosModules.foundryvtt];
 
-  services.foundryvtt = {
-    enable = true;
-    hostName = domain;
-    minifyStaticFiles = true;
-  };
+  # services.foundryvtt = {
+  #   enable = true;
+  #   hostName = domain;
+  #   minifyStaticFiles = true;
+  # };
 
   # Want to start it manually when I need it, not have it constantly
   # running
diff --git a/configuration/services/metrics/default.nix b/configuration/services/metrics/default.nix
index 4b163d3..84e126a 100644
--- a/configuration/services/metrics/default.nix
+++ b/configuration/services/metrics/default.nix
@@ -1,186 +1,9 @@
 {
-  config,
-  pkgs,
-  lib,
-  ...
-}: let
-  domain = "metrics.${config.services.nginx.domain}";
-  yaml = pkgs.formats.yaml {};
-in {
   imports = [
+    ./options.nix
+
     ./exporters.nix
+    ./grafana.nix
+    ./victoriametrics.nix
   ];
-
-  services.victoriametrics.enable = true;
-
-  services.grafana = {
-    enable = true;
-    settings = {
-      server.http_port = 3001; # Default overlaps with gitea
-
-      security = {
-        admin_user = "tlater";
-        admin_password = "$__file{${config.sops.secrets."grafana/adminPassword".path}}";
-        secret_key = "$__file{${config.sops.secrets."grafana/secretKey".path}}";
-        cookie_secure = true;
-        cookie_samesite = "strict";
-        content_security_policy = true;
-      };
-
-      database = {
-        user = "grafana";
-        name = "grafana";
-        type = "postgres";
-        host = "/run/postgresql";
-      };
-    };
-
-    provision = {
-      enable = true;
-
-      datasources.settings.datasources = [
-        {
-          name = "Victoriametrics - tlater.net";
-          url = "http://localhost:8428";
-          type = "prometheus";
-        }
-      ];
-    };
-  };
-
-  services.prometheus.exporters = {
-    domain = {
-      enable = true;
-      listenAddress = "127.0.0.1";
-      extraFlags = let
-        conf.domains = [
-          "tlater.net"
-          "tlater.com"
-        ];
-      in [
-        "--config=${yaml.generate "domains.yml" conf}"
-      ];
-    };
-
-    node = {
-      enable = true;
-      listenAddress = "127.0.0.1";
-    };
-
-    nginx = {
-      enable = true;
-      listenAddress = "127.0.0.1";
-    };
-
-    nginxlog = {
-      enable = true;
-      listenAddress = "127.0.0.1";
-      group = "nginx";
-
-      settings.namespaces =
-        lib.mapAttrsToList (name: virtualHost: {
-          inherit name;
-          metrics_override.prefix = "nginxlog";
-          namespace_label = "vhost";
-
-          format = lib.concatStringsSep " " [
-            "$remote_addr - $remote_user [$time_local]"
-            ''"$request" $status $body_bytes_sent''
-            ''"$http_referer" "$http_user_agent"''
-            ''rt=$request_time uct="$upstream_connect_time"''
-            ''uht="$upstream_header_time" urt="$upstream_response_time"''
-          ];
-
-          source.files = [
-            "/var/log/nginx/${name}/access.log"
-          ];
-        })
-        config.services.nginx.virtualHosts;
-    };
-
-    systemd = {
-      enable = true;
-      listenAddress = "127.0.0.1";
-      extraFlags = [
-        # Disabled by default because only supported from systemd 235+
-        "--systemd.collector.enable-restart-count"
-        "--systemd.collector.enable-ip-accounting"
-      ];
-    };
-  };
-
-  services.prometheus.local-exporters = {
-    prometheus-fail2ban-exporter = rec {
-      enable = true;
-      after = ["fail2ban.service"];
-
-      port = 9191;
-      listenAddress = "127.0.0.1";
-
-      serviceConfig = {
-        Group = "fail2ban";
-
-        RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"];
-
-        ExecStart = lib.concatStringsSep " " [
-          "${pkgs.local.prometheus-fail2ban-exporter}/bin/fail2ban-prometheus-exporter"
-          "--collector.f2b.socket=/var/run/fail2ban/fail2ban.sock"
-          "--web.listen-address='${listenAddress}:${toString port}'"
-        ];
-      };
-    };
-  };
-
-  systemd.services.export-to-victoriametrics = let
-    promscrape = yaml.generate "prometheus.yml" {
-      scrape_configs = [
-        {
-          job_name = "tlater.net";
-          static_configs = [
-            {
-              targets = let
-                exporters = config.services.prometheus.exporters;
-                localExporters = config.services.prometheus.local-exporters;
-              in
-                map (exporter: "${exporter.listenAddress}:${toString exporter.port}") [
-                  exporters.domain
-                  exporters.node
-                  exporters.nginx
-                  exporters.nginxlog
-                  exporters.systemd
-
-                  localExporters.prometheus-fail2ban-exporter
-
-                  {
-                    # coturn
-                    listenAddress = "127.0.0.1";
-                    port = "9641";
-                  }
-                  {
-                    # gitea
-                    listenAddress = "127.0.0.1";
-                    port = "3000";
-                  }
-                ];
-            }
-          ];
-        }
-      ];
-    };
-  in {
-    enable = true;
-    path = [pkgs.victoriametrics];
-    wantedBy = ["multi-user.target"];
-    script = "vmagent -promscrape.config=${promscrape} -remoteWrite.url=http://localhost:8428/api/v1/write";
-  };
-
-  services.nginx.virtualHosts."${domain}" = {
-    forceSSL = true;
-    enableACME = true;
-    extraConfig = ''
-      add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
-      access_log /var/log/nginx/${domain}/access.log upstream_time;
-    '';
-    locations."/".proxyPass = "http://localhost:3001";
-  };
 }
diff --git a/configuration/services/metrics/exporters.nix b/configuration/services/metrics/exporters.nix
index fc56316..b4af2a0 100644
--- a/configuration/services/metrics/exporters.nix
+++ b/configuration/services/metrics/exporters.nix
@@ -1,45 +1,100 @@
 {
   config,
+  pkgs,
   lib,
   ...
-}: {
-  options.services.prometheus.local-exporters = lib.mkOption {
-    type = lib.types.anything;
-  };
+}: let
+  yaml = pkgs.formats.yaml {};
+in {
+  services.prometheus = {
+    exporters = {
+      # Periodically check domain registration status
+      domain = {
+        enable = true;
+        listenAddress = "127.0.0.1";
+        extraFlags = let
+          conf.domains = [
+            "tlater.net"
+            "tlater.com"
+          ];
+        in [
+          "--config=${yaml.generate "domains.yml" conf}"
+        ];
+      };
 
-  config.systemd.services = lib.mapAttrs (_: exporter:
-    lib.mkMerge [
-      {
-        wantedBy = ["multi-user.target"];
-        after = ["network.target"];
+      # System statistics
+      node = {
+        enable = true;
+        listenAddress = "127.0.0.1";
+      };
+      systemd = {
+        enable = true;
+        listenAddress = "127.0.0.1";
+        extraFlags = [
+          # Disabled by default because only supported from systemd 235+
+          "--systemd.collector.enable-restart-count"
+          "--systemd.collector.enable-ip-accounting"
+        ];
+      };
 
-        serviceConfig = {
-          Restart = "always";
-          PrivateTmp = true;
-          WorkingDirectory = "/tmp";
-          DynamicUser = true;
-          LockPersonality = true;
-          MemoryDenyWriteExecute = true;
-          NonNewPrivileges = true;
-          PrivateDevices = true;
-          ProtectClock = true;
-          ProtectControlGroups = true;
-          ProtectHome = true;
-          ProtectHostname = true;
-          ProtectKernelLogs = true;
-          ProtectKernelModules = true;
-          ProtectKernelTunables = true;
-          ProtectSystem = "strict";
-          RemoveIPC = true;
-          RestrictAddressFamilies = lib.mkDefault ["AF_INET" "AF_INET6"];
-          RestrictNamespaces = true;
-          RestrictRealtime = true;
-          RestrictSUIDSGID = true;
-          SystemCallArchitectures = "native";
-          UMask = "0077";
+      # Various nginx metrics
+      nginx = {
+        enable = true;
+        listenAddress = "127.0.0.1";
+      };
+
+      nginxlog = {
+        enable = true;
+        listenAddress = "127.0.0.1";
+        group = "nginx";
+
+        settings.namespaces =
+          lib.mapAttrsToList (name: virtualHost: {
+            inherit name;
+            metrics_override.prefix = "nginxlog";
+            namespace_label = "vhost";
+
+            format = lib.concatStringsSep " " [
+              "$remote_addr - $remote_user [$time_local]"
+              ''"$request" $status $body_bytes_sent''
+              ''"$http_referer" "$http_user_agent"''
+              ''rt=$request_time uct="$upstream_connect_time"''
+              ''uht="$upstream_header_time" urt="$upstream_response_time"''
+            ];
+
+            source.files = [
+              "/var/log/nginx/${name}/access.log"
+            ];
+          })
+          config.services.nginx.virtualHosts;
+      };
+    };
+
+    extraExporters = {
+      fail2ban = let
+        cfg = config.services.prometheus.extraExporters.fail2ban;
+      in {
+        port = 9191;
+        serviceOpts = {
+          after = ["fail2ban.service"];
+          requires = ["fail2ban.service"];
+          serviceConfig = {
+            Group = "fail2ban";
+            RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"];
+            ExecStart = lib.concatStringsSep " " [
+              "${pkgs.local.prometheus-fail2ban-exporter}/bin/fail2ban-prometheus-exporter"
+              "--collector.f2b.socket=/var/run/fail2ban/fail2ban.sock"
+              "--web.listen-address='${cfg.listenAddress}:${toString cfg.port}'"
+              "--collector.f2b.exit-on-socket-connection-error=true"
+            ];
+          };
         };
-      }
-      (removeAttrs exporter ["port" "listenAddress"])
-    ])
-  config.services.prometheus.local-exporters;
+      };
+    };
+
+    # TODO(tlater):
+    #   - wireguard (?)
+    #   - postgres (?)
+    #   - blackbox (?) (curl to see if http and similar is up)
+  };
 }
diff --git a/configuration/services/metrics/grafana.nix b/configuration/services/metrics/grafana.nix
new file mode 100644
index 0000000..8538dc7
--- /dev/null
+++ b/configuration/services/metrics/grafana.nix
@@ -0,0 +1,48 @@
+{config, ...}: let
+  domain = "metrics.${config.services.nginx.domain}";
+in {
+  services.grafana = {
+    enable = true;
+    settings = {
+      server.http_port = 3001; # Default overlaps with gitea
+
+      security = {
+        admin_user = "tlater";
+        admin_password = "$__file{${config.sops.secrets."grafana/adminPassword".path}}";
+        secret_key = "$__file{${config.sops.secrets."grafana/secretKey".path}}";
+        cookie_secure = true;
+        cookie_samesite = "strict";
+        content_security_policy = true;
+      };
+
+      database = {
+        user = "grafana";
+        name = "grafana";
+        type = "postgres";
+        host = "/run/postgresql";
+      };
+    };
+
+    provision = {
+      enable = true;
+
+      datasources.settings.datasources = [
+        {
+          name = "Victoriametrics - tlater.net";
+          url = "http://localhost:8428";
+          type = "prometheus";
+        }
+      ];
+    };
+  };
+
+  services.nginx.virtualHosts."${domain}" = {
+    forceSSL = true;
+    enableACME = true;
+    extraConfig = ''
+      add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
+      access_log /var/log/nginx/${domain}/access.log upstream_time;
+    '';
+    locations."/".proxyPass = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
+  };
+}
diff --git a/configuration/services/metrics/options.nix b/configuration/services/metrics/options.nix
new file mode 100644
index 0000000..65bcf83
--- /dev/null
+++ b/configuration/services/metrics/options.nix
@@ -0,0 +1,90 @@
+{
+  config,
+  lib,
+  ...
+}: let
+  inherit (lib) types mkOption mkDefault;
+in {
+  options.services.prometheus = {
+    extraExporters = mkOption {
+      type = types.attrsOf (types.submodule {
+        options = {
+          port = mkOption {
+            type = types.int;
+            description = "The port on which this exporter listens.";
+          };
+          listenAddress = mkOption {
+            type = types.str;
+            default = "127.0.0.1";
+            description = "Address to listen on.";
+          };
+          serviceOpts = mkOption {
+            type = types.attrs;
+            description = "An attrset to be merged with the exporter's systemd service.";
+          };
+        };
+      });
+    };
+
+    allExporters = mkOption {
+      internal = true;
+      description = "The full list of scraping-relevant settings of all exporters, extra or built-in.";
+      type = types.attrsOf (types.submodule {
+        port = mkOption {
+          type = types.int;
+        };
+        listenAddress = mkOption {
+          type = types.str;
+        };
+        extraSettings = mkOption {
+          type = types.anything;
+          default = {};
+        };
+      });
+    };
+  };
+
+  config = {
+    systemd.services = lib.mapAttrs' (name: exporter:
+      lib.nameValuePair "prometheus-${name}-exporter" (lib.mkMerge [
+        {
+          # Shamelessly copied from upstream because the upstream
+          # module is an intractable mess
+          wantedBy = ["multi-user.target"];
+          after = ["network.target"];
+          serviceConfig.Restart = mkDefault "always";
+          serviceConfig.PrivateTmp = mkDefault true;
+          serviceConfig.WorkingDirectory = mkDefault /tmp;
+          serviceConfig.DynamicUser = mkDefault true;
+          # Hardening
+          serviceConfig.CapabilityBoundingSet = mkDefault [""];
+          serviceConfig.DeviceAllow = [""];
+          serviceConfig.LockPersonality = true;
+          serviceConfig.MemoryDenyWriteExecute = true;
+          serviceConfig.NoNewPrivileges = true;
+          serviceConfig.PrivateDevices = mkDefault true;
+          serviceConfig.ProtectClock = mkDefault true;
+          serviceConfig.ProtectControlGroups = true;
+          serviceConfig.ProtectHome = true;
+          serviceConfig.ProtectHostname = true;
+          serviceConfig.ProtectKernelLogs = true;
+          serviceConfig.ProtectKernelModules = true;
+          serviceConfig.ProtectKernelTunables = true;
+          serviceConfig.ProtectSystem = mkDefault "strict";
+          serviceConfig.RemoveIPC = true;
+          serviceConfig.RestrictAddressFamilies = ["AF_INET" "AF_INET6"];
+          serviceConfig.RestrictNamespaces = true;
+          serviceConfig.RestrictRealtime = true;
+          serviceConfig.RestrictSUIDSGID = true;
+          serviceConfig.SystemCallArchitectures = "native";
+          serviceConfig.UMask = "0077";
+        }
+        exporter.serviceOpts
+      ]))
+    config.services.prometheus.extraExporters;
+
+    services.prometheus.allExporters = lib.mapAttrs (name: exporter: {
+      inherit (exporter) listenAddress port;
+    }) (config.services.prometheus.exporters ++ config.services.prometheus.extraExporters);
+  };
+}
diff --git a/configuration/services/metrics/victoriametrics.nix b/configuration/services/metrics/victoriametrics.nix
new file mode 100644
index 0000000..a9fb3a2
--- /dev/null
+++ b/configuration/services/metrics/victoriametrics.nix
@@ -0,0 +1,31 @@
+{config, ...}: {
+  services.victoriametrics = let
+    scrapeConfigFromExporters = conf: conf // {inherit (config.services.prometheus.exporters.${conf.name}) listenAddress port;};
+    scrapeConfigFromLocalExporters = conf: conf // {inherit (config.services.prometheus.local-exporters.${conf.name}) listenAddress port;};
+  in {
+    enable = true;
+    vmagent-scraping.static_configs =
+      [
+        {
+          name = "gitea";
+          listenAddress = "127.0.0.1";
+          port = 3000;
+        }
+        {
+          name = "coturn";
+          listenAddress = "127.0.0.1";
+          port = 9641;
+        }
+      ]
+      ++ (map scrapeConfigFromLocalExporters [
+        {name = "prometheus-fail2ban-exporter";}
+      ])
+      ++ (map scrapeConfigFromExporters [
+        {name = "domain";}
+        {name = "node";}
+        {name = "nginx";}
+        {name = "nginxlog";}
+        {name = "systemd";}
+      ]);
+  };
+}