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