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
}