diff --git a/configuration/default.nix b/configuration/default.nix
index f874733..d4c422f 100644
--- a/configuration/default.nix
+++ b/configuration/default.nix
@@ -17,7 +17,7 @@
 
     ./services/backups.nix
     ./services/battery-manager.nix
-    ./services/conduit
+    ./services/conduit.nix
     ./services/crowdsec.nix
     ./services/foundryvtt.nix
     ./services/gitea.nix
diff --git a/configuration/services/conduit/default.nix b/configuration/services/conduit.nix
similarity index 62%
rename from configuration/services/conduit/default.nix
rename to configuration/services/conduit.nix
index c3803f4..4e53241 100644
--- a/configuration/services/conduit/default.nix
+++ b/configuration/services/conduit.nix
@@ -1,4 +1,5 @@
 {
+  pkgs,
   config,
   lib,
   ...
@@ -11,11 +12,6 @@ let
   turn-realm = "turn.${config.services.nginx.domain}";
 in
 {
-  imports = [
-    ./heisenbridge.nix
-    ./matrix-hookshot.nix
-  ];
-
   services.matrix-conduit = {
     enable = true;
     settings.global = {
@@ -44,6 +40,91 @@ in
     };
   };
 
+  systemd.services.heisenbridge =
+    let
+      replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
+      registrationFile = builtins.toFile "heisenbridge-registration.yaml" (
+        builtins.toJSON {
+          id = "heisenbridge";
+          url = "http://127.0.0.1:9898";
+          as_token = "@AS_TOKEN@";
+          hs_token = "@HS_TOKEN@";
+          rate_limited = false;
+          sender_localpart = "heisenbridge";
+          namespaces = {
+            users = [
+              {
+                regex = "@irc_.*";
+                exclusive = true;
+              }
+              {
+                regex = "@heisenbridge:.*";
+                exclusive = true;
+              }
+            ];
+            aliases = [ ];
+            rooms = [ ];
+          };
+        }
+      );
+
+      # TODO(tlater): Starting with systemd 253 it will become possible
+      # to do the credential setup as part of ExecStartPre/preStart
+      # instead.
+      #
+      # This will also make it possible to actually set caps on the
+      # heisenbridge process using systemd, so that we can run the
+      # identd process.
+      execScript = pkgs.writeShellScript "heisenbridge" ''
+        cp ${registrationFile} "$RUNTIME_DIRECTORY/heisenbridge-registration.yaml"
+        chmod 600 $RUNTIME_DIRECTORY/heisenbridge-registration.yaml
+        ${replaceSecretBin} '@AS_TOKEN@' "$CREDENTIALS_DIRECTORY/heisenbridge_as-token" "$RUNTIME_DIRECTORY/heisenbridge-registration.yaml"
+        ${replaceSecretBin} '@HS_TOKEN@' "$CREDENTIALS_DIRECTORY/heisenbridge_hs-token" "$RUNTIME_DIRECTORY/heisenbridge-registration.yaml"
+        chmod 400 $RUNTIME_DIRECTORY/heisenbridge-registration.yaml
+
+        ${pkgs.heisenbridge}/bin/heisenbridge \
+            --config $RUNTIME_DIRECTORY/heisenbridge-registration.yaml \
+            --owner @tlater:matrix.tlater.net \
+            'http://localhost:${toString cfg.settings.global.port}'
+      '';
+    in
+    {
+      description = "Matrix<->IRC bridge";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "conduit.service" ];
+
+      serviceConfig = {
+        Type = "simple";
+
+        LoadCredential = "heisenbridge:/run/secrets/heisenbridge";
+
+        ExecStart = execScript;
+
+        DynamicUser = true;
+        RuntimeDirectory = "heisenbridge";
+        RuntimeDirectoryMode = "0700";
+
+        RestrictNamespaces = true;
+        PrivateUsers = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_INET AF_INET6" ];
+        LockPersonality = true;
+        RestrictRealtime = true;
+        ProtectProc = "invisible";
+        ProcSubset = "pid";
+        UMask = 77;
+
+        # For the identd port
+        # CapabilityBoundingSet = ["CAP_NET_BIND_SERVICE"];
+        # AmbientCapabilities = ["CAP_NET_BIND_SERVICE"];
+      };
+    };
+
   # Pass in the TURN secret via EnvironmentFile, not supported by
   # upstream module currently.
   #
diff --git a/configuration/services/conduit/heisenbridge.nix b/configuration/services/conduit/heisenbridge.nix
deleted file mode 100644
index f0f7e49..0000000
--- a/configuration/services/conduit/heisenbridge.nix
+++ /dev/null
@@ -1,78 +0,0 @@
-{
-  pkgs,
-  lib,
-  config,
-  ...
-}:
-let
-  conduitCfg = config.services.matrix-conduit;
-  matrixLib = pkgs.callPackage ./lib.nix { };
-in
-{
-  systemd.services.heisenbridge =
-    let
-      registration = matrixLib.writeRegistrationScript {
-        id = "heisenbridge";
-        url = "http://127.0.0.1:9898";
-        sender_localpart = "heisenbridge";
-
-        namespaces = {
-          users = [
-            {
-              regex = "@irc_.*";
-              exclusive = true;
-            }
-            {
-              regex = "@heisenbridge:.*";
-              exclusive = true;
-            }
-          ];
-
-          aliases = [ ];
-          rooms = [ ];
-        };
-      };
-    in
-    {
-      description = "Matrix<->IRC bridge";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "conduit.service" ];
-
-      serviceConfig = {
-        Type = "exec";
-
-        LoadCredential = "heisenbridge:/run/secrets/heisenbridge";
-
-        inherit (registration) ExecStartPre;
-        ExecStart = lib.concatStringsSep " " [
-          "${lib.getExe pkgs.heisenbridge}"
-          "--config \${RUNTIME_DIRECTORY}/heisenbridge-registration.yaml"
-          "--owner @tlater:matrix.tlater.net"
-          "http://localhost:${toString conduitCfg.settings.global.port}"
-        ];
-
-        DynamicUser = true;
-        RuntimeDirectory = "heisenbridge";
-        RuntimeDirectoryMode = "0700";
-
-        RestrictNamespaces = true;
-        PrivateUsers = true;
-        ProtectHostname = true;
-        ProtectClock = true;
-        ProtectKernelTunables = true;
-        ProtectKernelModules = true;
-        ProtectKernelLogs = true;
-        ProtectControlGroups = true;
-        RestrictAddressFamilies = [ "AF_INET AF_INET6" ];
-        LockPersonality = true;
-        RestrictRealtime = true;
-        ProtectProc = "invisible";
-        ProcSubset = "pid";
-        UMask = 77;
-
-        # For the identd port
-        # CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
-        # AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
-      };
-    };
-}
diff --git a/configuration/services/conduit/lib.nix b/configuration/services/conduit/lib.nix
deleted file mode 100644
index ef407cf..0000000
--- a/configuration/services/conduit/lib.nix
+++ /dev/null
@@ -1,67 +0,0 @@
-{
-  lib,
-  writeShellScript,
-  formats,
-  replace-secret,
-}:
-let
-  replaceSecretBin = "${lib.getExe replace-secret}";
-in
-{
-  # Write a script that will set up the service's registration.yaml
-  # with secrets from systemd credentials.
-  #
-  # The credentials should be named `${id}_as-token` and
-  # `${id}_hs-token`.
-  #
-  # This registration file needs to be manually added to conduit by
-  # messaging the admin with the yaml file.
-  #
-  # TODO(tlater): Conduwuit seems to support a CLI interface for this,
-  # may want to migrate to that sometime.
-  writeRegistrationScript =
-    {
-      id, # Must be unique among all registered appservices/bots
-      url, # The URL on which the service listens
-      sender_localpart,
-      rate_limited ? false,
-      namespaces ? {
-        aliases = [ ];
-        rooms = [ ];
-        users = [ ];
-      },
-      extraSettings ? { },
-      # The location to place the file; assumes systemd runtime dir
-      runtimeRegistration ? "$RUNTIME_DIRECTORY/${id}-registration.yaml",
-    }:
-    let
-      registrationFile = (formats.yaml { }).generate "${id}-registration.yaml" (
-        {
-          inherit
-            id
-            url
-            sender_localpart
-            rate_limited
-            namespaces
-            ;
-
-          as_token = "@AS_TOKEN@";
-          hs_token = "@HS_TOKEN@";
-        }
-        // extraSettings
-      );
-    in
-    {
-      inherit runtimeRegistration;
-      ExecStartPre = writeShellScript "${id}-registration-setup.sh" ''
-        cp -f ${registrationFile} "${runtimeRegistration}"
-        chmod 600 "${runtimeRegistration}"
-
-        # Write actual secrets into config
-        ${replaceSecretBin} '@AS_TOKEN@' "$CREDENTIALS_DIRECTORY/${id}_as-token" "${runtimeRegistration}"
-        ${replaceSecretBin} '@HS_TOKEN@' "$CREDENTIALS_DIRECTORY/${id}_hs-token" "${runtimeRegistration}"
-
-        chmod 400 "${runtimeRegistration}"
-      '';
-    };
-}
diff --git a/configuration/services/conduit/matrix-hookshot.nix b/configuration/services/conduit/matrix-hookshot.nix
deleted file mode 100644
index 4b3a7d9..0000000
--- a/configuration/services/conduit/matrix-hookshot.nix
+++ /dev/null
@@ -1,134 +0,0 @@
-{
-  pkgs,
-  lib,
-  config,
-  ...
-}:
-let
-  matrixLib = pkgs.callPackage ./lib.nix { };
-
-  cfg = config.services.matrix-hookshot;
-  conduitCfg = config.services.matrix-conduit;
-
-  domain = conduitCfg.settings.global.server_name;
-
-  registration = matrixLib.writeRegistrationScript {
-    id = "matrix-hookshot";
-    url = "http://127.0.0.1:9993";
-    sender_localpart = "hookshot";
-
-    namespaces = {
-      aliases = [ ];
-      rooms = [ ];
-      users = [
-        {
-          regex = "@${cfg.settings.generic.userIdPrefix}.*:${domain}";
-          exclusive = true;
-        }
-      ];
-    };
-
-    runtimeRegistration = "${cfg.registrationFile}";
-  };
-in
-{
-  systemd.services.matrix-hookshot = {
-    serviceConfig = {
-      Type = lib.mkForce "exec";
-
-      LoadCredential = "matrix-hookshot:/run/secrets/matrix-hookshot";
-      inherit (registration) ExecStartPre;
-
-      # Some library in matrix-hookshot wants a home directory
-      Environment = [ "HOME=/run/matrix-hookshot" ];
-
-      DynamicUser = true;
-      StateDirectory = "matrix-hookshot";
-      RuntimeDirectory = "matrix-hookshot";
-      RuntimeDirectoryMode = "0700";
-
-      RestrictNamespaces = true;
-      PrivateUsers = true;
-      ProtectHostname = true;
-      ProtectClock = true;
-      ProtectKernelTunables = true;
-      ProtectKernelModules = true;
-      ProtectKernelLogs = true;
-      ProtectControlGroups = true;
-      RestrictAddressFamilies = [ "AF_INET AF_INET6" ];
-      LockPersonality = true;
-      RestrictRealtime = true;
-      ProtectProc = "invisible";
-      ProcSubset = "pid";
-      UMask = 77;
-    };
-  };
-
-  services.matrix-hookshot = {
-    enable = true;
-
-    serviceDependencies = [
-      "conduit.service"
-    ];
-
-    registrationFile = "/run/matrix-hookshot/registration.yaml";
-
-    settings = {
-      bridge = {
-        inherit domain;
-        url = "http://localhost:${toString conduitCfg.settings.global.port}";
-        mediaUrl = conduitCfg.settings.global.well_known.client;
-        port = 9993;
-        bindAddress = "127.0.0.1";
-      };
-
-      bot.displayname = "Hookshot";
-
-      generic = {
-        enabled = true;
-        outbound = false;
-        # Only allow webhooks from localhost for the moment
-        urlPrefix = "${cfg.settings.bridge.url}/webhook";
-        userIdPrefix = "_webhooks_";
-      };
-
-      permissions = [
-        {
-          actor = "matrix.tlater.net";
-          services = [
-            {
-              service = "*";
-              level = "notifications";
-            }
-          ];
-        }
-        {
-          actor = "@tlater:matrix.tlater.net";
-          services = [
-            {
-              service = "*";
-              level = "admin";
-            }
-          ];
-        }
-      ];
-
-      listeners = [
-        {
-          port = 9000;
-          resources = [
-            "webhooks"
-          ];
-        }
-        {
-          port = 9001;
-          resources = [
-            "metrics"
-          ];
-        }
-      ];
-
-      metrics.enable = true;
-    };
-  };
-}
diff --git a/configuration/services/metrics/victoriametrics.nix b/configuration/services/metrics/victoriametrics.nix
index d72215e..5cfc614 100644
--- a/configuration/services/metrics/victoriametrics.nix
+++ b/configuration/services/metrics/victoriametrics.nix
@@ -26,10 +26,6 @@
             config.security.crowdsec.remediationComponents.firewallBouncer.settings.prometheus.listen_port;
         in
         [ "${address}:${toString port}" ];
-
-      # Configured in the hookshot listeners, but it's hard to filter
-      # the correct values out of that config.
-      matrixHookshot.targets = [ "127.0.0.1:9001" ];
     };
   };
 }
diff --git a/configuration/sops.nix b/configuration/sops.nix
index 3a1c3d8..72f5a01 100644
--- a/configuration/sops.nix
+++ b/configuration/sops.nix
@@ -35,10 +35,6 @@
       "heisenbridge/as-token" = { };
       "heisenbridge/hs-token" = { };
 
-      # Matrix-hookshot
-      "matrix-hookshot/as-token" = { };
-      "matrix-hookshot/hs-token" = { };
-
       # Nextcloud
       "nextcloud/tlater" = {
         owner = "nextcloud";
diff --git a/keys/production.yaml b/keys/production.yaml
index 0e906bd..19df9bd 100644
--- a/keys/production.yaml
+++ b/keys/production.yaml
@@ -16,9 +16,6 @@ steam:
 heisenbridge:
     as-token: ENC[AES256_GCM,data:+2yo6T18j34622H8ZWblAFB2phLw1q0k0vUQEZ5sFj7dQaRnkEiAMi0R3p17Zq0pOtGEC0RRZuPLYkcZ1oKP0w==,iv:lGwrQYp//FufpmJocrLIVyy9RK7lEEVcpAi0wmkjr34=,tag:yV06UbhAYJQz36O2XdhY+A==,type:str]
     hs-token: ENC[AES256_GCM,data:u52WpkQFd/J7JFoE/rfNluebyZQLOokvkVdL7+AEAvrhJhrkJli1ztkD79lbC+6tGUH4tT3T+nX9wvGKnrRUQg==,iv:as+9fVuvMg2IoE2WIKD9mHi+znhNcWRh5Zq+yr0xcDQ=,tag:mZ7fh7U0MfgI8hyq/28Bcg==,type:str]
-matrix-hookshot:
-    as-token: ENC[AES256_GCM,data:nXTanPhDyDF7R3AllLqpM5dzljBrHwlh1KJnTGIi5PhbDY2lPj4+uXkMEwvm1u+hQjPyM7vKZPfK+0/dms6Y7A==,iv:fSakJN+yai0gfOJKFxxaxgyUtk0pNmIeqVgrdq92/24=,tag:Qc7+SUnm5/Nq5+QIScR9kQ==,type:str]
-    hs-token: ENC[AES256_GCM,data:Bwyj0JTTN0NNnwOs1zA8CqbtZSNcvlINeT7QVc2eJiHda92J6vQk7bSxy6KuqCN9DxlUsK13ggYjNORY2vic5w==,iv:Npnp8arYQ3Yb6CXrnKgE03hD7ZjGINPa/DwFI8D+5tA=,tag:FqNE6yI0nF4puEUw9MGAjQ==,type:str]
 wireguard:
     server-key: ENC[AES256_GCM,data:mXb7ZznJHf5CgV8rI4uzPBATMRbmd7LimgtCkQM9kAjbIaGwUBqJZBN3fXs=,iv:3Po1Orinzov9rnEm9cLzgJY1PeD+5Jl9115MriABHh8=,tag:E/2CjDO1JCvJzxCnqKcNyw==,type:str]
 restic:
@@ -37,8 +34,8 @@ sops:
     azure_kv: []
     hc_vault: []
     age: []
-    lastmodified: "2025-02-07T19:44:49Z"
-    mac: ENC[AES256_GCM,data:+0hpd/E7GxK/27f2Itf0hDV+3Ga4gHb8xxLutJ32HLBWLZ5Y+dN03xgkz8jBTiM+BeHwS4gz70Cs9X3zLMHbosWVuIV9DLuRaHRq/IU9KiADwqmCySZALqCf3+T5QKZr3Qs4AZJHwaAXkRX9HbnRFriIAFDJW/BGdIHdoROquxY=,iv:TeXI8LGqHVa5wo61sGdNbZ2nJvSlPdgn9R3Lq5qUggU=,tag:TFort5wxVTdi9LMlMeT/DQ==,type:str]
+    lastmodified: "2025-02-01T10:16:20Z"
+    mac: ENC[AES256_GCM,data:oFJNljU0RJdgsdK7qRXKCqRs7kPXgHqSyYcexEs8kXFnn68mKHNKKfl7skepCPKk0U7h6JqJQ+EOnAA0eo6mraBAMKLSXUcucTzqsfcI+V04rYcP2nGPMUiNDGdKHCcb6OmBhfvKw7+elnonPxKsBlyK31AqB9RFDKaTKXpcNMw=,iv:Q9t7ZkUrevHm5I4JBW95TfvZ88dl2Fq3Yq/E642dV6s=,tag:p2XWfii168qq29wX/RCJuQ==,type:str]
     pgp:
         - created_at: "2025-01-21T17:55:44Z"
           enc: |-
diff --git a/keys/staging.yaml b/keys/staging.yaml
index 876d60e..67e47ad 100644
--- a/keys/staging.yaml
+++ b/keys/staging.yaml
@@ -16,9 +16,6 @@ steam:
 heisenbridge:
     as-token: ENC[AES256_GCM,data:tXbOeo7nv8I=,iv:wJAKcOXX9nGIw4n38ThOoj29u7dUWhsxSQG/p79JlEw=,tag:rTVaGS2UuWcea1uBa8YX2g==,type:str]
     hs-token: ENC[AES256_GCM,data:VBwvwomv0Xg=,iv:q6INtJ+rg+QiXj8uBdBzQYQZUBBXp+9odxDHwvu8Jxc=,tag:XKhm8nxygAkKaiVPJ2Fcdg==,type:str]
-matrix-hookshot:
-    as-token: ENC[AES256_GCM,data:uSUOo4f2KqA=,iv:Xb9G8Ecv6m59m51kDw2bOfq3SMJt4g9/6/EdH74R+KM=,tag:K9MSfO2c2Y4rlf0eYrmTnw==,type:str]
-    hs-token: ENC[AES256_GCM,data:0KsyA06InL4=,iv:zAR0Y1fk8SyodcSLBHlQ8I+BAmttz9Hkd8Q3OREFqs4=,tag:t1Et8N/3seq95DeGoUd7Sw==,type:str]
 wireguard:
     server-key: ENC[AES256_GCM,data:FvY897XdKoa/mckE8JQLCkklsnYD6Wz1wpsu5t3uhEnW3iarnDQxF9msuYU=,iv:jqGXfekM+Vs+J9b5nlZ5Skd1ZKHajoUo2Dc4tMYPm1w=,tag:EehikjI/FCU8wqtpvJRamQ==,type:str]
 restic:
@@ -37,8 +34,8 @@ sops:
     azure_kv: []
     hc_vault: []
     age: []
-    lastmodified: "2025-02-07T17:43:24Z"
-    mac: ENC[AES256_GCM,data:akmD/bfgeTyFzW1quvM16cdj0fC6+CbJ8WyX9173H11yKGxvE1USQYcErpl1SHOx9Jk8LVb7f+MsUm2fjQF1MEq6xaWI74jem12lZ9CGXFaTL7e87JvfbK7pV+aKpxSBBNFyJgbYm30ibdUwxwKmNVfPb1e0HT9qwenvoV7RobM=,iv:mKqOW0ULXL711uczUbRf9NPo6uPTQoS/IbR46S+JID4=,tag:vE6NYzYLbQHDImov1XGTcg==,type:str]
+    lastmodified: "2025-02-01T10:16:31Z"
+    mac: ENC[AES256_GCM,data:N4RQHOyWvSXW16fepQvRznNbmGerct03kptyiY3IoTpYaJ+43cyFjW15ZqfpaRFyV66QIeqmceqV8c4eP8YSndj6e55e04w0RCyqREXQlFPR6Eh5elaBenokoJhjF6BCsq+xX1C+LUEcxiR/dgy5cwA3mAD/dLCm+G11a06EG6k=,iv:wt5fEOVP6CXHCzmMH9hNCQDDgPa66bLMOa39Eipux9Y=,tag:kWZPnWD1stANVAmWmvOjCg==,type:str]
     pgp:
         - created_at: "2025-01-21T17:55:30Z"
           enc: |-