nixos/paisa: init

This commit is contained in:
Sebastian Kowalak
2025-07-02 16:25:39 +02:00
parent 6554afc3ba
commit e33d7087b6
7 changed files with 198 additions and 13 deletions

View File

@@ -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"
],

View File

@@ -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}
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->

View File

@@ -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

View File

@@ -0,0 +1,32 @@
# 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).
:::

View File

@@ -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
<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;
};
}

View File

@@ -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")
'';
}

View File

@@ -79,10 +79,6 @@ buildGoModule (finalAttrs: {
];
versionCheckProgramArg = "version";
passthru.tests = {
inherit (nixosTests) paisa;
};
preBuild = ''
cp -r ${finalAttrs.frontend}/web/static ./web
'';