at 25.11-pre 11 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 options, 6 ... 7}: 8 9let 10 cfg = config.services.firefox-syncserver; 11 opt = options.services.firefox-syncserver; 12 defaultDatabase = "firefox_syncserver"; 13 defaultUser = "firefox-syncserver"; 14 15 dbIsLocal = cfg.database.host == "localhost"; 16 dbURL = "mysql://${cfg.database.user}@${cfg.database.host}/${cfg.database.name}"; 17 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 48 49 schema_configured() { 50 mysql ${cfg.database.name} -Ne 'SHOW TABLES' | grep -q services 51 } 52 53 update_config() { 54 mysql ${cfg.database.name} <<"EOF" 55 BEGIN; 56 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}; 65 66 COMMIT; 67 EOF 68 } 69 70 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 79 80 echo "Single-node setup failed" 81 exit 1 82 ''; 83in 84 85{ 86 options = { 87 services.firefox-syncserver = { 88 enable = lib.mkEnableOption '' 89 the Firefox Sync storage service. 90 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 94 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 ``` 101 102 {option}`${opt.singleNode.enable}` does this automatically when enabled 103 ''; 104 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 }; 113 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 }; 125 126 database.user = lib.mkOption { 127 type = lib.types.str; 128 default = defaultUser; 129 description = '' 130 Username for database connections. 131 ''; 132 }; 133 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 }; 142 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 }; 151 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 }; 160 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 }; 170 171 singleNode = { 172 enable = lib.mkEnableOption "auto-configuration for a simple single-node setup"; 173 174 enableTLS = lib.mkEnableOption "automatic TLS setup"; 175 176 enableNginx = lib.mkEnableOption "nginx virtualhost definitions"; 177 178 hostname = lib.mkOption { 179 type = lib.types.str; 180 description = '' 181 Host name to use for this service. 182 ''; 183 }; 184 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 }; 194 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 }; 207 208 settings = lib.mkOption { 209 type = lib.types.submodule { 210 freeformType = format.type; 211 212 options = { 213 port = lib.mkOption { 214 type = lib.types.port; 215 default = 5000; 216 description = '' 217 Port to bind to. 218 ''; 219 }; 220 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. 234 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 }; 246 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 }; 260 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}"; 272 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 }; 311 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 }; 319 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 }; 334 335 meta = { 336 maintainers = [ ]; 337 doc = ./firefox-syncserver.md; 338 }; 339}