From d7b79ab6e9d3ab2cf9de1356727332b375686f6d Mon Sep 17 00:00:00 2001 From: oddlama Date: Sat, 26 Apr 2025 14:39:43 +0200 Subject: [PATCH] feat: add firefly pico --- config/users.nix | 1 + hosts/kroma/default.nix | 3 + hosts/ward/guests/firefly.nix | 45 +- modules/default.nix | 1 + modules/firefly-pico.nix | 410 ++++++++++++++++++ pkgs/default.nix | 1 + pkgs/firefly-pico-frontend.nix | 50 +++ pkgs/firefly-pico.nix | 73 ++++ ...ly-app-key.age => firefly-iii-app-key.age} | 0 .../ward-firefly/firefly-pico-app-key.age | Bin 0 -> 417 bytes ...e868a44f8f70bd383-firefly-pico-app-key.age | 7 + ...9cc7bd4199c9db79d103de-firefly-app-key.age | 7 - ...bd4199c9db79d103de-firefly-iii-app-key.age | Bin 0 -> 353 bytes 13 files changed, 587 insertions(+), 11 deletions(-) create mode 100644 modules/firefly-pico.nix create mode 100644 pkgs/firefly-pico-frontend.nix create mode 100644 pkgs/firefly-pico.nix rename secrets/generated/ward-firefly/{firefly-app-key.age => firefly-iii-app-key.age} (100%) create mode 100644 secrets/generated/ward-firefly/firefly-pico-app-key.age create mode 100644 secrets/rekeyed/ward-firefly/0c03e82003735b0e868a44f8f70bd383-firefly-pico-app-key.age delete mode 100644 secrets/rekeyed/ward-firefly/3e9f3cf2a49cc7bd4199c9db79d103de-firefly-app-key.age create mode 100644 secrets/rekeyed/ward-firefly/3e9f3cf2a49cc7bd4199c9db79d103de-firefly-iii-app-key.age diff --git a/config/users.nix b/config/users.nix index cb06188..22dbea5 100644 --- a/config/users.nix +++ b/config/users.nix @@ -44,5 +44,6 @@ plugdev.gid = 967; tss = uidGid 966; firefly-iii = uidGid 965; + firefly-pico = uidGid 964; }; } diff --git a/hosts/kroma/default.nix b/hosts/kroma/default.nix index 2df3790..53a7361 100644 --- a/hosts/kroma/default.nix +++ b/hosts/kroma/default.nix @@ -91,6 +91,9 @@ programs.nix-ld.enable = true; topology.self.icon = "devices.desktop"; + # Mainly for client-side formatting in websites like firefly-iii + i18n.supportedLocales = [ "de_DE.UTF-8/UTF-8" ]; + hardware.nvidia-container-toolkit.enable = true; virtualisation.containers.enable = true; virtualisation.podman = { diff --git a/hosts/ward/guests/firefly.nix b/hosts/ward/guests/firefly.nix index f7639f3..02e0bb5 100644 --- a/hosts/ward/guests/firefly.nix +++ b/hosts/ward/guests/firefly.nix @@ -20,8 +20,20 @@ in expectedBodyRegex = "Firefly III"; network = "home-lan.vlans.services"; }; + globals.monitoring.http.firefly-pico = { + url = "https://${fireflyDomain}/pico"; + expectedBodyRegex = "Pico"; + network = "home-lan.vlans.services"; + }; - age.secrets.firefly-app-key = { + age.secrets.firefly-iii-app-key = { + generator.script = _: '' + echo "base64:$(head -c 32 /dev/urandom | base64)" + ''; + owner = "firefly-iii"; + }; + + age.secrets.firefly-pico-app-key = { generator.script = _: '' echo "base64:$(head -c 32 /dev/urandom | base64)" ''; @@ -33,21 +45,39 @@ in directory = "/var/lib/firefly-iii"; user = "firefly-iii"; } + { + directory = "/var/lib/firefly-pico"; + user = "firefly-pico"; + } ]; - i18n.supportedLocales = [ "all" ]; services.firefly-iii = { enable = true; enableNginx = true; virtualHost = globals.services.firefly.domain; settings = { AUDIT_LOG_LEVEL = "emergency"; # disable audit logs - LOG_CHANNEL = "stdout"; + LOG_CHANNEL = "syslog"; APP_URL = "https://${globals.services.firefly.domain}"; TZ = "Europe/Berlin"; TRUSTED_PROXIES = wardWebProxyCfg.wireguard.proxy-home.ipv4; SITE_OWNER = "admin@${globals.domains.me}"; - APP_KEY_FILE = config.age.secrets.firefly-app-key.path; + APP_KEY_FILE = config.age.secrets.firefly-iii-app-key.path; + }; + }; + + services.firefly-pico = { + enable = true; + enableNginx = true; + virtualHost = "pico.internal"; + settings = { + LOG_CHANNEL = "syslog"; + APP_URL = "https://${globals.services.firefly.domain}/pico"; + TZ = "Europe/Berlin"; + FIREFLY_URL = config.services.firefly-iii.settings.APP_URL; + TRUSTED_PROXIES = wardWebProxyCfg.wireguard.proxy-home.ipv4; + SITE_OWNER = "admin@${globals.domains.me}"; + APP_KEY_FILE = config.age.secrets.firefly-pico-app-key.path; }; }; @@ -71,6 +101,13 @@ in proxyPass = "http://firefly"; proxyWebsockets = true; }; + locations."/pico" = { + proxyPass = "http://firefly/"; # Trailing slash matters! (remove location suffix) + proxyWebsockets = true; + extraConfig = '' + proxy_set_header Host pico.internal; + ''; + }; extraConfig = '' allow ${globals.net.home-lan.vlans.home.cidrv4}; allow ${globals.net.home-lan.vlans.home.cidrv6}; diff --git a/modules/default.nix b/modules/default.nix index fe00f12..fc7701e 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -4,6 +4,7 @@ ./backups.nix ./deterministic-ids.nix ./distributed-config.nix + ./firefly-pico.nix ./globals.nix ./meta.nix ./nginx-upstream-monitoring.nix diff --git a/modules/firefly-pico.nix b/modules/firefly-pico.nix new file mode 100644 index 0000000..aededec --- /dev/null +++ b/modules/firefly-pico.nix @@ -0,0 +1,410 @@ +{ + pkgs, + config, + lib, + ... +}: + +let + cfg = config.services.firefly-pico; + + inherit (cfg) user; + inherit (cfg) group; + + defaultUser = "firefly-pico"; + defaultGroup = "firefly-pico"; + + artisan = "${cfg.package}/share/php/firefly-pico/artisan"; + + env-file-values = lib.attrsets.mapAttrs' ( + n: v: lib.attrsets.nameValuePair (lib.strings.removeSuffix "_FILE" n) v + ) (lib.attrsets.filterAttrs (n: _v: lib.strings.hasSuffix "_FILE" n) cfg.settings); + env-nonfile-values = lib.attrsets.filterAttrs ( + n: _v: !lib.strings.hasSuffix "_FILE" n + ) cfg.settings; + + firefly-pico-maintenance = pkgs.writeShellScript "firefly-pico-maintenance.sh" '' + set -a + ${lib.strings.toShellVars env-nonfile-values} + ${lib.strings.concatLines ( + lib.attrsets.mapAttrsToList (n: v: "${n}=\"$(< ${v})\"") env-file-values + )} + set +a + ${lib.optionalString ( + cfg.settings.DB_CONNECTION == "sqlite" + ) "touch ${cfg.dataDir}/storage/database/database.sqlite"} + ${artisan} migrate --isolated --force + ${artisan} config:clear + ${artisan} config:cache + ${artisan} cache:clear + ''; + + commonServiceConfig = { + Type = "oneshot"; + User = user; + Group = group; + StateDirectory = "firefly-pico"; + ReadWritePaths = [ 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-pico = { + + enable = lib.mkEnableOption "Firefly-Pico: A delightful Firefly III companion web app for effortless transaction tracking"; + + user = lib.mkOption { + type = lib.types.str; + default = defaultUser; + description = "User account under which firefly-pico runs."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = if cfg.enableNginx then "nginx" else defaultGroup; + defaultText = "If `services.firefly-pico.enableNginx` is true then `nginx` else ${defaultGroup}"; + description = '' + Group under which firefly-pico runs. It is best to set this to the group + of whatever webserver is being used as the frontend. + ''; + }; + + dataDir = lib.mkOption { + type = lib.types.path; + default = "/var/lib/firefly-pico"; + description = '' + The place where firefly-pico stores its state. + ''; + }; + + package = + lib.mkPackageOption pkgs "firefly-pico" { } + // lib.mkOption { + apply = + firefly-pico: + firefly-pico.override { + inherit (cfg) dataDir; + }; + }; + + enableNginx = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to enable nginx or not. If enabled, an nginx virtual host will + be created for access to firefly-pico. If not enabled, then you may use + `''${config.services.firefly-pico.package}` as your document root in + whichever webserver you wish to setup. + ''; + }; + + virtualHost = lib.mkOption { + type = lib.types.str; + default = "localhost"; + description = '' + The hostname at which you wish firefly-pico to be served. If you have + enabled nginx using `services.firefly-pico.enableNginx` then this will + be used. + ''; + }; + + poolConfig = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.oneOf [ + lib.types.str + lib.types.int + lib.types.bool + ] + ); + default = { }; + defaultText = '' + { + "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 php-fpm.conf + for details on configuration directives. + ''; + }; + + settings = lib.mkOption { + default = { }; + description = '' + Options for firefly-iii configuration. Refer to + for + details on supported values. All