···
9
+
cfg = config.services.agorakit;
10
+
agorakit = pkgs.agorakit.override { dataDir = cfg.dataDir; };
17
+
# shell script for local administration
18
+
artisan = pkgs.writeScriptBin "agorakit" ''
19
+
#! ${pkgs.runtimeShell}
22
+
if [[ "$USER" != ${user} ]]; then
23
+
exec /run/wrappers/bin/sudo -u ${user} "$@"
28
+
sudo ${lib.getExe pkgs.php} artisan "$@"
31
+
tlsEnabled = cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME;
34
+
options.services.agorakit = {
35
+
enable = mkEnableOption "agorakit";
38
+
default = "agorakit";
39
+
description = "User agorakit runs as.";
44
+
default = "agorakit";
45
+
description = "Group agorakit runs as.";
49
+
appKeyFile = mkOption {
51
+
A file containing the Laravel APP_KEY - a 32 character long,
52
+
base64 encoded key used for encryption where needed. Can be
53
+
generated with <code>head -c 32 /dev/urandom | base64</code>.
55
+
example = "/run/keys/agorakit-appkey";
59
+
hostName = lib.mkOption {
60
+
type = lib.types.str;
62
+
if config.networking.domain != null then config.networking.fqdn else config.networking.hostName;
63
+
defaultText = lib.literalExpression "config.networking.fqdn";
64
+
example = "agorakit.example.com";
66
+
The hostname to serve agorakit on.
72
+
The root URL that you want to host agorakit on. All URLs in agorakit will be generated using this value.
73
+
If you change this in the future you may need to run a command to update stored URLs in the database.
74
+
Command example: <code>php artisan agorakit:update-url https://old.example.com https://new.example.com</code>
76
+
default = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostName}";
77
+
defaultText = ''http''${lib.optionalString tlsEnabled "s"}://''${cfg.hostName}'';
78
+
example = "https://example.com";
82
+
dataDir = mkOption {
83
+
description = "agorakit data directory";
84
+
default = "/var/lib/agorakit";
91
+
default = "localhost";
92
+
description = "Database host address.";
97
+
description = "Database host port.";
101
+
default = "agorakit";
102
+
description = "Database name.";
107
+
defaultText = lib.literalExpression "user";
108
+
description = "Database username.";
110
+
passwordFile = mkOption {
111
+
type = with types; nullOr path;
113
+
example = "/run/keys/agorakit-dbpassword";
115
+
A file containing the password corresponding to
116
+
<option>database.user</option>.
119
+
createLocally = mkOption {
122
+
description = "Create the database and database user locally.";
127
+
driver = mkOption {
128
+
type = types.enum [
133
+
description = "Mail driver to use.";
137
+
default = "localhost";
138
+
description = "Mail host address.";
143
+
description = "Mail host port.";
145
+
fromName = mkOption {
147
+
default = "agorakit";
148
+
description = "Mail \"from\" name.";
152
+
default = "mail@agorakit.com";
153
+
description = "Mail \"from\" email.";
156
+
type = with types; nullOr str;
158
+
example = "agorakit";
159
+
description = "Mail username.";
161
+
passwordFile = mkOption {
162
+
type = with types; nullOr path;
164
+
example = "/run/keys/agorakit-mailpassword";
166
+
A file containing the password corresponding to
167
+
<option>mail.user</option>.
170
+
encryption = mkOption {
171
+
type = with types; nullOr (enum [ "tls" ]);
173
+
description = "SMTP encryption mechanism to use.";
177
+
maxUploadSize = mkOption {
181
+
description = "The maximum size for uploads (e.g. images).";
184
+
poolConfig = mkOption {
194
+
"pm.max_children" = 32;
195
+
"pm.start_servers" = 2;
196
+
"pm.min_spare_servers" = 2;
197
+
"pm.max_spare_servers" = 4;
198
+
"pm.max_requests" = 500;
201
+
Options for the agorakit PHP pool. See the documentation on <literal>php-fpm.conf</literal>
202
+
for details on configuration directives.
207
+
type = types.submodule (
208
+
recursiveUpdate (import ../web-servers/nginx/vhost-options.nix {
209
+
inherit config lib;
216
+
"agorakit.''${config.networking.domain}"
218
+
# To enable encryption and let let's encrypt take care of certificate
224
+
With this option, you can customize the nginx virtualHost settings.
228
+
config = mkOption {
243
+
_secret = mkOption {
246
+
The path to a file containing the value the
247
+
option should be set to in the final
248
+
configuration file.
258
+
ALLOWED_IFRAME_HOSTS = "https://example.com";
259
+
AUTH_METHOD = "oidc";
260
+
OIDC_NAME = "MyLogin";
261
+
OIDC_DISPLAY_NAME_CLAIMS = "name";
262
+
OIDC_CLIENT_ID = "agorakit";
263
+
OIDC_CLIENT_SECRET = {_secret = "/run/keys/oidc_secret"};
264
+
OIDC_ISSUER = "https://keycloak.example.com/auth/realms/My%20Realm";
265
+
OIDC_ISSUER_DISCOVER = true;
269
+
Agorakit configuration options to set in the
270
+
<filename>.env</filename> file.
272
+
Refer to <link xlink:href="https://github.com/agorakit/agorakit"/>
273
+
for details on supported values.
275
+
Settings containing secret data should be set to an attribute
276
+
set containing the attribute <literal>_secret</literal> - a
277
+
string pointing to a file containing the value the option
278
+
should be set to. See the example to get a better picture of
279
+
this: in the resulting <filename>.env</filename> file, the
280
+
<literal>OIDC_CLIENT_SECRET</literal> key will be set to the
281
+
contents of the <filename>/run/keys/oidc_secret</filename>
287
+
config = mkIf cfg.enable {
290
+
assertion = db.createLocally -> db.user == user;
291
+
message = "services.agorakit.database.user must be set to ${user} if services.agorakit.database.createLocally is set true.";
294
+
assertion = db.createLocally -> db.passwordFile == null;
295
+
message = "services.agorakit.database.passwordFile cannot be specified if services.agorakit.database.createLocally is set to true.";
299
+
services.agorakit.config = {
300
+
APP_ENV = "production";
301
+
APP_KEY._secret = cfg.appKeyFile;
302
+
APP_URL = cfg.appURL;
305
+
DB_DATABASE = db.name;
306
+
DB_USERNAME = db.user;
307
+
MAIL_DRIVER = mail.driver;
308
+
MAIL_FROM_NAME = mail.fromName;
309
+
MAIL_FROM = mail.from;
310
+
MAIL_HOST = mail.host;
311
+
MAIL_PORT = mail.port;
312
+
MAIL_USERNAME = mail.user;
313
+
MAIL_ENCRYPTION = mail.encryption;
314
+
DB_PASSWORD._secret = db.passwordFile;
315
+
MAIL_PASSWORD._secret = mail.passwordFile;
316
+
APP_SERVICES_CACHE = "/run/agorakit/cache/services.php";
317
+
APP_PACKAGES_CACHE = "/run/agorakit/cache/packages.php";
318
+
APP_CONFIG_CACHE = "/run/agorakit/cache/config.php";
319
+
APP_ROUTES_CACHE = "/run/agorakit/cache/routes-v7.php";
320
+
APP_EVENTS_CACHE = "/run/agorakit/cache/events.php";
321
+
SESSION_SECURE_COOKIE = tlsEnabled;
324
+
environment.systemPackages = [ artisan ];
326
+
services.mysql = mkIf db.createLocally {
328
+
package = mkDefault pkgs.mysql;
329
+
ensureDatabases = [ db.name ];
333
+
ensurePermissions = {
334
+
"${db.name}.*" = "ALL PRIVILEGES";
340
+
services.phpfpm.pools.agorakit = {
341
+
inherit user group;
344
+
post_max_size = ${cfg.maxUploadSize}
345
+
upload_max_filesize = ${cfg.maxUploadSize}
348
+
"listen.mode" = "0660";
349
+
"listen.owner" = user;
350
+
"listen.group" = group;
351
+
} // cfg.poolConfig;
355
+
enable = mkDefault true;
356
+
recommendedTlsSettings = true;
357
+
recommendedOptimisation = true;
358
+
recommendedGzipSettings = true;
359
+
recommendedBrotliSettings = true;
360
+
recommendedProxySettings = true;
361
+
virtualHosts.${cfg.hostName} = mkMerge [
364
+
root = mkForce "${agorakit}/public";
367
+
index = "index.php";
368
+
tryFiles = "$uri $uri/ /index.php?$query_string";
370
+
"~ \.php$".extraConfig = ''
371
+
fastcgi_pass unix:${config.services.phpfpm.pools."agorakit".socket};
373
+
"~ \.(js|css|gif|png|ico|jpg|jpeg)$" = {
374
+
extraConfig = "expires 365d;";
381
+
systemd.services.agorakit-setup = {
382
+
description = "Preparation tasks for agorakit";
383
+
before = [ "phpfpm-agorakit.service" ];
384
+
after = optional db.createLocally "mysql.service";
385
+
wantedBy = [ "multi-user.target" ];
388
+
RemainAfterExit = true;
391
+
WorkingDirectory = "${agorakit}";
392
+
RuntimeDirectory = "agorakit/cache";
393
+
RuntimeDirectoryMode = 700;
395
+
path = [ pkgs.replace-secret ];
398
+
isSecret = v: isAttrs v && v ? _secret && isString v._secret;
399
+
agorakitEnvVars = lib.generators.toKeyValue {
400
+
mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
406
+
else if isString v then
408
+
else if true == v then
410
+
else if false == v then
412
+
else if isSecret v then
413
+
hashString "sha256" v._secret
415
+
throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty { }) v}";
418
+
secretPaths = lib.mapAttrsToList (_: v: v._secret) (lib.filterAttrs (_: isSecret) cfg.config);
419
+
mkSecretReplacement = file: ''
422
+
(builtins.hashString "sha256" file)
424
+
"${cfg.dataDir}/.env"
428
+
secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
429
+
filteredConfig = lib.converge (lib.filterAttrsRecursive (
436
+
agorakitEnv = pkgs.writeText "agorakit.env" (agorakitEnvVars filteredConfig);
443
+
install -T -m 0600 -o ${user} ${agorakitEnv} "${cfg.dataDir}/.env"
444
+
${secretReplacements}
445
+
if ! grep 'APP_KEY=base64:' "${cfg.dataDir}/.env" >/dev/null; then
446
+
sed -i 's/APP_KEY=/APP_KEY=base64:/' "${cfg.dataDir}/.env"
449
+
# migrate & seed db
450
+
${pkgs.php}/bin/php artisan key:generate --force
451
+
${pkgs.php}/bin/php artisan migrate --force
452
+
${pkgs.php}/bin/php artisan config:cache
456
+
systemd.tmpfiles.rules = [
457
+
"d ${cfg.dataDir} 0710 ${user} ${group} - -"
458
+
"d ${cfg.dataDir}/public 0750 ${user} ${group} - -"
459
+
"d ${cfg.dataDir}/public/uploads 0750 ${user} ${group} - -"
460
+
"d ${cfg.dataDir}/storage 0700 ${user} ${group} - -"
461
+
"d ${cfg.dataDir}/storage/app 0700 ${user} ${group} - -"
462
+
"d ${cfg.dataDir}/storage/fonts 0700 ${user} ${group} - -"
463
+
"d ${cfg.dataDir}/storage/framework 0700 ${user} ${group} - -"
464
+
"d ${cfg.dataDir}/storage/framework/cache 0700 ${user} ${group} - -"
465
+
"d ${cfg.dataDir}/storage/framework/sessions 0700 ${user} ${group} - -"
466
+
"d ${cfg.dataDir}/storage/framework/views 0700 ${user} ${group} - -"
467
+
"d ${cfg.dataDir}/storage/logs 0700 ${user} ${group} - -"
468
+
"d ${cfg.dataDir}/storage/uploads 0700 ${user} ${group} - -"
472
+
users = mkIf (user == "agorakit") {
475
+
isSystemUser = true;
477
+
"${config.services.nginx.user}".extraGroups = [ group ];
479
+
groups = mkIf (group == "agorakit") { agorakit = { }; };