1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 inherit (lib)
10 filterAttrsRecursive
11 generators
12 literalExpression
13 mkDefault
14 mkIf
15 mkOption
16 mkEnableOption
17 mkPackageOption
18 mkMerge
19 pipe
20 types
21 ;
22
23 cfg = config.services.movim;
24
25 defaultPHPCfg = {
26 "output_buffering" = 0;
27 "error_reporting" = "E_ALL & ~E_DEPRECATED & ~E_STRICT";
28 "opcache.enable_cli" = 1;
29 "opcache.interned_strings_buffer" = 8;
30 "opcache.max_accelerated_files" = 6144;
31 "opcache.memory_consumption" = 128;
32 "opcache.revalidate_freq" = 2;
33 "opcache.fast_shutdown" = 1;
34 };
35
36 phpCfg = generators.toKeyValue { mkKeyValue = generators.mkKeyValueDefault { } " = "; } (
37 defaultPHPCfg // cfg.phpCfg
38 );
39
40 podConfigFlags =
41 let
42 bevalue = a: lib.escapeShellArg (generators.mkValueStringDefault { } a);
43 in
44 lib.concatStringsSep " " (
45 lib.attrsets.foldlAttrs (
46 acc: k: v:
47 acc ++ lib.optional (v != null) "--${k}=${bevalue v}"
48 ) [ ] cfg.podConfig
49 );
50
51 package =
52 let
53 p = cfg.package.override (
54 {
55 inherit phpCfg;
56 inherit (cfg) minifyStaticFiles;
57 }
58 // lib.optionalAttrs (cfg.database.type == "postgresql") {
59 withPostgreSQL = true;
60 }
61 // lib.optionalAttrs (cfg.database.type == "mariadb") {
62 withMySQL = true;
63 }
64 );
65 in
66 p.overrideAttrs (
67 finalAttrs: prevAttrs:
68 let
69 appDir = "$out/share/php/${finalAttrs.pname}";
70
71 stateDirectories = # sh
72 ''
73 # Symlinking in our state directories
74 rm -rf $out/{.env,cache} ${appDir}/{log,public/cache}
75 ln -s ${cfg.dataDir}/.env ${appDir}/.env
76 ln -s ${cfg.dataDir}/public/cache ${appDir}/public/cache
77 ln -s ${cfg.logDir} ${appDir}/log
78 ln -s ${cfg.runtimeDir}/cache ${appDir}/cache
79 '';
80
81 exposeComposer = # sh
82 ''
83 # Expose PHP Composer for scripts
84 mkdir -p $out/bin
85 echo "#!${lib.getExe pkgs.dash}" > $out/bin/movim-composer
86 echo "${finalAttrs.php.packages.composer}/bin/composer --working-dir="${appDir}" \"\$@\"" >> $out/bin/movim-composer
87 chmod +x $out/bin/movim-composer
88 '';
89
90 podConfigInputDisableReplace = lib.optionalString (podConfigFlags != "") (
91 lib.concatStringsSep "\n" (
92 lib.attrsets.foldlAttrs (
93 acc: k: v:
94 acc
95 ++
96 lib.optional (v != null)
97 # Disable all Admin panel options that were set in the
98 # `cfg.podConfig` to prevent confusing situtions where the
99 # values are rewritten on server reboot
100 # sh
101 ''
102 substituteInPlace ${appDir}/app/Widgets/AdminMain/adminmain.tpl \
103 --replace-warn 'name="${k}"' 'name="${k}" readonly'
104 ''
105 ) [ ] cfg.podConfig
106 )
107 );
108
109 precompressStaticFilesJobs =
110 let
111 inherit (cfg.precompressStaticFiles) brotli gzip;
112
113 findTextFileNames = lib.concatStringsSep " -o " (
114 builtins.map (n: ''-iname "*.${n}"'') [
115 "css"
116 "ini"
117 "js"
118 "json"
119 "manifest"
120 "mjs"
121 "svg"
122 "webmanifest"
123 ]
124 );
125 in
126 lib.concatStringsSep "\n" [
127 (lib.optionalString brotli.enable # sh
128 ''
129 echo -n "Precompressing static files with Brotli …"
130 find ${appDir}/public -type f ${findTextFileNames} -print0 \
131 | xargs -0 -P$NIX_BUILD_CORES -n1 -I{} \
132 ${lib.getExe brotli.package} --keep --quality=${builtins.toString brotli.compressionLevel} --output={}.br {}
133 echo " done."
134 ''
135 )
136 (lib.optionalString gzip.enable # sh
137 ''
138 echo -n "Precompressing static files with Gzip …"
139 find ${appDir}/public -type f ${findTextFileNames} -print0 \
140 | xargs -0 -P$NIX_BUILD_CORES -n1 -I{} \
141 ${lib.getExe gzip.package} -c -${builtins.toString gzip.compressionLevel} {} > {}.gz
142 echo " done."
143 ''
144 )
145 ];
146 in
147 {
148 postInstall = lib.concatStringsSep "\n\n" [
149 prevAttrs.postInstall
150 stateDirectories
151 exposeComposer
152 podConfigInputDisableReplace
153 precompressStaticFilesJobs
154 ];
155 }
156 );
157
158 configFile = pipe cfg.settings [
159 (filterAttrsRecursive (_: v: v != null))
160 (generators.toKeyValue { })
161 (pkgs.writeText "movim-env")
162 ];
163
164 pool = "movim";
165 fpm = config.services.phpfpm.pools.${pool};
166 phpExecutionUnit = "phpfpm-${pool}";
167
168 dbService =
169 {
170 "postgresql" = "postgresql.service";
171 "mariadb" = "mysql.service";
172 }
173 .${cfg.database.type};
174
175 # exclusivity asserted in `assertions`
176 webServerService =
177 if cfg.h2o != null then
178 "h2o.service"
179 else if cfg.nginx != null then
180 "nginx.service"
181 else
182 null;
183
184 socketOwner =
185 if cfg.h2o != null then
186 config.services.h2o.user
187 else if cfg.nginx != null then
188 config.services.nginx.user
189 else
190 cfg.user;
191
192 # Movim needs a lot of unsafe values to function at this time. Perhaps if
193 # this is ever addressed in the future, the PHP application will send up the
194 # proper directive. For now this fairly conservative CSP will restrict a lot
195 # of potentially bad stuff as well as take in inventory of the features used.
196 #
197 # See: https://github.com/movim/movim/issues/314
198 movimCSP = lib.concatStringsSep "; " [
199 "default-src 'self'"
200 "img-src 'self' aesgcm: data: https:"
201 "media-src 'self' aesgcm: https:"
202 "script-src 'self' 'unsafe-eval' 'unsafe-inline'"
203 "style-src 'self' 'unsafe-inline'"
204 ];
205in
206{
207 options.services = {
208 movim = {
209 enable = mkEnableOption "a Movim instance";
210 package = mkPackageOption pkgs "movim" { };
211 phpPackage = mkPackageOption pkgs "php" { };
212
213 phpCfg = mkOption {
214 type =
215 with types;
216 attrsOf (oneOf [
217 int
218 str
219 bool
220 ]);
221 defaultText = literalExpression (generators.toPretty { } defaultPHPCfg);
222 default = { };
223 description = "Extra PHP INI options such as `memory_limit`, `max_execution_time`, etc.";
224 };
225
226 user = mkOption {
227 type = types.nonEmptyStr;
228 default = "movim";
229 description = "User running Movim service";
230 };
231
232 group = mkOption {
233 type = types.nonEmptyStr;
234 default = "movim";
235 description = "Group running Movim service";
236 };
237
238 dataDir = mkOption {
239 type = types.path;
240 default = "/var/lib/movim";
241 description = "State directory of the `movim` user which holds the application’s state & data.";
242 };
243
244 logDir = mkOption {
245 type = types.path;
246 default = "/var/log/movim";
247 description = "Log directory of the `movim` user which holds the application’s logs.";
248 };
249
250 runtimeDir = mkOption {
251 type = types.path;
252 default = "/run/movim";
253 description = "Runtime directory of the `movim` user which holds the application’s caches & temporary files.";
254 };
255
256 domain = mkOption {
257 type = types.nonEmptyStr;
258 description = "Fully-qualified domain name (FQDN) for the Movim instance.";
259 };
260
261 port = mkOption {
262 type = types.port;
263 default = 8080;
264 description = "Movim daemon port.";
265 };
266
267 debug = mkOption {
268 type = types.bool;
269 default = false;
270 description = "Debugging logs.";
271 };
272
273 verbose = mkOption {
274 type = types.bool;
275 default = false;
276 description = "Verbose logs.";
277 };
278
279 minifyStaticFiles = mkOption {
280 type = types.either types.bool (
281 types.submodule {
282 options = {
283 script = mkOption {
284 type = types.submodule {
285 options = {
286 enable = mkEnableOption "Script minification via esbuild";
287 target = mkOption {
288 type = types.nullOr types.nonEmptyStr;
289 default = null;
290 description = ''
291 esbuild target environment string. If not set, a sane
292 default will be provided. See:
293 <https://esbuild.github.io/api/#target>.
294 '';
295 };
296 };
297 };
298 };
299 style = mkOption {
300 type = types.submodule {
301 options = {
302 enable = mkEnableOption "Script minification via Lightning CSS";
303 target = mkOption {
304 type = types.nullOr types.nonEmptyStr;
305 default = null;
306 description = ''
307 Browserslists string target for browser compatibility.
308 If not set, a sane default will be provided. See:
309 <https://browsersl.ist>.
310 '';
311 };
312 };
313 };
314 };
315 svg = mkOption {
316 type = types.submodule {
317 options = {
318 enable = mkEnableOption "SVG minification via Scour";
319 };
320 };
321 };
322 };
323 }
324 );
325 default = true;
326 description = ''
327 Do minification on public static files which reduces the size of
328 assets — saving data for the server & users as well as offering a
329 performance improvement. This adds typing for the `minifyStaticFiles`
330 attribute for the Movim package which *will* override any existing
331 override value. The default `true` will enable minification for all
332 supported asset types with sane defaults.
333 '';
334 example =
335 lib.literalExpression # nix
336 ''
337 {
338 script.enable = false;
339 style = {
340 enable = true;
341 target = "> 0.5%, last 2 versions, Firefox ESR, not dead";
342 };
343 svg.enable = true;
344 }
345 '';
346 };
347
348 precompressStaticFiles = mkOption {
349 type = types.submodule {
350 options = {
351 brotli = {
352 enable = mkEnableOption "Brotli precompression";
353 package = mkPackageOption pkgs "brotli" { };
354 compressionLevel = mkOption {
355 type = types.ints.between 0 11;
356 default = 11;
357 description = "Brotli compression level";
358 };
359 };
360 gzip = {
361 enable = mkEnableOption "Gzip precompression";
362 package = mkPackageOption pkgs "gzip" { };
363 compressionLevel = mkOption {
364 type = types.ints.between 1 9;
365 default = 9;
366 description = "Gzip compression level";
367 };
368 };
369 };
370 };
371 default = {
372 brotli.enable = true;
373 gzip.enable = false;
374 };
375 description = "Aggressively precompress static files";
376 };
377
378 podConfig = mkOption {
379 type = types.submodule {
380 options = {
381 info = mkOption {
382 type = types.nullOr types.nonEmptyStr;
383 default = null;
384 description = "Content of the info box on the login page";
385 };
386
387 description = mkOption {
388 type = types.nullOr types.nonEmptyStr;
389 default = null;
390 description = "General description of the instance";
391 };
392
393 timezone = mkOption {
394 type = types.nullOr types.nonEmptyStr;
395 default = null;
396 description = "The server timezone";
397 };
398
399 restrictsuggestions = mkOption {
400 type = types.nullOr types.bool;
401 default = null;
402 description = "Only suggest chatrooms, Communities and other contents that are available on the user XMPP server and related services";
403 };
404
405 chatonly = mkOption {
406 type = types.nullOr types.bool;
407 default = null;
408 description = "Disable all the social feature (Communities, Blog…) and keep only the chat ones";
409 };
410
411 disableregistration = mkOption {
412 type = types.nullOr types.bool;
413 default = null;
414 description = "Remove the XMPP registration flow and buttons from the interface";
415 };
416
417 loglevel = mkOption {
418 type = types.nullOr (types.ints.between 0 3);
419 default = null;
420 description = "The server loglevel";
421 };
422
423 locale = mkOption {
424 type = types.nullOr types.nonEmptyStr;
425 default = null;
426 description = "The server main locale";
427 };
428
429 xmppdomain = mkOption {
430 type = types.nullOr types.nonEmptyStr;
431 default = null;
432 description = "The default XMPP server domain";
433 };
434
435 xmppdescription = mkOption {
436 type = types.nullOr types.nonEmptyStr;
437 default = null;
438 description = "The default XMPP server description";
439 };
440
441 xmppwhitelist = mkOption {
442 type = types.nullOr types.nonEmptyStr;
443 default = null;
444 description = "The allowlisted XMPP servers";
445 };
446 };
447 };
448 default = { };
449 description = ''
450 Pod configuration (values from `php daemon.php config --help`).
451 Note that these values will now be disabled in the admin panel.
452 '';
453 };
454
455 settings = mkOption {
456 type =
457 with types;
458 attrsOf (
459 nullOr (oneOf [
460 int
461 str
462 bool
463 ])
464 );
465 default = { };
466 description = ".env settings for Movim. Secrets should use `secretFile` option instead. `null`s will be culled.";
467 };
468
469 secretFile = mkOption {
470 type = types.nullOr types.path;
471 default = null;
472 description = "The secret file to be sourced for the .env settings.";
473 };
474
475 database = {
476 type = mkOption {
477 type = types.enum [
478 "mariadb"
479 "postgresql"
480 ];
481 example = "mariadb";
482 default = "postgresql";
483 description = "Database engine to use.";
484 };
485
486 name = mkOption {
487 type = types.nonEmptyStr;
488 default = "movim";
489 description = "Database name.";
490 };
491
492 user = mkOption {
493 type = types.nonEmptyStr;
494 default = "movim";
495 description = "Database username.";
496 };
497
498 createLocally = mkOption {
499 type = types.bool;
500 default = true;
501 description = "local database using UNIX socket authentication";
502 };
503 };
504
505 h2o = mkOption {
506 type = types.nullOr (
507 types.submodule (import ../web-servers/h2o/vhost-options.nix { inherit config lib; })
508 );
509 default = null;
510 example =
511 lib.literalExpression # nix
512 ''
513 {
514 serverAliases = [
515 "pics.''${config.movim.domain}"
516 ];
517 acme.enable = true;
518 tls.policy = "force";
519 }
520 '';
521 description = ''
522 With this option, you can customize an H2O virtual host which already
523 has sensible defaults for Movim. Set to `{ }` if you do not need any
524 customization to the virtual host. If enabled, then by default, the
525 {option}`serverName` is `''${domain}`, If this is set to `null` (the
526 default), no H2O `hosts` will be configured.
527 '';
528 };
529
530 nginx = mkOption {
531 type = types.nullOr (
532 types.submodule (import ../web-servers/nginx/vhost-options.nix { inherit config lib; })
533 );
534 default = null;
535 example =
536 lib.literalExpression # nix
537 ''
538 {
539 serverAliases = [
540 "pics.''${config.movim.domain}"
541 ];
542 enableACME = true;
543 forceHttps = true;
544 }
545 '';
546 description = ''
547 With this option, you can customize an Nginx virtual host which
548 already has sensible defaults for Movim. Set to `{ }` if you do not
549 need any customization to the virtual host. If enabled, then by
550 default, the {option}`serverName` is `''${domain}`, If this is set to
551 `null` (the default), no Nginx `virtualHost` will be configured.
552 '';
553 };
554
555 poolConfig = mkOption {
556 type =
557 with types;
558 attrsOf (oneOf [
559 int
560 str
561 bool
562 ]);
563 default = { };
564 description = "Options for Movim’s PHP-FPM pool.";
565 };
566 };
567 };
568
569 config = mkIf cfg.enable {
570 assertions = [
571 (
572 let
573 webServers = [
574 "h2o"
575 "nginx"
576 ];
577 checkConfigs = lib.concatMapStringsSep ", " (ws: "services.movim.${ws}") webServers;
578 in
579 {
580 assertion = builtins.length (lib.lists.filter (ws: cfg.${ws} != null) webServers) <= 1;
581 message = ''
582 At most 1 web server virtual host configuration should be enabled
583 for Movim at a time. Check ${checkConfigs}.
584 '';
585 }
586 )
587 ];
588
589 environment.systemPackages = [ package ];
590
591 users = {
592 users =
593 {
594 movim = mkIf (cfg.user == "movim") {
595 isSystemUser = true;
596 group = cfg.group;
597 };
598 }
599 // lib.optionalAttrs (cfg.h2o != null) {
600 "${config.services.h2o.user}".extraGroups = [ cfg.group ];
601 }
602 // lib.optionalAttrs (cfg.nginx != null) {
603 "${config.services.nginx.user}".extraGroups = [ cfg.group ];
604 };
605 groups = {
606 ${cfg.group} = { };
607 };
608 };
609
610 services = {
611 movim = {
612 settings = mkMerge [
613 {
614 DAEMON_URL = "//${cfg.domain}";
615 DAEMON_PORT = cfg.port;
616 DAEMON_INTERFACE = "127.0.0.1";
617 DAEMON_DEBUG = cfg.debug;
618 DAEMON_VERBOSE = cfg.verbose;
619 }
620 (mkIf cfg.database.createLocally {
621 DB_DRIVER =
622 {
623 "postgresql" = "pgsql";
624 "mariadb" = "mysql";
625 }
626 .${cfg.database.type};
627 DB_HOST = "localhost";
628 DB_PORT = config.services.${cfg.database.type}.settings.port;
629 DB_DATABASE = cfg.database.name;
630 DB_USERNAME = cfg.database.user;
631 DB_PASSWORD = "";
632 })
633 ];
634
635 poolConfig = lib.mapAttrs' (n: v: lib.nameValuePair n (lib.mkDefault v)) {
636 "pm" = "dynamic";
637 "php_admin_value[error_log]" = "stderr";
638 "php_admin_flag[log_errors]" = true;
639 "catch_workers_output" = true;
640 "pm.max_children" = 32;
641 "pm.start_servers" = 2;
642 "pm.min_spare_servers" = 2;
643 "pm.max_spare_servers" = 8;
644 "pm.max_requests" = 500;
645 };
646 };
647
648 h2o = mkIf (cfg.h2o != null) {
649 enable = true;
650 hosts."${cfg.domain}" = mkMerge [
651 {
652 settings = {
653 paths = {
654 "/ws/" = {
655 "proxy.preserve-host" = "ON";
656 "proxy.tunnel" = "ON";
657 "proxy.reverse.url" = "http://${cfg.settings.DAEMON_INTERFACE}:${builtins.toString cfg.port}/";
658 };
659 "/" =
660 {
661 "file.dir" = "${package}/share/php/movim/public";
662 "file.index" = [
663 "index.php"
664 "index.html"
665 ];
666 redirect = {
667 url = "/index.php/";
668 internal = "YES";
669 status = 307;
670 };
671 "header.set" = [
672 "Content-Security-Policy: ${movimCSP}"
673 ];
674 }
675 // lib.optionalAttrs (with cfg.precompressStaticFiles; brotli.enable || gzip.enable) {
676 "file.send-compressed" = "ON";
677 };
678 };
679 "file.custom-handler" = {
680 extension = [ ".php" ];
681 "fastcgi.document_root" = package;
682 "fastcgi.connect" = {
683 port = fpm.socket;
684 type = "unix";
685 };
686 };
687 };
688 }
689 cfg.h2o
690 ];
691 };
692
693 nginx = mkIf (cfg.nginx != null) (
694 {
695 enable = true;
696 recommendedOptimisation = mkDefault true;
697 recommendedProxySettings = true;
698 # TODO: recommended cache options already in Nginx⁇
699 appendHttpConfig = # nginx
700 ''
701 fastcgi_cache_path /tmp/nginx_cache levels=1:2 keys_zone=nginx_cache:100m inactive=60m;
702 fastcgi_cache_key "$scheme$request_method$host$request_uri";
703 '';
704 virtualHosts."${cfg.domain}" = mkMerge [
705 cfg.nginx
706 {
707 root = lib.mkForce "${package}/share/php/movim/public";
708 locations = {
709 "/favicon.ico" = {
710 priority = 100;
711 extraConfig = # nginx
712 ''
713 access_log off;
714 log_not_found off;
715 '';
716 };
717 "/robots.txt" = {
718 priority = 100;
719 extraConfig = # nginx
720 ''
721 access_log off;
722 log_not_found off;
723 '';
724 };
725 "~ /\\.(?!well-known).*" = {
726 priority = 210;
727 extraConfig = # nginx
728 ''
729 deny all;
730 '';
731 };
732 # Ask nginx to cache every URL starting with "/picture"
733 "/picture" = {
734 priority = 400;
735 tryFiles = "$uri $uri/ /index.php$is_args$args";
736 extraConfig = # nginx
737 ''
738 set $no_cache 0; # Enable cache only there
739 '';
740 };
741 "/" = {
742 priority = 490;
743 tryFiles = "$uri $uri/ /index.php$is_args$args";
744 extraConfig = # nginx
745 ''
746 add_header Content-Security-Policy "${movimCSP}";
747 set $no_cache 1;
748 '';
749 };
750 "~ \\.php$" = {
751 priority = 500;
752 tryFiles = "$uri =404";
753 extraConfig = # nginx
754 ''
755 include ${config.services.nginx.package}/conf/fastcgi.conf;
756 add_header X-Cache $upstream_cache_status;
757 fastcgi_ignore_headers "Cache-Control" "Expires" "Set-Cookie";
758 fastcgi_cache nginx_cache;
759 fastcgi_cache_valid any 7d;
760 fastcgi_cache_bypass $no_cache;
761 fastcgi_no_cache $no_cache;
762 fastcgi_split_path_info ^(.+\.php)(/.+)$;
763 fastcgi_index index.php;
764 fastcgi_pass unix:${fpm.socket};
765 '';
766 };
767 "/ws/" = {
768 priority = 900;
769 proxyPass = "http://${cfg.settings.DAEMON_INTERFACE}:${builtins.toString cfg.port}/";
770 proxyWebsockets = true;
771 recommendedProxySettings = true;
772 extraConfig = # nginx
773 ''
774 proxy_set_header X-Forwarded-Proto $scheme;
775 proxy_redirect off;
776 '';
777 };
778 };
779 extraConfig = # nginx
780 ''
781 index index.php;
782 '';
783 }
784 ];
785 }
786 // lib.optionalAttrs (cfg.precompressStaticFiles.gzip.enable) {
787 recommendedGzipSettings = mkDefault true;
788 }
789 // lib.optionalAttrs (cfg.precompressStaticFiles.brotli.enable) {
790 recommendedBrotliSettings = mkDefault true;
791 }
792 );
793
794 mysql = mkIf (cfg.database.createLocally && cfg.database.type == "mariadb") {
795 enable = mkDefault true;
796 package = mkDefault pkgs.mariadb;
797 ensureDatabases = [ cfg.database.name ];
798 ensureUsers = [
799 {
800 name = cfg.database.user;
801 ensureDBOwnership = true;
802 }
803 ];
804 };
805
806 postgresql = mkIf (cfg.database.createLocally && cfg.database.type == "postgresql") {
807 enable = mkDefault true;
808 ensureDatabases = [ cfg.database.name ];
809 ensureUsers = [
810 {
811 name = cfg.database.user;
812 ensureDBOwnership = true;
813 }
814 ];
815 authentication = ''
816 host ${cfg.database.name} ${cfg.database.user} localhost trust
817 '';
818 };
819
820 phpfpm.pools.${pool} = {
821 phpPackage = package.php;
822 user = cfg.user;
823 group = cfg.group;
824
825 phpOptions = ''
826 error_log = 'stderr'
827 log_errors = on
828 '';
829
830 settings = {
831 "listen.owner" = socketOwner;
832 "listen.group" = cfg.group;
833 "listen.mode" = "0660";
834 "catch_workers_output" = true;
835 } // cfg.poolConfig;
836 };
837 };
838
839 systemd = {
840 services.movim-data-setup = {
841 description = "Movim setup: .env file, databases init, cache reload";
842 wantedBy = [ "multi-user.target" ];
843 requiredBy = [ "${phpExecutionUnit}.service" ];
844 before = [ "${phpExecutionUnit}.service" ];
845 wants = [ "local-fs.target" ];
846 requires = lib.optional cfg.database.createLocally dbService;
847 after = lib.optional cfg.database.createLocally dbService;
848
849 serviceConfig =
850 {
851 Type = "oneshot";
852 User = cfg.user;
853 Group = cfg.group;
854 UMask = "077";
855 }
856 // lib.optionalAttrs (cfg.secretFile != null) {
857 LoadCredential = "env-secrets:${cfg.secretFile}";
858 };
859
860 script = # sh
861 ''
862 # Env vars
863 rm -f ${cfg.dataDir}/.env
864 cp --no-preserve=all ${configFile} ${cfg.dataDir}/.env
865 echo -e '\n' >> ${cfg.dataDir}/.env
866 if [[ -f "$CREDENTIALS_DIRECTORY/env-secrets" ]]; then
867 cat "$CREDENTIALS_DIRECTORY/env-secrets" >> ${cfg.dataDir}/.env
868 echo -e '\n' >> ${cfg.dataDir}/.env
869 fi
870
871 # Caches, logs
872 mkdir -p ${cfg.dataDir}/public/cache ${cfg.logDir} ${cfg.runtimeDir}/cache
873 chmod -R ug+rw ${cfg.dataDir}/public/cache
874 chmod -R ug+rw ${cfg.logDir}
875 chmod -R ug+rwx ${cfg.runtimeDir}/cache
876
877 # Migrations
878 MOVIM_VERSION="${package.version}"
879 if [[ ! -f "${cfg.dataDir}/.migration-version" ]] || [[ "$MOVIM_VERSION" != "$(<${cfg.dataDir}/.migration-version)" ]]; then
880 ${package}/bin/movim-composer movim:migrate && echo $MOVIM_VERSION > ${cfg.dataDir}/.migration-version
881 fi
882 ''
883 + lib.optionalString (podConfigFlags != "") (
884 let
885 flags = lib.concatStringsSep " " (
886 [ "--no-interaction" ]
887 ++ lib.optional cfg.debug "-vvv"
888 ++ lib.optional (!cfg.debug && cfg.verbose) "-v"
889 );
890 in
891 ''
892 ${lib.getExe package} config ${podConfigFlags}
893 ''
894 );
895 };
896
897 services.${phpExecutionUnit} = {
898 wantedBy = lib.optional (webServerService != null) webServerService;
899 requiredBy = [ "movim.service" ];
900 before = [ "movim.service" ] ++ lib.optional (webServerService != null) webServerService;
901 wants = [ "network.target" ];
902 requires = [ "movim-data-setup.service" ] ++ lib.optional cfg.database.createLocally dbService;
903 after = [ "movim-data-setup.service" ] ++ lib.optional cfg.database.createLocally dbService;
904 };
905
906 services.movim = {
907 description = "Movim daemon";
908 wantedBy = [ "multi-user.target" ];
909 wants = [
910 "network.target"
911 "local-fs.target"
912 ];
913 requires =
914 [
915 "movim-data-setup.service"
916 "${phpExecutionUnit}.service"
917 ]
918 ++ lib.optional cfg.database.createLocally dbService
919 ++ lib.optional (webServerService != null) webServerService;
920 after =
921 [
922 "movim-data-setup.service"
923 "${phpExecutionUnit}.service"
924 ]
925 ++ lib.optional cfg.database.createLocally dbService
926 ++ lib.optional (webServerService != null) webServerService;
927 environment = {
928 PUBLIC_URL = "//${cfg.domain}";
929 WS_PORT = builtins.toString cfg.port;
930 };
931
932 serviceConfig = {
933 User = cfg.user;
934 Group = cfg.group;
935 WorkingDirectory = "${package}/share/php/movim";
936 ExecStart = "${lib.getExe package} start";
937 };
938 };
939
940 tmpfiles.settings."10-movim" = with cfg; {
941 "${dataDir}".d = {
942 inherit user group;
943 mode = "0710";
944 };
945 "${dataDir}/public".d = {
946 inherit user group;
947 mode = "0750";
948 };
949 "${dataDir}/public/cache".d = {
950 inherit user group;
951 mode = "0750";
952 };
953 "${runtimeDir}".d = {
954 inherit user group;
955 mode = "0700";
956 };
957 "${runtimeDir}/cache".d = {
958 inherit user group;
959 mode = "0700";
960 };
961 "${logDir}".d = {
962 inherit user group;
963 mode = "0700";
964 };
965 };
966 };
967 };
968
969 imports = [
970 (lib.mkRemovedOptionModule [ "minifyStaticFiles" "script" "package" ] ''
971 Override services.movim.package instead.
972 '')
973 (lib.mkRemovedOptionModule [ "minifyStaticFiles" "style" "package" ] ''
974 Override services.movim.package instead.
975 '')
976 (lib.mkRemovedOptionModule [ "minifyStaticFiles" "svg" "package" ] ''
977 Override services.movim.package instead.
978 '')
979 ];
980}