diff --git a/nixos/doc/manual/redirects.json b/nixos/doc/manual/redirects.json index fffee51d25b0..e7c195379cc7 100644 --- a/nixos/doc/manual/redirects.json +++ b/nixos/doc/manual/redirects.json @@ -587,6 +587,12 @@ "module-services-suwayomi-server-extra-config": [ "index.html#module-services-suwayomi-server-extra-config" ], + "module-services-paisa": [ + "index.html#module-services-paisa" + ], + "module-services-paisa-usage": [ + "index.html#module-services-paisa-usage" + ], "module-services-plausible": [ "index.html#module-services-plausible" ], diff --git a/nixos/doc/manual/release-notes/rl-2511.section.md b/nixos/doc/manual/release-notes/rl-2511.section.md index 683724e11fc7..d78db7d29652 100644 --- a/nixos/doc/manual/release-notes/rl-2511.section.md +++ b/nixos/doc/manual/release-notes/rl-2511.section.md @@ -62,6 +62,8 @@ - [SuiteNumérique Meet](https://github.com/suitenumerique/meet) is an open source alternative to Google Meet and Zoom powered by LiveKit: HD video calls, screen sharing, and chat features. Built with Django and React. Available as [services.lasuite-meet](#opt-services.lasuite-meet.enable). +- [paisa](https://github.com/ananthakumaran/paisa), a personal finance tracker and dashboard. Available as [services.paisa](#opt-services.paisa.enable). + ## Backward Incompatibilities {#sec-release-25.11-incompatibilities} diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index c87b80333d81..bcb96c69d461 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -881,6 +881,7 @@ ./services/misc/osrm.nix ./services/misc/owncast.nix ./services/misc/packagekit.nix + ./services/misc/paisa.nix ./services/misc/paperless.nix ./services/misc/parsoid.nix ./services/misc/persistent-evdev.nix diff --git a/nixos/modules/services/misc/paisa.md b/nixos/modules/services/misc/paisa.md new file mode 100644 index 000000000000..3d9878dd50af --- /dev/null +++ b/nixos/modules/services/misc/paisa.md @@ -0,0 +1,32 @@ +# Paisa {#module-services-paisa} + +*Source:* {file}`modules/services/misc/paisa.nix` + +*Upstream documentation:* + +[Paisa](https://github.com/ananthakumaran/paisa) is a personal finance manager +built on top of the ledger plain-text-accounting tool. + +## Usage {#module-services-paisa-usage} + +Paisa needs to have one of the following cli tools availabe in the PATH at +runtime: + +- ledger +- hledger +- beancount + +All of these are available from nixpkgs. Currently, it is not possible to +configure this in the module, but you can e.g. use systemd to give the unit +access to the command at runtime. + +```nix +systemd.services.paisa.path = [ pkgs.hledger ]; +``` + +::: {.note} +Paisa needs to be configured to use the correct cli tool. This is possible in +the web interface (make sure to enable [](#opt-services.paisa.mutableSettings) +if you want to persist these settings between service restarts), or in +[](#opt-services.paisa.settings). +::: diff --git a/nixos/modules/services/misc/paisa.nix b/nixos/modules/services/misc/paisa.nix new file mode 100644 index 000000000000..c4d8fe6c238e --- /dev/null +++ b/nixos/modules/services/misc/paisa.nix @@ -0,0 +1,145 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.paisa; + settingsFormat = pkgs.formats.yaml { }; + + args = lib.concatStringsSep " " [ + "--config /var/lib/paisa/paisa.yaml" + ]; + + settings = + if (cfg.settings != null) then + builtins.removeAttrs + ( + cfg.settings + // { + journal_path = cfg.settings.dataDir + cfg.settings.journalFile; + db_path = cfg.settings.dataDir + cfg.settings.dbFile; + } + ) + [ + "dataDir" + "journalFile" + "dbFile" + ] + else + null; + + configFile = (settingsFormat.generate "paisa.yaml" settings).overrideAttrs (_: { + checkPhase = ""; + }); +in +{ + options.services.paisa = with lib.types; { + enable = lib.mkEnableOption "Paisa personal finance manager"; + + package = lib.mkPackageOption pkgs "paisa" { }; + + openFirewall = lib.mkOption { + default = false; + type = bool; + description = "Open ports in the firewall for the Paisa web server."; + }; + + mutableSettings = lib.mkOption { + default = true; + type = bool; + description = '' + Allow changes made on the web interface to persist between service + restarts. + ''; + }; + + host = lib.mkOption { + type = str; + default = "0.0.0.0"; + description = "Host bind IP address."; + }; + + port = lib.mkOption { + type = port; + default = 7500; + description = "Port to serve Paisa on."; + }; + + settings = lib.mkOption { + default = null; + type = nullOr (submodule { + freeformType = settingsFormat.type; + options = { + dataDir = lib.mkOption { + type = lib.types.str; + default = "/var/lib/paisa/"; + description = "Path to paisa data directory."; + }; + + journalFile = lib.mkOption { + type = lib.types.str; + default = "main.ledger"; + description = "Filename of the main journal / ledger file."; + }; + + dbFile = lib.mkOption { + type = lib.types.str; + default = "paisa.sqlite3"; + description = "Filename of the Paisa database."; + }; + + }; + }); + description = '' + Paisa configuration. Please refer to + for details. + + On start and if `mutableSettings` is `true`, these options are merged + into the configuration file on start, taking precedence over + configuration changes made on the web interface. + ''; + }; + }; + config = lib.mkIf cfg.enable { + assertions = [ ]; + + systemd.services.paisa = { + description = "Paisa: Web Application"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + unitConfig = { + StartLimitIntervalSec = 5; + StartLimitBurst = 10; + }; + + preStart = lib.optionalString (settings != null) '' + if [ -e "$STATE_DIRECTORY/paisa.yaml" ] && [ "${toString cfg.mutableSettings}" = "1" ]; then + # do not write directly to the config file + ${lib.getExe pkgs.yaml-merge} "$STATE_DIRECTORY/paisa.yaml" "${configFile}" > "$STATE_DIRECTORY/paisa.yaml.tmp" + mv "$STATE_DIRECTORY/paisa.yaml.tmp" "$STATE_DIRECTORY/paisa.yaml" + else + cp --force "${configFile}" "$STATE_DIRECTORY/paisa.yaml" + chmod 600 "$STATE_DIRECTORY/paisa.yaml" + fi + ''; + + serviceConfig = { + DynamicUser = true; + ExecStart = "${lib.getExe cfg.package} serve ${args}"; + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + Restart = "always"; + RestartSec = 5; + RuntimeDirectory = "paisa"; + StateDirectory = "paisa"; + }; + }; + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ]; + }; + + meta = { + maintainers = with lib.maintainers; [ skowalak ]; + doc = ./paisa.md; + }; +} diff --git a/nixos/tests/paisa.nix b/nixos/tests/paisa.nix index 0a16990606e3..f8baed78c88a 100644 --- a/nixos/tests/paisa.nix +++ b/nixos/tests/paisa.nix @@ -1,23 +1,26 @@ { ... }: { name = "paisa"; - nodes.machine = - { pkgs, lib, ... }: + nodes.serviceEmptyConf = + { ... }: { - systemd.services.paisa = { - description = "Paisa"; - wantedBy = [ "multi-user.target" ]; - serviceConfig.ExecStart = "${lib.getExe pkgs.paisa} serve"; + services.paisa = { + enable = true; + + settings = { }; }; }; + testScript = '' start_all() machine.systemctl("start network-online.target") machine.wait_for_unit("network-online.target") - machine.wait_for_unit("paisa.service") - machine.wait_for_open_port(7500) - machine.succeed("curl --location --fail http://localhost:7500") + with subtest("empty/default config test"): + serviceEmptyConf.wait_for_unit("paisa.service") + serviceEmptyConf.wait_for_open_port(7500) + + serviceEmptyConf.succeed("curl --location --fail http://localhost:7500") ''; } diff --git a/pkgs/by-name/pa/paisa/package.nix b/pkgs/by-name/pa/paisa/package.nix index 6bc71b13afb7..2e92515d2873 100644 --- a/pkgs/by-name/pa/paisa/package.nix +++ b/pkgs/by-name/pa/paisa/package.nix @@ -79,10 +79,6 @@ buildGoModule (finalAttrs: { ]; versionCheckProgramArg = "version"; - passthru.tests = { - inherit (nixosTests) paisa; - }; - preBuild = '' cp -r ${finalAttrs.frontend}/web/static ./web '';