From fe228cb25b88e2525510d8841d3588a4c662551a Mon Sep 17 00:00:00 2001 From: Jamie McClymont Date: Sun, 30 Aug 2020 19:40:31 +1200 Subject: [PATCH] nixos/matrix-appservice-irc: init Co-authored-by: Brian McKenna Co-authored-by: piegames --- nixos/modules/module-list.nix | 1 + .../services/misc/matrix-appservice-irc.nix | 228 ++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 nixos/modules/services/misc/matrix-appservice-irc.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 644229627b2..7e3b7ec5a6a 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -490,6 +490,7 @@ ./services/misc/lidarr.nix ./services/misc/mame.nix ./services/misc/matrix-appservice-discord.nix + ./services/misc/matrix-appservice-irc.nix ./services/misc/matrix-synapse.nix ./services/misc/mautrix-telegram.nix ./services/misc/mbpfan.nix diff --git a/nixos/modules/services/misc/matrix-appservice-irc.nix b/nixos/modules/services/misc/matrix-appservice-irc.nix new file mode 100644 index 00000000000..63dc313ad10 --- /dev/null +++ b/nixos/modules/services/misc/matrix-appservice-irc.nix @@ -0,0 +1,228 @@ +{ config, pkgs, lib, ... }: + +with 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.runCommandNoCC "matrix-appservice-irc.yml" { + # Because this program will be run at build time, we need `nativeBuildInputs` + nativeBuildInputs = [ (pkgs.python3.withPackages (ps: [ ps.pyyaml ps.jsonschema ])) ]; + preferLocalBuild = true; + + config = builtins.toJSON cfg.settings; + passAsFile = [ "config" ]; + } '' + # The schema is given as yaml, we need to convert it to json + python -c 'import json; import yaml; import sys; json.dump(yaml.safe_load(sys.stdin), sys.stdout)' \ + < ${pkg}/lib/node_modules/matrix-appservice-irc/config.schema.yml \ + > 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 types; { + enable = mkEnableOption "the Matrix/IRC bridge"; + + port = mkOption { + type = port; + description = "The port to listen on"; + default = 8009; + }; + + needBindingCap = mkOption { + type = bool; + description = "Whether the daemon needs to bind to ports below 1024 (e.g. for the ident service)"; + default = false; + }; + + passwordEncryptionKeyLength = mkOption { + type = ints.unsigned; + description = "Length of the key to encrypt IRC passwords with"; + default = 4096; + example = 8192; + }; + + registrationUrl = 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 = mkOption { + type = str; + description = "The user_id localpart to assign to the appservice"; + default = "appservice-irc"; + }; + + settings = mkOption { + description = '' + Configuration for the appservice, see + + for supported values + ''; + default = {}; + type = submodule { + freeformType = jsonType; + + options = { + homeserver = mkOption { + description = "Homeserver configuration"; + default = {}; + type = submodule { + freeformType = jsonType; + + options = { + url = mkOption { + type = str; + description = "The URL to the home server for client-server API calls"; + }; + + domain = 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 = mkOption { + default = {}; + description = "Configuration for the database"; + type = submodule { + freeformType = jsonType; + + options = { + engine = mkOption { + type = str; + description = "Which database engine to use"; + default = "nedb"; + example = "postgres"; + }; + + connectionString = mkOption { + type = str; + description = "The database connection string"; + default = "nedb://var/lib/matrix-appservice-irc/data"; + example = "postgres://username:password@host:port/databasename"; + }; + }; + }; + }; + + ircService = mkOption { + default = {}; + description = "IRC bridge configuration"; + type = submodule { + freeformType = jsonType; + + options = { + passwordEncryptionKeyPath = 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 = mkOption { + type = submodule { freeformType = jsonType; }; + description = "IRC servers to connect to"; + }; + }; + }; + }; + }; + }; + }; + }; + config = mkIf cfg.enable { + systemd.services.matrix-appservice-irc = { + description = "Matrix-IRC bridge"; + before = [ "matrix-synapse.service" ]; # So the registration can be used by Synapse + 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 + # Allow synapse access to the registration + if ${getBin pkgs.glibc}/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" ] ++ optional (cfg.needBindingCap) "CAP_NET_BIND_SERVICE"; + AmbientCapabilities = CapabilityBoundingSet; + NoNewPrivileges = true; + + LockPersonality = true; + RestrictRealtime = true; + PrivateMounts = true; + SystemCallFilter = "~@aio @clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @raw-io @setuid @swap"; + SystemCallArchitectures = "native"; + RestrictAddressFamilies = "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; + }; + }; +}