at 25.11-pre 9.5 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 10 inherit (lib) 11 generators 12 mapAttrs 13 mkDefault 14 mkEnableOption 15 mkIf 16 mkPackageOption 17 mkOption 18 types 19 ; 20 21 cfg = config.services.grav; 22 23 yamlFormat = pkgs.formats.yaml { }; 24 25 poolName = "grav"; 26 27 servedRoot = pkgs.runCommand "grav-served-root" { } '' 28 cp --reflink=auto --no-preserve=mode -r ${cfg.package} $out 29 30 for p in assets images user system/config; do 31 rm -rf $out/$p 32 ln -sf /var/lib/grav/$p $out/$p 33 done 34 ''; 35 36 systemSettingsYaml = yamlFormat.generate "grav-settings.yaml" cfg.systemSettings; 37 38in 39{ 40 options.services.grav = { 41 enable = mkEnableOption "grav"; 42 43 package = mkPackageOption pkgs "grav" { }; 44 45 root = mkOption { 46 type = types.path; 47 default = "/var/lib/grav"; 48 description = '' 49 Root of the application. 50 ''; 51 }; 52 53 pool = mkOption { 54 type = types.str; 55 default = "${poolName}"; 56 description = '' 57 Name of existing phpfpm pool that is used to run web-application. 58 If not specified a pool will be created automatically with 59 default values. 60 ''; 61 }; 62 63 virtualHost = mkOption { 64 type = types.nullOr types.str; 65 default = "grav"; 66 description = '' 67 Name of the nginx virtualhost to use and setup. If null, do not setup 68 any virtualhost. 69 ''; 70 }; 71 72 phpPackage = mkPackageOption pkgs "php" { }; 73 74 maxUploadSize = mkOption { 75 type = types.str; 76 default = "128M"; 77 description = '' 78 The upload limit for files. This changes the relevant options in 79 {file}`php.ini` and nginx if enabled. 80 ''; 81 }; 82 83 systemSettings = mkOption { 84 type = yamlFormat.type; 85 default = { 86 log = { 87 handler = "syslog"; 88 }; 89 }; 90 description = '' 91 Settings written to {file}`user/config/system.yaml`. 92 ''; 93 }; 94 }; 95 96 config = mkIf cfg.enable { 97 services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") { 98 ${poolName} = { 99 user = "grav"; 100 group = "grav"; 101 102 phpPackage = cfg.phpPackage.buildEnv { 103 extensions = 104 { all, enabled }: 105 enabled 106 ++ (with all; [ 107 apcu 108 xml 109 yaml 110 ]); 111 112 extraConfig = generators.toKeyValue { mkKeyValue = generators.mkKeyValueDefault { } " = "; } { 113 output_buffering = "0"; 114 short_open_tag = "Off"; 115 expose_php = "Off"; 116 error_reporting = "E_ALL"; 117 display_errors = "stderr"; 118 "opcache.interned_strings_buffer" = "8"; 119 "opcache.max_accelerated_files" = "10000"; 120 "opcache.memory_consumption" = "128"; 121 "opcache.revalidate_freq" = "1"; 122 "opcache.fast_shutdown" = "1"; 123 "openssl.cafile" = config.security.pki.caBundle; 124 catch_workers_output = "yes"; 125 126 upload_max_filesize = cfg.maxUploadSize; 127 post_max_size = cfg.maxUploadSize; 128 memory_limit = cfg.maxUploadSize; 129 "apc.enable_cli" = "1"; 130 }; 131 }; 132 133 phpEnv = { 134 GRAV_ROOT = toString servedRoot; 135 GRAV_SYSTEM_PATH = "${servedRoot}/system"; 136 GRAV_CACHE_PATH = "/var/cache/grav"; 137 GRAV_BACKUP_PATH = "/var/lib/grav/backup"; 138 GRAV_LOG_PATH = "/var/log/grav"; 139 GRAV_TMP_PATH = "/var/tmp/grav"; 140 }; 141 142 settings = mapAttrs (name: mkDefault) { 143 "listen.owner" = config.services.nginx.user; 144 "listen.group" = config.services.nginx.group; 145 "listen.mode" = "0600"; 146 "pm" = "dynamic"; 147 "pm.max_children" = 75; 148 "pm.start_servers" = 10; 149 "pm.min_spare_servers" = 5; 150 "pm.max_spare_servers" = 20; 151 "pm.max_requests" = 500; 152 "catch_workers_output" = 1; 153 }; 154 }; 155 }; 156 157 services.nginx = mkIf (cfg.virtualHost != null) { 158 enable = true; 159 virtualHosts = { 160 ${cfg.virtualHost} = { 161 root = "${servedRoot}"; 162 163 locations = { 164 "= /robots.txt" = { 165 priority = 100; 166 extraConfig = '' 167 allow all; 168 access_log off; 169 ''; 170 }; 171 172 "~ \\.php$" = { 173 priority = 200; 174 extraConfig = '' 175 fastcgi_split_path_info ^(.+\.php)(/.+)$; 176 fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket}; 177 fastcgi_index index.php; 178 ''; 179 }; 180 181 "~* /(\\.git|cache|bin|logs|backup|tests)/.*$" = { 182 priority = 300; 183 extraConfig = '' 184 return 403; 185 ''; 186 }; 187 188 # deny running scripts inside core system folders 189 "~* /(system|vendor)/.*\\.(txt|xml|md|html|htm|shtml|shtm|json|yaml|yml|php|php2|php3|php4|php5|phar|phtml|pl|py|cgi|twig|sh|bat)$" = 190 { 191 priority = 300; 192 extraConfig = '' 193 return 403; 194 ''; 195 }; 196 197 # deny running scripts inside user folder 198 "~* /user/.*\\.(txt|md|json|yaml|yml|php|php2|php3|php4|php5|phar|phtml|pl|py|cgi|twig|sh|bat)$" = { 199 priority = 300; 200 extraConfig = '' 201 return 403; 202 ''; 203 }; 204 205 # deny access to specific files in the root folder 206 "~ /(LICENSE\\.txt|composer\\.lock|composer\\.json|nginx\\.conf|web\\.config|htaccess\\.txt|\\.htaccess)" = 207 { 208 priority = 300; 209 extraConfig = '' 210 return 403; 211 ''; 212 }; 213 214 # deny all files and folder beginning with a dot (hidden files & folders) 215 "~ (^|/)\\." = { 216 priority = 300; 217 extraConfig = '' 218 return 403; 219 ''; 220 }; 221 222 "/" = { 223 priority = 400; 224 index = "index.php"; 225 extraConfig = '' 226 try_files $uri $uri/ /index.php?$query_string; 227 ''; 228 }; 229 }; 230 231 extraConfig = '' 232 index index.php index.html /index.php$request_uri; 233 add_header X-Content-Type-Options nosniff; 234 add_header X-XSS-Protection "1; mode=block"; 235 add_header X-Download-Options noopen; 236 add_header X-Permitted-Cross-Domain-Policies none; 237 add_header X-Frame-Options sameorigin; 238 add_header Referrer-Policy no-referrer; 239 client_max_body_size ${cfg.maxUploadSize}; 240 fastcgi_buffers 64 4K; 241 fastcgi_hide_header X-Powered-By; 242 gzip on; 243 gzip_vary on; 244 gzip_comp_level 4; 245 gzip_min_length 256; 246 gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; 247 gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; 248 ''; 249 }; 250 }; 251 }; 252 253 systemd.tmpfiles.rules = 254 let 255 datadir = "/var/lib/grav"; 256 in 257 map (dir: "d '${dir}' 0750 grav grav - -") [ 258 "/var/cache/grav" 259 "${datadir}/assets" 260 "${datadir}/backup" 261 "${datadir}/images" 262 "${datadir}/system/config" 263 "${datadir}/user/accounts" 264 "${datadir}/user/config" 265 "${datadir}/user/data" 266 "/var/log/grav" 267 ] 268 ++ [ "L+ ${datadir}/user/config/system.yaml - - - - ${systemSettingsYaml}" ]; 269 270 systemd.services = { 271 "phpfpm-${poolName}" = mkIf (cfg.pool == "${poolName}") { 272 restartTriggers = [ 273 servedRoot 274 systemSettingsYaml 275 ]; 276 277 serviceConfig = { 278 ExecStartPre = pkgs.writeShellScript "grav-pre-start" '' 279 function setPermits() { 280 chmod -R o-rx "$1" 281 chown -R grav:grav "$1" 282 } 283 284 tmpDir=/var/tmp/grav 285 dataDir=/var/lib/grav 286 287 mkdir $tmpDir 288 setPermits $tmpDir 289 290 for path in config/site.yaml pages plugins themes; do 291 fullPath="$dataDir/user/$path" 292 if [[ ! -e $fullPath ]]; then 293 cp --reflink=auto --no-preserve=mode -r \ 294 ${cfg.package}/user/$path $fullPath 295 fi 296 setPermits $fullPath 297 done 298 299 systemConfigDir=$dataDir/system/config 300 if [[ ! -e $systemConfigDir/system.yaml ]]; then 301 cp --reflink=auto --no-preserve=mode -r \ 302 ${cfg.package}/system/config/* $systemConfigDir/ 303 fi 304 setPermits $systemConfigDir 305 ''; 306 }; 307 }; 308 }; 309 310 users.users.grav = { 311 isSystemUser = true; 312 description = "Grav service user"; 313 home = "/var/lib/grav"; 314 group = "grav"; 315 }; 316 317 users.groups.grav = { 318 members = [ config.services.nginx.user ]; 319 }; 320 }; 321}