2020-08-30 07:40:31 +00:00
|
|
|
{ config, pkgs, lib, ... }:
|
|
|
|
let
|
|
|
|
cfg = config.services.matrix-appservice-irc;
|
|
|
|
|
|
|
|
pkg = pkgs.matrix-appservice-irc;
|
|
|
|
bin = "${pkg}/bin/matrix-appservice-irc";
|
|
|
|
|
|
|
|
jsonType = (pkgs.formats.json {}).type;
|
|
|
|
|
2021-08-15 15:12:23 +00:00
|
|
|
configFile = pkgs.runCommand "matrix-appservice-irc.yml" {
|
2020-08-30 07:40:31 +00:00
|
|
|
# Because this program will be run at build time, we need `nativeBuildInputs`
|
2023-07-30 03:26:44 +00:00
|
|
|
nativeBuildInputs = [ (pkgs.python3.withPackages (ps: [ ps.jsonschema ])) pkgs.remarshal ];
|
2020-08-30 07:40:31 +00:00
|
|
|
preferLocalBuild = true;
|
|
|
|
|
|
|
|
config = builtins.toJSON cfg.settings;
|
|
|
|
passAsFile = [ "config" ];
|
|
|
|
} ''
|
|
|
|
# The schema is given as yaml, we need to convert it to json
|
2023-07-30 03:26:44 +00:00
|
|
|
remarshal --if yaml --of json -i ${pkg}/config.schema.yml -o config.schema.json
|
2020-08-30 07:40:31 +00:00
|
|
|
python -m jsonschema config.schema.json -i $configPath
|
|
|
|
cp "$configPath" "$out"
|
|
|
|
'';
|
|
|
|
registrationFile = "/var/lib/matrix-appservice-irc/registration.yml";
|
|
|
|
in {
|
2024-08-24 20:05:36 +00:00
|
|
|
options.services.matrix-appservice-irc = with lib.types; {
|
|
|
|
enable = lib.mkEnableOption "the Matrix/IRC bridge";
|
2020-08-30 07:40:31 +00:00
|
|
|
|
2024-08-24 20:05:36 +00:00
|
|
|
port = lib.mkOption {
|
2020-08-30 07:40:31 +00:00
|
|
|
type = port;
|
|
|
|
description = "The port to listen on";
|
|
|
|
default = 8009;
|
|
|
|
};
|
|
|
|
|
2024-08-24 20:05:36 +00:00
|
|
|
needBindingCap = lib.mkOption {
|
2020-08-30 07:40:31 +00:00
|
|
|
type = bool;
|
|
|
|
description = "Whether the daemon needs to bind to ports below 1024 (e.g. for the ident service)";
|
|
|
|
default = false;
|
|
|
|
};
|
|
|
|
|
2024-08-24 20:05:36 +00:00
|
|
|
passwordEncryptionKeyLength = lib.mkOption {
|
2020-08-30 07:40:31 +00:00
|
|
|
type = ints.unsigned;
|
|
|
|
description = "Length of the key to encrypt IRC passwords with";
|
|
|
|
default = 4096;
|
|
|
|
example = 8192;
|
|
|
|
};
|
|
|
|
|
2024-08-24 20:05:36 +00:00
|
|
|
registrationUrl = lib.mkOption {
|
2020-08-30 07:40:31 +00:00
|
|
|
type = str;
|
|
|
|
description = ''
|
|
|
|
The URL where the application service is listening for homeserver requests,
|
|
|
|
from the Matrix homeserver perspective.
|
|
|
|
'';
|
|
|
|
example = "http://localhost:8009";
|
|
|
|
};
|
|
|
|
|
2024-08-24 20:05:36 +00:00
|
|
|
localpart = lib.mkOption {
|
2020-08-30 07:40:31 +00:00
|
|
|
type = str;
|
|
|
|
description = "The user_id localpart to assign to the appservice";
|
|
|
|
default = "appservice-irc";
|
|
|
|
};
|
|
|
|
|
2024-08-24 20:05:36 +00:00
|
|
|
settings = lib.mkOption {
|
2020-08-30 07:40:31 +00:00
|
|
|
description = ''
|
|
|
|
Configuration for the appservice, see
|
|
|
|
<https://github.com/matrix-org/matrix-appservice-irc/blob/${pkgs.matrix-appservice-irc.version}/config.sample.yaml>
|
|
|
|
for supported values
|
|
|
|
'';
|
|
|
|
default = {};
|
|
|
|
type = submodule {
|
|
|
|
freeformType = jsonType;
|
|
|
|
|
|
|
|
options = {
|
2024-08-24 20:05:36 +00:00
|
|
|
homeserver = lib.mkOption {
|
2020-08-30 07:40:31 +00:00
|
|
|
description = "Homeserver configuration";
|
|
|
|
default = {};
|
|
|
|
type = submodule {
|
|
|
|
freeformType = jsonType;
|
|
|
|
|
|
|
|
options = {
|
2024-08-24 20:05:36 +00:00
|
|
|
url = lib.mkOption {
|
2020-08-30 07:40:31 +00:00
|
|
|
type = str;
|
|
|
|
description = "The URL to the home server for client-server API calls";
|
|
|
|
};
|
|
|
|
|
2024-08-24 20:05:36 +00:00
|
|
|
domain = lib.mkOption {
|
2020-08-30 07:40:31 +00:00
|
|
|
type = str;
|
|
|
|
description = ''
|
|
|
|
The 'domain' part for user IDs on this home server. Usually
|
|
|
|
(but not always) is the "domain name" part of the homeserver URL.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2024-08-24 20:05:36 +00:00
|
|
|
database = lib.mkOption {
|
2020-08-30 07:40:31 +00:00
|
|
|
default = {};
|
|
|
|
description = "Configuration for the database";
|
|
|
|
type = submodule {
|
|
|
|
freeformType = jsonType;
|
|
|
|
|
|
|
|
options = {
|
2024-08-24 20:05:36 +00:00
|
|
|
engine = lib.mkOption {
|
2020-08-30 07:40:31 +00:00
|
|
|
type = str;
|
|
|
|
description = "Which database engine to use";
|
|
|
|
default = "nedb";
|
|
|
|
example = "postgres";
|
|
|
|
};
|
|
|
|
|
2024-08-24 20:05:36 +00:00
|
|
|
connectionString = lib.mkOption {
|
2020-08-30 07:40:31 +00:00
|
|
|
type = str;
|
|
|
|
description = "The database connection string";
|
|
|
|
default = "nedb://var/lib/matrix-appservice-irc/data";
|
|
|
|
example = "postgres://username:password@host:port/databasename";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2024-08-24 20:05:36 +00:00
|
|
|
ircService = lib.mkOption {
|
2020-08-30 07:40:31 +00:00
|
|
|
default = {};
|
|
|
|
description = "IRC bridge configuration";
|
|
|
|
type = submodule {
|
|
|
|
freeformType = jsonType;
|
|
|
|
|
|
|
|
options = {
|
2024-08-24 20:05:36 +00:00
|
|
|
passwordEncryptionKeyPath = lib.mkOption {
|
2020-08-30 07:40:31 +00:00
|
|
|
type = str;
|
|
|
|
description = ''
|
|
|
|
Location of the key with which IRC passwords are encrypted
|
|
|
|
for storage. Will be generated on first run if not present.
|
|
|
|
'';
|
|
|
|
default = "/var/lib/matrix-appservice-irc/passkey.pem";
|
|
|
|
};
|
|
|
|
|
2024-08-24 20:05:36 +00:00
|
|
|
servers = lib.mkOption {
|
2020-08-30 07:40:31 +00:00
|
|
|
type = submodule { freeformType = jsonType; };
|
|
|
|
description = "IRC servers to connect to";
|
|
|
|
};
|
2024-09-04 18:35:02 +00:00
|
|
|
|
|
|
|
mediaProxy = {
|
|
|
|
signingKeyPath = lib.mkOption {
|
|
|
|
type = path;
|
|
|
|
default = "/var/lib/matrix-appservice-irc/media-signingkey.jwk";
|
|
|
|
description = ''
|
|
|
|
Path to the signing key file for authenticated media.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
ttlSeconds = lib.mkOption {
|
2024-09-09 18:21:53 +00:00
|
|
|
type = ints.unsigned;
|
2024-09-04 18:35:02 +00:00
|
|
|
default = 3600;
|
2024-09-09 18:21:53 +00:00
|
|
|
example = 0;
|
2024-09-04 18:35:02 +00:00
|
|
|
description = ''
|
|
|
|
Lifetime in seconds, that generated URLs stay valid.
|
2024-09-09 18:21:53 +00:00
|
|
|
|
|
|
|
Set the lifetime to 0 to prevent URLs from becoming invalid.
|
2024-09-04 18:35:02 +00:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
bindPort = lib.mkOption {
|
|
|
|
type = port;
|
|
|
|
default = 11111;
|
|
|
|
description = ''
|
|
|
|
Port that the media proxy binds to.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
publicUrl = lib.mkOption {
|
|
|
|
type = str;
|
|
|
|
example = "https://matrix.example.com/media";
|
|
|
|
description = ''
|
|
|
|
URL under which the media proxy is publicly acccessible.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
2020-08-30 07:40:31 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
2024-09-04 18:35:02 +00:00
|
|
|
|
2024-08-24 20:05:36 +00:00
|
|
|
config = lib.mkIf cfg.enable {
|
2020-08-30 07:40:31 +00:00
|
|
|
systemd.services.matrix-appservice-irc = {
|
|
|
|
description = "Matrix-IRC bridge";
|
|
|
|
before = [ "matrix-synapse.service" ]; # So the registration can be used by Synapse
|
2022-06-24 06:37:43 +00:00
|
|
|
after = lib.optionals (cfg.settings.database.engine == "postgres") [
|
|
|
|
"postgresql.service"
|
|
|
|
];
|
2020-08-30 07:40:31 +00:00
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
|
|
|
|
preStart = ''
|
|
|
|
umask 077
|
|
|
|
# Generate key for crypting passwords
|
|
|
|
if ! [ -f "${cfg.settings.ircService.passwordEncryptionKeyPath}" ]; then
|
|
|
|
${pkgs.openssl}/bin/openssl genpkey \
|
|
|
|
-out "${cfg.settings.ircService.passwordEncryptionKeyPath}" \
|
|
|
|
-outform PEM \
|
|
|
|
-algorithm RSA \
|
|
|
|
-pkeyopt "rsa_keygen_bits:${toString cfg.passwordEncryptionKeyLength}"
|
|
|
|
fi
|
|
|
|
# Generate registration file
|
|
|
|
if ! [ -f "${registrationFile}" ]; then
|
|
|
|
# The easy case: the file has not been generated yet
|
|
|
|
${bin} --generate-registration --file ${registrationFile} --config ${configFile} --url ${cfg.registrationUrl} --localpart ${cfg.localpart}
|
|
|
|
else
|
|
|
|
# The tricky case: we already have a generation file. Because the NixOS configuration might have changed, we need to
|
|
|
|
# regenerate it. But this would give the service a new random ID and tokens, so we need to back up and restore them.
|
|
|
|
# 1. Backup
|
|
|
|
id=$(grep "^id:.*$" ${registrationFile})
|
|
|
|
hs_token=$(grep "^hs_token:.*$" ${registrationFile})
|
|
|
|
as_token=$(grep "^as_token:.*$" ${registrationFile})
|
|
|
|
# 2. Regenerate
|
|
|
|
${bin} --generate-registration --file ${registrationFile} --config ${configFile} --url ${cfg.registrationUrl} --localpart ${cfg.localpart}
|
|
|
|
# 3. Restore
|
|
|
|
sed -i "s/^id:.*$/$id/g" ${registrationFile}
|
|
|
|
sed -i "s/^hs_token:.*$/$hs_token/g" ${registrationFile}
|
|
|
|
sed -i "s/^as_token:.*$/$as_token/g" ${registrationFile}
|
|
|
|
fi
|
2024-09-04 18:35:02 +00:00
|
|
|
if ! [ -f "${cfg.settings.ircService.mediaProxy.signingKeyPath}"]; then
|
|
|
|
${lib.getExe pkgs.nodejs} ${pkg}/lib/generate-signing-key.js > "${cfg.settings.ircService.mediaProxy.signingKeyPath}"
|
|
|
|
fi
|
2020-08-30 07:40:31 +00:00
|
|
|
# Allow synapse access to the registration
|
2023-07-11 19:32:23 +00:00
|
|
|
if ${pkgs.getent}/bin/getent group matrix-synapse > /dev/null; then
|
2020-08-30 07:40:31 +00:00
|
|
|
chgrp matrix-synapse ${registrationFile}
|
|
|
|
chmod g+r ${registrationFile}
|
|
|
|
fi
|
|
|
|
'';
|
|
|
|
|
|
|
|
serviceConfig = rec {
|
|
|
|
Type = "simple";
|
|
|
|
ExecStart = "${bin} --config ${configFile} --file ${registrationFile} --port ${toString cfg.port}";
|
|
|
|
|
|
|
|
ProtectHome = true;
|
|
|
|
PrivateDevices = true;
|
|
|
|
ProtectKernelTunables = true;
|
|
|
|
ProtectKernelModules = true;
|
|
|
|
ProtectControlGroups = true;
|
|
|
|
StateDirectory = "matrix-appservice-irc";
|
|
|
|
StateDirectoryMode = "755";
|
|
|
|
|
|
|
|
User = "matrix-appservice-irc";
|
|
|
|
Group = "matrix-appservice-irc";
|
|
|
|
|
2024-08-24 20:05:36 +00:00
|
|
|
CapabilityBoundingSet = [ "CAP_CHOWN" ] ++ lib.optional (cfg.needBindingCap) "CAP_NET_BIND_SERVICE";
|
2020-08-30 07:40:31 +00:00
|
|
|
AmbientCapabilities = CapabilityBoundingSet;
|
|
|
|
NoNewPrivileges = true;
|
|
|
|
|
|
|
|
LockPersonality = true;
|
|
|
|
RestrictRealtime = true;
|
|
|
|
PrivateMounts = true;
|
2023-07-30 01:07:52 +00:00
|
|
|
SystemCallFilter = [
|
2024-04-27 13:19:28 +00:00
|
|
|
"@system-service @pkey"
|
2023-07-30 01:07:52 +00:00
|
|
|
"~@privileged @resources"
|
2024-04-27 13:19:28 +00:00
|
|
|
"@chown"
|
2023-07-30 01:07:52 +00:00
|
|
|
];
|
2020-08-30 07:40:31 +00:00
|
|
|
SystemCallArchitectures = "native";
|
2021-04-20 07:48:50 +00:00
|
|
|
# AF_UNIX is required to connect to a postgres socket.
|
|
|
|
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
|
2020-08-30 07:40:31 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
users.groups.matrix-appservice-irc = {};
|
|
|
|
users.users.matrix-appservice-irc = {
|
|
|
|
description = "Service user for the Matrix-IRC bridge";
|
|
|
|
group = "matrix-appservice-irc";
|
|
|
|
isSystemUser = true;
|
|
|
|
};
|
|
|
|
};
|
2021-11-18 23:26:27 +00:00
|
|
|
|
2020-08-30 07:40:31 +00:00
|
|
|
# uses attributes of the linked package
|
|
|
|
meta.buildDocsInSandbox = false;
|
|
|
|
}
|