···
+
{ pkgs, config, lib, ... }:
+
inherit (lib) optionalString mkDefault mkIf mkOption mkEnableOption literalExpression;
+
inherit (lib.types) nullOr attrsOf oneOf str int bool path package enum submodule;
+
inherit (lib.strings) concatMapStringsSep removePrefix toShellVars removeSuffix hasSuffix;
+
inherit (lib.attrsets) attrValues genAttrs filterAttrs mapAttrs' nameValuePair;
+
inherit (builtins) isInt isString toString typeOf;
+
cfg = config.services.firefly-iii;
+
defaultUser = "firefly-iii";
+
defaultGroup = "firefly-iii";
+
artisan = "${cfg.package}/artisan";
+
env-file-values = mapAttrs' (n: v: nameValuePair (removeSuffix "_FILE" n) v)
+
(filterAttrs (n: v: hasSuffix "_FILE" n) cfg.settings);
+
env-nonfile-values = filterAttrs (n: v: ! hasSuffix "_FILE" n) cfg.settings;
+
envfile = pkgs.writeText "firefly-iii-env" ''
+
${toShellVars env-file-values}
+
${toShellVars env-nonfile-values}
+
cp --no-preserve=mode ${envfile} /tmp/firefly-iii-env
+
${concatMapStringsSep "\n"
+
(n: "${pkgs.replace-secret}/bin/replace-secret ${n} ${n} /tmp/firefly-iii-env")
+
(attrValues env-file-values)}
+
firefly-iii-maintenance = pkgs.writeShellScript "firefly-iii-maintenance.sh" ''
+
${optionalString (cfg.settings.DB_CONNECTION == "sqlite")
+
"touch ${cfg.dataDir}/storage/database/database.sqlite"}
+
${artisan} migrate --seed --no-interaction --force
+
${artisan} firefly-iii:decrypt-all
+
${artisan} firefly-iii:upgrade-database
+
${artisan} firefly-iii:correct-database
+
${artisan} firefly-iii:report-integrity
+
${artisan} firefly-iii:laravel-passport-keys
+
mv /tmp/firefly-iii-env /run/phpfpm/firefly-iii-env
+
commonServiceConfig = {
+
StateDirectory = "${removePrefix "/var/lib/" cfg.dataDir}";
+
WorkingDirectory = cfg.package;
+
CapabilityBoundingSet = "";
+
AmbientCapabilities = "";
+
ProtectSystem = "strict";
+
ProtectKernelTunables = true;
+
ProtectKernelModules = true;
+
ProtectControlGroups = true;
+
ProtectHostname = true;
+
ProtectKernelLogs = true;
+
ProtectProc = "invisible";
+
PrivateNetwork = false;
+
RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
+
SystemCallArchitectures = "native";
+
"@system-service @resources"
+
"~@obsolete @privileged"
+
RestrictSUIDSGID = true;
+
NoNewPrivileges = true;
+
RestrictRealtime = true;
+
RestrictNamespaces = true;
+
LockPersonality = true;
+
options.services.firefly-iii = {
+
enable = mkEnableOption "Firefly III: A free and open source personal finance manager";
+
description = "User account under which firefly-iii runs.";
+
default = if cfg.enableNginx then "nginx" else defaultGroup;
+
defaultText = "If `services.firefly-iii.enableNginx` is true then `nginx` else ${defaultGroup}";
+
Group under which firefly-iii runs. It is best to set this to the group
+
of whatever webserver is being used as the frontend.
+
default = "/var/lib/firefly-iii";
+
The place where firefly-iii stores its state.
+
default = pkgs.firefly-iii;
+
defaultText = literalExpression "pkgs.firefly-iii";
+
The firefly-iii package served by php-fpm and the webserver of choice.
+
This option can be used to point the webserver to the correct root. It
+
may also be used to set the package to a different version, say a
+
apply = firefly-iii : firefly-iii.override (prev: {
+
enableNginx = mkOption {
+
Whether to enable nginx or not. If enabled, an nginx virtual host will
+
be created for access to firefly-iii. If not enabled, then you may use
+
`''${config.services.firefly-iii.package}` as your document root in
+
whichever webserver you wish to setup.
+
virtualHost = mkOption {
+
The hostname at which you wish firefly-iii to be served. If you have
+
enabled nginx using `services.firefly-iii.enableNginx` then this will
+
poolConfig = mkOption {
+
type = 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;
+
Options for the Firefly III PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+
for details on configuration directives.
+
Options for firefly-iii configuration. Refer to
+
<https://github.com/firefly-iii/firefly-iii/blob/main/.env.example> for
+
details on supported values. All <option>_FILE values supported by
+
upstream are supported here.
+
APP_URL will be set by `services.firefly-iii.virtualHost`, do not
+
example = literalExpression ''
+
APP_ENV = "production";
+
APP_KEY_FILE = "/var/secrets/firefly-iii-app-key.txt";
+
SITE_OWNER = "mail@example.com";
+
DB_CONNECTION = "mysql";
+
DB_DATABASE = "firefly";
+
DB_USERNAME = "firefly";
+
DB_PASSWORD_FILE = "/var/secrets/firefly-iii-mysql-password.txt;
+
freeformType = attrsOf (oneOf [str int bool]);
+
DB_CONNECTION = mkOption {
+
type = enum [ "sqlite" "pgsql" "mysql" ];
+
The type of database you wish to use. Can be one of "sqlite",
+
type = enum [ "local" "production" "testing" ];
+
example = "production";
+
The app environment. It is recommended to keep this at "local".
+
Possible values are "local", "production" and "testing"
+
default = if cfg.settings.DB_CONNECTION == "sqlite" then null
+
else if cfg.settings.DB_CONNECTION == "mysql" then 3306
+
`null` if DB_CONNECTION is "sqlite", `3306` if "mysql", `5432` if "pgsql"
+
The port your database is listening at. sqlite does not require
+
this value to be filled.
+
APP_KEY_FILE = mkOption {
+
The path to your appkey. The file should contain a 32 character
+
random app key. This may be set using `echo "base64:$(head -c 32
+
/dev/urandom | base64)" > /path/to/key-file`.
+
config = mkIf cfg.enable {
+
services.firefly-iii = {
+
APP_URL = cfg.virtualHost;
+
services.phpfpm.pools.firefly-iii = {
+
phpPackage = cfg.package.phpPackage;
+
"listen.mode" = "0660";
+
"listen.group" = group;
+
systemd.services.phpfpm-firefly-iii.serviceConfig = {
+
EnvironmentFile = "/run/phpfpm/firefly-iii-env";
+
ExecStartPost = "${pkgs.coreutils}/bin/rm /run/phpfpm/firefly-iii-env";
+
systemd.services.firefly-iii-setup = {
+
requiredBy = [ "phpfpm-firefly-iii.service" ];
+
before = [ "phpfpm-firefly-iii.service" ];
+
ExecStart = firefly-iii-maintenance;
+
RuntimeDirectory = "phpfpm";
+
RuntimeDirectoryPreserve = true;
+
} // commonServiceConfig;
+
unitConfig.JoinsNamespaceOf = "phpfpm-firefly-iii.service";
+
systemd.services.firefly-iii-cron = {
+
description = "Daily Firefly III cron job";
+
${artisan} firefly-iii:cron
+
serviceConfig = commonServiceConfig;
+
systemd.timers.firefly-iii-cron = {
+
description = "Trigger Firefly Cron";
+
RandomizedDelaySec = "1800s";
+
wantedBy = [ "timers.target" ];
+
services.nginx = mkIf cfg.enableNginx {
+
recommendedTlsSettings = mkDefault true;
+
recommendedOptimisation = mkDefault true;
+
recommendedGzipSettings = mkDefault true;
+
virtualHosts.${cfg.virtualHost} = {
+
root = "${cfg.package}/public";
+
tryFiles = "$uri $uri/ /index.php?$query_string";
+
include ${config.services.nginx.package}/conf/fastcgi_params ;
+
fastcgi_param SCRIPT_FILENAME $request_filename;
+
fastcgi_param modHeadersAvailable true; #Avoid sending the security headers twice
+
fastcgi_pass unix:${config.services.phpfpm.pools.firefly-iii.socket};
+
systemd.tmpfiles.settings."10-firefly-iii" = genAttrs [
+
"${cfg.dataDir}/storage"
+
"${cfg.dataDir}/storage/app"
+
"${cfg.dataDir}/storage/database"
+
"${cfg.dataDir}/storage/export"
+
"${cfg.dataDir}/storage/framework"
+
"${cfg.dataDir}/storage/framework/cache"
+
"${cfg.dataDir}/storage/framework/sessions"
+
"${cfg.dataDir}/storage/framework/views"
+
"${cfg.dataDir}/storage/logs"
+
"${cfg.dataDir}/storage/upload"
+
users = mkIf (user == defaultUser) {
+
description = "Firefly-iii service user";
+
groups = mkIf (group == defaultGroup) {