python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / misc / heisenbridge.nix
blob13ba362b33dbb2e61325ff0f7b9278226276f7b4
1 { config, pkgs, lib, ... }:
3 with lib;
5 let
6   cfg = config.services.heisenbridge;
8   pkg = config.services.heisenbridge.package;
9   bin = "${pkg}/bin/heisenbridge";
11   jsonType = (pkgs.formats.json { }).type;
13   registrationFile = "/var/lib/heisenbridge/registration.yml";
14   # JSON is a proper subset of YAML
15   bridgeConfig = builtins.toFile "heisenbridge-registration.yml" (builtins.toJSON {
16     id = "heisenbridge";
17     url = cfg.registrationUrl;
18     # Don't specify as_token and hs_token
19     rate_limited = false;
20     sender_localpart = "heisenbridge";
21     namespaces = cfg.namespaces;
22   });
25   options.services.heisenbridge = {
26     enable = mkEnableOption (lib.mdDoc "the Matrix to IRC bridge");
28     package = mkOption {
29       type = types.package;
30       default = pkgs.heisenbridge;
31       defaultText = "pkgs.heisenbridge";
32       example = "pkgs.heisenbridge.override { … = …; }";
33       description = lib.mdDoc ''
34         Package of the application to run, exposed for overriding purposes.
35       '';
36     };
38     homeserver = mkOption {
39       type = types.str;
40       description = lib.mdDoc "The URL to the home server for client-server API calls";
41       example = "http://localhost:8008";
42     };
44     registrationUrl = mkOption {
45       type = types.str;
46       description = lib.mdDoc ''
47         The URL where the application service is listening for HS requests, from the Matrix HS perspective.#
48         The default value assumes the bridge runs on the same host as the home server, in the same network.
49       '';
50       example = "https://matrix.example.org";
51       default = "http://${cfg.address}:${toString cfg.port}";
52       defaultText = "http://$${cfg.address}:$${toString cfg.port}";
53     };
55     address = mkOption {
56       type = types.str;
57       description = lib.mdDoc "Address to listen on. IPv6 does not seem to be supported.";
58       default = "127.0.0.1";
59       example = "0.0.0.0";
60     };
62     port = mkOption {
63       type = types.port;
64       description = lib.mdDoc "The port to listen on";
65       default = 9898;
66     };
68     debug = mkOption {
69       type = types.bool;
70       description = lib.mdDoc "More verbose logging. Recommended during initial setup.";
71       default = false;
72     };
74     owner = mkOption {
75       type = types.nullOr types.str;
76       description = lib.mdDoc ''
77         Set owner MXID otherwise first talking local user will claim the bridge
78       '';
79       default = null;
80       example = "@admin:example.org";
81     };
83     namespaces = mkOption {
84       description = lib.mdDoc "Configure the 'namespaces' section of the registration.yml for the bridge and the server";
85       # TODO link to Matrix documentation of the format
86       type = types.submodule {
87         freeformType = jsonType;
88       };
90       default = {
91         users = [
92           {
93             regex = "@irc_.*";
94             exclusive = true;
95           }
96         ];
97         aliases = [ ];
98         rooms = [ ];
99       };
100     };
102     identd.enable = mkEnableOption (lib.mdDoc "identd service support");
103     identd.port = mkOption {
104       type = types.port;
105       description = lib.mdDoc "identd listen port";
106       default = 113;
107     };
109     extraArgs = mkOption {
110       type = types.listOf types.str;
111       description = lib.mdDoc "Heisenbridge is configured over the command line. Append extra arguments here";
112       default = [ ];
113     };
114   };
116   config = mkIf cfg.enable {
117     systemd.services.heisenbridge = {
118       description = "Matrix<->IRC bridge";
119       before = [ "matrix-synapse.service" ]; # So the registration file can be used by Synapse
120       wantedBy = [ "multi-user.target" ];
122       preStart = ''
123         umask 077
124         set -e -u -o pipefail
126         if ! [ -f "${registrationFile}" ]; then
127           # Generate registration file if not present (actually, we only care about the tokens in it)
128           ${bin} --generate --config ${registrationFile}
129         fi
131         # Overwrite the registration file with our generated one (the config may have changed since then),
132         # but keep the tokens. Two step procedure to be failure safe
133         ${pkgs.yq}/bin/yq --slurp \
134           '.[0] + (.[1] | {as_token, hs_token})' \
135           ${bridgeConfig} \
136           ${registrationFile} \
137           > ${registrationFile}.new
138         mv -f ${registrationFile}.new ${registrationFile}
140         # Grant Synapse access to the registration
141         if ${getBin pkgs.glibc}/bin/getent group matrix-synapse > /dev/null; then
142           chgrp -v matrix-synapse ${registrationFile}
143           chmod -v g+r ${registrationFile}
144         fi
145       '';
147       serviceConfig = rec {
148         Type = "simple";
149         ExecStart = lib.concatStringsSep " " (
150           [
151             bin
152             (if cfg.debug then "-vvv" else "-v")
153             "--config"
154             registrationFile
155             "--listen-address"
156             (lib.escapeShellArg cfg.address)
157             "--listen-port"
158             (toString cfg.port)
159           ]
160           ++ (lib.optionals (cfg.owner != null) [
161             "--owner"
162             (lib.escapeShellArg cfg.owner)
163           ])
164           ++ (lib.optionals cfg.identd.enable [
165             "--identd"
166             "--identd-port"
167             (toString cfg.identd.port)
168           ])
169           ++ [
170             (lib.escapeShellArg cfg.homeserver)
171           ]
172           ++ (map (lib.escapeShellArg) cfg.extraArgs)
173         );
175         # Hardening options
177         User = "heisenbridge";
178         Group = "heisenbridge";
179         RuntimeDirectory = "heisenbridge";
180         RuntimeDirectoryMode = "0700";
181         StateDirectory = "heisenbridge";
182         StateDirectoryMode = "0755";
184         ProtectSystem = "strict";
185         ProtectHome = true;
186         PrivateTmp = true;
187         PrivateDevices = true;
188         ProtectKernelTunables = true;
189         ProtectControlGroups = true;
190         RestrictSUIDSGID = true;
191         PrivateMounts = true;
192         ProtectKernelModules = true;
193         ProtectKernelLogs = true;
194         ProtectHostname = true;
195         ProtectClock = true;
196         ProtectProc = "invisible";
197         ProcSubset = "pid";
198         RestrictNamespaces = true;
199         RemoveIPC = true;
200         UMask = "0077";
202         CapabilityBoundingSet = [ "CAP_CHOWN" ] ++ optional (cfg.port < 1024 || (cfg.identd.enable && cfg.identd.port < 1024)) "CAP_NET_BIND_SERVICE";
203         AmbientCapabilities = CapabilityBoundingSet;
204         NoNewPrivileges = true;
205         LockPersonality = true;
206         RestrictRealtime = true;
207         SystemCallFilter = ["@system-service" "~@privileged" "@chown"];
208         SystemCallArchitectures = "native";
209         RestrictAddressFamilies = "AF_INET AF_INET6";
210       };
211     };
213     users.groups.heisenbridge = {};
214     users.users.heisenbridge = {
215       description = "Service user for the Heisenbridge";
216       group = "heisenbridge";
217       isSystemUser = true;
218     };
219   };
221   meta.maintainers = [ lib.maintainers.piegames ];