1{
2 config,
3 lib,
4 pkgs,
5 utils,
6 ...
7}:
8
9let
10 inherit (lib)
11 mkDefault
12 mkEnableOption
13 mkIf
14 mkOption
15 mkPackageOption
16 mkRenamedOptionModule
17 types
18 ;
19
20 cfg = config.services.engelsystem;
21in
22{
23 imports = [
24 (mkRenamedOptionModule
25 [ "services" "engelsystem" "config" ]
26 [ "services" "engelsystem" "settings" ]
27 )
28 ];
29
30 options.services.engelsystem = {
31 enable = mkEnableOption "engelsystem, an online tool for coordinating volunteers and shifts on large events";
32
33 package = mkPackageOption pkgs "engelsystem" { };
34
35 domain = mkOption {
36 type = types.str;
37 example = "engelsystem.example.com";
38 description = "Domain to serve on.";
39 };
40
41 createDatabase = mkOption {
42 type = types.bool;
43 default = true;
44 description = ''
45 Whether to create a local database automatically.
46 This will override every database setting in {option}`services.engelsystem.settings`.
47 '';
48 };
49
50 settings = mkOption {
51 type = types.attrs;
52 default = {
53 database = {
54 host = "localhost";
55 database = "engelsystem";
56 username = "engelsystem";
57 };
58 };
59 example = {
60 maintenance = false;
61 database = {
62 host = "database.example.com";
63 database = "engelsystem";
64 username = "engelsystem";
65 password._secret = "/var/keys/engelsystem/database";
66 };
67 email = {
68 driver = "smtp";
69 host = "smtp.example.com";
70 port = 587;
71 from.address = "engelsystem@example.com";
72 from.name = "example engelsystem";
73 encryption = "tls";
74 username = "engelsystem@example.com";
75 password._secret = "/var/keys/engelsystem/mail";
76 };
77 autoarrive = true;
78 min_password_length = 6;
79 default_locale = "de_DE";
80 };
81 description = ''
82 Options to be added to config.php, as a nix attribute set. Options containing secret data
83 should be set to an attribute set containing the attribute _secret - a string pointing to a
84 file containing the value the option should be set to. See the example to get a better
85 picture of this: in the resulting config.php file, the email.password key will be set to
86 the contents of the /var/keys/engelsystem/mail file.
87
88 See <https://engelsystem.de/doc/admin/configuration/> for available options.
89
90 Note that the admin user login credentials cannot be set here - they always default to
91 admin:asdfasdf. Log in and change them immediately.
92 '';
93 };
94 };
95
96 config = mkIf cfg.enable {
97 # create database
98 services.mysql = mkIf cfg.createDatabase {
99 enable = true;
100 package = mkDefault pkgs.mariadb;
101 ensureUsers = [
102 {
103 name = "engelsystem";
104 ensurePermissions = {
105 "engelsystem.*" = "ALL PRIVILEGES";
106 };
107 }
108 ];
109 ensureDatabases = [ "engelsystem" ];
110 };
111
112 environment.etc."engelsystem/config.php".source = pkgs.writeText "config.php" ''
113 <?php
114 return json_decode(file_get_contents("/var/lib/engelsystem/config.json"), true);
115 '';
116
117 services.phpfpm.pools.engelsystem = {
118 user = "engelsystem";
119 settings = {
120 "listen.owner" = config.services.nginx.user;
121 "pm" = "dynamic";
122 "pm.max_children" = 32;
123 "pm.max_requests" = 500;
124 "pm.start_servers" = 2;
125 "pm.min_spare_servers" = 2;
126 "pm.max_spare_servers" = 5;
127 "php_admin_value[error_log]" = "stderr";
128 "php_admin_flag[log_errors]" = true;
129 "catch_workers_output" = true;
130 };
131 };
132
133 services.nginx = {
134 enable = true;
135 virtualHosts."${cfg.domain}".locations = {
136 "/" = {
137 root = "${cfg.package}/share/engelsystem/public";
138 extraConfig = ''
139 index index.php;
140 try_files $uri $uri/ /index.php?$args;
141 autoindex off;
142 '';
143 };
144 "~ \\.php$" = {
145 root = "${cfg.package}/share/engelsystem/public";
146 extraConfig = ''
147 fastcgi_pass unix:${config.services.phpfpm.pools.engelsystem.socket};
148 fastcgi_index index.php;
149 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
150 include ${config.services.nginx.package}/conf/fastcgi_params;
151 include ${config.services.nginx.package}/conf/fastcgi.conf;
152 '';
153 };
154 };
155 };
156
157 systemd.services."engelsystem-init" = {
158 wantedBy = [ "multi-user.target" ];
159 serviceConfig = {
160 Type = "oneshot";
161 };
162 script =
163 let
164 genConfigScript = pkgs.writeScript "engelsystem-gen-config.sh" (
165 utils.genJqSecretsReplacementSnippet cfg.settings "config.json"
166 );
167 in
168 ''
169 umask 077
170 mkdir -p /var/lib/engelsystem/storage/app
171 mkdir -p /var/lib/engelsystem/storage/cache/views
172 cd /var/lib/engelsystem
173 ${genConfigScript}
174 chmod 400 config.json
175 chown -R engelsystem .
176 '';
177 };
178 systemd.services."engelsystem-migrate" = {
179 wantedBy = [ "multi-user.target" ];
180 serviceConfig = {
181 Type = "oneshot";
182 User = "engelsystem";
183 Group = "engelsystem";
184 };
185 script = ''
186 versionFile="/var/lib/engelsystem/.version"
187 version=$(cat "$versionFile" 2>/dev/null || echo 0)
188
189 if [[ $version != ${cfg.package.version} ]]; then
190 # prune template cache between releases
191 rm -rfv /var/lib/engelsystem/storage/cache/*
192
193 ${cfg.package}/bin/migrate
194
195 echo ${cfg.package.version} > "$versionFile"
196 fi
197 '';
198 after = [
199 "engelsystem-init.service"
200 "mysql.service"
201 ];
202 };
203 systemd.services."phpfpm-engelsystem".after = [ "engelsystem-migrate.service" ];
204
205 users.users.engelsystem = {
206 isSystemUser = true;
207 createHome = true;
208 home = "/var/lib/engelsystem/storage";
209 group = "engelsystem";
210 };
211 users.groups.engelsystem = { };
212 };
213}