···62626363- [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).
64646565+- [paisa](https://github.com/ananthakumaran/paisa), a personal finance tracker and dashboard. Available as [services.paisa](#opt-services.paisa.enable).
6666+6567## Backward Incompatibilities {#sec-release-25.11-incompatibilities}
66686769<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
···11+# Paisa {#module-services-paisa}
22+33+*Source:* {file}`modules/services/misc/paisa.nix`
44+55+*Upstream documentation:* <https://paisa.fyi/>
66+77+[Paisa](https://github.com/ananthakumaran/paisa) is a personal finance manager
88+built on top of the ledger plain-text-accounting tool.
99+1010+## Usage {#module-services-paisa-usage}
1111+1212+Paisa needs to have one of the following cli tools availabe in the PATH at
1313+runtime:
1414+1515+- ledger
1616+- hledger
1717+- beancount
1818+1919+All of these are available from nixpkgs. Currently, it is not possible to
2020+configure this in the module, but you can e.g. use systemd to give the unit
2121+access to the command at runtime.
2222+2323+```nix
2424+systemd.services.paisa.path = [ pkgs.hledger ];
2525+```
2626+2727+::: {.note}
2828+Paisa needs to be configured to use the correct cli tool. This is possible in
2929+the web interface (make sure to enable [](#opt-services.paisa.mutableSettings)
3030+if you want to persist these settings between service restarts), or in
3131+[](#opt-services.paisa.settings).
3232+:::
+145
nixos/modules/services/misc/paisa.nix
···11+{
22+ config,
33+ lib,
44+ pkgs,
55+ ...
66+}:
77+let
88+ cfg = config.services.paisa;
99+ settingsFormat = pkgs.formats.yaml { };
1010+1111+ args = lib.concatStringsSep " " [
1212+ "--config /var/lib/paisa/paisa.yaml"
1313+ ];
1414+1515+ settings =
1616+ if (cfg.settings != null) then
1717+ builtins.removeAttrs
1818+ (
1919+ cfg.settings
2020+ // {
2121+ journal_path = cfg.settings.dataDir + cfg.settings.journalFile;
2222+ db_path = cfg.settings.dataDir + cfg.settings.dbFile;
2323+ }
2424+ )
2525+ [
2626+ "dataDir"
2727+ "journalFile"
2828+ "dbFile"
2929+ ]
3030+ else
3131+ null;
3232+3333+ configFile = (settingsFormat.generate "paisa.yaml" settings).overrideAttrs (_: {
3434+ checkPhase = "";
3535+ });
3636+in
3737+{
3838+ options.services.paisa = with lib.types; {
3939+ enable = lib.mkEnableOption "Paisa personal finance manager";
4040+4141+ package = lib.mkPackageOption pkgs "paisa" { };
4242+4343+ openFirewall = lib.mkOption {
4444+ default = false;
4545+ type = bool;
4646+ description = "Open ports in the firewall for the Paisa web server.";
4747+ };
4848+4949+ mutableSettings = lib.mkOption {
5050+ default = true;
5151+ type = bool;
5252+ description = ''
5353+ Allow changes made on the web interface to persist between service
5454+ restarts.
5555+ '';
5656+ };
5757+5858+ host = lib.mkOption {
5959+ type = str;
6060+ default = "0.0.0.0";
6161+ description = "Host bind IP address.";
6262+ };
6363+6464+ port = lib.mkOption {
6565+ type = port;
6666+ default = 7500;
6767+ description = "Port to serve Paisa on.";
6868+ };
6969+7070+ settings = lib.mkOption {
7171+ default = null;
7272+ type = nullOr (submodule {
7373+ freeformType = settingsFormat.type;
7474+ options = {
7575+ dataDir = lib.mkOption {
7676+ type = lib.types.str;
7777+ default = "/var/lib/paisa/";
7878+ description = "Path to paisa data directory.";
7979+ };
8080+8181+ journalFile = lib.mkOption {
8282+ type = lib.types.str;
8383+ default = "main.ledger";
8484+ description = "Filename of the main journal / ledger file.";
8585+ };
8686+8787+ dbFile = lib.mkOption {
8888+ type = lib.types.str;
8989+ default = "paisa.sqlite3";
9090+ description = "Filename of the Paisa database.";
9191+ };
9292+9393+ };
9494+ });
9595+ description = ''
9696+ Paisa configuration. Please refer to
9797+ <https://paisa.fyi/reference/config/> for details.
9898+9999+ On start and if `mutableSettings` is `true`, these options are merged
100100+ into the configuration file on start, taking precedence over
101101+ configuration changes made on the web interface.
102102+ '';
103103+ };
104104+ };
105105+ config = lib.mkIf cfg.enable {
106106+ assertions = [ ];
107107+108108+ systemd.services.paisa = {
109109+ description = "Paisa: Web Application";
110110+ after = [ "network.target" ];
111111+ wantedBy = [ "multi-user.target" ];
112112+ unitConfig = {
113113+ StartLimitIntervalSec = 5;
114114+ StartLimitBurst = 10;
115115+ };
116116+117117+ preStart = lib.optionalString (settings != null) ''
118118+ if [ -e "$STATE_DIRECTORY/paisa.yaml" ] && [ "${toString cfg.mutableSettings}" = "1" ]; then
119119+ # do not write directly to the config file
120120+ ${lib.getExe pkgs.yaml-merge} "$STATE_DIRECTORY/paisa.yaml" "${configFile}" > "$STATE_DIRECTORY/paisa.yaml.tmp"
121121+ mv "$STATE_DIRECTORY/paisa.yaml.tmp" "$STATE_DIRECTORY/paisa.yaml"
122122+ else
123123+ cp --force "${configFile}" "$STATE_DIRECTORY/paisa.yaml"
124124+ chmod 600 "$STATE_DIRECTORY/paisa.yaml"
125125+ fi
126126+ '';
127127+128128+ serviceConfig = {
129129+ DynamicUser = true;
130130+ ExecStart = "${lib.getExe cfg.package} serve ${args}";
131131+ AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
132132+ Restart = "always";
133133+ RestartSec = 5;
134134+ RuntimeDirectory = "paisa";
135135+ StateDirectory = "paisa";
136136+ };
137137+ };
138138+ networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
139139+ };
140140+141141+ meta = {
142142+ maintainers = with lib.maintainers; [ skowalak ];
143143+ doc = ./paisa.md;
144144+ };
145145+}