feat(conduit): Refactor matrix appservices and add matrix-hookshot

This commit is contained in:
Tristan Daniël Maat 2025-02-08 05:47:44 +08:00
parent 08ff591117
commit c495d165df
Signed by: tlater
GPG key ID: 49670FD774E43268
9 changed files with 303 additions and 91 deletions

View file

@ -17,7 +17,7 @@
./services/backups.nix
./services/battery-manager.nix
./services/conduit.nix
./services/conduit
./services/crowdsec.nix
./services/foundryvtt.nix
./services/gitea.nix

View file

@ -1,5 +1,4 @@
{
pkgs,
config,
lib,
...
@ -12,6 +11,11 @@ let
turn-realm = "turn.${config.services.nginx.domain}";
in
{
imports = [
./heisenbridge.nix
./matrix-hookshot.nix
];
services.matrix-conduit = {
enable = true;
settings.global = {
@ -40,91 +44,6 @@ 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.
#

View file

@ -0,0 +1,78 @@
{
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" ];
};
};
}

View file

@ -0,0 +1,67 @@
{
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}"
'';
};
}

View file

@ -0,0 +1,134 @@
{
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 = "http://127.0.0.1:9000/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;
};
};
}

View file

@ -26,6 +26,10 @@
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" ];
};
};
}

View file

@ -35,6 +35,10 @@
"heisenbridge/as-token" = { };
"heisenbridge/hs-token" = { };
# Matrix-hookshot
"matrix-hookshot/as-token" = { };
"matrix-hookshot/hs-token" = { };
# Nextcloud
"nextcloud/tlater" = {
owner = "nextcloud";