From 82213c29179b6ccabd0735d229eafa2c7752412f Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Tue, 5 May 2026 01:40:29 -0400 Subject: [PATCH] firefly-iii: init --- hosts/muffin/default.nix | 1 + hosts/muffin/service-configs.nix | 5 +++ modules/server-age-secrets.nix | 8 ++++ secrets/secrets.nix | Bin 3490 -> 3546 bytes secrets/server/firefly-iii-app-key.age | Bin 0 -> 286 bytes services/firefly-iii.nix | 59 +++++++++++++++++++++++++ 6 files changed, 73 insertions(+) create mode 100644 secrets/server/firefly-iii-app-key.age create mode 100644 services/firefly-iii.nix diff --git a/hosts/muffin/default.nix b/hosts/muffin/default.nix index 5401583..c0cd5ae 100644 --- a/hosts/muffin/default.nix +++ b/hosts/muffin/default.nix @@ -53,6 +53,7 @@ # ../../services/llama-cpp.nix ../../services/trilium.nix + ../../services/firefly-iii.nix ../../services/ups.nix diff --git a/hosts/muffin/service-configs.nix b/hosts/muffin/service-configs.nix index 037f3d7..2043d85 100644 --- a/hosts/muffin/service-configs.nix +++ b/hosts/muffin/service-configs.nix @@ -335,6 +335,11 @@ rec { dataDir = services_dir + "/trilium"; }; + firefly_iii = { + dataDir = services_dir + "/firefly-iii"; + domain = "firefly.${site_config.domain}"; + }; + media = { moviesDir = torrents_path + "/media/movies"; tvDir = torrents_path + "/media/tv"; diff --git a/modules/server-age-secrets.nix b/modules/server-age-secrets.nix index 116b4b7..9eaec0f 100644 --- a/modules/server-age-secrets.nix +++ b/modules/server-age-secrets.nix @@ -191,5 +191,13 @@ owner = "caddy"; group = "caddy"; }; + + # Firefly III application encryption key (base64:<32 random bytes>) + firefly-iii-app-key = { + file = ../secrets/server/firefly-iii-app-key.age; + mode = "0400"; + owner = "firefly-iii"; + group = "caddy"; + }; }; } diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 1f9ede2c77288c2976812f4654ee3fc34e109c00..afc5dd5fd84718d9310e02423f5e38aa8e6c5be8 100644 GIT binary patch literal 3546 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 literal 0 HcmV?d00001 diff --git a/services/firefly-iii.nix b/services/firefly-iii.nix new file mode 100644 index 0000000..5c7f0e2 --- /dev/null +++ b/services/firefly-iii.nix @@ -0,0 +1,59 @@ +{ + config, + lib, + service_configs, + site_config, + ... +}: +{ + imports = [ + # firefly-iii has no service of its own — phpfpm-firefly-iii.service runs + # the app and firefly-iii-setup.service runs migrations/cache rebuild. + # Wire the zfs mount into firefly-iii-setup so the upstream `requiredBy` + # chain (setup → phpfpm) inherits the dependency. + (lib.serviceMountWithZpool "firefly-iii-setup" service_configs.zpool_ssds [ + service_configs.firefly_iii.dataDir + ]) + ]; + + services.firefly-iii = { + enable = true; + dataDir = service_configs.firefly_iii.dataDir; + # Run under the caddy group so caddy can read the php-fpm unix socket + # (default mode 0660, owner = user, group = group). + group = "caddy"; + virtualHost = service_configs.firefly_iii.domain; + settings = { + APP_ENV = "production"; + APP_KEY_FILE = config.age.secrets.firefly-iii-app-key.path; + SITE_OWNER = site_config.contact_email; + + # PostgreSQL via local Unix socket + peer auth (DB_HOST defaults to + # /run/postgresql for pgsql, no password needed). + DB_CONNECTION = "pgsql"; + DB_DATABASE = "firefly-iii"; + DB_USERNAME = "firefly-iii"; + + # Trust X-Forwarded-* from caddy on the loopback. + TRUSTED_PROXIES = "127.0.0.1,::1"; + }; + }; + + services.postgresql = { + ensureDatabases = [ "firefly-iii" ]; + ensureUsers = [ + { + name = "firefly-iii"; + ensureDBOwnership = true; + } + ]; + }; + + services.caddy.virtualHosts.${service_configs.firefly_iii.domain}.extraConfig = '' + encode zstd gzip + + root * ${config.services.firefly-iii.package}/public + php_fastcgi unix/${config.services.phpfpm.pools.firefly-iii.socket} + file_server + ''; +}