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