1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.paisa;
9 settingsFormat = pkgs.formats.yaml { };
10
11 args = lib.concatStringsSep " " [
12 "--config /var/lib/paisa/paisa.yaml"
13 ];
14
15 settings =
16 if (cfg.settings != null) then
17 builtins.removeAttrs
18 (
19 cfg.settings
20 // {
21 journal_path = cfg.settings.dataDir + cfg.settings.journalFile;
22 db_path = cfg.settings.dataDir + cfg.settings.dbFile;
23 }
24 )
25 [
26 "dataDir"
27 "journalFile"
28 "dbFile"
29 ]
30 else
31 null;
32
33 configFile = (settingsFormat.generate "paisa.yaml" settings).overrideAttrs (_: {
34 checkPhase = "";
35 });
36in
37{
38 options.services.paisa = with lib.types; {
39 enable = lib.mkEnableOption "Paisa personal finance manager";
40
41 package = lib.mkPackageOption pkgs "paisa" { };
42
43 openFirewall = lib.mkOption {
44 default = false;
45 type = bool;
46 description = "Open ports in the firewall for the Paisa web server.";
47 };
48
49 mutableSettings = lib.mkOption {
50 default = true;
51 type = bool;
52 description = ''
53 Allow changes made on the web interface to persist between service
54 restarts.
55 '';
56 };
57
58 host = lib.mkOption {
59 type = str;
60 default = "0.0.0.0";
61 description = "Host bind IP address.";
62 };
63
64 port = lib.mkOption {
65 type = port;
66 default = 7500;
67 description = "Port to serve Paisa on.";
68 };
69
70 settings = lib.mkOption {
71 default = null;
72 type = nullOr (submodule {
73 freeformType = settingsFormat.type;
74 options = {
75 dataDir = lib.mkOption {
76 type = lib.types.str;
77 default = "/var/lib/paisa/";
78 description = "Path to paisa data directory.";
79 };
80
81 journalFile = lib.mkOption {
82 type = lib.types.str;
83 default = "main.ledger";
84 description = "Filename of the main journal / ledger file.";
85 };
86
87 dbFile = lib.mkOption {
88 type = lib.types.str;
89 default = "paisa.sqlite3";
90 description = "Filename of the Paisa database.";
91 };
92
93 };
94 });
95 description = ''
96 Paisa configuration. Please refer to
97 <https://paisa.fyi/reference/config/> for details.
98
99 On start and if `mutableSettings` is `true`, these options are merged
100 into the configuration file on start, taking precedence over
101 configuration changes made on the web interface.
102 '';
103 };
104 };
105 config = lib.mkIf cfg.enable {
106 assertions = [ ];
107
108 systemd.services.paisa = {
109 description = "Paisa: Web Application";
110 after = [ "network.target" ];
111 wantedBy = [ "multi-user.target" ];
112 unitConfig = {
113 StartLimitIntervalSec = 5;
114 StartLimitBurst = 10;
115 };
116
117 preStart = lib.optionalString (settings != null) ''
118 if [ -e "$STATE_DIRECTORY/paisa.yaml" ] && [ "${toString cfg.mutableSettings}" = "1" ]; then
119 # do not write directly to the config file
120 ${lib.getExe pkgs.yaml-merge} "$STATE_DIRECTORY/paisa.yaml" "${configFile}" > "$STATE_DIRECTORY/paisa.yaml.tmp"
121 mv "$STATE_DIRECTORY/paisa.yaml.tmp" "$STATE_DIRECTORY/paisa.yaml"
122 else
123 cp --force "${configFile}" "$STATE_DIRECTORY/paisa.yaml"
124 chmod 600 "$STATE_DIRECTORY/paisa.yaml"
125 fi
126 '';
127
128 serviceConfig = {
129 DynamicUser = true;
130 ExecStart = "${lib.getExe cfg.package} serve ${args}";
131 AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
132 Restart = "always";
133 RestartSec = 5;
134 RuntimeDirectory = "paisa";
135 StateDirectory = "paisa";
136 };
137 };
138 networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
139 };
140
141 meta = {
142 maintainers = with lib.maintainers; [ skowalak ];
143 doc = ./paisa.md;
144 };
145}