{ lib, config, ... }: let cfg = config.my.services.actual; in { options.my.services.actual = { enable = lib.mkEnableOption "Actual Budget server (services.actual)"; settings = lib.mkOption { default = { }; description = '' Merged into services.actual.settings. Use `._secret` for file-backed values per upstream Actual / NixOS module docs. ''; }; proxy = { enable = lib.mkEnableOption "Set the Caddy reverse proxy entry for this service"; domain = lib.mkOption { default = "example.com"; type = lib.types.str; description = '' The domain where Caddy is reachable ''; }; subdomain = lib.mkOption { default = "budget"; type = lib.types.str; description = '' Subdomain for Actual Budget ''; }; host = lib.mkOption { default = "localhost"; type = lib.types.str; description = '' Hostname where Actual is listening ''; }; }; }; config = lib.mkMerge [ (lib.mkIf cfg.enable { # Upstream services.actual uses DynamicUser; without a static passwd entry, # activation-time chown (e.g. agenix) for owner "actual" fails. users.groups.actual = { }; users.users.actual = { isSystemUser = true; group = "actual"; description = "Actual Budget server"; }; services.actual = { enable = true; openFirewall = true; settings = cfg.settings; }; }) (lib.mkIf cfg.proxy.enable { services.caddy = with cfg.proxy; { virtualHosts."${subdomain}.${domain}".extraConfig = '' reverse_proxy http://${host}:${toString config.services.actual.settings.port} import cloudflare_${domain} ''; }; }) ]; }