python312Packages.aiohomeconnect: 0.10.0 -> 0.11.0 (#374011)
[NixPkgs.git] / nixos / modules / services / networking / firefox-syncserver.nix
blob352faed492dc36a8c22ea9bbeb01c45856fbe142
2   config,
3   pkgs,
4   lib,
5   options,
6   ...
7 }:
9 let
10   cfg = config.services.firefox-syncserver;
11   opt = options.services.firefox-syncserver;
12   defaultDatabase = "firefox_syncserver";
13   defaultUser = "firefox-syncserver";
15   dbIsLocal = cfg.database.host == "localhost";
16   dbURL = "mysql://${cfg.database.user}@${cfg.database.host}/${cfg.database.name}";
18   format = pkgs.formats.toml { };
19   settings = {
20     human_logs = true;
21     syncstorage = {
22       database_url = dbURL;
23     };
24     tokenserver =
25       {
26         node_type = "mysql";
27         database_url = dbURL;
28         fxa_email_domain = "api.accounts.firefox.com";
29         fxa_oauth_server_url = "https://oauth.accounts.firefox.com/v1";
30         run_migrations = true;
31         # if JWK caching is not enabled the token server must verify tokens
32         # using the fxa api, on a thread pool with a static size.
33         additional_blocking_threads_for_fxa_requests = 10;
34       }
35       // lib.optionalAttrs cfg.singleNode.enable {
36         # Single-node mode is likely to be used on small instances with little
37         # capacity. The default value (0.1) can only ever release capacity when
38         # accounts are removed if the total capacity is 10 or larger to begin
39         # with.
40         # https://github.com/mozilla-services/syncstorage-rs/issues/1313#issuecomment-1145293375
41         node_capacity_release_rate = 1;
42       };
43   };
44   configFile = format.generate "syncstorage.toml" (lib.recursiveUpdate settings cfg.settings);
45   setupScript = pkgs.writeShellScript "firefox-syncserver-setup" ''
46     set -euo pipefail
47     shopt -s inherit_errexit
49     schema_configured() {
50       mysql ${cfg.database.name} -Ne 'SHOW TABLES' | grep -q services
51     }
53     update_config() {
54       mysql ${cfg.database.name} <<"EOF"
55         BEGIN;
57         INSERT INTO `services` (`id`, `service`, `pattern`)
58           VALUES (1, 'sync-1.5', '{node}/1.5/{uid}')
59           ON DUPLICATE KEY UPDATE service='sync-1.5', pattern='{node}/1.5/{uid}';
60         INSERT INTO `nodes` (`id`, `service`, `node`, `available`, `current_load`,
61                              `capacity`, `downed`, `backoff`)
62           VALUES (1, 1, '${cfg.singleNode.url}', ${toString cfg.singleNode.capacity},
63           0, ${toString cfg.singleNode.capacity}, 0, 0)
64           ON DUPLICATE KEY UPDATE node = '${cfg.singleNode.url}', capacity=${toString cfg.singleNode.capacity};
66         COMMIT;
67     EOF
68     }
71     for (( try = 0; try < 60; try++ )); do
72       if ! schema_configured; then
73         sleep 2
74       else
75         update_config
76         exit 0
77       fi
78     done
80     echo "Single-node setup failed"
81     exit 1
82   '';
86   options = {
87     services.firefox-syncserver = {
88       enable = lib.mkEnableOption ''
89         the Firefox Sync storage service.
91         Out of the box this will not be very useful unless you also configure at least
92         one service and one nodes by inserting them into the mysql database manually, e.g.
93         by running
95         ```
96           INSERT INTO `services` (`id`, `service`, `pattern`) VALUES ('1', 'sync-1.5', '{node}/1.5/{uid}');
97           INSERT INTO `nodes` (`id`, `service`, `node`, `available`, `current_load`,
98               `capacity`, `downed`, `backoff`)
99             VALUES ('1', '1', 'https://mydomain.tld', '1', '0', '10', '0', '0');
100         ```
102         {option}`${opt.singleNode.enable}` does this automatically when enabled
103       '';
105       package = lib.mkOption {
106         type = lib.types.package;
107         default = pkgs.syncstorage-rs;
108         defaultText = lib.literalExpression "pkgs.syncstorage-rs";
109         description = ''
110           Package to use.
111         '';
112       };
114       database.name = lib.mkOption {
115         # the mysql module does not allow `-quoting without resorting to shell
116         # escaping, so we restrict db names for forward compaitiblity should this
117         # behavior ever change.
118         type = lib.types.strMatching "[a-z_][a-z0-9_]*";
119         default = defaultDatabase;
120         description = ''
121           Database to use for storage. Will be created automatically if it does not exist
122           and `config.${opt.database.createLocally}` is set.
123         '';
124       };
126       database.user = lib.mkOption {
127         type = lib.types.str;
128         default = defaultUser;
129         description = ''
130           Username for database connections.
131         '';
132       };
134       database.host = lib.mkOption {
135         type = lib.types.str;
136         default = "localhost";
137         description = ''
138           Database host name. `localhost` is treated specially and inserts
139           systemd dependencies, other hostnames or IP addresses of the local machine do not.
140         '';
141       };
143       database.createLocally = lib.mkOption {
144         type = lib.types.bool;
145         default = true;
146         description = ''
147           Whether to create database and user on the local machine if they do not exist.
148           This includes enabling unix domain socket authentication for the configured user.
149         '';
150       };
152       logLevel = lib.mkOption {
153         type = lib.types.str;
154         default = "error";
155         description = ''
156           Log level to run with. This can be a simple log level like `error`
157           or `trace`, or a more complicated logging expression.
158         '';
159       };
161       secrets = lib.mkOption {
162         type = lib.types.path;
163         description = ''
164           A file containing the various secrets. Should be in the format expected by systemd's
165           `EnvironmentFile` directory. Two secrets are currently available:
166           `SYNC_MASTER_SECRET` and
167           `SYNC_TOKENSERVER__FXA_METRICS_HASH_SECRET`.
168         '';
169       };
171       singleNode = {
172         enable = lib.mkEnableOption "auto-configuration for a simple single-node setup";
174         enableTLS = lib.mkEnableOption "automatic TLS setup";
176         enableNginx = lib.mkEnableOption "nginx virtualhost definitions";
178         hostname = lib.mkOption {
179           type = lib.types.str;
180           description = ''
181             Host name to use for this service.
182           '';
183         };
185         capacity = lib.mkOption {
186           type = lib.types.ints.unsigned;
187           default = 10;
188           description = ''
189             How many sync accounts are allowed on this server. Setting this value
190             equal to or less than the number of currently active accounts will
191             effectively deny service to accounts not yet registered here.
192           '';
193         };
195         url = lib.mkOption {
196           type = lib.types.str;
197           default = "${if cfg.singleNode.enableTLS then "https" else "http"}://${cfg.singleNode.hostname}";
198           defaultText = lib.literalExpression ''
199             ''${if cfg.singleNode.enableTLS then "https" else "http"}://''${config.${opt.singleNode.hostname}}
200           '';
201           description = ''
202             URL of the host. If you are not using the automatic webserver proxy setup you will have
203             to change this setting or your sync server may not be functional.
204           '';
205         };
206       };
208       settings = lib.mkOption {
209         type = lib.types.submodule {
210           freeformType = format.type;
212           options = {
213             port = lib.mkOption {
214               type = lib.types.port;
215               default = 5000;
216               description = ''
217                 Port to bind to.
218               '';
219             };
221             tokenserver.enabled = lib.mkOption {
222               type = lib.types.bool;
223               default = true;
224               description = ''
225                 Whether to enable the token service as well.
226               '';
227             };
228           };
229         };
230         default = { };
231         description = ''
232           Settings for the sync server. These take priority over values computed
233           from NixOS options.
235           See the example config in
236           <https://github.com/mozilla-services/syncstorage-rs/blob/master/config/local.example.toml>
237           and the doc comments on the `Settings` structs in
238           <https://github.com/mozilla-services/syncstorage-rs/blob/master/syncstorage-settings/src/lib.rs>
239           and
240           <https://github.com/mozilla-services/syncstorage-rs/blob/master/tokenserver-settings/src/lib.rs>
241           for available options.
242         '';
243       };
244     };
245   };
247   config = lib.mkIf cfg.enable {
248     services.mysql = lib.mkIf cfg.database.createLocally {
249       enable = true;
250       ensureDatabases = [ cfg.database.name ];
251       ensureUsers = [
252         {
253           name = cfg.database.user;
254           ensurePermissions = {
255             "${cfg.database.name}.*" = "all privileges";
256           };
257         }
258       ];
259     };
261     systemd.services.firefox-syncserver = {
262       wantedBy = [ "multi-user.target" ];
263       requires = lib.mkIf dbIsLocal [ "mysql.service" ];
264       after = lib.mkIf dbIsLocal [ "mysql.service" ];
265       restartTriggers = lib.optional cfg.singleNode.enable setupScript;
266       environment.RUST_LOG = cfg.logLevel;
267       serviceConfig = {
268         User = defaultUser;
269         Group = defaultUser;
270         ExecStart = "${cfg.package}/bin/syncserver --config ${configFile}";
271         EnvironmentFile = lib.mkIf (cfg.secrets != null) "${cfg.secrets}";
273         # hardening
274         RemoveIPC = true;
275         CapabilityBoundingSet = [ "" ];
276         DynamicUser = true;
277         NoNewPrivileges = true;
278         PrivateDevices = true;
279         ProtectClock = true;
280         ProtectKernelLogs = true;
281         ProtectControlGroups = true;
282         ProtectKernelModules = true;
283         SystemCallArchitectures = "native";
284         # syncstorage-rs uses python-cffi internally, and python-cffi does not
285         # work with MemoryDenyWriteExecute=true
286         MemoryDenyWriteExecute = false;
287         RestrictNamespaces = true;
288         RestrictSUIDSGID = true;
289         ProtectHostname = true;
290         LockPersonality = true;
291         ProtectKernelTunables = true;
292         RestrictAddressFamilies = [
293           "AF_INET"
294           "AF_INET6"
295           "AF_UNIX"
296         ];
297         RestrictRealtime = true;
298         ProtectSystem = "strict";
299         ProtectProc = "invisible";
300         ProcSubset = "pid";
301         ProtectHome = true;
302         PrivateUsers = true;
303         PrivateTmp = true;
304         SystemCallFilter = [
305           "@system-service"
306           "~ @privileged @resources"
307         ];
308         UMask = "0077";
309       };
310     };
312     systemd.services.firefox-syncserver-setup = lib.mkIf cfg.singleNode.enable {
313       wantedBy = [ "firefox-syncserver.service" ];
314       requires = [ "firefox-syncserver.service" ] ++ lib.optional dbIsLocal "mysql.service";
315       after = [ "firefox-syncserver.service" ] ++ lib.optional dbIsLocal "mysql.service";
316       path = [ config.services.mysql.package ];
317       serviceConfig.ExecStart = [ "${setupScript}" ];
318     };
320     services.nginx.virtualHosts = lib.mkIf cfg.singleNode.enableNginx {
321       ${cfg.singleNode.hostname} = {
322         enableACME = cfg.singleNode.enableTLS;
323         forceSSL = cfg.singleNode.enableTLS;
324         locations."/" = {
325           proxyPass = "http://127.0.0.1:${toString cfg.settings.port}";
326           # We need to pass the Host header that matches the original Host header. Otherwise,
327           # Hawk authentication will fail (because it assumes that the client and server see
328           # the same value of the Host header).
329           recommendedProxySettings = true;
330         };
331       };
332     };
333   };
335   meta = {
336     maintainers = [ ];
337     doc = ./firefox-syncserver.md;
338   };