@cryptotaxi247 / infra-1 / commits / d9c24148

limesurvey: migrate to nixpkgs module

Martin Weinelt committed Nov 26, 2025 at 18:39 UTC d9c24148d71a84ba003bac392432629b5fc5060c
3 files changed +18 -468
non-critical-infra/hosts/caliban/default.nix
+1 -1
@@ -15,10 +15,10 @@
15 ../../modules/draupnir.nix
16 ../../modules/backup.nix
17 ../../modules/element-web.nix
18 + ../../modules/limesurvey.nix
19 ../../modules/matrix-synapse.nix
20 ../../modules/owncast.nix
21 ../../modules/vaultwarden.nix
21 - ./limesurvey-tmp.nix
22 ./nixpkgs-swh.nix
23 ];
24
non-critical-infra/hosts/caliban/limesurvey-tmp.nix deleted
-30
@@ -1,30 +0,0 @@
1 -# the content of this file should be put in the modules folder once the actual module has been upstreamed
2 -# PR: https://github.com/NixOS/nixpkgs/pull/325665/
3 -{ config, ... }:
4 -{
5 - disabledModules = [ "services/web-apps/limesurvey.nix" ];
6 -
7 - imports = [ ../../modules/limesurvey.nix ];
8 -
9 - services.limesurvey = {
10 - enable = true;
11 - encryptionKeyFile = config.sops.secrets.limesurvey-encryption-key.path;
12 - encryptionNonceFile = config.sops.secrets.limesurvey-encryption-nonce.path;
13 - virtualHost = {
14 - serverName = "survey.nixos.org";
15 - enableACME = true;
16 - forceSSL = true;
17 - };
18 - };
19 -
20 - sops.secrets.limesurvey-encryption-key = {
21 - format = "binary";
22 - sopsFile = ../../secrets/limesurvey-encryption-key.caliban;
23 - };
24 -
25 - sops.secrets.limesurvey-encryption-nonce = {
26 - format = "binary";
27 - sopsFile = ../../secrets/limesurvey-encryption-nonce.caliban;
28 - };
29 -
30 -}
non-critical-infra/modules/limesurvey.nix
+17 -437
@@ -1,448 +1,28 @@
1 {
2 config,
3 - lib,
4 - pkgs,
5 - inputs,
3 ...
4 }:
8 -
9 -let
10 -
11 - inherit (lib)
12 - mkDefault
13 - mkEnableOption
14 - mkForce
15 - mkIf
16 - mkMerge
17 - mkOption
18 - mkPackageOption
19 - ;
20 - inherit (lib)
21 - literalExpression
22 - mapAttrs
23 - optional
24 - optionalString
25 - types
26 - recursiveUpdate
27 - ;
28 -
29 - cfg = config.services.limesurvey;
30 -
31 - user = "limesurvey";
32 - group = config.services.nginx.group;
33 - stateDir = "/var/lib/limesurvey";
34 -
35 - configType =
36 - with types;
37 - oneOf [
38 - (attrsOf configType)
39 - str
40 - int
41 - bool
42 - ]
43 - // {
44 - description = "limesurvey config type (str, int, bool or attribute set thereof)";
45 - };
46 -
47 - limesurveyConfig = pkgs.writeText "config.php" ''
48 - <?php
49 - return \array_merge_recursive(
50 - \json_decode('${builtins.toJSON cfg.config}', true),
51 - [
52 - 'config' => [
53 - 'encryptionnonce' => \trim(\file_get_contents(\getenv('CREDENTIALS_DIRECTORY') . DIRECTORY_SEPARATOR . 'encryption_nonce')),
54 - 'encryptionsecretboxkey' => \trim(\file_get_contents(\getenv('CREDENTIALS_DIRECTORY') . DIRECTORY_SEPARATOR . 'encryption_key')),
55 - ]
56 - ]
57 - );
58 - ?>
59 - '';
60 -
61 - mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
62 - pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
63 -
64 -in
5 {
66 - # interface
67 -
68 - options.services.limesurvey = {
69 - enable = mkEnableOption "Limesurvey web application";
70 -
71 - package = mkPackageOption pkgs "limesurvey" { };
72 -
73 - encryptionKey = mkOption {
74 - type = types.nullOr types.str;
75 - default = null;
76 - visible = false;
77 - description = ''
78 - This is a 32-byte key used to encrypt variables in the database.
79 - You _must_ change this from the default value.
80 - '';
81 - };
82 -
83 - encryptionNonce = mkOption {
84 - type = types.nullOr types.str;
85 - default = null;
86 - visible = false;
87 - description = ''
88 - This is a 24-byte nonce used to encrypt variables in the database.
89 - You _must_ change this from the default value.
90 - '';
91 - };
92 -
93 - encryptionKeyFile = mkOption {
94 - type = types.nullOr types.path;
95 - default = null;
96 - description = ''
97 - 32-byte key used to encrypt variables in the database.
98 -
99 - Note: It should be string not a store path in order to prevent the password from being world readable
100 - '';
101 - };
102 -
103 - encryptionNonceFile = mkOption {
104 - type = types.nullOr types.path;
105 - default = null;
106 - description = ''
107 - 24-byte used to encrypt variables in the database.
108 -
109 - Note: It should be string not a store path in order to prevent the password from being world readable
110 - '';
111 - };
112 -
113 - database = {
114 - type = mkOption {
115 - type = types.enum [
116 - "mysql"
117 - "pgsql"
118 - "odbc"
119 - "mssql"
120 - ];
121 - example = "pgsql";
122 - default = "mysql";
123 - description = "Database engine to use.";
124 - };
125 -
126 - dbEngine = mkOption {
127 - type = types.enum [
128 - "MyISAM"
129 - "InnoDB"
130 - ];
131 - default = "InnoDB";
132 - description = "Database storage engine to use.";
133 - };
134 -
135 - host = mkOption {
136 - type = types.str;
137 - default = "localhost";
138 - description = "Database host address.";
139 - };
140 -
141 - port = mkOption {
142 - type = types.port;
143 - default = if cfg.database.type == "pgsql" then 5442 else 3306;
144 - defaultText = literalExpression "3306";
145 - description = "Database host port.";
146 - };
147 -
148 - name = mkOption {
149 - type = types.str;
150 - default = "limesurvey";
151 - description = "Database name.";
152 - };
153 -
154 - user = mkOption {
155 - type = types.str;
156 - default = "limesurvey";
157 - description = "Database user.";
158 - };
159 -
160 - passwordFile = mkOption {
161 - type = types.nullOr types.path;
162 - default = null;
163 - example = "/run/keys/limesurvey-dbpassword";
164 - description = ''
165 - A file containing the password corresponding to
166 - {option}`database.user`.
167 - '';
168 - };
169 -
170 - socket = mkOption {
171 - type = types.nullOr types.path;
172 - default =
173 - if mysqlLocal then
174 - "/run/mysqld/mysqld.sock"
175 - else if pgsqlLocal then
176 - "/run/postgresql"
177 - else
178 - null;
179 - defaultText = literalExpression "/run/mysqld/mysqld.sock";
180 - description = "Path to the unix socket file to use for authentication.";
181 - };
182 -
183 - createLocally = mkOption {
184 - type = types.bool;
185 - default = cfg.database.type == "mysql";
186 - defaultText = literalExpression "true";
187 - description = ''
188 - Create the database and database user locally.
189 - This currently only applies if database type "mysql" is selected.
190 - '';
191 - };
192 - };
193 -
194 - virtualHost = mkOption {
195 - type = types.submodule (
196 - recursiveUpdate (import
197 - "${inputs.nixpkgs}/nixos/modules/services/web-servers/nginx/vhost-options.nix"
198 - { inherit config lib; }
199 - ) { }
200 - );
201 - example = literalExpression ''
202 - {
203 - serverName = "survey.example.org";
204 - forceSSL = true;
205 - enableACME = true;
206 - }
207 - '';
208 - description = ''
209 - Nginx configuration can be done by adapting `services.nginx.virtualHosts.<name>`.
210 - See [](#opt-services.nginx.virtualHosts) for further information.
211 - '';
212 - };
213 -
214 - poolConfig = mkOption {
215 - type =
216 - with types;
217 - attrsOf (oneOf [
218 - str
219 - int
220 - bool
221 - ]);
222 - default = {
223 - "pm" = "dynamic";
224 - "pm.max_children" = 32;
225 - "pm.start_servers" = 2;
226 - "pm.min_spare_servers" = 2;
227 - "pm.max_spare_servers" = 4;
228 - "pm.max_requests" = 500;
229 - };
230 - description = ''
231 - Options for the LimeSurvey PHP pool. See the documentation on `php-fpm.conf`
232 - for details on configuration directives.
233 - '';
234 - };
235 -
236 - config = mkOption {
237 - type = configType;
238 - default = { };
239 - description = ''
240 - LimeSurvey configuration. Refer to
241 - <https://manual.limesurvey.org/Optional_settings>
242 - for details on supported values.
243 - '';
6 + services.limesurvey = {
7 + enable = true;
8 + encryptionKeyFile = config.sops.secrets.limesurvey-encryption-key.path;
9 + encryptionNonceFile = config.sops.secrets.limesurvey-encryption-nonce.path;
10 + webserver = "nginx";
11 + nginx.virtualHost = {
12 + serverName = "survey.nixos.org";
13 + enableACME = true;
14 + forceSSL = true;
15 };
16 };
17
247 - # implementation
248 -
249 - config = mkIf cfg.enable {
250 -
251 - assertions = [
252 - {
253 - assertion = cfg.database.createLocally -> cfg.database.type == "mysql";
254 - message = "services.limesurvey.createLocally is currently only supported for database type 'mysql'";
255 - }
256 - {
257 - assertion = cfg.database.createLocally -> cfg.database.user == user;
258 - message = "services.limesurvey.database.user must be set to ${user} if services.limesurvey.database.createLocally is set true";
259 - }
260 - {
261 - assertion = cfg.database.createLocally -> cfg.database.socket != null;
262 - message = "services.limesurvey.database.socket must be set if services.limesurvey.database.createLocally is set to true";
263 - }
264 - {
265 - assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
266 - message = "a password cannot be specified if services.limesurvey.database.createLocally is set to true";
267 - }
268 - {
269 - assertion = cfg.encryptionKey != null || cfg.encryptionKeyFile != null;
270 - message = ''
271 - You must set `services.limesurvey.encryptionKeyFile` to a file containing a 32-character uppercase hex string.
272 -
273 - If this message appears when updating your system, please turn off encryption
274 - in the LimeSurvey interface and create backups before filling the key.
275 - '';
276 - }
277 - {
278 - assertion = cfg.encryptionNonce != null || cfg.encryptionNonceFile != null;
279 - message = ''
280 - You must set `services.limesurvey.encryptionNonceFile` to a file containing a 24-character uppercase hex string.
281 -
282 - If this message appears when updating your system, please turn off encryption
283 - in the LimeSurvey interface and create backups before filling the nonce.
284 - '';
285 - }
286 - ];
287 -
288 - services.limesurvey.config = mapAttrs (_name: mkDefault) {
289 - runtimePath = "${stateDir}/tmp/runtime";
290 - components = {
291 - db = {
292 - connectionString =
293 - "${cfg.database.type}:dbname=${cfg.database.name};host=${
294 - if pgsqlLocal then cfg.database.socket else cfg.database.host
295 - };port=${toString cfg.database.port}"
296 - + optionalString mysqlLocal ";socket=${cfg.database.socket}";
297 - username = cfg.database.user;
298 - password = mkIf (
299 - cfg.database.passwordFile != null
300 - ) "file_get_contents(\"${toString cfg.database.passwordFile}\");";
301 - tablePrefix = "limesurvey_";
302 - };
303 - assetManager.basePath = "${stateDir}/tmp/assets";
304 - urlManager = {
305 - urlFormat = "path";
306 - showScriptName = false;
307 - };
308 - };
309 - config = {
310 - tempdir = "${stateDir}/tmp";
311 - uploaddir = "${stateDir}/upload";
312 - userquestionthemerootdir = "${stateDir}/upload/themes/question";
313 - force_ssl = mkIf (
314 - cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL
315 - ) "on";
316 - config.defaultlang = "en";
317 - };
318 - };
319 -
320 - services.mysql = mkIf mysqlLocal {
321 - enable = true;
322 - package = mkDefault pkgs.mariadb;
323 - ensureDatabases = [ cfg.database.name ];
324 - ensureUsers = [
325 - {
326 - name = cfg.database.user;
327 - ensurePermissions = {
328 - "${cfg.database.name}.*" = "SELECT, CREATE, INSERT, UPDATE, DELETE, ALTER, DROP, INDEX";
329 - };
330 - }
331 - ];
332 - };
333 -
334 - services.phpfpm.pools.limesurvey = {
335 - inherit user group;
336 - phpPackage = pkgs.php81;
337 - phpEnv.DBENGINE = "${cfg.database.dbEngine}";
338 - phpEnv.LIMESURVEY_CONFIG = "${limesurveyConfig}";
339 - # App code cannot access credentials directly since the service starts
340 - # with the root user so we copy the credentials to a place accessible to Limesurvey
341 - phpEnv.CREDENTIALS_DIRECTORY = "${stateDir}/credentials";
342 - settings = {
343 - "listen.owner" = config.services.nginx.user;
344 - "listen.group" = config.services.nginx.group;
345 - }
346 - // cfg.poolConfig;
347 - };
348 - systemd.services.phpfpm-limesurvey.serviceConfig = {
349 - ExecStartPre = pkgs.writeShellScript "limesurvey-phpfpm-exec-pre" ''
350 - cp -f "''${CREDENTIALS_DIRECTORY}"/encryption_key "${stateDir}/credentials/encryption_key"
351 - chown ${user}:${group} "${stateDir}/credentials/encryption_key"
352 - cp -f "''${CREDENTIALS_DIRECTORY}"/encryption_nonce "${stateDir}/credentials/encryption_nonce"
353 - chown ${user}:${group} "${stateDir}/credentials/encryption_nonce"
354 - '';
355 - LoadCredential = [
356 - "encryption_key:${
357 - if cfg.encryptionKeyFile != null then
358 - cfg.encryptionKeyFile
359 - else
360 - pkgs.writeText "key" cfg.encryptionKey
361 - }"
362 - "encryption_nonce:${
363 - if cfg.encryptionNonceFile != null then
364 - cfg.encryptionNonceFile
365 - else
366 - pkgs.writeText "nonce" cfg.encryptionKey
367 - }"
368 - ];
369 - };
370 -
371 - services.nginx = {
372 - enable = true;
373 - virtualHosts.${cfg.virtualHost.serverName} = lib.mkMerge [
374 - cfg.virtualHost
375 - {
376 - root = lib.mkForce "${cfg.package}/share/limesurvey";
377 - locations = {
378 - "/" = {
379 - index = "index.php";
380 - tryFiles = "$uri /index.php?$args";
381 - };
382 -
383 - "~ \.php$".extraConfig = ''
384 - fastcgi_pass unix:${config.services.phpfpm.pools."limesurvey".socket};
385 - '';
386 - "/tmp".root = "/var/lib/limesurvey";
387 - "/upload/".root = "/var/lib/limesurvey";
388 -
389 - };
390 - extraConfig = ''
391 - access_log off;
392 - '';
393 - }
394 - ];
395 - };
396 -
397 - systemd.tmpfiles.rules = [
398 - "d ${stateDir} 0750 ${user} ${group} - -"
399 - "d ${stateDir}/tmp 0750 ${user} ${group} - -"
400 - "d ${stateDir}/tmp/assets 0750 ${user} ${group} - -"
401 - "d ${stateDir}/tmp/runtime 0750 ${user} ${group} - -"
402 - "d ${stateDir}/tmp/upload 0750 ${user} ${group} - -"
403 - "d ${stateDir}/credentials 0700 ${user} ${group} - -"
404 - "C ${stateDir}/upload 0750 ${user} ${group} - ${cfg.package}/share/limesurvey/upload"
405 - ];
406 -
407 - systemd.services.limesurvey-init = {
408 - wantedBy = [ "multi-user.target" ];
409 - before = [ "phpfpm-limesurvey.service" ];
410 - after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
411 - environment.DBENGINE = "${cfg.database.dbEngine}";
412 - environment.LIMESURVEY_CONFIG = limesurveyConfig;
413 - script = ''
414 - # update or install the database as required
415 - ${pkgs.php81}/bin/php ${cfg.package}/share/limesurvey/application/commands/console.php updatedb || \
416 - ${pkgs.php81}/bin/php ${cfg.package}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose
417 - '';
418 - serviceConfig = {
419 - User = user;
420 - Group = group;
421 - Type = "oneshot";
422 - LoadCredential = [
423 - "encryption_key:${
424 - if cfg.encryptionKeyFile != null then
425 - cfg.encryptionKeyFile
426 - else
427 - pkgs.writeText "key" cfg.encryptionKey
428 - }"
429 - "encryption_nonce:${
430 - if cfg.encryptionNonceFile != null then
431 - cfg.encryptionNonceFile
432 - else
433 - pkgs.writeText "nonce" cfg.encryptionKey
434 - }"
435 - ];
436 - };
437 - };
438 -
439 - systemd.services.nginx.after =
440 - optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
441 -
442 - users.users.${user} = {
443 - group = group;
444 - isSystemUser = true;
445 - };
18 + sops.secrets.limesurvey-encryption-key = {
19 + format = "binary";
20 + sopsFile = ../secrets/limesurvey-encryption-key.caliban;
21 + };
22
23 + sops.secrets.limesurvey-encryption-nonce = {
24 + format = "binary";
25 + sopsFile = ../secrets/limesurvey-encryption-nonce.caliban;
26 };
27 +
28 }