1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.grocy;
7in {
8 options.services.grocy = {
9 enable = mkEnableOption "grocy";
10
11 package = mkPackageOption pkgs "grocy" { };
12
13 hostName = mkOption {
14 type = types.str;
15 description = ''
16 FQDN for the grocy instance.
17 '';
18 };
19
20 nginx.enableSSL = mkOption {
21 type = types.bool;
22 default = true;
23 description = ''
24 Whether or not to enable SSL (with ACME and let's encrypt)
25 for the grocy vhost.
26 '';
27 };
28
29 phpfpm.settings = mkOption {
30 type = with types; attrsOf (oneOf [ int str bool ]);
31 default = {
32 "pm" = "dynamic";
33 "php_admin_value[error_log]" = "stderr";
34 "php_admin_flag[log_errors]" = true;
35 "listen.owner" = "nginx";
36 "catch_workers_output" = true;
37 "pm.max_children" = "32";
38 "pm.start_servers" = "2";
39 "pm.min_spare_servers" = "2";
40 "pm.max_spare_servers" = "4";
41 "pm.max_requests" = "500";
42 };
43
44 description = ''
45 Options for grocy's PHPFPM pool.
46 '';
47 };
48
49 dataDir = mkOption {
50 type = types.str;
51 default = "/var/lib/grocy";
52 description = ''
53 Home directory of the `grocy` user which contains
54 the application's state.
55 '';
56 };
57
58 settings = {
59 currency = mkOption {
60 type = types.str;
61 default = "USD";
62 example = "EUR";
63 description = ''
64 ISO 4217 code for the currency to display.
65 '';
66 };
67
68 culture = mkOption {
69 type = types.enum [ "de" "en" "da" "en_GB" "es" "fr" "hu" "it" "nl" "no" "pl" "pt_BR" "ru" "sk_SK" "sv_SE" "tr" ];
70 default = "en";
71 description = ''
72 Display language of the frontend.
73 '';
74 };
75
76 calendar = {
77 showWeekNumber = mkOption {
78 default = true;
79 type = types.bool;
80 description = ''
81 Show the number of the weeks in the calendar views.
82 '';
83 };
84 firstDayOfWeek = mkOption {
85 default = null;
86 type = types.nullOr (types.enum (range 0 6));
87 description = ''
88 Which day of the week (0=Sunday, 1=Monday etc.) should be the
89 first day.
90 '';
91 };
92 };
93 };
94 };
95
96 config = mkIf cfg.enable {
97 environment.etc."grocy/config.php".text = ''
98 <?php
99 Setting('CULTURE', '${cfg.settings.culture}');
100 Setting('CURRENCY', '${cfg.settings.currency}');
101 Setting('CALENDAR_FIRST_DAY_OF_WEEK', '${toString cfg.settings.calendar.firstDayOfWeek}');
102 Setting('CALENDAR_SHOW_WEEK_OF_YEAR', ${boolToString cfg.settings.calendar.showWeekNumber});
103 '';
104
105 users.users.grocy = {
106 isSystemUser = true;
107 createHome = true;
108 home = cfg.dataDir;
109 group = "nginx";
110 };
111
112 systemd.tmpfiles.rules = map (
113 dirName: "d '${cfg.dataDir}/${dirName}' - grocy nginx - -"
114 ) [ "viewcache" "plugins" "settingoverrides" "storage" ];
115
116 services.phpfpm.pools.grocy = {
117 user = "grocy";
118 group = "nginx";
119
120 # PHP 8.1 and 8.2 are the only version which are supported/tested by upstream:
121 # https://github.com/grocy/grocy/blob/v4.0.2/README.md#platform-support
122 phpPackage = pkgs.php82;
123
124 inherit (cfg.phpfpm) settings;
125
126 phpEnv = {
127 GROCY_CONFIG_FILE = "/etc/grocy/config.php";
128 GROCY_DB_FILE = "${cfg.dataDir}/grocy.db";
129 GROCY_STORAGE_DIR = "${cfg.dataDir}/storage";
130 GROCY_PLUGIN_DIR = "${cfg.dataDir}/plugins";
131 GROCY_CACHE_DIR = "${cfg.dataDir}/viewcache";
132 };
133 };
134
135 # After an update of grocy, the viewcache needs to be deleted. Otherwise grocy will not work
136 # https://github.com/grocy/grocy#how-to-update
137 systemd.services.grocy-setup = {
138 wantedBy = [ "multi-user.target" ];
139 before = [ "phpfpm-grocy.service" ];
140 script = ''
141 rm -rf ${cfg.dataDir}/viewcache/*
142 '';
143 };
144
145 services.nginx = {
146 enable = true;
147 virtualHosts."${cfg.hostName}" = mkMerge [
148 { root = "${cfg.package}/public";
149 locations."/".extraConfig = ''
150 rewrite ^ /index.php;
151 '';
152 locations."~ \\.php$".extraConfig = ''
153 fastcgi_split_path_info ^(.+\.php)(/.+)$;
154 fastcgi_pass unix:${config.services.phpfpm.pools.grocy.socket};
155 include ${config.services.nginx.package}/conf/fastcgi.conf;
156 include ${config.services.nginx.package}/conf/fastcgi_params;
157 '';
158 locations."~ \\.(js|css|ttf|woff2?|png|jpe?g|svg)$".extraConfig = ''
159 add_header Cache-Control "public, max-age=15778463";
160 add_header X-Content-Type-Options nosniff;
161 add_header X-XSS-Protection "1; mode=block";
162 add_header X-Robots-Tag none;
163 add_header X-Download-Options noopen;
164 add_header X-Permitted-Cross-Domain-Policies none;
165 add_header Referrer-Policy no-referrer;
166 access_log off;
167 '';
168 extraConfig = ''
169 try_files $uri /index.php;
170 '';
171 }
172 (mkIf cfg.nginx.enableSSL {
173 enableACME = true;
174 forceSSL = true;
175 })
176 ];
177 };
178 };
179
180 meta = {
181 maintainers = with maintainers; [ n0emis ];
182 doc = ./grocy.md;
183 };
184}