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 { };
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;
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
40 # https://github.com/mozilla-services/syncstorage-rs/issues/1313#issuecomment-1145293375
41 node_capacity_release_rate = 1;
44 configFile = format.generate "syncstorage.toml" (lib.recursiveUpdate settings cfg.settings);
45 setupScript = pkgs.writeShellScript "firefox-syncserver-setup" ''
47 shopt -s inherit_errexit
50 mysql ${cfg.database.name} -Ne 'SHOW TABLES' | grep -q services
54 mysql ${cfg.database.name} <<"EOF"
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};
71 for (( try = 0; try < 60; try++ )); do
72 if ! schema_configured; then
80 echo "Single-node setup failed"
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.
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');
102 {option}`${opt.singleNode.enable}` does this automatically when enabled
105 package = lib.mkOption {
106 type = lib.types.package;
107 default = pkgs.syncstorage-rs;
108 defaultText = lib.literalExpression "pkgs.syncstorage-rs";
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;
121 Database to use for storage. Will be created automatically if it does not exist
122 and `config.${opt.database.createLocally}` is set.
126 database.user = lib.mkOption {
127 type = lib.types.str;
128 default = defaultUser;
130 Username for database connections.
134 database.host = lib.mkOption {
135 type = lib.types.str;
136 default = "localhost";
138 Database host name. `localhost` is treated specially and inserts
139 systemd dependencies, other hostnames or IP addresses of the local machine do not.
143 database.createLocally = lib.mkOption {
144 type = lib.types.bool;
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.
152 logLevel = lib.mkOption {
153 type = lib.types.str;
156 Log level to run with. This can be a simple log level like `error`
157 or `trace`, or a more complicated logging expression.
161 secrets = lib.mkOption {
162 type = lib.types.path;
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`.
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;
181 Host name to use for this service.
185 capacity = lib.mkOption {
186 type = lib.types.ints.unsigned;
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.
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}}
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.
208 settings = lib.mkOption {
209 type = lib.types.submodule {
210 freeformType = format.type;
213 port = lib.mkOption {
214 type = lib.types.port;
221 tokenserver.enabled = lib.mkOption {
222 type = lib.types.bool;
225 Whether to enable the token service as well.
232 Settings for the sync server. These take priority over values computed
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>
240 <https://github.com/mozilla-services/syncstorage-rs/blob/master/tokenserver-settings/src/lib.rs>
241 for available options.
247 config = lib.mkIf cfg.enable {
248 services.mysql = lib.mkIf cfg.database.createLocally {
250 ensureDatabases = [ cfg.database.name ];
253 name = cfg.database.user;
254 ensurePermissions = {
255 "${cfg.database.name}.*" = "all privileges";
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;
270 ExecStart = "${cfg.package}/bin/syncserver --config ${configFile}";
271 EnvironmentFile = lib.mkIf (cfg.secrets != null) "${cfg.secrets}";
275 CapabilityBoundingSet = [ "" ];
277 NoNewPrivileges = true;
278 PrivateDevices = 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 = [
297 RestrictRealtime = true;
298 ProtectSystem = "strict";
299 ProtectProc = "invisible";
306 "~ @privileged @resources"
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}" ];
320 services.nginx.virtualHosts = lib.mkIf cfg.singleNode.enableNginx {
321 ${cfg.singleNode.hostname} = {
322 enableACME = cfg.singleNode.enableTLS;
323 forceSSL = cfg.singleNode.enableTLS;
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;
337 doc = ./firefox-syncserver.md;