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