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