at 25.11-pre 13 kB view raw
1{ 2 pkgs, 3 config, 4 lib, 5 ... 6}: 7 8let 9 cfg = config.services.firefly-iii; 10 11 user = cfg.user; 12 group = cfg.group; 13 14 defaultUser = "firefly-iii"; 15 defaultGroup = "firefly-iii"; 16 17 artisan = "${cfg.package}/artisan"; 18 19 env-file-values = lib.attrsets.mapAttrs' ( 20 n: v: lib.attrsets.nameValuePair (lib.strings.removeSuffix "_FILE" n) v 21 ) (lib.attrsets.filterAttrs (n: v: lib.strings.hasSuffix "_FILE" n) cfg.settings); 22 env-nonfile-values = lib.attrsets.filterAttrs (n: v: !lib.strings.hasSuffix "_FILE" n) cfg.settings; 23 24 firefly-iii-maintenance = pkgs.writeShellScript "firefly-iii-maintenance.sh" '' 25 set -a 26 ${lib.strings.toShellVars env-nonfile-values} 27 ${lib.strings.concatLines ( 28 lib.attrsets.mapAttrsToList (n: v: "${n}=\"$(< ${v})\"") env-file-values 29 )} 30 set +a 31 ${lib.optionalString ( 32 cfg.settings.DB_CONNECTION == "sqlite" 33 ) "touch ${cfg.dataDir}/storage/database/database.sqlite"} 34 ${artisan} optimize:clear 35 rm ${cfg.dataDir}/cache/*.php 36 ${artisan} package:discover 37 ${artisan} firefly-iii:upgrade-database 38 ${artisan} firefly-iii:laravel-passport-keys 39 ${artisan} view:cache 40 ${artisan} route:cache 41 ${artisan} config:cache 42 ''; 43 44 commonServiceConfig = { 45 Type = "oneshot"; 46 User = user; 47 Group = group; 48 StateDirectory = "firefly-iii"; 49 ReadWritePaths = [ cfg.dataDir ]; 50 WorkingDirectory = cfg.package; 51 PrivateTmp = true; 52 PrivateDevices = true; 53 CapabilityBoundingSet = ""; 54 AmbientCapabilities = ""; 55 ProtectSystem = "strict"; 56 ProtectKernelTunables = true; 57 ProtectKernelModules = true; 58 ProtectControlGroups = true; 59 ProtectClock = true; 60 ProtectHostname = true; 61 ProtectHome = "tmpfs"; 62 ProtectKernelLogs = true; 63 ProtectProc = "invisible"; 64 ProcSubset = "pid"; 65 PrivateNetwork = false; 66 RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX"; 67 SystemCallArchitectures = "native"; 68 SystemCallFilter = [ 69 "@system-service @resources" 70 "~@obsolete @privileged" 71 ]; 72 RestrictSUIDSGID = true; 73 RemoveIPC = true; 74 NoNewPrivileges = true; 75 RestrictRealtime = true; 76 RestrictNamespaces = true; 77 LockPersonality = true; 78 PrivateUsers = true; 79 }; 80 81in 82{ 83 84 options.services.firefly-iii = { 85 86 enable = lib.mkEnableOption "Firefly III: A free and open source personal finance manager"; 87 88 user = lib.mkOption { 89 type = lib.types.str; 90 default = defaultUser; 91 description = "User account under which firefly-iii runs."; 92 }; 93 94 group = lib.mkOption { 95 type = lib.types.str; 96 default = if cfg.enableNginx then "nginx" else defaultGroup; 97 defaultText = "If `services.firefly-iii.enableNginx` is true then `nginx` else ${defaultGroup}"; 98 description = '' 99 Group under which firefly-iii runs. It is best to set this to the group 100 of whatever webserver is being used as the frontend. 101 ''; 102 }; 103 104 dataDir = lib.mkOption { 105 type = lib.types.path; 106 default = "/var/lib/firefly-iii"; 107 description = '' 108 The place where firefly-iii stores its state. 109 ''; 110 }; 111 112 package = 113 lib.mkPackageOption pkgs "firefly-iii" { } 114 // lib.mkOption { 115 apply = 116 firefly-iii: 117 firefly-iii.override (prev: { 118 dataDir = cfg.dataDir; 119 }); 120 }; 121 122 enableNginx = lib.mkOption { 123 type = lib.types.bool; 124 default = false; 125 description = '' 126 Whether to enable nginx or not. If enabled, an nginx virtual host will 127 be created for access to firefly-iii. If not enabled, then you may use 128 `''${config.services.firefly-iii.package}` as your document root in 129 whichever webserver you wish to setup. 130 ''; 131 }; 132 133 virtualHost = lib.mkOption { 134 type = lib.types.str; 135 default = "localhost"; 136 description = '' 137 The hostname at which you wish firefly-iii to be served. If you have 138 enabled nginx using `services.firefly-iii.enableNginx` then this will 139 be used. 140 ''; 141 }; 142 143 poolConfig = lib.mkOption { 144 type = lib.types.attrsOf ( 145 lib.types.oneOf [ 146 lib.types.str 147 lib.types.int 148 lib.types.bool 149 ] 150 ); 151 default = { }; 152 defaultText = '' 153 { 154 "pm" = "dynamic"; 155 "pm.max_children" = 32; 156 "pm.start_servers" = 2; 157 "pm.min_spare_servers" = 2; 158 "pm.max_spare_servers" = 4; 159 "pm.max_requests" = 500; 160 } 161 ''; 162 description = '' 163 Options for the Firefly III PHP pool. See the documentation on <literal>php-fpm.conf</literal> 164 for details on configuration directives. 165 ''; 166 }; 167 168 settings = lib.mkOption { 169 default = { }; 170 description = '' 171 Options for firefly-iii configuration. Refer to 172 <https://github.com/firefly-iii/firefly-iii/blob/main/.env.example> for 173 details on supported values. All <option>_FILE values supported by 174 upstream are supported here. 175 176 APP_URL will be the same as `services.firefly-iii.virtualHost` if the 177 former is unset in `services.firefly-iii.settings`. 178 ''; 179 example = lib.literalExpression '' 180 { 181 APP_ENV = "production"; 182 APP_KEY_FILE = "/var/secrets/firefly-iii-app-key.txt"; 183 SITE_OWNER = "mail@example.com"; 184 DB_CONNECTION = "mysql"; 185 DB_HOST = "db"; 186 DB_PORT = 3306; 187 DB_DATABASE = "firefly"; 188 DB_USERNAME = "firefly"; 189 DB_PASSWORD_FILE = "/var/secrets/firefly-iii-mysql-password.txt"; 190 } 191 ''; 192 type = lib.types.submodule { 193 freeformType = lib.types.attrsOf ( 194 lib.types.oneOf [ 195 lib.types.str 196 lib.types.int 197 lib.types.bool 198 ] 199 ); 200 options = { 201 DB_CONNECTION = lib.mkOption { 202 type = lib.types.enum [ 203 "sqlite" 204 "pgsql" 205 "mysql" 206 ]; 207 default = "sqlite"; 208 example = "pgsql"; 209 description = '' 210 The type of database you wish to use. Can be one of "sqlite", 211 "mysql" or "pgsql". 212 ''; 213 }; 214 APP_ENV = lib.mkOption { 215 type = lib.types.enum [ 216 "local" 217 "production" 218 "testing" 219 ]; 220 default = "local"; 221 example = "production"; 222 description = '' 223 The app environment. It is recommended to keep this at "local". 224 Possible values are "local", "production" and "testing" 225 ''; 226 }; 227 DB_PORT = lib.mkOption { 228 type = lib.types.nullOr lib.types.int; 229 default = 230 if cfg.settings.DB_CONNECTION == "pgsql" then 231 5432 232 else if cfg.settings.DB_CONNECTION == "mysql" then 233 3306 234 else 235 null; 236 defaultText = '' 237 `null` if DB_CONNECTION is "sqlite", `3306` if "mysql", `5432` if "pgsql" 238 ''; 239 description = '' 240 The port your database is listening at. sqlite does not require 241 this value to be filled. 242 ''; 243 }; 244 DB_HOST = lib.mkOption { 245 type = lib.types.str; 246 default = if cfg.settings.DB_CONNECTION == "pgsql" then "/run/postgresql" else "localhost"; 247 defaultText = '' 248 "localhost" if DB_CONNECTION is "sqlite" or "mysql", "/run/postgresql" if "pgsql". 249 ''; 250 description = '' 251 The machine which hosts your database. This is left at the 252 default value for "mysql" because we use the "DB_SOCKET" option 253 to connect to a unix socket instead. "pgsql" requires that the 254 unix socket location be specified here instead of at "DB_SOCKET". 255 This option does not affect "sqlite". 256 ''; 257 }; 258 APP_KEY_FILE = lib.mkOption { 259 type = lib.types.path; 260 description = '' 261 The path to your appkey. The file should contain a 32 character 262 random app key. This may be set using `echo "base64:$(head -c 32 263 /dev/urandom | base64)" > /path/to/key-file`. 264 ''; 265 }; 266 APP_URL = lib.mkOption { 267 type = lib.types.str; 268 default = 269 if cfg.virtualHost == "localhost" then 270 "http://${cfg.virtualHost}" 271 else 272 "https://${cfg.virtualHost}"; 273 defaultText = '' 274 http(s)://''${config.services.firefly-iii.virtualHost} 275 ''; 276 description = '' 277 The APP_URL used by firefly-iii internally. Please make sure this 278 URL matches the external URL of your Firefly III installation. It 279 is used to validate specific requests and to generate URLs in 280 emails. 281 ''; 282 }; 283 }; 284 }; 285 }; 286 }; 287 288 config = lib.mkIf cfg.enable { 289 290 services.phpfpm.pools.firefly-iii = { 291 inherit user group; 292 phpPackage = cfg.package.phpPackage; 293 phpOptions = '' 294 log_errors = on 295 ''; 296 settings = { 297 "listen.mode" = lib.mkDefault "0660"; 298 "listen.owner" = lib.mkDefault user; 299 "listen.group" = lib.mkDefault group; 300 "pm" = lib.mkDefault "dynamic"; 301 "pm.max_children" = lib.mkDefault 32; 302 "pm.start_servers" = lib.mkDefault 2; 303 "pm.min_spare_servers" = lib.mkDefault 2; 304 "pm.max_spare_servers" = lib.mkDefault 4; 305 "pm.max_requests" = lib.mkDefault 500; 306 } // cfg.poolConfig; 307 }; 308 309 systemd.services.firefly-iii-setup = { 310 after = [ 311 "postgresql.service" 312 "mysql.service" 313 ]; 314 requiredBy = [ "phpfpm-firefly-iii.service" ]; 315 before = [ "phpfpm-firefly-iii.service" ]; 316 serviceConfig = { 317 ExecStart = firefly-iii-maintenance; 318 RemainAfterExit = true; 319 } // commonServiceConfig; 320 unitConfig.JoinsNamespaceOf = "phpfpm-firefly-iii.service"; 321 restartTriggers = [ cfg.package ]; 322 partOf = [ "phpfpm-firefly-iii.service" ]; 323 }; 324 325 systemd.services.firefly-iii-cron = { 326 after = [ 327 "firefly-iii-setup.service" 328 "postgresql.service" 329 "mysql.service" 330 ]; 331 wants = [ "firefly-iii-setup.service" ]; 332 description = "Daily Firefly III cron job"; 333 serviceConfig = { 334 ExecStart = "${artisan} firefly-iii:cron"; 335 } // commonServiceConfig; 336 }; 337 338 systemd.timers.firefly-iii-cron = { 339 description = "Trigger Firefly Cron"; 340 timerConfig = { 341 OnCalendar = "Daily"; 342 RandomizedDelaySec = "1800s"; 343 Persistent = true; 344 }; 345 wantedBy = [ "timers.target" ]; 346 restartTriggers = [ cfg.package ]; 347 }; 348 349 services.nginx = lib.mkIf cfg.enableNginx { 350 enable = true; 351 recommendedTlsSettings = lib.mkDefault true; 352 recommendedOptimisation = lib.mkDefault true; 353 recommendedGzipSettings = lib.mkDefault true; 354 virtualHosts.${cfg.virtualHost} = { 355 root = "${cfg.package}/public"; 356 locations = { 357 "/" = { 358 tryFiles = "$uri $uri/ /index.php?$query_string"; 359 index = "index.php"; 360 extraConfig = '' 361 sendfile off; 362 ''; 363 }; 364 "~ \\.php$" = { 365 extraConfig = '' 366 include ${config.services.nginx.package}/conf/fastcgi_params ; 367 fastcgi_param SCRIPT_FILENAME $request_filename; 368 fastcgi_param modHeadersAvailable true; #Avoid sending the security headers twice 369 fastcgi_pass unix:${config.services.phpfpm.pools.firefly-iii.socket}; 370 ''; 371 }; 372 }; 373 }; 374 }; 375 376 systemd.tmpfiles.settings."10-firefly-iii" = 377 lib.attrsets.genAttrs 378 [ 379 "${cfg.dataDir}/storage" 380 "${cfg.dataDir}/storage/app" 381 "${cfg.dataDir}/storage/database" 382 "${cfg.dataDir}/storage/export" 383 "${cfg.dataDir}/storage/framework" 384 "${cfg.dataDir}/storage/framework/cache" 385 "${cfg.dataDir}/storage/framework/sessions" 386 "${cfg.dataDir}/storage/framework/views" 387 "${cfg.dataDir}/storage/logs" 388 "${cfg.dataDir}/storage/upload" 389 "${cfg.dataDir}/cache" 390 ] 391 (n: { 392 d = { 393 group = group; 394 mode = "0700"; 395 user = user; 396 }; 397 }) 398 // { 399 "${cfg.dataDir}".d = { 400 group = group; 401 mode = "0710"; 402 user = user; 403 }; 404 }; 405 406 users = { 407 users = lib.mkIf (user == defaultUser) { 408 ${defaultUser} = { 409 description = "Firefly-iii service user"; 410 inherit group; 411 isSystemUser = true; 412 home = cfg.dataDir; 413 }; 414 }; 415 groups = lib.mkIf (group == defaultGroup) { ${defaultGroup} = { }; }; 416 }; 417 }; 418}