]> git.immae.eu Git - perso/Immae/Config/Nix.git/blob - modules/websites/php-application.nix
8ad7a0dfe33e71ac3970242c5cbb3ff874259d1f
[perso/Immae/Config/Nix.git] / modules / websites / php-application.nix
1 { lib, config, ... }:
2 with lib;
3 let
4 cfg = config.services.phpApplication;
5 cfgByEnv = lists.groupBy (x: x.websiteEnv) (builtins.attrValues cfg.apps);
6 in
7 {
8 options = with types; {
9 services.phpApplication.apps = mkOption {
10 default = {};
11 description = ''
12 php applications to define
13 '';
14 type = attrsOf (submodule {
15 options = {
16 varDir = mkOption {
17 type = nullOr path;
18 description = ''
19 Path to application’s vardir.
20 '';
21 };
22 varDirPaths = mkOption {
23 type = attrsOf str;
24 default = {};
25 description = ''
26 Map of additional folders => mode to create under varDir
27 '';
28 };
29 mode = mkOption {
30 type = str;
31 default = "0700";
32 description = ''
33 Mode to apply to the vardir
34 '';
35 };
36 phpSession = mkOption {
37 type = bool;
38 default = true;
39 description = "Handle phpsession files separately in vardir";
40 };
41 phpListen = mkOption {
42 type = nullOr str;
43 default = null;
44 description = "Name of the socket to listen to. Defaults to app name if null";
45 };
46 phpPool = mkOption {
47 type = lines;
48 default = "";
49 description = "Pool configuration to append";
50 };
51 phpOptions = mkOption {
52 type = lines;
53 default = "";
54 description = "php configuration to append";
55 };
56 phpOpenbasedir = mkOption {
57 type = listOf path;
58 default = [];
59 description = ''
60 paths to add to php open_basedir configuration in addition to app and vardir
61 '';
62 };
63 phpWatchFiles = mkOption {
64 type = listOf path;
65 default = [];
66 description = ''
67 Path to other files to watch to trigger preStart scripts
68 '';
69 };
70 websiteEnv = mkOption {
71 type = str;
72 description = ''
73 website instance name to use
74 '';
75 };
76 httpdUser = mkOption {
77 type = str;
78 default = config.services.httpd.user;
79 description = ''
80 httpd user to run the prestart scripts as.
81 '';
82 };
83 httpdGroup = mkOption {
84 type = str;
85 default = config.services.httpd.group;
86 description = ''
87 httpd group to run the prestart scripts as.
88 '';
89 };
90 httpdWatchFiles = mkOption {
91 type = listOf path;
92 default = [];
93 description = ''
94 Path to other files to watch to trigger httpd reload
95 '';
96 };
97 app = mkOption {
98 type = path;
99 description = ''
100 Path to application root
101 '';
102 };
103 webappName = mkOption {
104 type = nullOr str;
105 default = null;
106 description = ''
107 Alias name for the app, to be used in services.websites.webappDirs
108 '';
109 };
110 webRoot = mkOption {
111 type = nullOr path;
112 description = ''
113 Path to the web root path of the application. May differ from the application itself (usually a subdirectory)
114 '';
115 };
116 preStartActions = mkOption {
117 type = listOf str;
118 default = [];
119 description = ''
120 List of actions to run as apache user at preStart when
121 whatchFiles or app dir changed.
122 '';
123 };
124 serviceDeps = mkOption {
125 type = listOf str;
126 default = [];
127 description = ''
128 List of systemd services this application depends on
129 '';
130 };
131 };
132 });
133 };
134 # Read-only variables
135 services.phpApplication.phpListenPaths = mkOption {
136 type = attrsOf path;
137 default = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair
138 name "/run/phpfpm/${if icfg.phpListen == null then name else icfg.phpListen}.sock"
139 ) cfg.apps;
140 readOnly = true;
141 description = ''
142 Full paths to listen for php
143 '';
144 };
145 services.phpApplication.webappDirs = mkOption {
146 type = attrsOf path;
147 default = attrsets.filterAttrs (n: v: builtins.hasAttr n cfg.apps) config.services.websites.webappDirsPaths;
148 readOnly = true;
149 description = ''
150 Stable name webapp dirs for httpd
151 '';
152 };
153 };
154
155 config = {
156 services.websites.env = attrsets.mapAttrs' (name: cfgs: attrsets.nameValuePair
157 name {
158 modules = [ "proxy_fcgi" ];
159 watchPaths = builtins.concatLists (map (c: c.httpdWatchFiles) cfgs);
160 }
161 ) cfgByEnv;
162
163 services.phpfpm.pools = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair
164 name {
165 listen = cfg.phpListenPaths."${name}";
166 extraConfig = ''
167 user = ${icfg.httpdUser}
168 group = ${icfg.httpdGroup}
169 listen.owner = ${icfg.httpdUser}
170 listen.group = ${icfg.httpdGroup}
171 ${optionalString (icfg.phpSession) ''
172 php_admin_value[session.save_path] = "${icfg.varDir}/phpSessions"''}
173 php_admin_value[open_basedir] = "${builtins.concatStringsSep ":" ([icfg.app icfg.varDir] ++ icfg.phpWatchFiles ++ icfg.phpOpenbasedir)}"
174 '' + icfg.phpPool;
175 phpOptions = config.services.phpfpm.phpOptions + icfg.phpOptions;
176 }
177 ) cfg.apps;
178
179 services.websites.webappDirs = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair
180 (if icfg.webappName == null then name else icfg.webappName) icfg.webRoot
181 ) (attrsets.filterAttrs (n: v: !isNull v.webRoot) cfg.apps);
182
183 services.filesWatcher = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair
184 "phpfpm-${name}" {
185 restart = true;
186 paths = icfg.phpWatchFiles;
187 }
188 ) (attrsets.filterAttrs (n: v: builtins.length v.phpWatchFiles > 0) cfg.apps);
189
190 systemd.services = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair
191 "phpfpm-${name}" {
192 after = lib.mkAfter icfg.serviceDeps;
193 wants = icfg.serviceDeps;
194 preStart = lib.mkAfter (optionalString (!isNull icfg.varDir) ''
195 watchFilesChanged() {
196 ${optionalString (builtins.length icfg.phpWatchFiles == 0) "return 1"}
197 [ ! -f "${icfg.varDir}"/watchedFiles ] \
198 || ! sha512sum -c --status ${icfg.varDir}/watchedFiles
199 }
200 appDirChanged() {
201 [ ! -f "${icfg.varDir}/currentWebappDir" -o \
202 "${icfg.app}" != "$(cat ${icfg.varDir}/currentWebappDir 2>/dev/null)" ]
203 }
204 updateWatchFiles() {
205 ${optionalString (builtins.length icfg.phpWatchFiles == 0) "return 0"}
206 sha512sum ${builtins.concatStringsSep " " icfg.phpWatchFiles} > ${icfg.varDir}/watchedFiles
207 }
208
209 if watchFilesChanged || appDirChanged; then
210 pushd ${icfg.app} > /dev/null
211 ${builtins.concatStringsSep "\n " (map (c: "/run/wrappers/bin/sudo -u ${icfg.httpdUser} ${c}") icfg.preStartActions) }
212 popd > /dev/null
213 echo -n "${icfg.app}" > ${icfg.varDir}/currentWebappDir
214 updateWatchFiles
215 fi
216 '');
217 }
218 ) cfg.apps;
219
220 system.activationScripts = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair
221 name {
222 deps = [];
223 text = optionalString (!isNull icfg.varDir) ''
224 install -m ${icfg.mode} -o ${icfg.httpdUser} -g ${icfg.httpdGroup} -d ${icfg.varDir}
225 '' + optionalString (icfg.phpSession) ''
226 install -m 0700 -o ${icfg.httpdUser} -g ${icfg.httpdGroup} -d ${icfg.varDir}/phpSessions
227 '' + builtins.concatStringsSep "\n" (attrsets.mapAttrsToList (n: v: ''
228 install -m ${v} -o ${icfg.httpdUser} -g ${icfg.httpdGroup} -d ${icfg.varDir}/${n}
229 '') icfg.varDirPaths);
230 }
231 ) cfg.apps;
232 };
233 }