···
+
cfg = config.services.monica;
+
monica = pkgs.monica.override {
+
# shell script for local administration
+
artisan = pkgs.writeScriptBin "monica" ''
+
#! ${pkgs.runtimeShell}
+
if [[ "$USER" != ${user} ]]; then
+
exec /run/wrappers/bin/sudo -u ${user} "$@"
+
sudo ${pkgs.php}/bin/php artisan "$@"
+
tlsEnabled = cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME;
+
options.services.monica = {
+
enable = mkEnableOption (lib.mdDoc "monica");
+
description = lib.mdDoc "User monica runs as.";
+
description = lib.mdDoc "Group monica runs as.";
+
appKeyFile = mkOption {
+
description = lib.mdDoc ''
+
A file containing the Laravel APP_KEY - a 32 character long,
+
base64 encoded key used for encryption where needed. Can be
+
generated with <code>head -c 32 /dev/urandom | base64</code>.
+
example = "/run/keys/monica-appkey";
+
hostname = lib.mkOption {
+
if config.networking.domain != null
+
then config.networking.fqdn
+
else config.networking.hostName;
+
defaultText = lib.literalExpression "config.networking.fqdn";
+
example = "monica.example.com";
+
description = lib.mdDoc ''
+
The hostname to serve monica on.
+
description = lib.mdDoc ''
+
The root URL that you want to host monica on. All URLs in monica will be generated using this value.
+
If you change this in the future you may need to run a command to update stored URLs in the database.
+
Command example: <code>php artisan monica:update-url https://old.example.com https://new.example.com</code>
+
default = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostname}";
+
defaultText = ''http''${lib.optionalString tlsEnabled "s"}://''${cfg.hostname}'';
+
example = "https://example.com";
+
description = lib.mdDoc "monica data directory";
+
default = "/var/lib/monica";
+
description = lib.mdDoc "Database host address.";
+
description = lib.mdDoc "Database host port.";
+
description = lib.mdDoc "Database name.";
+
defaultText = lib.literalExpression "user";
+
description = lib.mdDoc "Database username.";
+
passwordFile = mkOption {
+
type = with types; nullOr path;
+
example = "/run/keys/monica-dbpassword";
+
description = lib.mdDoc ''
+
A file containing the password corresponding to
+
<option>database.user</option>.
+
createLocally = mkOption {
+
description = lib.mdDoc "Create the database and database user locally.";
+
type = types.enum ["smtp" "sendmail"];
+
description = lib.mdDoc "Mail driver to use.";
+
description = lib.mdDoc "Mail host address.";
+
description = lib.mdDoc "Mail host port.";
+
description = lib.mdDoc "Mail \"from\" name.";
+
default = "mail@monica.com";
+
description = lib.mdDoc "Mail \"from\" email.";
+
type = with types; nullOr str;
+
description = lib.mdDoc "Mail username.";
+
passwordFile = mkOption {
+
type = with types; nullOr path;
+
example = "/run/keys/monica-mailpassword";
+
description = lib.mdDoc ''
+
A file containing the password corresponding to
+
<option>mail.user</option>.
+
encryption = mkOption {
+
type = with types; nullOr (enum ["tls"]);
+
description = lib.mdDoc "SMTP encryption mechanism to use.";
+
maxUploadSize = mkOption {
+
description = lib.mdDoc "The maximum size for uploads (e.g. images).";
+
poolConfig = mkOption {
+
type = with types; attrsOf (oneOf [str int bool]);
+
"pm.max_children" = 32;
+
"pm.start_servers" = 2;
+
"pm.min_spare_servers" = 2;
+
"pm.max_spare_servers" = 4;
+
"pm.max_requests" = 500;
+
description = lib.mdDoc ''
+
Options for the monica PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+
for details on configuration directives.
+
type = types.submodule (
+
(import ../web-servers/nginx/vhost-options.nix {inherit config lib;}) {}
+
"monica.''${config.networking.domain}"
+
# To enable encryption and let let's encrypt take care of certificate
+
description = lib.mdDoc ''
+
With this option, you can customize the nginx virtualHost settings.
+
description = lib.mdDoc ''
+
The path to a file containing the value the
+
option should be set to in the final
+
ALLOWED_IFRAME_HOSTS = "https://example.com";
+
WKHTMLTOPDF = "/home/user/bins/wkhtmltopdf";
+
OIDC_DISPLAY_NAME_CLAIMS = "name";
+
OIDC_CLIENT_ID = "monica";
+
OIDC_CLIENT_SECRET = {_secret = "/run/keys/oidc_secret"};
+
OIDC_ISSUER = "https://keycloak.example.com/auth/realms/My%20Realm";
+
OIDC_ISSUER_DISCOVER = true;
+
description = lib.mdDoc ''
+
monica configuration options to set in the
+
<filename>.env</filename> file.
+
Refer to <link xlink:href="https://github.com/monicahq/monica"/>
+
for details on supported values.
+
Settings containing secret data should be set to an attribute
+
set containing the attribute <literal>_secret</literal> - a
+
string pointing to a file containing the value the option
+
should be set to. See the example to get a better picture of
+
this: in the resulting <filename>.env</filename> file, the
+
<literal>OIDC_CLIENT_SECRET</literal> key will be set to the
+
contents of the <filename>/run/keys/oidc_secret</filename>
+
config = mkIf cfg.enable {
+
assertion = db.createLocally -> db.user == user;
+
message = "services.monica.database.user must be set to ${user} if services.monica.database.createLocally is set true.";
+
assertion = db.createLocally -> db.passwordFile == null;
+
message = "services.monica.database.passwordFile cannot be specified if services.monica.database.createLocally is set to true.";
+
services.monica.config = {
+
APP_ENV = "production";
+
APP_KEY._secret = cfg.appKeyFile;
+
MAIL_DRIVER = mail.driver;
+
MAIL_FROM_NAME = mail.fromName;
+
MAIL_USERNAME = mail.user;
+
MAIL_ENCRYPTION = mail.encryption;
+
DB_PASSWORD._secret = db.passwordFile;
+
MAIL_PASSWORD._secret = mail.passwordFile;
+
APP_SERVICES_CACHE = "/run/monica/cache/services.php";
+
APP_PACKAGES_CACHE = "/run/monica/cache/packages.php";
+
APP_CONFIG_CACHE = "/run/monica/cache/config.php";
+
APP_ROUTES_CACHE = "/run/monica/cache/routes-v7.php";
+
APP_EVENTS_CACHE = "/run/monica/cache/events.php";
+
SESSION_SECURE_COOKIE = tlsEnabled;
+
environment.systemPackages = [artisan];
+
services.mysql = mkIf db.createLocally {
+
package = mkDefault pkgs.mariadb;
+
ensureDatabases = [db.name];
+
ensurePermissions = {"${db.name}.*" = "ALL PRIVILEGES";};
+
services.phpfpm.pools.monica = {
+
post_max_size = ${cfg.maxUploadSize}
+
upload_max_filesize = ${cfg.maxUploadSize}
+
"listen.mode" = "0660";
+
"listen.group" = group;
+
enable = mkDefault true;
+
recommendedTlsSettings = true;
+
recommendedOptimisation = true;
+
recommendedGzipSettings = true;
+
recommendedBrotliSettings = true;
+
recommendedProxySettings = true;
+
virtualHosts.${cfg.hostname} = mkMerge [
+
root = mkForce "${monica}/public";
+
tryFiles = "$uri $uri/ /index.php?$query_string";
+
"~ \.php$".extraConfig = ''
+
fastcgi_pass unix:${config.services.phpfpm.pools."monica".socket};
+
"~ \.(js|css|gif|png|ico|jpg|jpeg)$" = {
+
extraConfig = "expires 365d;";
+
systemd.services.monica-setup = {
+
description = "Preperation tasks for monica";
+
before = ["phpfpm-monica.service"];
+
after = optional db.createLocally "mysql.service";
+
wantedBy = ["multi-user.target"];
+
RemainAfterExit = true;
+
WorkingDirectory = "${monica}";
+
RuntimeDirectory = "monica/cache";
+
RuntimeDirectoryMode = 0700;
+
path = [pkgs.replace-secret];
+
isSecret = v: isAttrs v && v ? _secret && isString v._secret;
+
monicaEnvVars = lib.generators.toKeyValue {
+
mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
+
then hashString "sha256" v._secret
+
else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
+
secretPaths = lib.mapAttrsToList (_: v: v._secret) (lib.filterAttrs (_: isSecret) cfg.config);
+
mkSecretReplacement = file: ''
+
replace-secret ${escapeShellArgs [(builtins.hashString "sha256" file) file "${cfg.dataDir}/.env"]}
+
secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
+
filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [{} null])) cfg.config;
+
monicaEnv = pkgs.writeText "monica.env" (monicaEnvVars filteredConfig);
+
install -T -m 0600 -o ${user} ${monicaEnv} "${cfg.dataDir}/.env"
+
if ! grep 'APP_KEY=base64:' "${cfg.dataDir}/.env" >/dev/null; then
+
sed -i 's/APP_KEY=/APP_KEY=base64:/' "${cfg.dataDir}/.env"
+
${pkgs.php}/bin/php artisan key:generate --force
+
${pkgs.php}/bin/php artisan setup:production -v --force
+
systemd.services.monica-scheduler = {
+
description = "Background tasks for monica";
+
after = ["monica-setup.service"];
+
WorkingDirectory = "${monica}";
+
ExecStart = "${pkgs.php}/bin/php ${monica}/artisan schedule:run -v";
+
systemd.tmpfiles.rules = [
+
"d ${cfg.dataDir} 0710 ${user} ${group} - -"
+
"d ${cfg.dataDir}/public 0750 ${user} ${group} - -"
+
"d ${cfg.dataDir}/public/uploads 0750 ${user} ${group} - -"
+
"d ${cfg.dataDir}/storage 0700 ${user} ${group} - -"
+
"d ${cfg.dataDir}/storage/app 0700 ${user} ${group} - -"
+
"d ${cfg.dataDir}/storage/fonts 0700 ${user} ${group} - -"
+
"d ${cfg.dataDir}/storage/framework 0700 ${user} ${group} - -"
+
"d ${cfg.dataDir}/storage/framework/cache 0700 ${user} ${group} - -"
+
"d ${cfg.dataDir}/storage/framework/sessions 0700 ${user} ${group} - -"
+
"d ${cfg.dataDir}/storage/framework/views 0700 ${user} ${group} - -"
+
"d ${cfg.dataDir}/storage/logs 0700 ${user} ${group} - -"
+
"d ${cfg.dataDir}/storage/uploads 0700 ${user} ${group} - -"
+
users = mkIf (user == "monica") {
+
"${config.services.nginx.user}".extraGroups = [group];
+
groups = mkIf (group == "monica") {