···
8
+
cfg = config.services.monica;
9
+
monica = pkgs.monica.override {
10
+
dataDir = cfg.dataDir;
18
+
# shell script for local administration
19
+
artisan = pkgs.writeScriptBin "monica" ''
20
+
#! ${pkgs.runtimeShell}
23
+
if [[ "$USER" != ${user} ]]; then
24
+
exec /run/wrappers/bin/sudo -u ${user} "$@"
29
+
sudo ${pkgs.php}/bin/php artisan "$@"
32
+
tlsEnabled = cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME;
34
+
options.services.monica = {
35
+
enable = mkEnableOption (lib.mdDoc "monica");
39
+
description = lib.mdDoc "User monica runs as.";
45
+
description = lib.mdDoc "Group monica runs as.";
49
+
appKeyFile = mkOption {
50
+
description = lib.mdDoc ''
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/monica-appkey";
59
+
hostname = lib.mkOption {
60
+
type = lib.types.str;
62
+
if config.networking.domain != null
63
+
then config.networking.fqdn
64
+
else config.networking.hostName;
65
+
defaultText = lib.literalExpression "config.networking.fqdn";
66
+
example = "monica.example.com";
67
+
description = lib.mdDoc ''
68
+
The hostname to serve monica on.
73
+
description = lib.mdDoc ''
74
+
The root URL that you want to host monica on. All URLs in monica will be generated using this value.
75
+
If you change this in the future you may need to run a command to update stored URLs in the database.
76
+
Command example: <code>php artisan monica:update-url https://old.example.com https://new.example.com</code>
78
+
default = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostname}";
79
+
defaultText = ''http''${lib.optionalString tlsEnabled "s"}://''${cfg.hostname}'';
80
+
example = "https://example.com";
84
+
dataDir = mkOption {
85
+
description = lib.mdDoc "monica data directory";
86
+
default = "/var/lib/monica";
93
+
default = "localhost";
94
+
description = lib.mdDoc "Database host address.";
99
+
description = lib.mdDoc "Database host port.";
103
+
default = "monica";
104
+
description = lib.mdDoc "Database name.";
109
+
defaultText = lib.literalExpression "user";
110
+
description = lib.mdDoc "Database username.";
112
+
passwordFile = mkOption {
113
+
type = with types; nullOr path;
115
+
example = "/run/keys/monica-dbpassword";
116
+
description = lib.mdDoc ''
117
+
A file containing the password corresponding to
118
+
<option>database.user</option>.
121
+
createLocally = mkOption {
124
+
description = lib.mdDoc "Create the database and database user locally.";
129
+
driver = mkOption {
130
+
type = types.enum ["smtp" "sendmail"];
132
+
description = lib.mdDoc "Mail driver to use.";
136
+
default = "localhost";
137
+
description = lib.mdDoc "Mail host address.";
142
+
description = lib.mdDoc "Mail host port.";
144
+
fromName = mkOption {
146
+
default = "monica";
147
+
description = lib.mdDoc "Mail \"from\" name.";
151
+
default = "mail@monica.com";
152
+
description = lib.mdDoc "Mail \"from\" email.";
155
+
type = with types; nullOr str;
157
+
example = "monica";
158
+
description = lib.mdDoc "Mail username.";
160
+
passwordFile = mkOption {
161
+
type = with types; nullOr path;
163
+
example = "/run/keys/monica-mailpassword";
164
+
description = lib.mdDoc ''
165
+
A file containing the password corresponding to
166
+
<option>mail.user</option>.
169
+
encryption = mkOption {
170
+
type = with types; nullOr (enum ["tls"]);
172
+
description = lib.mdDoc "SMTP encryption mechanism to use.";
176
+
maxUploadSize = mkOption {
180
+
description = lib.mdDoc "The maximum size for uploads (e.g. images).";
183
+
poolConfig = mkOption {
184
+
type = with types; attrsOf (oneOf [str int bool]);
187
+
"pm.max_children" = 32;
188
+
"pm.start_servers" = 2;
189
+
"pm.min_spare_servers" = 2;
190
+
"pm.max_spare_servers" = 4;
191
+
"pm.max_requests" = 500;
193
+
description = lib.mdDoc ''
194
+
Options for the monica PHP pool. See the documentation on <literal>php-fpm.conf</literal>
195
+
for details on configuration directives.
200
+
type = types.submodule (
202
+
(import ../web-servers/nginx/vhost-options.nix {inherit config lib;}) {}
208
+
"monica.''${config.networking.domain}"
210
+
# To enable encryption and let let's encrypt take care of certificate
215
+
description = lib.mdDoc ''
216
+
With this option, you can customize the nginx virtualHost settings.
220
+
config = mkOption {
234
+
_secret = mkOption {
236
+
description = lib.mdDoc ''
237
+
The path to a file containing the value the
238
+
option should be set to in the final
239
+
configuration file.
247
+
ALLOWED_IFRAME_HOSTS = "https://example.com";
248
+
WKHTMLTOPDF = "/home/user/bins/wkhtmltopdf";
249
+
AUTH_METHOD = "oidc";
250
+
OIDC_NAME = "MyLogin";
251
+
OIDC_DISPLAY_NAME_CLAIMS = "name";
252
+
OIDC_CLIENT_ID = "monica";
253
+
OIDC_CLIENT_SECRET = {_secret = "/run/keys/oidc_secret"};
254
+
OIDC_ISSUER = "https://keycloak.example.com/auth/realms/My%20Realm";
255
+
OIDC_ISSUER_DISCOVER = true;
258
+
description = lib.mdDoc ''
259
+
monica configuration options to set in the
260
+
<filename>.env</filename> file.
262
+
Refer to <link xlink:href="https://github.com/monicahq/monica"/>
263
+
for details on supported values.
265
+
Settings containing secret data should be set to an attribute
266
+
set containing the attribute <literal>_secret</literal> - a
267
+
string pointing to a file containing the value the option
268
+
should be set to. See the example to get a better picture of
269
+
this: in the resulting <filename>.env</filename> file, the
270
+
<literal>OIDC_CLIENT_SECRET</literal> key will be set to the
271
+
contents of the <filename>/run/keys/oidc_secret</filename>
277
+
config = mkIf cfg.enable {
280
+
assertion = db.createLocally -> db.user == user;
281
+
message = "services.monica.database.user must be set to ${user} if services.monica.database.createLocally is set true.";
284
+
assertion = db.createLocally -> db.passwordFile == null;
285
+
message = "services.monica.database.passwordFile cannot be specified if services.monica.database.createLocally is set to true.";
289
+
services.monica.config = {
290
+
APP_ENV = "production";
291
+
APP_KEY._secret = cfg.appKeyFile;
292
+
APP_URL = cfg.appURL;
295
+
DB_DATABASE = db.name;
296
+
DB_USERNAME = db.user;
297
+
MAIL_DRIVER = mail.driver;
298
+
MAIL_FROM_NAME = mail.fromName;
299
+
MAIL_FROM = mail.from;
300
+
MAIL_HOST = mail.host;
301
+
MAIL_PORT = mail.port;
302
+
MAIL_USERNAME = mail.user;
303
+
MAIL_ENCRYPTION = mail.encryption;
304
+
DB_PASSWORD._secret = db.passwordFile;
305
+
MAIL_PASSWORD._secret = mail.passwordFile;
306
+
APP_SERVICES_CACHE = "/run/monica/cache/services.php";
307
+
APP_PACKAGES_CACHE = "/run/monica/cache/packages.php";
308
+
APP_CONFIG_CACHE = "/run/monica/cache/config.php";
309
+
APP_ROUTES_CACHE = "/run/monica/cache/routes-v7.php";
310
+
APP_EVENTS_CACHE = "/run/monica/cache/events.php";
311
+
SESSION_SECURE_COOKIE = tlsEnabled;
314
+
environment.systemPackages = [artisan];
316
+
services.mysql = mkIf db.createLocally {
318
+
package = mkDefault pkgs.mariadb;
319
+
ensureDatabases = [db.name];
323
+
ensurePermissions = {"${db.name}.*" = "ALL PRIVILEGES";};
328
+
services.phpfpm.pools.monica = {
329
+
inherit user group;
332
+
post_max_size = ${cfg.maxUploadSize}
333
+
upload_max_filesize = ${cfg.maxUploadSize}
336
+
"listen.mode" = "0660";
337
+
"listen.owner" = user;
338
+
"listen.group" = group;
339
+
} // cfg.poolConfig;
343
+
enable = mkDefault true;
344
+
recommendedTlsSettings = true;
345
+
recommendedOptimisation = true;
346
+
recommendedGzipSettings = true;
347
+
recommendedBrotliSettings = true;
348
+
recommendedProxySettings = true;
349
+
virtualHosts.${cfg.hostname} = mkMerge [
352
+
root = mkForce "${monica}/public";
355
+
index = "index.php";
356
+
tryFiles = "$uri $uri/ /index.php?$query_string";
358
+
"~ \.php$".extraConfig = ''
359
+
fastcgi_pass unix:${config.services.phpfpm.pools."monica".socket};
361
+
"~ \.(js|css|gif|png|ico|jpg|jpeg)$" = {
362
+
extraConfig = "expires 365d;";
369
+
systemd.services.monica-setup = {
370
+
description = "Preperation tasks for monica";
371
+
before = ["phpfpm-monica.service"];
372
+
after = optional db.createLocally "mysql.service";
373
+
wantedBy = ["multi-user.target"];
376
+
RemainAfterExit = true;
379
+
WorkingDirectory = "${monica}";
380
+
RuntimeDirectory = "monica/cache";
381
+
RuntimeDirectoryMode = 0700;
383
+
path = [pkgs.replace-secret];
385
+
isSecret = v: isAttrs v && v ? _secret && isString v._secret;
386
+
monicaEnvVars = lib.generators.toKeyValue {
387
+
mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
399
+
then hashString "sha256" v._secret
400
+
else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
403
+
secretPaths = lib.mapAttrsToList (_: v: v._secret) (lib.filterAttrs (_: isSecret) cfg.config);
404
+
mkSecretReplacement = file: ''
405
+
replace-secret ${escapeShellArgs [(builtins.hashString "sha256" file) file "${cfg.dataDir}/.env"]}
407
+
secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
408
+
filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [{} null])) cfg.config;
409
+
monicaEnv = pkgs.writeText "monica.env" (monicaEnvVars filteredConfig);
415
+
install -T -m 0600 -o ${user} ${monicaEnv} "${cfg.dataDir}/.env"
416
+
${secretReplacements}
417
+
if ! grep 'APP_KEY=base64:' "${cfg.dataDir}/.env" >/dev/null; then
418
+
sed -i 's/APP_KEY=/APP_KEY=base64:/' "${cfg.dataDir}/.env"
421
+
# migrate & seed db
422
+
${pkgs.php}/bin/php artisan key:generate --force
423
+
${pkgs.php}/bin/php artisan setup:production -v --force
427
+
systemd.services.monica-scheduler = {
428
+
description = "Background tasks for monica";
429
+
startAt = "minutely";
430
+
after = ["monica-setup.service"];
434
+
WorkingDirectory = "${monica}";
435
+
ExecStart = "${pkgs.php}/bin/php ${monica}/artisan schedule:run -v";
439
+
systemd.tmpfiles.rules = [
440
+
"d ${cfg.dataDir} 0710 ${user} ${group} - -"
441
+
"d ${cfg.dataDir}/public 0750 ${user} ${group} - -"
442
+
"d ${cfg.dataDir}/public/uploads 0750 ${user} ${group} - -"
443
+
"d ${cfg.dataDir}/storage 0700 ${user} ${group} - -"
444
+
"d ${cfg.dataDir}/storage/app 0700 ${user} ${group} - -"
445
+
"d ${cfg.dataDir}/storage/fonts 0700 ${user} ${group} - -"
446
+
"d ${cfg.dataDir}/storage/framework 0700 ${user} ${group} - -"
447
+
"d ${cfg.dataDir}/storage/framework/cache 0700 ${user} ${group} - -"
448
+
"d ${cfg.dataDir}/storage/framework/sessions 0700 ${user} ${group} - -"
449
+
"d ${cfg.dataDir}/storage/framework/views 0700 ${user} ${group} - -"
450
+
"d ${cfg.dataDir}/storage/logs 0700 ${user} ${group} - -"
451
+
"d ${cfg.dataDir}/storage/uploads 0700 ${user} ${group} - -"
455
+
users = mkIf (user == "monica") {
458
+
isSystemUser = true;
460
+
"${config.services.nginx.user}".extraGroups = [group];
462
+
groups = mkIf (group == "monica") {