nixos/paisa: init (#421807)

Changed files
+198 -9
nixos
doc
manual
modules
tests
+6
nixos/doc/manual/redirects.json
···
"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"
],
+2
nixos/doc/manual/release-notes/rl-2511.section.md
···
- [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}
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+1
nixos/modules/module-list.nix
···
./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
+32
nixos/modules/services/misc/paisa.md
···
+
# Paisa {#module-services-paisa}
+
+
*Source:* {file}`modules/services/misc/paisa.nix`
+
+
*Upstream documentation:* <https://paisa.fyi/>
+
+
[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).
+
:::
+145
nixos/modules/services/misc/paisa.nix
···
+
{
+
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
+
<https://paisa.fyi/reference/config/> 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;
+
};
+
}
+12 -9
nixos/tests/paisa.nix
···
{ ... }:
{
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")
'';
}