diff options
Diffstat (limited to 'nixpkgs/nixos/modules/services/web-apps/firefly-iii.nix')
-rw-r--r-- | nixpkgs/nixos/modules/services/web-apps/firefly-iii.nix | 367 |
1 files changed, 367 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/services/web-apps/firefly-iii.nix b/nixpkgs/nixos/modules/services/web-apps/firefly-iii.nix new file mode 100644 index 000000000000..b0024ce09c38 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-apps/firefly-iii.nix @@ -0,0 +1,367 @@ +{ pkgs, config, lib, ... }: + +let + 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; + + user = cfg.user; + group = cfg.group; + + 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} + ''; + + fileenv-func = '' + 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)} + set -a + . /tmp/firefly-iii-env + set +a + ''; + + firefly-iii-maintenance = pkgs.writeShellScript "firefly-iii-maintenance.sh" '' + ${fileenv-func} + + ${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 + ${artisan} cache:clear + + mv /tmp/firefly-iii-env /run/phpfpm/firefly-iii-env + ''; + + commonServiceConfig = { + Type = "oneshot"; + User = user; + Group = group; + StateDirectory = "${removePrefix "/var/lib/" cfg.dataDir}"; + WorkingDirectory = cfg.package; + PrivateTmp = true; + PrivateDevices = true; + CapabilityBoundingSet = ""; + AmbientCapabilities = ""; + ProtectSystem = "strict"; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + ProtectClock = true; + ProtectHostname = true; + ProtectHome = "tmpfs"; + ProtectKernelLogs = true; + ProtectProc = "invisible"; + ProcSubset = "pid"; + PrivateNetwork = false; + RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX"; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service @resources" + "~@obsolete @privileged" + ]; + RestrictSUIDSGID = true; + RemoveIPC = true; + NoNewPrivileges = true; + RestrictRealtime = true; + RestrictNamespaces = true; + LockPersonality = true; + PrivateUsers = true; + }; + +in { + + options.services.firefly-iii = { + + enable = mkEnableOption "Firefly III: A free and open source personal finance manager"; + + user = mkOption { + type = str; + default = defaultUser; + description = "User account under which firefly-iii runs."; + }; + + group = mkOption { + type = str; + default = if cfg.enableNginx then "nginx" else defaultGroup; + defaultText = "If `services.firefly-iii.enableNginx` is true then `nginx` else ${defaultGroup}"; + description = '' + Group under which firefly-iii runs. It is best to set this to the group + of whatever webserver is being used as the frontend. + ''; + }; + + dataDir = mkOption { + type = path; + default = "/var/lib/firefly-iii"; + description = '' + The place where firefly-iii stores its state. + ''; + }; + + package = mkOption { + type = package; + default = pkgs.firefly-iii; + defaultText = literalExpression "pkgs.firefly-iii"; + description = '' + 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 + development version. + ''; + apply = firefly-iii : firefly-iii.override (prev: { + dataDir = cfg.dataDir; + }); + }; + + enableNginx = mkOption { + type = bool; + default = false; + description = '' + 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 { + type = str; + description = '' + The hostname at which you wish firefly-iii to be served. If you have + enabled nginx using `services.firefly-iii.enableNginx` then this will + be used. + ''; + }; + + poolConfig = mkOption { + type = attrsOf (oneOf [ str int bool ]); + default = { + "pm" = "dynamic"; + "pm.max_children" = 32; + "pm.start_servers" = 2; + "pm.min_spare_servers" = 2; + "pm.max_spare_servers" = 4; + "pm.max_requests" = 500; + }; + description = '' + Options for the Firefly III PHP pool. See the documentation on <literal>php-fpm.conf</literal> + for details on configuration directives. + ''; + }; + + settings = mkOption { + description = '' + 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 + redefine it here. + ''; + example = literalExpression '' + { + APP_ENV = "production"; + APP_KEY_FILE = "/var/secrets/firefly-iii-app-key.txt"; + SITE_OWNER = "mail@example.com"; + DB_CONNECTION = "mysql"; + DB_HOST = "db"; + DB_PORT = 3306; + DB_DATABASE = "firefly"; + DB_USERNAME = "firefly"; + DB_PASSWORD_FILE = "/var/secrets/firefly-iii-mysql-password.txt; + } + ''; + default = {}; + type = submodule { + freeformType = attrsOf (oneOf [str int bool]); + options = { + DB_CONNECTION = mkOption { + type = enum [ "sqlite" "pgsql" "mysql" ]; + default = "sqlite"; + example = "pgsql"; + description = '' + The type of database you wish to use. Can be one of "sqlite", + "mysql" or "pgsql". + ''; + }; + APP_ENV = mkOption { + type = enum [ "local" "production" "testing" ]; + default = "local"; + example = "production"; + description = '' + The app environment. It is recommended to keep this at "local". + Possible values are "local", "production" and "testing" + ''; + }; + DB_PORT = mkOption { + type = nullOr int; + default = if cfg.settings.DB_CONNECTION == "sqlite" then null + else if cfg.settings.DB_CONNECTION == "mysql" then 3306 + else 5432; + defaultText = '' + `null` if DB_CONNECTION is "sqlite", `3306` if "mysql", `5432` if "pgsql" + ''; + description = '' + The port your database is listening at. sqlite does not require + this value to be filled. + ''; + }; + APP_KEY_FILE = mkOption { + type = path; + description = '' + 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 = { + settings = { + APP_URL = cfg.virtualHost; + }; + }; + + services.phpfpm.pools.firefly-iii = { + inherit user group; + phpPackage = cfg.package.phpPackage; + phpOptions = '' + log_errors = on + ''; + settings = { + "listen.mode" = "0660"; + "listen.owner" = user; + "listen.group" = group; + "clear_env" = "no"; + } // cfg.poolConfig; + }; + + 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" ]; + serviceConfig = { + 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"; + script = '' + ${fileenv-func} + ${artisan} firefly-iii:cron + ''; + serviceConfig = commonServiceConfig; + }; + + systemd.timers.firefly-iii-cron = { + description = "Trigger Firefly Cron"; + timerConfig = { + OnCalendar = "Daily"; + RandomizedDelaySec = "1800s"; + Persistent = true; + }; + wantedBy = [ "timers.target" ]; + }; + + services.nginx = mkIf cfg.enableNginx { + enable = true; + recommendedTlsSettings = mkDefault true; + recommendedOptimisation = mkDefault true; + recommendedGzipSettings = mkDefault true; + virtualHosts.${cfg.virtualHost} = { + root = "${cfg.package}/public"; + locations = { + "/" = { + tryFiles = "$uri $uri/ /index.php?$query_string"; + index = "index.php"; + extraConfig = '' + sendfile off; + ''; + }; + "~ \.php$" = { + extraConfig = '' + 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" + "${cfg.dataDir}/cache" + ] (n: { + d = { + group = group; + mode = "0700"; + user = user; + }; + }) // { + "${cfg.dataDir}".d = { + group = group; + mode = "0710"; + user = user; + }; + }; + + users = { + users = mkIf (user == defaultUser) { + ${defaultUser} = { + description = "Firefly-iii service user"; + inherit group; + isSystemUser = true; + home = cfg.dataDir; + }; + }; + groups = mkIf (group == defaultGroup) { + ${defaultGroup} = {}; + }; + }; + }; +} |