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 }
153 // cfg.poolConfig;
154 };
155
156 postgresql = mkIf cfg.enablePostgresql {
157 enable = true;
158 ensureUsers = [
159 {
160 name = "part-db";
161 ensureDBOwnership = true;
162 }
163 ];
164 ensureDatabases = [ "part-db" ];
165 };
166
167 nginx = mkIf cfg.enableNginx {
168 enable = true;
169 recommendedTlsSettings = lib.mkDefault true;
170 recommendedOptimisation = lib.mkDefault true;
171 recommendedGzipSettings = lib.mkDefault true;
172 virtualHosts.${cfg.virtualHost} = {
173 root = "${pkg}/public";
174 locations = {
175 "/" = {
176 tryFiles = "$uri $uri/ /index.php";
177 index = "index.php";
178 extraConfig = ''
179 sendfile off;
180 '';
181 };
182 "~ \.php$" = {
183 extraConfig = ''
184 include ${config.services.nginx.package}/conf/fastcgi_params ;
185 fastcgi_param SCRIPT_FILENAME $request_filename;
186 fastcgi_param modHeadersAvailable true; #Avoid sending the security headers twice
187 fastcgi_pass unix:${config.services.phpfpm.pools.part-db.socket};
188 '';
189 };
190 };
191 };
192 };
193 };
194
195 systemd = {
196 services = {
197 part-db-migrate = {
198 before = [ "phpfpm-part-db.service" ];
199 after = [ "postgresql.target" ];
200 requires = [ "postgresql.target" ];
201 wantedBy = [ "multi-user.target" ];
202 serviceConfig = {
203 Type = "oneshot";
204 RemainAfterExit = true;
205 User = "part-db";
206 };
207 restartTriggers = [
208 cfg.package
209 ];
210 script = ''
211 set -euo pipefail
212 ${lib.getExe cfg.phpPackage} ${lib.getExe' cfg.package "console"} doctrine:migrations:migrate --no-interaction
213 '';
214 };
215
216 phpfpm-part-db = {
217 after = [ "part-db-migrate.service" ];
218 requires = [
219 "part-db-migrate.service"
220 "postgresql.target"
221 ];
222 # ensure nginx can access the php-fpm socket
223 postStart = ''
224 ${lib.getExe' pkgs.acl "setfacl"} -m 'u:${config.services.nginx.user}:rw' ${config.services.phpfpm.pools.part-db.socket}
225 '';
226 };
227 };
228
229 tmpfiles.settings."part-db" = {
230 "/var/cache/part-db/".d = {
231 mode = "0750";
232 user = "part-db";
233 group = "part-db";
234 };
235 "/var/lib/part-db/env.local"."L+" = {
236 argument = "${pkgs.writeText "part-db-env" (
237 lib.concatStringsSep "\n" (lib.mapAttrsToList (key: value: "${key}=\"${value}\"") cfg.settings)
238 )}";
239 };
240 "/var/log/part-db/".d = {
241 mode = "0750";
242 user = "part-db";
243 group = "part-db";
244 };
245 };
246 };
247 };
248}