From f4d8ab880a58e00af5a7fcda112baa7de2f8f0df Mon Sep 17 00:00:00 2001 From: sefidel Date: Sun, 11 Feb 2024 02:11:21 +0900 Subject: feat(project)!: support nixos-rebuild and hydra --- default.nix | 18 +- flake.lock | 75 +++- flake.nix | 20 +- hydra/default.nix | 1 + hydra/jobsets.nix | 34 ++ hydra/spec.json | 24 ++ lib/system.nix | 47 ++- modules/services/git-daemon.nix | 29 -- modules/services/git-daemon/_git-daemon-module.nix | 137 ++++++++ modules/services/git-daemon/default.nix | 29 ++ modules/services/matrix-bridge.nix | 390 --------------------- .../matrix-bridge/_mautrix-discord-module.nix | 205 +++++++++++ .../matrix-bridge/_mautrix-signal-module.nix | 204 +++++++++++ modules/services/matrix-bridge/default.nix | 390 +++++++++++++++++++++ modules/services/soju.nix | 48 --- modules/services/soju/_soju-module.nix | 132 +++++++ modules/services/soju/default.nix | 48 +++ overlays/git-daemon-module.nix | 137 -------- overlays/mautrix-discord-module.nix | 205 ----------- overlays/mautrix-signal-module.nix | 204 ----------- overlays/soju-module.nix | 132 ------- 21 files changed, 1335 insertions(+), 1174 deletions(-) create mode 100644 hydra/default.nix create mode 100644 hydra/jobsets.nix create mode 100644 hydra/spec.json delete mode 100644 modules/services/git-daemon.nix create mode 100644 modules/services/git-daemon/_git-daemon-module.nix create mode 100644 modules/services/git-daemon/default.nix delete mode 100644 modules/services/matrix-bridge.nix create mode 100644 modules/services/matrix-bridge/_mautrix-discord-module.nix create mode 100644 modules/services/matrix-bridge/_mautrix-signal-module.nix create mode 100644 modules/services/matrix-bridge/default.nix delete mode 100644 modules/services/soju.nix create mode 100644 modules/services/soju/_soju-module.nix create mode 100644 modules/services/soju/default.nix delete mode 100644 overlays/git-daemon-module.nix delete mode 100644 overlays/mautrix-discord-module.nix delete mode 100644 overlays/mautrix-signal-module.nix delete mode 100644 overlays/soju-module.nix diff --git a/default.nix b/default.nix index 63f1796..f620865 100644 --- a/default.nix +++ b/default.nix @@ -1,8 +1,10 @@ -{ inputs, lib, ... }: -with lib; -with lib.my; -{ - imports = mapModulesRec' (toString ./modules) import; - - networking.useDHCP = mkDefault false; -} +(import + ( + let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in + fetchTarball { + url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + { src = ./.; } +).defaultNix diff --git a/flake.lock b/flake.lock index ea89f0d..56f7328 100644 --- a/flake.lock +++ b/flake.lock @@ -60,6 +60,29 @@ "type": "gitlab" } }, + "colmena": { + "inputs": { + "flake-compat": "flake-compat_2", + "flake-utils": "flake-utils_2", + "nixpkgs": [ + "unstable" + ], + "stable": "stable" + }, + "locked": { + "lastModified": 1706509311, + "narHash": "sha256-QQKQ6r3CID8aXn2ZXZ79ZJxdCOeVP+JTnOctDALErOw=", + "owner": "zhaofengli", + "repo": "colmena", + "rev": "c84ccd0a7a712475e861c2b111574472b1a8d0cd", + "type": "github" + }, + "original": { + "owner": "zhaofengli", + "repo": "colmena", + "type": "github" + } + }, "flake-compat": { "flake": false, "locked": { @@ -77,6 +100,22 @@ } }, "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1650374568, + "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "b4a34015c698c7793d592d66adbab377907a2be8", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_3": { "flake": false, "locked": { "lastModified": 1668681692, @@ -129,6 +168,21 @@ } }, "flake-utils_2": { + "locked": { + "lastModified": 1659877975, + "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_3": { "locked": { "lastModified": 1667395993, "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", @@ -208,7 +262,7 @@ "nixos-mailserver": { "inputs": { "blobs": "blobs", - "flake-compat": "flake-compat_2", + "flake-compat": "flake-compat_3", "nixpkgs": [ "unstable" ], @@ -373,6 +427,7 @@ "root": { "inputs": { "authentik-nix": "authentik-nix", + "colmena": "colmena", "impermanence": "impermanence", "nixos-mailserver": "nixos-mailserver", "nixpkgs-2111": "nixpkgs-2111", @@ -384,7 +439,7 @@ }, "sefidel-web": { "inputs": { - "flake-utils": "flake-utils_2", + "flake-utils": "flake-utils_3", "nixpkgs": "nixpkgs" }, "locked": { @@ -422,6 +477,22 @@ "type": "github" } }, + "stable": { + "locked": { + "lastModified": 1696039360, + "narHash": "sha256-g7nIUV4uq1TOVeVIDEZLb005suTWCUjSY0zYOlSBsyE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "32dcb45f66c0487e92db8303a798ebc548cadedc", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, "systems": { "locked": { "lastModified": 1681028828, diff --git a/flake.nix b/flake.nix index c0da437..3ef69b4 100644 --- a/flake.nix +++ b/flake.nix @@ -6,6 +6,12 @@ unstable-small.url = "github:nixos/nixpkgs/nixos-unstable-small"; nixpkgs-2111.url = "github:nixos/nixpkgs/nixos-21.11"; + colmena.url = "github:zhaofengli/colmena"; + colmena.inputs.nixpkgs.follows = "unstable"; + + flake-compat.url = "github:edolstra/flake-compat"; + flake-compat.flake = false; + impermanence.url = "github:nix-community/impermanence"; authentik-nix.url = "github:nix-community/authentik-nix"; @@ -36,12 +42,10 @@ nixosModules = mapModulesRec ./modules import; - colmena = { - meta = { - nixpkgs = import unstable { inherit system; overlays = [ (import ./overlays) ]; }; - specialArgs = { inherit lib inputs system; }; - }; - } - // mapSystems ./systems { }; - }; + nixosConfigurations = mapSystems ./systems { }; + + colmena = self.lib.mkColmenaFromNixOSConfigurations self.nixosConfigurations; + + hydraJobs = self.lib.mkHydraFromNixOSConfigurations self.nixosConfigurations; + }; } diff --git a/hydra/default.nix b/hydra/default.nix new file mode 100644 index 0000000..85a57e2 --- /dev/null +++ b/hydra/default.nix @@ -0,0 +1 @@ +{ infra }: (import infra).hydraJobs diff --git a/hydra/jobsets.nix b/hydra/jobsets.nix new file mode 100644 index 0000000..daf1a5a --- /dev/null +++ b/hydra/jobsets.nix @@ -0,0 +1,34 @@ +{ nixpkgs, ... }: + +let + pkgs = import nixpkgs {}; + + jobsets = { + main = { + enabled = 1; + hidden = false; + description = "main branch"; + nixexprinput = "infra"; + nixexprpath = "hydra/default.nix"; + checkinterval = 300; + schedulingshares = 100; + enableemail = false; + emailoverride = ""; + keepnr = 1; + inputs = { + infra = { + type = "git"; + value = "https://git.exotic.sh/infra main"; + emailresponsible = false; + }; + nixpkgs = { + type = "git"; + value = "https://github.com/NixOS/nixpkgs.git nixos-unstable"; + emailresponsible = false; + }; + }; + }; + }; +in { + jobsets = pkgs.writeText "jobsets.json" (builtins.toJSON jobsets); +} diff --git a/hydra/spec.json b/hydra/spec.json new file mode 100644 index 0000000..73aed01 --- /dev/null +++ b/hydra/spec.json @@ -0,0 +1,24 @@ +{ + "enabled": 1, + "hidden": false, + "description": "Jobsets", + "nixexprinput": "infra", + "nixexprpath": "hydra/jobsets.nix", + "checkinterval": 300, + "schedulingshares": 100, + "enableemail": false, + "emailoverride": "", + "keepnr": 1, + "inputs": { + "infra": { + "type": "git", + "value": "https://git.exotic.sh/infra main", + "emailresponsible": false + }, + "nixpkgs": { + "type": "git", + "value": "https://github.com/NixOS/nixpkgs.git nixos-unstable", + "emailresponsible": false + } + } +} diff --git a/lib/system.nix b/lib/system.nix index 8fc4dce..e1709e3 100644 --- a/lib/system.nix +++ b/lib/system.nix @@ -1,22 +1,47 @@ -{ self, inputs, lib, pkgs, ... }: +{ self, inputs, lib, pkgs, ... } @ args: with lib; with lib.my; { - mkSystem = path: attrs @ { ... }: { - imports = [ + mkSystem = { hostPath, nixpkgs ? inputs.unstable, system ? "x86_64-linux", overlays ? null, extraModules ? null }: + nixpkgs.lib.nixosSystem ( { - networking.hostName = mkDefault - (removeSuffix ".nix" (baseNameOf path)); - system.configurationRevision = self.rev or "dirty"; + system = system; + + specialArgs = args; + + modules = [ + { + networking.hostName = mkDefault (removeSuffix ".nix" (baseNameOf hostPath)); + nix.flakes.enable = true; + system.configurationRevision = self.rev or "dirty"; + documentation.man = { enable = true; generateCaches = true; }; + } + (import hostPath) + ] ++ mapModulesRec' ../modules import + ++ nixpkgs.lib.optional (overlays != null) { nixpkgs.overlays = overlays; } + ++ nixpkgs.lib.optionals (extraModules != null) extraModules; + + # let deployment options to be set without breaking eval on nixosConfigurations + extraModules = [ inputs.colmena.nixosModules.deploymentOptions ]; } - ../. # /default.nix - (import path) - ]; - }; + ); mapSystems = dir: attrs @ { system ? system, ... }: mapModules dir - (hostPath: mkSystem hostPath attrs); + (hostPath: mkSystem { inherit hostPath; }); + + mkColmenaFromNixOSConfigurations = conf: + { + meta = { + # Colmena requirement. Will be overridden in nodeNixpkgs. + nixpkgs = import inputs.unstable { system = "x86_64-linux"; }; + nodeNixpkgs = builtins.mapAttrs (_: value: value.pkgs) conf; + nodeSpecialArgs = builtins.mapAttrs (_: value: value._module.specialArgs) conf; + }; + } // builtins.mapAttrs (_: value: { imports = value._module.args.modules; }) conf; + + mkHydraFromNixOSConfigurations = conf: + mapAttrs' (name: value: { name = "nixos-${name}"; value = value.config.system.build.toplevel; }) conf; } diff --git a/modules/services/git-daemon.nix b/modules/services/git-daemon.nix deleted file mode 100644 index 5d027de..0000000 --- a/modules/services/git-daemon.nix +++ /dev/null @@ -1,29 +0,0 @@ -{ config, lib, ... }: - -with lib; -let - cfg = config.modules.services.gitDaemon; -in -{ - disabledModules = [ - "services/networking/git-daemon.nix" - ]; - - imports = [ - ../../overlays/git-daemon-module.nix - ]; - - options.modules.services.gitDaemon = { - enable = mkEnableOption "git daemon"; - }; - - config = mkIf cfg.enable { - services.gitDaemon = { - enable = true; - createUserAndGroup = false; - basePath = "/var/lib/gitolite/repositories"; - }; - - networking.firewall.allowedTCPPorts = [ 9418 ]; - }; -} diff --git a/modules/services/git-daemon/_git-daemon-module.nix b/modules/services/git-daemon/_git-daemon-module.nix new file mode 100644 index 0000000..76b395e --- /dev/null +++ b/modules/services/git-daemon/_git-daemon-module.nix @@ -0,0 +1,137 @@ +{ config, lib, pkgs, ... }: +with lib; +let + + cfg = config.services.gitDaemon; + +in +{ + + ###### interface + + options = { + services.gitDaemon = { + + enable = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Enable Git daemon, which allows public hosting of git repositories + without any access controls. This is mostly intended for read-only access. + + You can allow write access by setting daemon.receivepack configuration + item of the repository to true. This is solely meant for a closed LAN setting + where everybody is friendly. + + If you need any access controls, use something else. + ''; + }; + + basePath = mkOption { + type = types.str; + default = ""; + example = "/srv/git/"; + description = lib.mdDoc '' + Remap all the path requests as relative to the given path. For example, + if you set base-path to /srv/git, then if you later try to pull + git://example.com/hello.git, Git daemon will interpret the path as /srv/git/hello.git. + ''; + }; + + exportAll = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Publish all directories that look like Git repositories (have the objects + and refs subdirectories), even if they do not have the git-daemon-export-ok file. + + If disabled, you need to touch .git/git-daemon-export-ok in each repository + you want the daemon to publish. + + Warning: enabling this without a repository whitelist or basePath + publishes every git repository you have. + ''; + }; + + repositories = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "/srv/git" "/home/user/git/repo2" ]; + description = lib.mdDoc '' + A whitelist of paths of git repositories, or directories containing repositories + all of which would be published. Paths must not end in "/". + + Warning: leaving this empty and enabling exportAll publishes all + repositories in your filesystem or basePath if specified. + ''; + }; + + listenAddress = mkOption { + type = types.str; + default = ""; + example = "example.com"; + description = lib.mdDoc "Listen on a specific IP address or hostname."; + }; + + port = mkOption { + type = types.port; + default = 9418; + description = lib.mdDoc "Port to listen on."; + }; + + options = mkOption { + type = types.str; + default = ""; + description = lib.mdDoc "Extra configuration options to be passed to Git daemon."; + }; + + user = mkOption { + type = types.str; + default = "git"; + description = lib.mdDoc "User under which Git daemon would be running."; + }; + + group = mkOption { + type = types.str; + default = "git"; + description = lib.mdDoc "Group under which Git daemon would be running."; + }; + + createUserAndGroup = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Create the specified group and user. + Disable this option if you want to use the existing user + ''; + }; + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + + users.users.${cfg.user} = optionalAttrs (cfg.createUserAndGroup == true) { + uid = config.ids.uids.git; + group = cfg.group; + description = "Git daemon user"; + }; + + users.groups.${cfg.group} = optionalAttrs (cfg.createUserAndGroup == true) { + gid = config.ids.gids.git; + }; + + systemd.services.git-daemon = { + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + script = "${pkgs.git}/bin/git daemon --reuseaddr " + + (optionalString (cfg.basePath != "") "--base-path=${cfg.basePath} ") + + (optionalString (cfg.listenAddress != "") "--listen=${cfg.listenAddress} ") + + "--port=${toString cfg.port} --user=${cfg.user} --group=${cfg.group} ${cfg.options} " + + "--verbose " + (optionalString cfg.exportAll "--export-all ") + concatStringsSep " " cfg.repositories; + }; + + }; + +} diff --git a/modules/services/git-daemon/default.nix b/modules/services/git-daemon/default.nix new file mode 100644 index 0000000..bc06342 --- /dev/null +++ b/modules/services/git-daemon/default.nix @@ -0,0 +1,29 @@ +{ config, lib, ... }: + +with lib; +let + cfg = config.modules.services.gitDaemon; +in +{ + disabledModules = [ + "services/networking/git-daemon.nix" + ]; + + imports = [ + ./_git-daemon-module.nix + ]; + + options.modules.services.gitDaemon = { + enable = mkEnableOption "git daemon"; + }; + + config = mkIf cfg.enable { + services.gitDaemon = { + enable = true; + createUserAndGroup = false; + basePath = "/var/lib/gitolite/repositories"; + }; + + networking.firewall.allowedTCPPorts = [ 9418 ]; + }; +} diff --git a/modules/services/matrix-bridge.nix b/modules/services/matrix-bridge.nix deleted file mode 100644 index 3ea46d8..0000000 --- a/modules/services/matrix-bridge.nix +++ /dev/null @@ -1,390 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; -let - cfg = config.modules.services.matrix-bridge; -in -{ - imports = [ - ../../overlays/mautrix-signal-module.nix - ../../overlays/mautrix-discord-module.nix - ]; - - options.modules.services.matrix-bridge = { - enable = mkEnableOption "matrix-bridge"; - domain = mkOption { type = types.str; }; - realHost = mkOption { type = types.str; default = "matrix.${cfg.domain}"; }; - secrets.mautrix-envs = mkOption { type = types.path; description = "path to the mautrix-* environment file"; }; - }; - - config = mkIf cfg.enable { - sops.secrets.double-puppet-as-token = { }; - sops.secrets.double-puppet-hs-token = { }; - - sops.templates."double-puppet-registration.yaml".content = '' - id: doublepuppet - url: - as_token: ${config.sops.placeholder.double-puppet-as-token} - hs_token: ${config.sops.placeholder.double-puppet-hs-token} - sender_localpart: 55e126746dad19e50d9c4e646b6f5ac9ba21b346a24b840330cd8d8a1d65ce80 - rate_limited: false - namespaces: - users: - - regex: '@.*:exotic\.sh' - exclusive: false - ''; - - services.mautrix-telegram = { - enable = true; - - environmentFile = cfg.secrets.mautrix-envs; - serviceDependencies = [ "matrix-synapse.service" ]; - - settings = { - homeserver.address = "https://${cfg.realHost}"; - homeserver.domain = cfg.domain; - homeserver.verify_ssl = true; - appservice = { - address = "http://localhost:29317"; - hostname = "localhost"; - port = 29317; - database = "postgres:///mautrix-telegram?host=/run/postgresql"; - bot_avatar = "mxc://maunium.net/tJCRmUyJDsgRNgqhOgoiHWbX"; - id = "telegram"; - max_body_size = 1; - provisioning.enabled = false; - }; - bridge = { - alias_template = "tg_{groupname}"; - username_templace = "tg_{userid}"; - allow_matrix_login = true; - bot_messages_as_notices = true; - catch_up = true; - plaintext_highlights = true; - startup_sync = true; - animated_stickers = { - target = "webp"; - convert_from_webm = true; - }; - encryption = { - allow = true; - default = true; - require = true; - allow_key_sharing = true; - }; - # NOTE: python bridge - managed via env variable - # login_shared_secret_map = { - # "${cfg.domain}" = "as_token:$DOUBLE_PUPPET_AS_TOKEN"; - # }; - permissions = { - "@sef:exotic.sh" = "admin"; - "exotic.sh" = "full"; - }; - }; - }; - }; - - services.mautrix-signal = { - enable = true; - package = pkgs.mautrix-signal; - - environmentFile = cfg.secrets.mautrix-envs; - serviceDependencies = [ "matrix-synapse.service" ]; - - settings = { - homeserver.address = "https://${cfg.realHost}"; - homeserver.domain = cfg.domain; - homeserver.verify_ssl = true; - appservice = { - address = "http://localhost:29318"; - port = 29318; - database = "postgres:///mautrix-signal?host=/run/postgresql"; - bot_avatar = "mxc://maunium.net/wPJgTQbZOtpBFmDNkiNEMDUp"; - id = "signal"; - max_body_size = 1; - provisioning.enabled = false; - }; - - signal = { - avatar_dir = "/var/lib/signald/avatars"; - data_dir = "/var/lib/signald/data"; - }; - - bridge = { - alias_template = "sig_{groupname}"; - username_templace = "sig_{userid}"; - allow_matrix_login = true; - catch_up = true; - plaintext_highlights = true; - startup_sync = true; - animated_stickers = { - target = "webp"; - convert_from_webm = true; - }; - encryption = { - allow = true; - default = true; - require = true; - allow_key_sharing = true; - }; - login_shared_secret_map = { - "${cfg.domain}" = "as_token:$DOUBLE_PUPPET_AS_TOKEN"; - }; - permissions = { - "@sef:exotic.sh" = "admin"; - "exotic.sh" = "full"; - }; - }; - }; - }; - - services.mautrix-whatsapp = { - enable = true; - environmentFile = cfg.secrets.mautrix-envs; - serviceDependencies = [ "matrix-synapse.service" ]; - - settings = { - homeserver.address = "https://${cfg.realHost}"; - homeserver.domain = cfg.domain; - homeserver.verify_ssl = true; - appservice = { - address = "http://localhost:29319"; - hostname = "localhost"; - port = 29319; - database = { - type = "postgres"; - uri = "postgres://mautrix-whatsapp:@/mautrix-whatsapp?host=/run/postgresql"; - }; - bot_avatar = "mxc://maunium.net/NeXNQarUbrlYBiPCpprYsRqr"; - id = "whatsapp"; - max_body_size = 1; - provisioning.enabled = false; - }; - - bridge = { - alias_template = "wa_{groupname}"; - username_templace = "wa_{userid}"; - personal_filtering_spaces = true; - delivery_receipts = true; - identity_change_notices = true; - history_sync = { - backfill = false; # MSC2716 - request_full_sync = true; - }; - send_presence_on_typing = true; - double_puppet_server_map = { }; - login_shared_secret_map = { - "${cfg.domain}" = "as_token:$DOUBLE_PUPPET_AS_TOKEN"; - }; - private_chat_portal_meta = true; - mute_bridging = true; - pinned_tag = "m.favourite"; - archive_tag = "m.lowpriority"; - allow_user_invite = true; - disappearing_messages_in_groups = true; - url_previews = true; - encryption = { - allow = true; - default = true; - require = true; - allow_key_sharing = true; - }; - sync_manual_marked_unread = true; - force_active_delivery_receipts = true; - parallel_member_sync = true; - extev_polls = true; - send_whatsapp_edits = true; - permissions = { - "@sef:exotic.sh" = "admin"; - "exotic.sh" = "full"; - }; - }; - }; - }; - - services.mautrix-discord = { - enable = true; - environmentFile = cfg.secrets.mautrix-envs; - serviceDependencies = [ "matrix-synapse.service" ]; - - settings = { - homeserver.address = "https://${cfg.realHost}"; - homeserver.domain = cfg.domain; - homeserver.verify_ssl = true; - appservice = { - address = "http://localhost:29320"; - port = 29320; - database = { - type = "postgres"; - uri = "postgres://mautrix-discord:@/mautrix-discord?host=/run/postgresql"; - }; - bot_avatar = "mxc://maunium.net/nIdEykemnwdisvHbpxflpDlC"; - id = "discord"; - max_body_size = 1; - provisioning.enabled = false; - }; - - bridge = { - username_template = "dsc_{{.}}"; - delivery_receipts = true; - encryption = { - allow = true; - default = true; - require = true; - allow_key_sharing = true; - }; - login_shared_secret_map = { - "${cfg.domain}" = "as_token:$DOUBLE_PUPPET_AS_TOKEN"; - }; - permissions = { - "@sef:exotic.sh" = "admin"; - "exotic.sh" = "full"; - }; - }; - }; - }; - - services.matrix-appservice-irc = { - enable = true; - registrationUrl = "http://localhost:29321"; - port = 29321; - - settings = { - homeserver.url = "https://${cfg.realHost}"; - homeserver.domain = cfg.domain; - homeserver.dropMatrixMessagesAfterSecs = 600; # 10 minutes - - database.engine = "postgres"; - database.connectionString = "postgres://matrix-appservice-irc:@/matrix-appservice-irc?host=/run/postgresql"; - - ircService.servers = let - # nix-community/nur-combined/repos/colinsane/hosts/by-name/servo/services/matrix/irc.nix@b2e96d5 - ircServer = { name, additionalAddresses ? [], sasl ? true, port ? 6697}: - let lowerName = lib.toLower name; - in { - inherit name additionalAddresses sasl port; - ssl = true; - # Disable bridging of Matrix bots - botConfig.enabled = false; - dynamicChannels = { - enabled = true; - aliasTemplate = "#irc_${lowerName}_$CHANNEL"; - published = false; - federate = false; - }; - ircClients = { - nickTemplate = "$DISPLAY[m]"; - allowNickChanges = true; - realNameFormat = "reverse-mxid"; - lineLimit = 20; - # Safeguard: don't flood servers - maxClients = 2; - idleTimeout = 0; - concurrentReconnectLimit = 2; - reconnectIntervalMs = 60000; - kickOn = { - # only kick Matrix user from room when user quits - channelJoinFailure = false; - ircConnectionFailure = false; - userQuit = true; - }; - }; - matrixClients.userTemplate = "@irc_${lowerName}_$NICK"; - - "@sef:exotic.sh" = "admin"; - - memberShipLists = { - enabled = true; - # NOTE: when serving lots of Matrix users, these configs should - # be changed to reduce strain on IRC servers - global = { - ircToMatrix = { - initial = true; - incremental = true; - requireMatrixJoined = false; - }; - matrixToIrc = { - initial = true; - incremental = true; - }; - # always bridge users, even if idle - ignoreIdleUsersOnStartup.enabled = false; - }; - bridgeInfoState = { - enabled = true; - initial = true; - }; - }; - }; - in { - "irc.libera.chat" = ircServer { - name = "libera"; - # sasl = false; - }; - "irc.oftc.net" = ircServer { - name = "oftc"; - # sasl = false; - }; - }; - }; - }; - - # HACK: https://github.com/NixOS/nixpkgs/issues/273929 - systemd.services.matrix-appservice-irc.serviceConfig.SystemCallFilter = lib.mkForce '' - @system-service @pkey ~@privileged @resources @chown - ''; - - - modules.persistence.directories = [ - "/var/lib/private/mautrix-telegram" - "/var/lib/private/mautrix-signal" - "/var/lib/private/mautrix-whatsapp" - "/var/lib/private/mautrix-discord" - "/var/lib/matrix-appservice-irc" - "/var/lib/signald" - ]; - - modules.services.postgresql.enable = true; - services.postgresql.ensureDatabases = [ "mautrix-telegram" "mautrix-signal" "mautrix-whatsapp" "mautrix-discord" "matrix-appservice-irc" ]; - services.postgresql.ensureUsers = [ - { - name = "mautrix-telegram"; - ensureDBOwnership = true; - } - { - name = "mautrix-signal"; - ensureDBOwnership = true; - } - { - name = "mautrix-whatsapp"; - ensureDBOwnership = true; - } - { - name = "mautrix-discord"; - ensureDBOwnership = true; - } - { - name = "matrix-appservice-irc"; - ensureDBOwnership = true; - } - ]; - - systemd.services.matrix-synapse.serviceConfig.LoadCredential = [ - "mautrix-telegram:/var/lib/mautrix-telegram/telegram-registration.yaml" - "mautrix-signal:/var/lib/mautrix-signal/signal-registration.yaml" - "mautrix-whatsapp:/var/lib/mautrix-whatsapp/whatsapp-registration.yaml" - "mautrix-discord:/var/lib/mautrix-discord/discord-registration.yaml" - "double-puppet:${config.sops.templates."double-puppet-registration.yaml".path}" - "appservice-irc:/var/lib/matrix-appservice-irc/registration.yml" - ]; - - services.matrix-synapse.settings.app_service_config_files = [ - "/run/credentials/matrix-synapse.service/mautrix-telegram" - "/run/credentials/matrix-synapse.service/mautrix-signal" - "/run/credentials/matrix-synapse.service/mautrix-whatsapp" - "/run/credentials/matrix-synapse.service/mautrix-discord" - "/run/credentials/matrix-synapse.service/double-puppet" - "/run/credentials/matrix-synapse.service/appservice-irc" - ]; - }; -} diff --git a/modules/services/matrix-bridge/_mautrix-discord-module.nix b/modules/services/matrix-bridge/_mautrix-discord-module.nix new file mode 100644 index 0000000..36cafe6 --- /dev/null +++ b/modules/services/matrix-bridge/_mautrix-discord-module.nix @@ -0,0 +1,205 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + dataDir = "/var/lib/mautrix-discord"; + registrationFile = "${dataDir}/discord-registration.yaml"; + cfg = config.services.mautrix-discord; + settingsFormat = pkgs.formats.json { }; + settingsFile = "${dataDir}/config.json"; + settingsFileUnsubstituted = settingsFormat.generate "mautrix-discord-config.json" cfg.settings; + +in +{ + options = { + services.mautrix-discord = { + enable = mkEnableOption (lib.mdDoc "Mautrix-discord, a Matrix-discord puppeting bridge."); + + package = mkOption { type = types.package; default = pkgs.mautrix-discord; }; + + settings = mkOption rec { + apply = recursiveUpdate default; + inherit (settingsFormat) type; + default = { + homeserver = { + software = "standard"; + }; + + appservice = rec { + database = "sqlite:///${dataDir}/mautrix-discord.db"; + database_opts = { }; + hostname = "localhost"; + port = 8080; + address = "http://localhost:${toString port}"; + }; + + bridge = { + permissions."*" = "relay"; + relay.whitelist = [ ]; + double_puppet_server_map = { }; + login_shared_secret_map = { }; + }; + + logging = { + min_level = "debug"; + writers = [ + { + type = "stdout"; + format = "pretty-colored"; + } + ]; + }; + }; + example = literalExpression '' + { + homeserver = { + address = "http://localhost:8008"; + domain = "public-domain.tld"; + }; + + appservice.public = { + prefix = "/public"; + external = "https://public-appservice-address/public"; + }; + + bridge.permissions = { + "example.com" = "full"; + "@admin:example.com" = "admin"; + }; + } + ''; + description = lib.mdDoc '' + {file}`config.yaml` configuration as a Nix attribute set. + Configuration options should match those described in + [example-config.yaml](https://github.com/mautrix/discord/blob/master/discord/example-config.yaml). + + Secret tokens should be specified using {option}`environmentFile` + instead of this world-readable attribute set. + ''; + }; + + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + description = lib.mdDoc '' + File containing environment variables to be passed to the mautrix-discord service, + in which secret tokens can be specified securely by defining values for e.g. + `MAUTRIX_DISCORD_APPSERVICE_AS_TOKEN`, + `MAUTRIX_DISCORD_APPSERVICE_HS_TOKEN`, + + These environment variables can also be used to set other options by + replacing hierarchy levels by `.`, converting the name to uppercase + and prepending `MAUTRIX_DISCORD_`. + For example, the first value above maps to + {option}`settings.appservice.as_token`. + + The environment variable values can be prefixed with `json::` to have + them be parsed as JSON. For example, `login_shared_secret_map` can be + set as follows: + `MAUTRIX_DISCORD_BRIDGE_LOGIN_SHARED_SECRET_MAP=json::{"example.com":"secret"}`. + ''; + }; + + serviceDependencies = mkOption { + type = with types; listOf str; + default = optional config.services.matrix-synapse.enable "matrix-synapse.service"; + defaultText = literalExpression '' + optional config.services.matrix-synapse.enable "matrix-synapse.service" + ''; + description = lib.mdDoc '' + List of Systemd services to require and wait for when starting the application service. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + users.users.mautrix-discord = { + isSystemUser = true; + group = "mautrix-discord"; + home = dataDir; + description = "Mautrix-Discord bridge user"; + }; + + users.groups.mautrix-discord = {}; + + systemd.services.mautrix-discord = { + description = "Mautrix-discord, a Matrix-discord puppeting bridge."; + + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ] ++ cfg.serviceDependencies; + after = [ "network-online.target" ] ++ cfg.serviceDependencies; + + preStart = '' + # substitute the settings file by environment variables + # in this case read from EnvironmentFile + test -f '${settingsFile}' && rm -f '${settingsFile}' + old_umask=$(umask) + umask 0177 + ${pkgs.envsubst}/bin/envsubst \ + -o '${settingsFile}' \ + -i '${settingsFileUnsubstituted}' \ + umask $old_umask + + # generate the appservice's registration file if absent + if [ ! -f '${registrationFile}' ]; then + ${pkgs.mautrix-discord}/bin/mautrix-discord \ + --generate-registration \ + --config='${settingsFile}' \ + --registration='${registrationFile}' + fi + chmod 640 ${registrationFile} + + umask 0177 + ${pkgs.yq}/bin/yq -s '.[0].appservice.as_token = .[1].as_token + | .[0].appservice.hs_token = .[1].hs_token + | .[0]' '${settingsFile}' '${registrationFile}' \ + > '${settingsFile}.tmp' + mv '${settingsFile}.tmp' '${settingsFile}' + umask $old_umask + ''; + + serviceConfig = { + Type = "simple"; + Restart = "on-failure"; + + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + ProtectSystem = "strict"; + ProtectHome = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + ProtectClock = true; + ProtectHostname = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + + SystemCallArchitectures = "native"; + SystemCallErrorNumber = "EPERM"; + SystemCallFilter = ["@system-service"]; + + PrivateTmp = true; + PrivateDevices = true; + PrivateUsers = true; + + User = "mautrix-discord"; + Group = "mautrix-discord"; + WorkingDirectory = dataDir; + StateDirectory = baseNameOf dataDir; + UMask = "0027"; + EnvironmentFile = cfg.environmentFile; + + ExecStart = '' + ${cfg.package}/bin/mautrix-discord \ + --config='${settingsFile}' \ + --registration='${registrationFile}' + ''; + }; + restartTriggers = [settingsFileUnsubstituted]; + }; + }; +} diff --git a/modules/services/matrix-bridge/_mautrix-signal-module.nix b/modules/services/matrix-bridge/_mautrix-signal-module.nix new file mode 100644 index 0000000..983d635 --- /dev/null +++ b/modules/services/matrix-bridge/_mautrix-signal-module.nix @@ -0,0 +1,204 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + dataDir = "/var/lib/mautrix-signal"; + registrationFile = "${dataDir}/signal-registration.yaml"; + cfg = config.services.mautrix-signal; + settingsFormat = pkgs.formats.json { }; + settingsFile = "${dataDir}/config.json"; + settingsFileUnsubstituted = + settingsFormat.generate "mautrix-signal-config.json" cfg.settings; + +in +{ + # NOTE(2024-01-11): Upstream has been moved to a Go version. + # Environment-based credential setting might not work. + options = { + services.mautrix-signal = { + enable = mkEnableOption (lib.mdDoc "Mautrix-Signal, a Matrix-Signal puppeting bridge."); + + package = mkOption { type = types.package; default = pkgs.mautrix-signal; }; + + settings = mkOption rec { + apply = recursiveUpdate default; + inherit (settingsFormat) type; + default = { + homeserver = { + software = "standard"; + }; + + appservice = rec { + database = "sqlite:///${dataDir}/mautrix-signal.db"; + database_opts = { }; + hostname = "localhost"; + port = 8080; + address = "http://localhost:${toString port}"; + }; + + signal.socket_path = config.services.signald.socketPath; + + bridge = { + permissions."*" = "relay"; + relay.whitelist = [ ]; + double_puppet_server_map = { }; + login_shared_secret_map = { }; + }; + + logging = { + min_level = "debug"; + writers = [ + { + type = "stdout"; + format = "pretty-colored"; + } + ]; + }; + }; + example = literalExpression '' + { + homeserver = { + address = "http://localhost:8008"; + domain = "public-domain.tld"; + }; + + appservice.public = { + prefix = "/public"; + external = "https://public-appservice-address/public"; + }; + + bridge.permissions = { + "example.com" = "full"; + "@admin:example.com" = "admin"; + }; + } + ''; + description = lib.mdDoc '' + {file}`config.yaml` configuration as a Nix attribute set. + Configuration options should match those described in + [example-config.yaml](https://github.com/mautrix/signal/blob/master/signal/example-config.yaml). + + Secret tokens should be specified using {option}`environmentFile` + instead of this world-readable attribute set. + ''; + }; + + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + description = lib.mdDoc '' + File containing environment variables to be passed to the mautrix-signal service, + in which secret tokens can be specified securely by defining values for e.g. + `MAUTRIX_SIGNAL_APPSERVICE_AS_TOKEN`, + `MAUTRIX_SIGNAL_APPSERVICE_HS_TOKEN`, + + These environment variables can also be used to set other options by + replacing hierarchy levels by `.`, converting the name to uppercase + and prepending `MAUTRIX_SIGNAL_`. + For example, the first value above maps to + {option}`settings.appservice.as_token`. + + The environment variable values can be prefixed with `json::` to have + them be parsed as JSON. For example, `login_shared_secret_map` can be + set as follows: + `MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET_MAP=json::{"example.com":"secret"}`. + ''; + }; + + serviceDependencies = mkOption { + type = with types; listOf str; + default = optional config.services.matrix-synapse.enable "matrix-synapse.service"; + defaultText = literalExpression '' + optional config.services.matrix-synapse.enable "matrix-synapse.service" + ''; + description = lib.mdDoc '' + List of Systemd services to require and wait for when starting the application service. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + services.signald.enable = true; + + systemd.services.mautrix-signal = { + description = "Mautrix-Signal, a Matrix-Signal puppeting bridge."; + + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ] ++ cfg.serviceDependencies; + after = [ "network-online.target" ] ++ cfg.serviceDependencies; + path = [ pkgs.lottieconverter pkgs.ffmpeg-full ]; + + # TODO(2023-01-11): Still relevant in Go version? + # mautrix-signal tries to generate a dotfile in the home directory of + # the running user if using a postgresql database: + # + # File "python3.10/site-packages/asyncpg/connect_utils.py", line 257, in _dot_postgre> + # return (pathlib.Path.home() / '.postgresql' / filename).resolve() + # File "python3.10/pathlib.py", line 1000, in home + # return cls("~").expanduser() + # File "python3.10/pathlib.py", line 1440, in expanduser + # raise RuntimeError("Could not determine home directory.") + # RuntimeError: Could not determine home directory. + environment.HOME = dataDir; + + preStart = '' + # substitute the settings file by environment variables + # in this case read from EnvironmentFile + test -f '${settingsFile}' && rm -f '${settingsFile}' + old_umask=$(umask) + umask 0177 + ${pkgs.envsubst}/bin/envsubst \ + -o '${settingsFile}' \ + -i '${settingsFileUnsubstituted}' \ + umask $old_umask + + # generate the appservice's registration file if absent + if [ ! -f '${registrationFile}' ]; then + ${cfg.package}/bin/mautrix-signal \ + --generate-registration \ + --config='${settingsFile}' \ + --registration='${registrationFile}' + fi + chmod 640 ${registrationFile} + + umask 0177 + ${pkgs.yq}/bin/yq -s '.[0].appservice.as_token = .[1].as_token + | .[0].appservice.hs_token = .[1].hs_token + | .[0]' '${settingsFile}' '${registrationFile}' \ + > '${settingsFile}.tmp' + mv '${settingsFile}.tmp' '${settingsFile}' + umask $old_umask + ''; + + serviceConfig = { + Type = "simple"; + Restart = "always"; + + ProtectSystem = "strict"; + ProtectHome = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + + DynamicUser = true; + SupplementaryGroups = [ "signald" ]; + PrivateTmp = true; + WorkingDirectory = cfg.package; # necessary for the database migration scripts to be found + StateDirectory = baseNameOf dataDir; + UMask = "0027"; + EnvironmentFile = cfg.environmentFile; + + ExecStart = '' + ${cfg.package}/bin/mautrix-signal \ + --config='${settingsFile}' + ''; + + restartTriggers = [settingsFileUnsubstituted]; + }; + }; + }; + + # meta.maintainers = with maintainers; [ boppyt ]; +} diff --git a/modules/services/matrix-bridge/default.nix b/modules/services/matrix-bridge/default.nix new file mode 100644 index 0000000..4d53223 --- /dev/null +++ b/modules/services/matrix-bridge/default.nix @@ -0,0 +1,390 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.modules.services.matrix-bridge; +in +{ + imports = [ + ./_mautrix-signal-module.nix + ./_mautrix-discord-module.nix + ]; + + options.modules.services.matrix-bridge = { + enable = mkEnableOption "matrix-bridge"; + domain = mkOption { type = types.str; }; + realHost = mkOption { type = types.str; default = "matrix.${cfg.domain}"; }; + secrets.mautrix-envs = mkOption { type = types.path; description = "path to the mautrix-* environment file"; }; + }; + + config = mkIf cfg.enable { + sops.secrets.double-puppet-as-token = { }; + sops.secrets.double-puppet-hs-token = { }; + + sops.templates."double-puppet-registration.yaml".content = '' + id: doublepuppet + url: + as_token: ${config.sops.placeholder.double-puppet-as-token} + hs_token: ${config.sops.placeholder.double-puppet-hs-token} + sender_localpart: 55e126746dad19e50d9c4e646b6f5ac9ba21b346a24b840330cd8d8a1d65ce80 + rate_limited: false + namespaces: + users: + - regex: '@.*:exotic\.sh' + exclusive: false + ''; + + services.mautrix-telegram = { + enable = true; + + environmentFile = cfg.secrets.mautrix-envs; + serviceDependencies = [ "matrix-synapse.service" ]; + + settings = { + homeserver.address = "https://${cfg.realHost}"; + homeserver.domain = cfg.domain; + homeserver.verify_ssl = true; + appservice = { + address = "http://localhost:29317"; + hostname = "localhost"; + port = 29317; + database = "postgres:///mautrix-telegram?host=/run/postgresql"; + bot_avatar = "mxc://maunium.net/tJCRmUyJDsgRNgqhOgoiHWbX"; + id = "telegram"; + max_body_size = 1; + provisioning.enabled = false; + }; + bridge = { + alias_template = "tg_{groupname}"; + username_templace = "tg_{userid}"; + allow_matrix_login = true; + bot_messages_as_notices = true; + catch_up = true; + plaintext_highlights = true; + startup_sync = true; + animated_stickers = { + target = "webp"; + convert_from_webm = true; + }; + encryption = { + allow = true; + default = true; + require = true; + allow_key_sharing = true; + }; + # NOTE: python bridge - managed via env variable + # login_shared_secret_map = { + # "${cfg.domain}" = "as_token:$DOUBLE_PUPPET_AS_TOKEN"; + # }; + permissions = { + "@sef:exotic.sh" = "admin"; + "exotic.sh" = "full"; + }; + }; + }; + }; + + services.mautrix-signal = { + enable = true; + package = pkgs.mautrix-signal; + + environmentFile = cfg.secrets.mautrix-envs; + serviceDependencies = [ "matrix-synapse.service" ]; + + settings = { + homeserver.address = "https://${cfg.realHost}"; + homeserver.domain = cfg.domain; + homeserver.verify_ssl = true; + appservice = { + address = "http://localhost:29318"; + port = 29318; + database = "postgres:///mautrix-signal?host=/run/postgresql"; + bot_avatar = "mxc://maunium.net/wPJgTQbZOtpBFmDNkiNEMDUp"; + id = "signal"; + max_body_size = 1; + provisioning.enabled = false; + }; + + signal = { + avatar_dir = "/var/lib/signald/avatars"; + data_dir = "/var/lib/signald/data"; + }; + + bridge = { + alias_template = "sig_{groupname}"; + username_templace = "sig_{userid}"; + allow_matrix_login = true; + catch_up = true; + plaintext_highlights = true; + startup_sync = true; + animated_stickers = { + target = "webp"; + convert_from_webm = true; + }; + encryption = { + allow = true; + default = true; + require = true; + allow_key_sharing = true; + }; + login_shared_secret_map = { + "${cfg.domain}" = "as_token:$DOUBLE_PUPPET_AS_TOKEN"; + }; + permissions = { + "@sef:exotic.sh" = "admin"; + "exotic.sh" = "full"; + }; + }; + }; + }; + + services.mautrix-whatsapp = { + enable = true; + environmentFile = cfg.secrets.mautrix-envs; + serviceDependencies = [ "matrix-synapse.service" ]; + + settings = { + homeserver.address = "https://${cfg.realHost}"; + homeserver.domain = cfg.domain; + homeserver.verify_ssl = true; + appservice = { + address = "http://localhost:29319"; + hostname = "localhost"; + port = 29319; + database = { + type = "postgres"; + uri = "postgres://mautrix-whatsapp:@/mautrix-whatsapp?host=/run/postgresql"; + }; + bot_avatar = "mxc://maunium.net/NeXNQarUbrlYBiPCpprYsRqr"; + id = "whatsapp"; + max_body_size = 1; + provisioning.enabled = false; + }; + + bridge = { + alias_template = "wa_{groupname}"; + username_templace = "wa_{userid}"; + personal_filtering_spaces = true; + delivery_receipts = true; + identity_change_notices = true; + history_sync = { + backfill = false; # MSC2716 + request_full_sync = true; + }; + send_presence_on_typing = true; + double_puppet_server_map = { }; + login_shared_secret_map = { + "${cfg.domain}" = "as_token:$DOUBLE_PUPPET_AS_TOKEN"; + }; + private_chat_portal_meta = true; + mute_bridging = true; + pinned_tag = "m.favourite"; + archive_tag = "m.lowpriority"; + allow_user_invite = true; + disappearing_messages_in_groups = true; + url_previews = true; + encryption = { + allow = true; + default = true; + require = true; + allow_key_sharing = true; + }; + sync_manual_marked_unread = true; + force_active_delivery_receipts = true; + parallel_member_sync = true; + extev_polls = true; + send_whatsapp_edits = true; + permissions = { + "@sef:exotic.sh" = "admin"; + "exotic.sh" = "full"; + }; + }; + }; + }; + + services.mautrix-discord = { + enable = true; + environmentFile = cfg.secrets.mautrix-envs; + serviceDependencies = [ "matrix-synapse.service" ]; + + settings = { + homeserver.address = "https://${cfg.realHost}"; + homeserver.domain = cfg.domain; + homeserver.verify_ssl = true; + appservice = { + address = "http://localhost:29320"; + port = 29320; + database = { + type = "postgres"; + uri = "postgres://mautrix-discord:@/mautrix-discord?host=/run/postgresql"; + }; + bot_avatar = "mxc://maunium.net/nIdEykemnwdisvHbpxflpDlC"; + id = "discord"; + max_body_size = 1; + provisioning.enabled = false; + }; + + bridge = { + username_template = "dsc_{{.}}"; + delivery_receipts = true; + encryption = { + allow = true; + default = true; + require = true; + allow_key_sharing = true; + }; + login_shared_secret_map = { + "${cfg.domain}" = "as_token:$DOUBLE_PUPPET_AS_TOKEN"; + }; + permissions = { + "@sef:exotic.sh" = "admin"; + "exotic.sh" = "full"; + }; + }; + }; + }; + + services.matrix-appservice-irc = { + enable = true; + registrationUrl = "http://localhost:29321"; + port = 29321; + + settings = { + homeserver.url = "https://${cfg.realHost}"; + homeserver.domain = cfg.domain; + homeserver.dropMatrixMessagesAfterSecs = 600; # 10 minutes + + database.engine = "postgres"; + database.connectionString = "postgres://matrix-appservice-irc:@/matrix-appservice-irc?host=/run/postgresql"; + + ircService.servers = let + # nix-community/nur-combined/repos/colinsane/hosts/by-name/servo/services/matrix/irc.nix@b2e96d5 + ircServer = { name, additionalAddresses ? [], sasl ? true, port ? 6697}: + let lowerName = lib.toLower name; + in { + inherit name additionalAddresses sasl port; + ssl = true; + # Disable bridging of Matrix bots + botConfig.enabled = false; + dynamicChannels = { + enabled = true; + aliasTemplate = "#irc_${lowerName}_$CHANNEL"; + published = false; + federate = false; + }; + ircClients = { + nickTemplate = "$DISPLAY[m]"; + allowNickChanges = true; + realNameFormat = "reverse-mxid"; + lineLimit = 20; + # Safeguard: don't flood servers + maxClients = 2; + idleTimeout = 0; + concurrentReconnectLimit = 2; + reconnectIntervalMs = 60000; + kickOn = { + # only kick Matrix user from room when user quits + channelJoinFailure = false; + ircConnectionFailure = false; + userQuit = true; + }; + }; + matrixClients.userTemplate = "@irc_${lowerName}_$NICK"; + + "@sef:exotic.sh" = "admin"; + + memberShipLists = { + enabled = true; + # NOTE: when serving lots of Matrix users, these configs should + # be changed to reduce strain on IRC servers + global = { + ircToMatrix = { + initial = true; + incremental = true; + requireMatrixJoined = false; + }; + matrixToIrc = { + initial = true; + incremental = true; + }; + # always bridge users, even if idle + ignoreIdleUsersOnStartup.enabled = false; + }; + bridgeInfoState = { + enabled = true; + initial = true; + }; + }; + }; + in { + "irc.libera.chat" = ircServer { + name = "libera"; + # sasl = false; + }; + "irc.oftc.net" = ircServer { + name = "oftc"; + # sasl = false; + }; + }; + }; + }; + + # HACK: https://github.com/NixOS/nixpkgs/issues/273929 + systemd.services.matrix-appservice-irc.serviceConfig.SystemCallFilter = lib.mkForce '' + @system-service @pkey ~@privileged @resources @chown + ''; + + + modules.persistence.directories = [ + "/var/lib/private/mautrix-telegram" + "/var/lib/private/mautrix-signal" + "/var/lib/private/mautrix-whatsapp" + "/var/lib/private/mautrix-discord" + "/var/lib/matrix-appservice-irc" + "/var/lib/signald" + ]; + + modules.services.postgresql.enable = true; + services.postgresql.ensureDatabases = [ "mautrix-telegram" "mautrix-signal" "mautrix-whatsapp" "mautrix-discord" "matrix-appservice-irc" ]; + services.postgresql.ensureUsers = [ + { + name = "mautrix-telegram"; + ensureDBOwnership = true; + } + { + name = "mautrix-signal"; + ensureDBOwnership = true; + } + { + name = "mautrix-whatsapp"; + ensureDBOwnership = true; + } + { + name = "mautrix-discord"; + ensureDBOwnership = true; + } + { + name = "matrix-appservice-irc"; + ensureDBOwnership = true; + } + ]; + + systemd.services.matrix-synapse.serviceConfig.LoadCredential = [ + "mautrix-telegram:/var/lib/mautrix-telegram/telegram-registration.yaml" + "mautrix-signal:/var/lib/mautrix-signal/signal-registration.yaml" + "mautrix-whatsapp:/var/lib/mautrix-whatsapp/whatsapp-registration.yaml" + "mautrix-discord:/var/lib/mautrix-discord/discord-registration.yaml" + "double-puppet:${config.sops.templates."double-puppet-registration.yaml".path}" + "appservice-irc:/var/lib/matrix-appservice-irc/registration.yml" + ]; + + services.matrix-synapse.settings.app_service_config_files = [ + "/run/credentials/matrix-synapse.service/mautrix-telegram" + "/run/credentials/matrix-synapse.service/mautrix-signal" + "/run/credentials/matrix-synapse.service/mautrix-whatsapp" + "/run/credentials/matrix-synapse.service/mautrix-discord" + "/run/credentials/matrix-synapse.service/double-puppet" + "/run/credentials/matrix-synapse.service/appservice-irc" + ]; + }; +} diff --git a/modules/services/soju.nix b/modules/services/soju.nix deleted file mode 100644 index b2f4faf..0000000 --- a/modules/services/soju.nix +++ /dev/null @@ -1,48 +0,0 @@ -{ config, lib, ... }: - -with lib; -let - cfg = config.modules.services.soju; -in -{ - disabledModules = [ - "services/networking/soju.nix" - ]; - - imports = [ - ../../overlays/soju-module.nix - ]; - - options.modules.services.soju = { - enable = mkEnableOption "soju bouncer"; - - hostName = mkOption { type = types.str; default = config.networking.hostName; }; - port = mkOption { type = types.port; default = 6697; }; - tls = { - enable = mkEnableOption "enable TLS encryption"; - acmeHost = mkOption { type = types.str; }; - }; - }; - - config = mkIf cfg.enable { - services.soju = { - enable = true; - extraGroups = [ "acme" ]; - hostName = cfg.hostName; - listen = [ ":${toString cfg.port}" ]; - } // optionalAttrs cfg.tls.enable { - tlsCertificate = "${config.security.acme.certs.${cfg.tls.acmeHost}.directory}/cert.pem"; - tlsCertificateKey = "${config.security.acme.certs.${cfg.tls.acmeHost}.directory}/key.pem"; - }; - - systemd.services.soju = { - after = [ "acme-finished-${cfg.tls.acmeHost}.target" ]; - }; - - networking.firewall.allowedTCPPorts = [ cfg.port ]; - - modules.persistence.directories = [ - "/var/lib/private/soju" - ]; - }; -} diff --git a/modules/services/soju/_soju-module.nix b/modules/services/soju/_soju-module.nix new file mode 100644 index 0000000..d14082c --- /dev/null +++ b/modules/services/soju/_soju-module.nix @@ -0,0 +1,132 @@ +# Not an overlay, module replacement +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.soju; + stateDir = "/var/lib/soju"; + listenCfg = concatMapStringsSep "\n" (l: "listen ${l}") cfg.listen; + tlsCfg = optionalString (cfg.tlsCertificate != null) + "tls ${cfg.tlsCertificate} ${cfg.tlsCertificateKey}"; + logCfg = optionalString cfg.enableMessageLogging + "log fs ${stateDir}/logs"; + + configFile = pkgs.writeText "soju.conf" '' + ${listenCfg} + hostname ${cfg.hostName} + ${tlsCfg} + db sqlite3 ${stateDir}/soju.db + ${logCfg} + http-origin ${concatStringsSep " " cfg.httpOrigins} + accept-proxy-ip ${concatStringsSep " " cfg.acceptProxyIP} + + ${cfg.extraConfig} + ''; +in +{ + ###### interface + + options.services.soju = { + enable = mkEnableOption (lib.mdDoc "soju"); + + listen = mkOption { + type = types.listOf types.str; + default = [ ":6697" ]; + description = lib.mdDoc '' + Where soju should listen for incoming connections. See the + `listen` directive in + {manpage}`soju(1)`. + ''; + }; + + hostName = mkOption { + type = types.str; + default = config.networking.hostName; + defaultText = literalExpression "config.networking.hostName"; + description = lib.mdDoc "Server hostname."; + }; + + tlsCertificate = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/host.cert"; + description = lib.mdDoc "Path to server TLS certificate."; + }; + + tlsCertificateKey = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/host.key"; + description = lib.mdDoc "Path to server TLS certificate key."; + }; + + enableMessageLogging = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc "Whether to enable message logging."; + }; + + httpOrigins = mkOption { + type = types.listOf types.str; + default = [ ]; + description = lib.mdDoc '' + List of allowed HTTP origins for WebSocket listeners. The parameters are + interpreted as shell patterns, see + {manpage}`glob(7)`. + ''; + }; + + acceptProxyIP = mkOption { + type = types.listOf types.str; + default = [ ]; + description = lib.mdDoc '' + Allow the specified IPs to act as a proxy. Proxys have the ability to + overwrite the remote and local connection addresses (via the X-Forwarded-\* + HTTP header fields). The special name "localhost" accepts the loopback + addresses 127.0.0.0/8 and ::1/128. By default, all IPs are rejected. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = lib.mdDoc "Lines added verbatim to the configuration file."; + }; + + extraGroups = mkOption { + type = types.listOf types.str; + default = [ ]; + description = lib.mdDoc "Extra groups for the dynamic user."; + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + assertions = [ + { + assertion = (cfg.tlsCertificate != null) == (cfg.tlsCertificateKey != null); + message = '' + services.soju.tlsCertificate and services.soju.tlsCertificateKey + must both be specified to enable TLS. + ''; + } + ]; + + systemd.services.soju = { + description = "soju IRC bouncer"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + serviceConfig = { + DynamicUser = true; + SupplementaryGroups = cfg.extraGroups; + Restart = "always"; + ExecStart = "${pkgs.soju}/bin/soju -config ${configFile}"; + StateDirectory = "soju"; + }; + }; + }; + + meta.maintainers = with maintainers; [ malvo ]; +} diff --git a/modules/services/soju/default.nix b/modules/services/soju/default.nix new file mode 100644 index 0000000..557222e --- /dev/null +++ b/modules/services/soju/default.nix @@ -0,0 +1,48 @@ +{ config, lib, ... }: + +with lib; +let + cfg = config.modules.services.soju; +in +{ + disabledModules = [ + "services/networking/soju.nix" + ]; + + imports = [ + ./_soju-module.nix + ]; + + options.modules.services.soju = { + enable = mkEnableOption "soju bouncer"; + + hostName = mkOption { type = types.str; default = config.networking.hostName; }; + port = mkOption { type = types.port; default = 6697; }; + tls = { + enable = mkEnableOption "enable TLS encryption"; + acmeHost = mkOption { type = types.str; }; + }; + }; + + config = mkIf cfg.enable { + services.soju = { + enable = true; + extraGroups = [ "acme" ]; + hostName = cfg.hostName; + listen = [ ":${toString cfg.port}" ]; + } // optionalAttrs cfg.tls.enable { + tlsCertificate = "${config.security.acme.certs.${cfg.tls.acmeHost}.directory}/cert.pem"; + tlsCertificateKey = "${config.security.acme.certs.${cfg.tls.acmeHost}.directory}/key.pem"; + }; + + systemd.services.soju = { + after = [ "acme-finished-${cfg.tls.acmeHost}.target" ]; + }; + + networking.firewall.allowedTCPPorts = [ cfg.port ]; + + modules.persistence.directories = [ + "/var/lib/private/soju" + ]; + }; +} diff --git a/overlays/git-daemon-module.nix b/overlays/git-daemon-module.nix deleted file mode 100644 index 76b395e..0000000 --- a/overlays/git-daemon-module.nix +++ /dev/null @@ -1,137 +0,0 @@ -{ config, lib, pkgs, ... }: -with lib; -let - - cfg = config.services.gitDaemon; - -in -{ - - ###### interface - - options = { - services.gitDaemon = { - - enable = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Enable Git daemon, which allows public hosting of git repositories - without any access controls. This is mostly intended for read-only access. - - You can allow write access by setting daemon.receivepack configuration - item of the repository to true. This is solely meant for a closed LAN setting - where everybody is friendly. - - If you need any access controls, use something else. - ''; - }; - - basePath = mkOption { - type = types.str; - default = ""; - example = "/srv/git/"; - description = lib.mdDoc '' - Remap all the path requests as relative to the given path. For example, - if you set base-path to /srv/git, then if you later try to pull - git://example.com/hello.git, Git daemon will interpret the path as /srv/git/hello.git. - ''; - }; - - exportAll = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Publish all directories that look like Git repositories (have the objects - and refs subdirectories), even if they do not have the git-daemon-export-ok file. - - If disabled, you need to touch .git/git-daemon-export-ok in each repository - you want the daemon to publish. - - Warning: enabling this without a repository whitelist or basePath - publishes every git repository you have. - ''; - }; - - repositories = mkOption { - type = types.listOf types.str; - default = [ ]; - example = [ "/srv/git" "/home/user/git/repo2" ]; - description = lib.mdDoc '' - A whitelist of paths of git repositories, or directories containing repositories - all of which would be published. Paths must not end in "/". - - Warning: leaving this empty and enabling exportAll publishes all - repositories in your filesystem or basePath if specified. - ''; - }; - - listenAddress = mkOption { - type = types.str; - default = ""; - example = "example.com"; - description = lib.mdDoc "Listen on a specific IP address or hostname."; - }; - - port = mkOption { - type = types.port; - default = 9418; - description = lib.mdDoc "Port to listen on."; - }; - - options = mkOption { - type = types.str; - default = ""; - description = lib.mdDoc "Extra configuration options to be passed to Git daemon."; - }; - - user = mkOption { - type = types.str; - default = "git"; - description = lib.mdDoc "User under which Git daemon would be running."; - }; - - group = mkOption { - type = types.str; - default = "git"; - description = lib.mdDoc "Group under which Git daemon would be running."; - }; - - createUserAndGroup = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Create the specified group and user. - Disable this option if you want to use the existing user - ''; - }; - }; - }; - - ###### implementation - - config = mkIf cfg.enable { - - users.users.${cfg.user} = optionalAttrs (cfg.createUserAndGroup == true) { - uid = config.ids.uids.git; - group = cfg.group; - description = "Git daemon user"; - }; - - users.groups.${cfg.group} = optionalAttrs (cfg.createUserAndGroup == true) { - gid = config.ids.gids.git; - }; - - systemd.services.git-daemon = { - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - script = "${pkgs.git}/bin/git daemon --reuseaddr " - + (optionalString (cfg.basePath != "") "--base-path=${cfg.basePath} ") - + (optionalString (cfg.listenAddress != "") "--listen=${cfg.listenAddress} ") - + "--port=${toString cfg.port} --user=${cfg.user} --group=${cfg.group} ${cfg.options} " - + "--verbose " + (optionalString cfg.exportAll "--export-all ") + concatStringsSep " " cfg.repositories; - }; - - }; - -} diff --git a/overlays/mautrix-discord-module.nix b/overlays/mautrix-discord-module.nix deleted file mode 100644 index 36cafe6..0000000 --- a/overlays/mautrix-discord-module.nix +++ /dev/null @@ -1,205 +0,0 @@ -{ config, pkgs, lib, ... }: - -with lib; - -let - dataDir = "/var/lib/mautrix-discord"; - registrationFile = "${dataDir}/discord-registration.yaml"; - cfg = config.services.mautrix-discord; - settingsFormat = pkgs.formats.json { }; - settingsFile = "${dataDir}/config.json"; - settingsFileUnsubstituted = settingsFormat.generate "mautrix-discord-config.json" cfg.settings; - -in -{ - options = { - services.mautrix-discord = { - enable = mkEnableOption (lib.mdDoc "Mautrix-discord, a Matrix-discord puppeting bridge."); - - package = mkOption { type = types.package; default = pkgs.mautrix-discord; }; - - settings = mkOption rec { - apply = recursiveUpdate default; - inherit (settingsFormat) type; - default = { - homeserver = { - software = "standard"; - }; - - appservice = rec { - database = "sqlite:///${dataDir}/mautrix-discord.db"; - database_opts = { }; - hostname = "localhost"; - port = 8080; - address = "http://localhost:${toString port}"; - }; - - bridge = { - permissions."*" = "relay"; - relay.whitelist = [ ]; - double_puppet_server_map = { }; - login_shared_secret_map = { }; - }; - - logging = { - min_level = "debug"; - writers = [ - { - type = "stdout"; - format = "pretty-colored"; - } - ]; - }; - }; - example = literalExpression '' - { - homeserver = { - address = "http://localhost:8008"; - domain = "public-domain.tld"; - }; - - appservice.public = { - prefix = "/public"; - external = "https://public-appservice-address/public"; - }; - - bridge.permissions = { - "example.com" = "full"; - "@admin:example.com" = "admin"; - }; - } - ''; - description = lib.mdDoc '' - {file}`config.yaml` configuration as a Nix attribute set. - Configuration options should match those described in - [example-config.yaml](https://github.com/mautrix/discord/blob/master/discord/example-config.yaml). - - Secret tokens should be specified using {option}`environmentFile` - instead of this world-readable attribute set. - ''; - }; - - environmentFile = mkOption { - type = types.nullOr types.path; - default = null; - description = lib.mdDoc '' - File containing environment variables to be passed to the mautrix-discord service, - in which secret tokens can be specified securely by defining values for e.g. - `MAUTRIX_DISCORD_APPSERVICE_AS_TOKEN`, - `MAUTRIX_DISCORD_APPSERVICE_HS_TOKEN`, - - These environment variables can also be used to set other options by - replacing hierarchy levels by `.`, converting the name to uppercase - and prepending `MAUTRIX_DISCORD_`. - For example, the first value above maps to - {option}`settings.appservice.as_token`. - - The environment variable values can be prefixed with `json::` to have - them be parsed as JSON. For example, `login_shared_secret_map` can be - set as follows: - `MAUTRIX_DISCORD_BRIDGE_LOGIN_SHARED_SECRET_MAP=json::{"example.com":"secret"}`. - ''; - }; - - serviceDependencies = mkOption { - type = with types; listOf str; - default = optional config.services.matrix-synapse.enable "matrix-synapse.service"; - defaultText = literalExpression '' - optional config.services.matrix-synapse.enable "matrix-synapse.service" - ''; - description = lib.mdDoc '' - List of Systemd services to require and wait for when starting the application service. - ''; - }; - }; - }; - - config = mkIf cfg.enable { - users.users.mautrix-discord = { - isSystemUser = true; - group = "mautrix-discord"; - home = dataDir; - description = "Mautrix-Discord bridge user"; - }; - - users.groups.mautrix-discord = {}; - - systemd.services.mautrix-discord = { - description = "Mautrix-discord, a Matrix-discord puppeting bridge."; - - wantedBy = [ "multi-user.target" ]; - wants = [ "network-online.target" ] ++ cfg.serviceDependencies; - after = [ "network-online.target" ] ++ cfg.serviceDependencies; - - preStart = '' - # substitute the settings file by environment variables - # in this case read from EnvironmentFile - test -f '${settingsFile}' && rm -f '${settingsFile}' - old_umask=$(umask) - umask 0177 - ${pkgs.envsubst}/bin/envsubst \ - -o '${settingsFile}' \ - -i '${settingsFileUnsubstituted}' \ - umask $old_umask - - # generate the appservice's registration file if absent - if [ ! -f '${registrationFile}' ]; then - ${pkgs.mautrix-discord}/bin/mautrix-discord \ - --generate-registration \ - --config='${settingsFile}' \ - --registration='${registrationFile}' - fi - chmod 640 ${registrationFile} - - umask 0177 - ${pkgs.yq}/bin/yq -s '.[0].appservice.as_token = .[1].as_token - | .[0].appservice.hs_token = .[1].hs_token - | .[0]' '${settingsFile}' '${registrationFile}' \ - > '${settingsFile}.tmp' - mv '${settingsFile}.tmp' '${settingsFile}' - umask $old_umask - ''; - - serviceConfig = { - Type = "simple"; - Restart = "on-failure"; - - LockPersonality = true; - MemoryDenyWriteExecute = true; - NoNewPrivileges = true; - ProtectSystem = "strict"; - ProtectHome = true; - ProtectKernelTunables = true; - ProtectKernelModules = true; - ProtectKernelLogs = true; - ProtectControlGroups = true; - ProtectClock = true; - ProtectHostname = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - - SystemCallArchitectures = "native"; - SystemCallErrorNumber = "EPERM"; - SystemCallFilter = ["@system-service"]; - - PrivateTmp = true; - PrivateDevices = true; - PrivateUsers = true; - - User = "mautrix-discord"; - Group = "mautrix-discord"; - WorkingDirectory = dataDir; - StateDirectory = baseNameOf dataDir; - UMask = "0027"; - EnvironmentFile = cfg.environmentFile; - - ExecStart = '' - ${cfg.package}/bin/mautrix-discord \ - --config='${settingsFile}' \ - --registration='${registrationFile}' - ''; - }; - restartTriggers = [settingsFileUnsubstituted]; - }; - }; -} diff --git a/overlays/mautrix-signal-module.nix b/overlays/mautrix-signal-module.nix deleted file mode 100644 index 983d635..0000000 --- a/overlays/mautrix-signal-module.nix +++ /dev/null @@ -1,204 +0,0 @@ -{ config, pkgs, lib, ... }: - -with lib; - -let - dataDir = "/var/lib/mautrix-signal"; - registrationFile = "${dataDir}/signal-registration.yaml"; - cfg = config.services.mautrix-signal; - settingsFormat = pkgs.formats.json { }; - settingsFile = "${dataDir}/config.json"; - settingsFileUnsubstituted = - settingsFormat.generate "mautrix-signal-config.json" cfg.settings; - -in -{ - # NOTE(2024-01-11): Upstream has been moved to a Go version. - # Environment-based credential setting might not work. - options = { - services.mautrix-signal = { - enable = mkEnableOption (lib.mdDoc "Mautrix-Signal, a Matrix-Signal puppeting bridge."); - - package = mkOption { type = types.package; default = pkgs.mautrix-signal; }; - - settings = mkOption rec { - apply = recursiveUpdate default; - inherit (settingsFormat) type; - default = { - homeserver = { - software = "standard"; - }; - - appservice = rec { - database = "sqlite:///${dataDir}/mautrix-signal.db"; - database_opts = { }; - hostname = "localhost"; - port = 8080; - address = "http://localhost:${toString port}"; - }; - - signal.socket_path = config.services.signald.socketPath; - - bridge = { - permissions."*" = "relay"; - relay.whitelist = [ ]; - double_puppet_server_map = { }; - login_shared_secret_map = { }; - }; - - logging = { - min_level = "debug"; - writers = [ - { - type = "stdout"; - format = "pretty-colored"; - } - ]; - }; - }; - example = literalExpression '' - { - homeserver = { - address = "http://localhost:8008"; - domain = "public-domain.tld"; - }; - - appservice.public = { - prefix = "/public"; - external = "https://public-appservice-address/public"; - }; - - bridge.permissions = { - "example.com" = "full"; - "@admin:example.com" = "admin"; - }; - } - ''; - description = lib.mdDoc '' - {file}`config.yaml` configuration as a Nix attribute set. - Configuration options should match those described in - [example-config.yaml](https://github.com/mautrix/signal/blob/master/signal/example-config.yaml). - - Secret tokens should be specified using {option}`environmentFile` - instead of this world-readable attribute set. - ''; - }; - - environmentFile = mkOption { - type = types.nullOr types.path; - default = null; - description = lib.mdDoc '' - File containing environment variables to be passed to the mautrix-signal service, - in which secret tokens can be specified securely by defining values for e.g. - `MAUTRIX_SIGNAL_APPSERVICE_AS_TOKEN`, - `MAUTRIX_SIGNAL_APPSERVICE_HS_TOKEN`, - - These environment variables can also be used to set other options by - replacing hierarchy levels by `.`, converting the name to uppercase - and prepending `MAUTRIX_SIGNAL_`. - For example, the first value above maps to - {option}`settings.appservice.as_token`. - - The environment variable values can be prefixed with `json::` to have - them be parsed as JSON. For example, `login_shared_secret_map` can be - set as follows: - `MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET_MAP=json::{"example.com":"secret"}`. - ''; - }; - - serviceDependencies = mkOption { - type = with types; listOf str; - default = optional config.services.matrix-synapse.enable "matrix-synapse.service"; - defaultText = literalExpression '' - optional config.services.matrix-synapse.enable "matrix-synapse.service" - ''; - description = lib.mdDoc '' - List of Systemd services to require and wait for when starting the application service. - ''; - }; - }; - }; - - config = mkIf cfg.enable { - services.signald.enable = true; - - systemd.services.mautrix-signal = { - description = "Mautrix-Signal, a Matrix-Signal puppeting bridge."; - - wantedBy = [ "multi-user.target" ]; - wants = [ "network-online.target" ] ++ cfg.serviceDependencies; - after = [ "network-online.target" ] ++ cfg.serviceDependencies; - path = [ pkgs.lottieconverter pkgs.ffmpeg-full ]; - - # TODO(2023-01-11): Still relevant in Go version? - # mautrix-signal tries to generate a dotfile in the home directory of - # the running user if using a postgresql database: - # - # File "python3.10/site-packages/asyncpg/connect_utils.py", line 257, in _dot_postgre> - # return (pathlib.Path.home() / '.postgresql' / filename).resolve() - # File "python3.10/pathlib.py", line 1000, in home - # return cls("~").expanduser() - # File "python3.10/pathlib.py", line 1440, in expanduser - # raise RuntimeError("Could not determine home directory.") - # RuntimeError: Could not determine home directory. - environment.HOME = dataDir; - - preStart = '' - # substitute the settings file by environment variables - # in this case read from EnvironmentFile - test -f '${settingsFile}' && rm -f '${settingsFile}' - old_umask=$(umask) - umask 0177 - ${pkgs.envsubst}/bin/envsubst \ - -o '${settingsFile}' \ - -i '${settingsFileUnsubstituted}' \ - umask $old_umask - - # generate the appservice's registration file if absent - if [ ! -f '${registrationFile}' ]; then - ${cfg.package}/bin/mautrix-signal \ - --generate-registration \ - --config='${settingsFile}' \ - --registration='${registrationFile}' - fi - chmod 640 ${registrationFile} - - umask 0177 - ${pkgs.yq}/bin/yq -s '.[0].appservice.as_token = .[1].as_token - | .[0].appservice.hs_token = .[1].hs_token - | .[0]' '${settingsFile}' '${registrationFile}' \ - > '${settingsFile}.tmp' - mv '${settingsFile}.tmp' '${settingsFile}' - umask $old_umask - ''; - - serviceConfig = { - Type = "simple"; - Restart = "always"; - - ProtectSystem = "strict"; - ProtectHome = true; - ProtectKernelTunables = true; - ProtectKernelModules = true; - ProtectControlGroups = true; - - DynamicUser = true; - SupplementaryGroups = [ "signald" ]; - PrivateTmp = true; - WorkingDirectory = cfg.package; # necessary for the database migration scripts to be found - StateDirectory = baseNameOf dataDir; - UMask = "0027"; - EnvironmentFile = cfg.environmentFile; - - ExecStart = '' - ${cfg.package}/bin/mautrix-signal \ - --config='${settingsFile}' - ''; - - restartTriggers = [settingsFileUnsubstituted]; - }; - }; - }; - - # meta.maintainers = with maintainers; [ boppyt ]; -} diff --git a/overlays/soju-module.nix b/overlays/soju-module.nix deleted file mode 100644 index d14082c..0000000 --- a/overlays/soju-module.nix +++ /dev/null @@ -1,132 +0,0 @@ -# Not an overlay, module replacement -{ config, lib, pkgs, ... }: - -with lib; - -let - cfg = config.services.soju; - stateDir = "/var/lib/soju"; - listenCfg = concatMapStringsSep "\n" (l: "listen ${l}") cfg.listen; - tlsCfg = optionalString (cfg.tlsCertificate != null) - "tls ${cfg.tlsCertificate} ${cfg.tlsCertificateKey}"; - logCfg = optionalString cfg.enableMessageLogging - "log fs ${stateDir}/logs"; - - configFile = pkgs.writeText "soju.conf" '' - ${listenCfg} - hostname ${cfg.hostName} - ${tlsCfg} - db sqlite3 ${stateDir}/soju.db - ${logCfg} - http-origin ${concatStringsSep " " cfg.httpOrigins} - accept-proxy-ip ${concatStringsSep " " cfg.acceptProxyIP} - - ${cfg.extraConfig} - ''; -in -{ - ###### interface - - options.services.soju = { - enable = mkEnableOption (lib.mdDoc "soju"); - - listen = mkOption { - type = types.listOf types.str; - default = [ ":6697" ]; - description = lib.mdDoc '' - Where soju should listen for incoming connections. See the - `listen` directive in - {manpage}`soju(1)`. - ''; - }; - - hostName = mkOption { - type = types.str; - default = config.networking.hostName; - defaultText = literalExpression "config.networking.hostName"; - description = lib.mdDoc "Server hostname."; - }; - - tlsCertificate = mkOption { - type = types.nullOr types.path; - default = null; - example = "/var/host.cert"; - description = lib.mdDoc "Path to server TLS certificate."; - }; - - tlsCertificateKey = mkOption { - type = types.nullOr types.path; - default = null; - example = "/var/host.key"; - description = lib.mdDoc "Path to server TLS certificate key."; - }; - - enableMessageLogging = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc "Whether to enable message logging."; - }; - - httpOrigins = mkOption { - type = types.listOf types.str; - default = [ ]; - description = lib.mdDoc '' - List of allowed HTTP origins for WebSocket listeners. The parameters are - interpreted as shell patterns, see - {manpage}`glob(7)`. - ''; - }; - - acceptProxyIP = mkOption { - type = types.listOf types.str; - default = [ ]; - description = lib.mdDoc '' - Allow the specified IPs to act as a proxy. Proxys have the ability to - overwrite the remote and local connection addresses (via the X-Forwarded-\* - HTTP header fields). The special name "localhost" accepts the loopback - addresses 127.0.0.0/8 and ::1/128. By default, all IPs are rejected. - ''; - }; - - extraConfig = mkOption { - type = types.lines; - default = ""; - description = lib.mdDoc "Lines added verbatim to the configuration file."; - }; - - extraGroups = mkOption { - type = types.listOf types.str; - default = [ ]; - description = lib.mdDoc "Extra groups for the dynamic user."; - }; - }; - - ###### implementation - - config = mkIf cfg.enable { - assertions = [ - { - assertion = (cfg.tlsCertificate != null) == (cfg.tlsCertificateKey != null); - message = '' - services.soju.tlsCertificate and services.soju.tlsCertificateKey - must both be specified to enable TLS. - ''; - } - ]; - - systemd.services.soju = { - description = "soju IRC bouncer"; - wantedBy = [ "multi-user.target" ]; - after = [ "network-online.target" ]; - serviceConfig = { - DynamicUser = true; - SupplementaryGroups = cfg.extraGroups; - Restart = "always"; - ExecStart = "${pkgs.soju}/bin/soju -config ${configFile}"; - StateDirectory = "soju"; - }; - }; - }; - - meta.maintainers = with maintainers; [ malvo ]; -} -- cgit 1.4.1