0
0
Fork 0
mirror of https://github.com/NixOS/nixpkgs.git synced 2025-07-21 01:20:34 +03:00
nixpkgs/nixos/modules/services/matrix/appservice-irc.nix
Wolfgang Walther 41c5662cbe
nixos/postgresql: move postStart into separate unit
This avoids restarting the postgresql server, when only ensureDatabases
or ensureUsers have been changed. It will also allow to properly wait
for recovery to finish later.

To wait for "postgresql is ready" in other services, we now provide a
postgresql.target.

Resolves #400018

Co-authored-by: Marcel <me@m4rc3l.de>
2025-06-24 15:26:47 +02:00

284 lines
9.8 KiB
Nix

{
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;
configFile =
pkgs.runCommand "matrix-appservice-irc.yml"
{
# Because this program will be run at build time, we need `nativeBuildInputs`
nativeBuildInputs = [
(pkgs.python3.withPackages (ps: [ ps.jsonschema ]))
pkgs.remarshal
];
preferLocalBuild = true;
config = builtins.toJSON cfg.settings;
passAsFile = [ "config" ];
}
''
# The schema is given as yaml, we need to convert it to json
remarshal --if yaml --of json -i ${pkg}/config.schema.yml -o config.schema.json
python -m jsonschema config.schema.json -i $configPath
cp "$configPath" "$out"
'';
registrationFile = "/var/lib/matrix-appservice-irc/registration.yml";
in
{
options.services.matrix-appservice-irc = with lib.types; {
enable = lib.mkEnableOption "the Matrix/IRC bridge";
port = lib.mkOption {
type = port;
description = "The port to listen on";
default = 8009;
};
needBindingCap = lib.mkOption {
type = bool;
description = "Whether the daemon needs to bind to ports below 1024 (e.g. for the ident service)";
default = false;
};
passwordEncryptionKeyLength = lib.mkOption {
type = ints.unsigned;
description = "Length of the key to encrypt IRC passwords with";
default = 4096;
example = 8192;
};
registrationUrl = lib.mkOption {
type = str;
description = ''
The URL where the application service is listening for homeserver requests,
from the Matrix homeserver perspective.
'';
example = "http://localhost:8009";
};
localpart = lib.mkOption {
type = str;
description = "The user_id localpart to assign to the appservice";
default = "appservice-irc";
};
settings = lib.mkOption {
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 = {
homeserver = lib.mkOption {
description = "Homeserver configuration";
default = { };
type = submodule {
freeformType = jsonType;
options = {
url = lib.mkOption {
type = str;
description = "The URL to the home server for client-server API calls";
};
domain = lib.mkOption {
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.
'';
};
};
};
};
database = lib.mkOption {
default = { };
description = "Configuration for the database";
type = submodule {
freeformType = jsonType;
options = {
engine = lib.mkOption {
type = str;
description = "Which database engine to use";
default = "nedb";
example = "postgres";
};
connectionString = lib.mkOption {
type = str;
description = "The database connection string";
default = "nedb://var/lib/matrix-appservice-irc/data";
example = "postgres://username:password@host:port/databasename";
};
};
};
};
ircService = lib.mkOption {
default = { };
description = "IRC bridge configuration";
type = submodule {
freeformType = jsonType;
options = {
passwordEncryptionKeyPath = lib.mkOption {
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";
};
servers = lib.mkOption {
type = submodule { freeformType = jsonType; };
description = "IRC servers to connect to";
};
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 {
type = ints.unsigned;
default = 3600;
example = 0;
description = ''
Lifetime in seconds, that generated URLs stay valid.
Set the lifetime to 0 to prevent URLs from becoming invalid.
'';
};
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.
'';
};
};
};
};
};
};
};
};
};
config = lib.mkIf cfg.enable {
systemd.services.matrix-appservice-irc = {
description = "Matrix-IRC bridge";
before = [ "matrix-synapse.service" ]; # So the registration can be used by Synapse
after = lib.optionals (cfg.settings.database.engine == "postgres") [
"postgresql.target"
];
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
if ! [ -f "${cfg.settings.ircService.mediaProxy.signingKeyPath}" ]; then
${lib.getExe pkgs.nodejs} ${pkg}/lib/generate-signing-key.js > "${cfg.settings.ircService.mediaProxy.signingKeyPath}"
fi
# Allow synapse access to the registration
if ${pkgs.getent}/bin/getent group matrix-synapse > /dev/null; then
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";
CapabilityBoundingSet = [ "CAP_CHOWN" ] ++ lib.optional (cfg.needBindingCap) "CAP_NET_BIND_SERVICE";
AmbientCapabilities = CapabilityBoundingSet;
NoNewPrivileges = true;
LockPersonality = true;
RestrictRealtime = true;
PrivateMounts = true;
SystemCallFilter = [
"@system-service @pkey"
"~@privileged @resources"
"@chown"
];
SystemCallArchitectures = "native";
# AF_UNIX is required to connect to a postgres socket.
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
};
};
users.groups.matrix-appservice-irc = { };
users.users.matrix-appservice-irc = {
description = "Service user for the Matrix-IRC bridge";
group = "matrix-appservice-irc";
isSystemUser = true;
};
};
# uses attributes of the linked package
meta.buildDocsInSandbox = false;
}