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 "monica";
39 description = "User monica runs as.";
45 description = "Group monica 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/monica-appkey";
59 hostname = lib.mkOption {
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";
68 The hostname to serve monica on.
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";
85 description = "monica data directory";
86 default = "/var/lib/monica";
93 default = "localhost";
94 description = "Database host address.";
99 description = "Database host port.";
104 description = "Database name.";
109 defaultText = lib.literalExpression "user";
110 description = "Database username.";
112 passwordFile = mkOption {
113 type = with types; nullOr path;
115 example = "/run/keys/monica-dbpassword";
117 A file containing the password corresponding to
118 <option>database.user</option>.
121 createLocally = mkOption {
124 description = "Create the database and database user locally.";
130 type = types.enum ["smtp" "sendmail"];
132 description = "Mail driver to use.";
136 default = "localhost";
137 description = "Mail host address.";
142 description = "Mail host port.";
144 fromName = mkOption {
147 description = "Mail \"from\" name.";
151 default = "mail@monica.com";
152 description = "Mail \"from\" email.";
155 type = with types; nullOr str;
158 description = "Mail username.";
160 passwordFile = mkOption {
161 type = with types; nullOr path;
163 example = "/run/keys/monica-mailpassword";
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 = "SMTP encryption mechanism to use.";
176 maxUploadSize = mkOption {
180 description = "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;
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
216 With this option, you can customize the nginx virtualHost settings.
237 The path to a file containing the value the
238 option should be set to in the final
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;
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 = {
332 post_max_size = ${cfg.maxUploadSize}
333 upload_max_filesize = ${cfg.maxUploadSize}
336 "listen.mode" = "0660";
337 "listen.owner" = user;
338 "listen.group" = group;
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";
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 = "Preparation 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"
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") {
460 "${config.services.nginx.user}".extraGroups = [group];
462 groups = mkIf (group == "monica") {