1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.firefox.syncserver;
7
8 defaultDbLocation = "/var/db/firefox-sync-server/firefox-sync-server.db";
9 defaultSqlUri = "sqlite:///${defaultDbLocation}";
10
11 syncServerIni = pkgs.writeText "syncserver.ini" ''
12 [DEFAULT]
13 overrides = ${cfg.privateConfig}
14
15 [server:main]
16 use = egg:Paste#http
17 host = ${cfg.listen.address}
18 port = ${toString cfg.listen.port}
19
20 [app:main]
21 use = egg:syncserver
22
23 [syncserver]
24 public_url = ${cfg.publicUrl}
25 ${optionalString (cfg.sqlUri != "") "sqluri = ${cfg.sqlUri}"}
26 allow_new_users = ${boolToString cfg.allowNewUsers}
27
28 [browserid]
29 backend = tokenserver.verifiers.LocalVerifier
30 audiences = ${removeSuffix "/" cfg.publicUrl}
31 '';
32
33in
34
35{
36 options = {
37 services.firefox.syncserver = {
38 enable = mkOption {
39 type = types.bool;
40 default = false;
41 description = ''
42 Whether to enable a Firefox Sync Server, this give the opportunity to
43 Firefox users to store all synchronized data on their own server. To use this
44 server, Firefox users should visit the <option>about:config</option>, and
45 replicate the following change
46
47 <screen>
48 services.sync.tokenServerURI: http://localhost:5000/token/1.0/sync/1.5
49 </screen>
50
51 where <option>http://localhost:5000/</option> corresponds to the
52 public url of the server.
53 '';
54 };
55
56 listen.address = mkOption {
57 type = types.str;
58 default = "127.0.0.1";
59 example = "0.0.0.0";
60 description = ''
61 Address on which the sync server listen to.
62 '';
63 };
64
65 listen.port = mkOption {
66 type = types.int;
67 default = 5000;
68 description = ''
69 Port on which the sync server listen to.
70 '';
71 };
72
73 user = mkOption {
74 type = types.str;
75 default = "syncserver";
76 description = "User account under which syncserver runs.";
77 };
78
79 group = mkOption {
80 type = types.str;
81 default = "syncserver";
82 description = "Group account under which syncserver runs.";
83 };
84
85 publicUrl = mkOption {
86 type = types.str;
87 default = "http://localhost:5000/";
88 example = "http://sync.example.com/";
89 description = ''
90 Public URL with which firefox users can use to access the sync server.
91 '';
92 };
93
94 allowNewUsers = mkOption {
95 type = types.bool;
96 default = true;
97 description = ''
98 Whether to allow new-user signups on the server. Only request by
99 existing accounts will be honored.
100 '';
101 };
102
103 sqlUri = mkOption {
104 type = types.str;
105 default = defaultSqlUri;
106 example = "postgresql://scott:tiger@localhost/test";
107 description = ''
108 The location of the database. This URL is composed of
109 <option>dialect[+driver]://user:password@host/dbname[?key=value..]</option>,
110 where <option>dialect</option> is a database name such as
111 <option>mysql</option>, <option>oracle</option>, <option>postgresql</option>,
112 etc., and <option>driver</option> the name of a DBAPI, such as
113 <option>psycopg2</option>, <option>pyodbc</option>, <option>cx_oracle</option>,
114 etc. The <link
115 xlink:href="http://docs.sqlalchemy.org/en/rel_0_9/core/engines.html#database-urls">
116 SQLAlchemy documentation</link> provides more examples and describe the syntax of
117 the expected URL.
118 '';
119 };
120
121 privateConfig = mkOption {
122 type = types.str;
123 default = "/etc/firefox/syncserver-secret.ini";
124 description = ''
125 The private config file is used to extend the generated config with confidential
126 information, such as the <option>syncserver.sqlUri</option> setting if it contains a
127 password, and the <option>syncserver.secret</option> setting is used by the server to
128 generate cryptographically-signed authentication tokens.
129
130 If this file does not exists, then it is created with a generated
131 <option>syncserver.secret</option> settings.
132 '';
133 };
134 };
135 };
136
137 config = mkIf cfg.enable {
138
139 systemd.services.syncserver = let
140 syncServerEnv = pkgs.python.withPackages(ps: with ps; [ syncserver pasteScript ]);
141 in {
142 after = [ "network.target" ];
143 description = "Firefox Sync Server";
144 wantedBy = [ "multi-user.target" ];
145 path = [ pkgs.coreutils syncServerEnv ];
146
147 serviceConfig = {
148 User = cfg.user;
149 Group = cfg.group;
150 PermissionsStartOnly = true;
151 };
152
153 preStart = ''
154 if ! test -e ${cfg.privateConfig}; then
155 mkdir -m 700 -p $(dirname ${cfg.privateConfig})
156 echo > ${cfg.privateConfig} '[syncserver]'
157 echo >> ${cfg.privateConfig} "secret = $(head -c 20 /dev/urandom | sha1sum | tr -d ' -')"
158 fi
159 chown ${cfg.user}:${cfg.group} ${cfg.privateConfig}
160 '' + optionalString (cfg.sqlUri == defaultSqlUri) ''
161 if ! test -e $(dirname ${defaultDbLocation}); then
162 mkdir -m 700 -p $(dirname ${defaultDbLocation})
163 chown ${cfg.user}:${cfg.group} $(dirname ${defaultDbLocation})
164 fi
165 # Move previous database file if it exists
166 oldDb="/var/db/firefox-sync-server.db"
167 if test -f $oldDb; then
168 mv $oldDb ${defaultDbLocation}
169 chown ${cfg.user}:${cfg.group} ${defaultDbLocation}
170 fi
171 '';
172 serviceConfig.ExecStart = "${syncServerEnv}/bin/paster serve ${syncServerIni}";
173 };
174
175 users.extraUsers = optionalAttrs (cfg.user == "syncserver")
176 (singleton {
177 name = "syncserver";
178 group = cfg.group;
179 isSystemUser = true;
180 });
181
182 users.extraGroups = optionalAttrs (cfg.group == "syncserver")
183 (singleton {
184 name = "syncserver";
185 });
186 };
187}