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