1{
2 pkgs,
3 config,
4 lib,
5 ...
6}:
7let
8 cfg = config.services.part-db;
9 pkg = cfg.package;
10
11 inherit (lib)
12 mkEnableOption
13 mkPackageOption
14 mkOption
15 types
16 mkIf
17 ;
18in
19{
20 meta.maintainers = with lib.maintainers; [ felbinger ];
21
22 options.services.part-db = {
23 enable = mkEnableOption "PartDB";
24
25 package = mkPackageOption pkgs "part-db" { };
26
27 phpPackage = mkPackageOption pkgs "php" { } // {
28 apply =
29 pkg:
30 pkg.buildEnv {
31 extraConfig = ''
32 memory_limit = 256M;
33 '';
34 };
35 };
36
37 enableNginx = mkOption {
38 type = types.bool;
39 default = true;
40 description = ''
41 Whether to enable nginx or not. If enabled, an nginx virtual host will
42 be created for access to part-db. If not enabled, then you may use
43 `''${config.services.part-db.package}/public` as your document root in
44 whichever webserver you wish to setup.
45 '';
46 };
47
48 enablePostgresql = mkOption {
49 type = types.bool;
50 default = true;
51 description = ''
52 Whether to configure the postgresql database for part-db. If enabled,
53 a database and user will be created for part-db.
54 '';
55 };
56
57 virtualHost = mkOption {
58 type = types.str;
59 default = "localhost";
60 description = ''
61 The virtualHost at which you wish part-db to be served.
62 '';
63 };
64
65 poolConfig = lib.mkOption {
66 type = lib.types.attrsOf (
67 lib.types.oneOf [
68 lib.types.str
69 lib.types.int
70 lib.types.bool
71 ]
72 );
73 default = { };
74 defaultText = ''
75 {
76 "pm" = "dynamic";
77 "pm.max_children" = 32;
78 "pm.start_servers" = 2;
79 "pm.min_spare_servers" = 2;
80 "pm.max_spare_servers" = 4;
81 "pm.max_requests" = 500;
82 }
83 '';
84 description = ''
85 Options for the PartDB PHP pool. See the documentation on <literal>php-fpm.conf</literal>
86 for details on configuration directives.
87 '';
88 };
89
90 settings = lib.mkOption {
91 default = { };
92 description = ''
93 Options for part-db configuration. Refer to
94 <https://github.com/Part-DB/Part-DB-server/blob/master/.env> for
95 details on supported values. All <option>_FILE values supported by
96 upstream are supported here.
97 '';
98 example = lib.literalExpression ''
99 {
100 DATABASE_URL = "postgresql://db_user@localhost/db_name?serverVersion=16.6&charset=utf8&host=/var/run/postgresql";
101 }
102 '';
103 type = lib.types.submodule {
104 freeformType = lib.types.attrsOf (
105 with lib.types;
106 oneOf [
107 str
108 int
109 bool
110 ]
111 );
112 options = {
113 DATABASE_URL = lib.mkOption {
114 type = lib.types.str;
115 default = "postgresql://part-db@localhost/part-db?serverVersion=${config.services.postgresql.package.version}&host=/run/postgresql";
116 defaultText = "postgresql://part-db@localhost/part-db?serverVersion=\${config.services.postgresql.package.version}&host=/run/postgresql";
117 description = ''
118 The postgresql database server to connect to.
119 Defauls to local postgresql unix socket
120 '';
121 };
122 };
123 };
124 };
125 };
126
127 config = mkIf cfg.enable {
128 users.groups.part-db = { };
129 users.users.part-db = {
130 group = "part-db";
131 isSystemUser = true;
132 };
133
134 services = {
135 phpfpm.pools.part-db = {
136 user = "part-db";
137 group = "part-db";
138 phpPackage = cfg.phpPackage;
139 phpOptions = ''
140 log_errors = on
141 '';
142 settings = {
143 "listen.mode" = lib.mkDefault "0660";
144 "listen.owner" = lib.mkDefault "part-db";
145 "listen.group" = lib.mkDefault "part-db";
146 "pm" = lib.mkDefault "dynamic";
147 "pm.max_children" = lib.mkDefault 32;
148 "pm.start_servers" = lib.mkDefault 2;
149 "pm.min_spare_servers" = lib.mkDefault 2;
150 "pm.max_spare_servers" = lib.mkDefault 4;
151 "pm.max_requests" = lib.mkDefault 500;
152 } // cfg.poolConfig;
153 };
154
155 postgresql = mkIf cfg.enablePostgresql {
156 enable = true;
157 ensureUsers = [
158 {
159 name = "part-db";
160 ensureDBOwnership = true;
161 }
162 ];
163 ensureDatabases = [ "part-db" ];
164 };
165
166 nginx = mkIf cfg.enableNginx {
167 enable = true;
168 recommendedTlsSettings = lib.mkDefault true;
169 recommendedOptimisation = lib.mkDefault true;
170 recommendedGzipSettings = lib.mkDefault true;
171 virtualHosts.${cfg.virtualHost} = {
172 root = "${pkg}/public";
173 locations = {
174 "/" = {
175 tryFiles = "$uri $uri/ /index.php";
176 index = "index.php";
177 extraConfig = ''
178 sendfile off;
179 '';
180 };
181 "~ \.php$" = {
182 extraConfig = ''
183 include ${config.services.nginx.package}/conf/fastcgi_params ;
184 fastcgi_param SCRIPT_FILENAME $request_filename;
185 fastcgi_param modHeadersAvailable true; #Avoid sending the security headers twice
186 fastcgi_pass unix:${config.services.phpfpm.pools.part-db.socket};
187 '';
188 };
189 };
190 };
191 };
192 };
193
194 systemd = {
195 services = {
196 part-db-migrate = {
197 before = [ "phpfpm-part-db.service" ];
198 after = [ "postgresql.service" ];
199 requires = [ "postgresql.service" ];
200 wantedBy = [ "multi-user.target" ];
201 serviceConfig = {
202 Type = "oneshot";
203 RemainAfterExit = true;
204 User = "part-db";
205 };
206 restartTriggers = [
207 cfg.package
208 ];
209 script = ''
210 set -euo pipefail
211 ${lib.getExe cfg.phpPackage} ${lib.getExe' cfg.package "console"} doctrine:migrations:migrate --no-interaction
212 '';
213 };
214
215 phpfpm-part-db = {
216 after = [ "part-db-migrate.service" ];
217 requires = [
218 "part-db-migrate.service"
219 "postgresql.service"
220 ];
221 # ensure nginx can access the php-fpm socket
222 postStart = ''
223 ${lib.getExe' pkgs.acl "setfacl"} -m 'u:${config.services.nginx.user}:rw' ${config.services.phpfpm.pools.part-db.socket}
224 '';
225 };
226 };
227
228 tmpfiles.settings."part-db" = {
229 "/var/cache/part-db/".d = {
230 mode = "0750";
231 user = "part-db";
232 group = "part-db";
233 };
234 "/var/lib/part-db/env.local"."L+" = {
235 argument = "${pkgs.writeText "part-db-env" (
236 lib.concatStringsSep "\n" (lib.mapAttrsToList (key: value: "${key}=\"${value}\"") cfg.settings)
237 )}";
238 };
239 "/var/log/part-db/".d = {
240 mode = "0750";
241 user = "part-db";
242 group = "part-db";
243 };
244 };
245 };
246 };
247}