1{
2 config,
3 pkgs,
4 lib,
5 ...
6}:
7
8with lib;
9
10let
11 cfg = config.services.cloudlog;
12 dbFile =
13 let
14 password =
15 if cfg.database.createLocally then
16 "''"
17 else
18 "trim(file_get_contents('${cfg.database.passwordFile}'))";
19 in
20 pkgs.writeText "database.php" ''
21 <?php
22 defined('BASEPATH') OR exit('No direct script access allowed');
23 $active_group = 'default';
24 $query_builder = TRUE;
25 $db['default'] = array(
26 'dsn' => "",
27 'hostname' => '${cfg.database.host}',
28 'username' => '${cfg.database.user}',
29 'password' => ${password},
30 'database' => '${cfg.database.name}',
31 'dbdriver' => 'mysqli',
32 'dbprefix' => "",
33 'pconnect' => TRUE,
34 'db_debug' => (ENVIRONMENT !== 'production'),
35 'cache_on' => FALSE,
36 'cachedir' => "",
37 'char_set' => 'utf8mb4',
38 'dbcollat' => 'utf8mb4_general_ci',
39 'swap_pre' => "",
40 'encrypt' => FALSE,
41 'compress' => FALSE,
42 'stricton' => FALSE,
43 'failover' => array(),
44 'save_queries' => TRUE
45 );
46 '';
47 configFile = pkgs.writeText "config.php" ''
48 <?php
49 include('${pkgs.cloudlog}/install/config/config.php');
50 $config['datadir'] = "${cfg.dataDir}/";
51 $config['base_url'] = "${cfg.baseUrl}";
52 ${cfg.extraConfig}
53 '';
54 package = pkgs.stdenv.mkDerivation rec {
55 pname = "cloudlog";
56 version = src.version;
57 src = pkgs.cloudlog;
58 installPhase = ''
59 mkdir -p $out
60 cp -r * $out/
61
62 ln -s ${configFile} $out/application/config/config.php
63 ln -s ${dbFile} $out/application/config/database.php
64
65 # link writable directories
66 for directory in updates uploads backup logbook; do
67 rm -rf $out/$directory
68 ln -s ${cfg.dataDir}/$directory $out/$directory
69 done
70
71 # link writable asset files
72 for asset in dok sota wwff; do
73 rm -rf $out/assets/json/$asset.txt
74 ln -s ${cfg.dataDir}/assets/json/$asset.txt $out/assets/json/$asset.txt
75 done
76 '';
77 };
78in
79{
80 options.services.cloudlog = with types; {
81 enable = mkEnableOption "Cloudlog";
82 dataDir = mkOption {
83 type = str;
84 default = "/var/lib/cloudlog";
85 description = "Cloudlog data directory.";
86 };
87 baseUrl = mkOption {
88 type = str;
89 default = "http://localhost";
90 description = "Cloudlog base URL";
91 };
92 user = mkOption {
93 type = str;
94 default = "cloudlog";
95 description = "User account under which Cloudlog runs.";
96 };
97 database = {
98 createLocally = mkOption {
99 type = types.bool;
100 default = true;
101 description = "Create the database and database user locally.";
102 };
103 host = mkOption {
104 type = str;
105 description = "MySQL database host";
106 default = "localhost";
107 };
108 name = mkOption {
109 type = str;
110 description = "MySQL database name.";
111 default = "cloudlog";
112 };
113 user = mkOption {
114 type = str;
115 description = "MySQL user name.";
116 default = "cloudlog";
117 };
118 passwordFile = mkOption {
119 type = nullOr str;
120 description = "MySQL user password file.";
121 default = null;
122 };
123 };
124 poolConfig = mkOption {
125 type = attrsOf (oneOf [
126 str
127 int
128 bool
129 ]);
130 default = {
131 "pm" = "dynamic";
132 "pm.max_children" = 32;
133 "pm.start_servers" = 2;
134 "pm.min_spare_servers" = 2;
135 "pm.max_spare_servers" = 4;
136 "pm.max_requests" = 500;
137 };
138 description = ''
139 Options for Cloudlog's PHP-FPM pool.
140 '';
141 };
142 virtualHost = mkOption {
143 type = nullOr str;
144 default = "localhost";
145 description = ''
146 Name of the nginx virtualhost to use and setup. If null, do not setup
147 any virtualhost.
148 '';
149 };
150 extraConfig = mkOption {
151 description = ''
152 Any additional text to be appended to the config.php
153 configuration file. This is a PHP script. For configuration
154 settings, see <https://github.com/magicbug/Cloudlog/wiki/Cloudlog.php-Configuration-File>.
155 '';
156 default = "";
157 type = str;
158 example = ''
159 $config['show_time'] = TRUE;
160 '';
161 };
162 upload-lotw = {
163 enable = mkOption {
164 type = bool;
165 default = true;
166 description = ''
167 Whether to periodically upload logs to LoTW. If enabled, a systemd
168 timer will run the log upload task as specified by the interval
169 option.
170 '';
171 };
172 interval = mkOption {
173 type = str;
174 default = "daily";
175 description = ''
176 Specification (in the format described by {manpage}`systemd.time(7)`) of the
177 time at which the LoTW upload will occur.
178 '';
179 };
180 };
181 upload-clublog = {
182 enable = mkOption {
183 type = bool;
184 default = true;
185 description = ''
186 Whether to periodically upload logs to Clublog. If enabled, a systemd
187 timer will run the log upload task as specified by the interval option.
188 '';
189 };
190 interval = mkOption {
191 type = str;
192 default = "daily";
193 description = ''
194 Specification (in the format described by {manpage}`systemd.time(7)`) of the time
195 at which the Clublog upload will occur.
196 '';
197 };
198 };
199 update-lotw-users = {
200 enable = mkOption {
201 type = bool;
202 default = true;
203 description = ''
204 Whether to periodically update the list of LoTW users. If enabled, a
205 systemd timer will run the update task as specified by the interval
206 option.
207 '';
208 };
209 interval = mkOption {
210 type = str;
211 default = "weekly";
212 description = ''
213 Specification (in the format described by {manpage}`systemd.time(7)`) of the
214 time at which the LoTW user update will occur.
215 '';
216 };
217 };
218 update-dok = {
219 enable = mkOption {
220 type = bool;
221 default = true;
222 description = ''
223 Whether to periodically update the DOK resource file. If enabled, a
224 systemd timer will run the update task as specified by the interval option.
225 '';
226 };
227 interval = mkOption {
228 type = str;
229 default = "monthly";
230 description = ''
231 Specification (in the format described by {manpage}`systemd.time(7)`) of the
232 time at which the DOK update will occur.
233 '';
234 };
235 };
236 update-clublog-scp = {
237 enable = mkOption {
238 type = bool;
239 default = true;
240 description = ''
241 Whether to periodically update the Clublog SCP database. If enabled,
242 a systemd timer will run the update task as specified by the interval
243 option.
244 '';
245 };
246 interval = mkOption {
247 type = str;
248 default = "monthly";
249 description = ''
250 Specification (in the format described by {manpage}`systemd.time(7)`) of the time
251 at which the Clublog SCP update will occur.
252 '';
253 };
254 };
255 update-wwff = {
256 enable = mkOption {
257 type = bool;
258 default = true;
259 description = ''
260 Whether to periodically update the WWFF database. If enabled, a
261 systemd timer will run the update task as specified by the interval
262 option.
263 '';
264 };
265 interval = mkOption {
266 type = str;
267 default = "monthly";
268 description = ''
269 Specification (in the format described by {manpage}`systemd.time(7)`) of the time
270 at which the WWFF update will occur.
271 '';
272 };
273 };
274 upload-qrz = {
275 enable = mkOption {
276 type = bool;
277 default = true;
278 description = ''
279 Whether to periodically upload logs to QRZ. If enabled, a systemd
280 timer will run the update task as specified by the interval option.
281 '';
282 };
283 interval = mkOption {
284 type = str;
285 default = "daily";
286 description = ''
287 Specification (in the format described by {manpage}`systemd.time(7)`) of the
288 time at which the QRZ upload will occur.
289 '';
290 };
291 };
292 update-sota = {
293 enable = mkOption {
294 type = bool;
295 default = true;
296 description = ''
297 Whether to periodically update the SOTA database. If enabled, a
298 systemd timer will run the update task as specified by the interval option.
299 '';
300 };
301 interval = mkOption {
302 type = str;
303 default = "monthly";
304 description = ''
305 Specification (in the format described by {manpage}`systemd.time(7)`) of the time
306 at which the SOTA update will occur.
307 '';
308 };
309 };
310 };
311 config = mkIf cfg.enable {
312
313 assertions = [
314 {
315 assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
316 message = "services.cloudlog.database.passwordFile cannot be specified if services.cloudlog.database.createLocally is set to true.";
317 }
318 ];
319
320 services.phpfpm = {
321 pools.cloudlog = {
322 inherit (cfg) user;
323 group = config.services.nginx.group;
324 settings = {
325 "listen.owner" = config.services.nginx.user;
326 "listen.group" = config.services.nginx.group;
327 } // cfg.poolConfig;
328 };
329 };
330
331 services.nginx = mkIf (cfg.virtualHost != null) {
332 enable = true;
333 virtualHosts = {
334 "${cfg.virtualHost}" = {
335 root = "${package}";
336 locations."/".tryFiles = "$uri /index.php$is_args$args";
337 locations."~ ^/index.php(/|$)".extraConfig = ''
338 include ${config.services.nginx.package}/conf/fastcgi_params;
339 include ${pkgs.nginx}/conf/fastcgi.conf;
340 fastcgi_split_path_info ^(.+\.php)(.+)$;
341 fastcgi_pass unix:${config.services.phpfpm.pools.cloudlog.socket};
342 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
343 '';
344 };
345 };
346 };
347
348 services.mysql = mkIf cfg.database.createLocally {
349 enable = true;
350 ensureDatabases = [ cfg.database.name ];
351 ensureUsers = [
352 {
353 name = cfg.database.user;
354 ensurePermissions = {
355 "${cfg.database.name}.*" = "ALL PRIVILEGES";
356 };
357 }
358 ];
359 };
360
361 systemd = {
362 services = {
363 cloudlog-setup-database = mkIf cfg.database.createLocally {
364 description = "Set up cloudlog database";
365 serviceConfig = {
366 Type = "oneshot";
367 RemainAfterExit = true;
368 };
369 wantedBy = [ "phpfpm-cloudlog.service" ];
370 after = [ "mysql.service" ];
371 script =
372 let
373 mysql = "${config.services.mysql.package}/bin/mysql";
374 in
375 ''
376 if [ ! -f ${cfg.dataDir}/.dbexists ]; then
377 ${mysql} ${cfg.database.name} < ${pkgs.cloudlog}/install/assets/install.sql
378 touch ${cfg.dataDir}/.dbexists
379 fi
380 '';
381 };
382 cloudlog-upload-lotw = {
383 description = "Upload QSOs to LoTW if certs have been provided";
384 enable = cfg.upload-lotw.enable;
385 script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/lotw/lotw_upload";
386 };
387 cloudlog-update-lotw-users = {
388 description = "Update LOTW Users Database";
389 enable = cfg.update-lotw-users.enable;
390 script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/lotw/load_users";
391 };
392 cloudlog-update-dok = {
393 description = "Update DOK File for autocomplete";
394 enable = cfg.update-dok.enable;
395 script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/update/update_dok";
396 };
397 cloudlog-update-clublog-scp = {
398 description = "Update Clublog SCP Database File";
399 enable = cfg.update-clublog-scp.enable;
400 script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/update/update_clublog_scp";
401 };
402 cloudlog-update-wwff = {
403 description = "Update WWFF File for autocomplete";
404 enable = cfg.update-wwff.enable;
405 script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/update/update_wwff";
406 };
407 cloudlog-upload-qrz = {
408 description = "Upload QSOs to QRZ Logbook";
409 enable = cfg.upload-qrz.enable;
410 script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/qrz/upload";
411 };
412 cloudlog-update-sota = {
413 description = "Update SOTA File for autocomplete";
414 enable = cfg.update-sota.enable;
415 script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/update/update_sota";
416 };
417 };
418 timers = {
419 cloudlog-upload-lotw = {
420 enable = cfg.upload-lotw.enable;
421 wantedBy = [ "timers.target" ];
422 partOf = [ "cloudlog-upload-lotw.service" ];
423 after = [ "phpfpm-cloudlog.service" ];
424 timerConfig = {
425 OnCalendar = cfg.upload-lotw.interval;
426 Persistent = true;
427 };
428 };
429 cloudlog-upload-clublog = {
430 enable = cfg.upload-clublog.enable;
431 wantedBy = [ "timers.target" ];
432 partOf = [ "cloudlog-upload-clublog.service" ];
433 after = [ "phpfpm-cloudlog.service" ];
434 timerConfig = {
435 OnCalendar = cfg.upload-clublog.interval;
436 Persistent = true;
437 };
438 };
439 cloudlog-update-lotw-users = {
440 enable = cfg.update-lotw-users.enable;
441 wantedBy = [ "timers.target" ];
442 partOf = [ "cloudlog-update-lotw-users.service" ];
443 after = [ "phpfpm-cloudlog.service" ];
444 timerConfig = {
445 OnCalendar = cfg.update-lotw-users.interval;
446 Persistent = true;
447 };
448 };
449 cloudlog-update-dok = {
450 enable = cfg.update-dok.enable;
451 wantedBy = [ "timers.target" ];
452 partOf = [ "cloudlog-update-dok.service" ];
453 after = [ "phpfpm-cloudlog.service" ];
454 timerConfig = {
455 OnCalendar = cfg.update-dok.interval;
456 Persistent = true;
457 };
458 };
459 cloudlog-update-clublog-scp = {
460 enable = cfg.update-clublog-scp.enable;
461 wantedBy = [ "timers.target" ];
462 partOf = [ "cloudlog-update-clublog-scp.service" ];
463 after = [ "phpfpm-cloudlog.service" ];
464 timerConfig = {
465 OnCalendar = cfg.update-clublog-scp.interval;
466 Persistent = true;
467 };
468 };
469 cloudlog-update-wwff = {
470 enable = cfg.update-wwff.enable;
471 wantedBy = [ "timers.target" ];
472 partOf = [ "cloudlog-update-wwff.service" ];
473 after = [ "phpfpm-cloudlog.service" ];
474 timerConfig = {
475 OnCalendar = cfg.update-wwff.interval;
476 Persistent = true;
477 };
478 };
479 cloudlog-upload-qrz = {
480 enable = cfg.upload-qrz.enable;
481 wantedBy = [ "timers.target" ];
482 partOf = [ "cloudlog-upload-qrz.service" ];
483 after = [ "phpfpm-cloudlog.service" ];
484 timerConfig = {
485 OnCalendar = cfg.upload-qrz.interval;
486 Persistent = true;
487 };
488 };
489 cloudlog-update-sota = {
490 enable = cfg.update-sota.enable;
491 wantedBy = [ "timers.target" ];
492 partOf = [ "cloudlog-update-sota.service" ];
493 after = [ "phpfpm-cloudlog.service" ];
494 timerConfig = {
495 OnCalendar = cfg.update-sota.interval;
496 Persistent = true;
497 };
498 };
499 };
500 tmpfiles.rules =
501 let
502 group = config.services.nginx.group;
503 in
504 [
505 "d ${cfg.dataDir} 0750 ${cfg.user} ${group} - -"
506 "d ${cfg.dataDir}/updates 0750 ${cfg.user} ${group} - -"
507 "d ${cfg.dataDir}/uploads 0750 ${cfg.user} ${group} - -"
508 "d ${cfg.dataDir}/backup 0750 ${cfg.user} ${group} - -"
509 "d ${cfg.dataDir}/logbook 0750 ${cfg.user} ${group} - -"
510 "d ${cfg.dataDir}/assets/json 0750 ${cfg.user} ${group} - -"
511 "d ${cfg.dataDir}/assets/qslcard 0750 ${cfg.user} ${group} - -"
512 ];
513 };
514
515 users.users."${cfg.user}" = {
516 isSystemUser = true;
517 group = config.services.nginx.group;
518 };
519 };
520
521 meta.maintainers = with maintainers; [ melling ];
522}