diff --git a/src/Service/WebserverConfig/NginxEmitter.php b/src/Service/WebserverConfig/NginxEmitter.php index 8376052..0d1e8a9 100644 --- a/src/Service/WebserverConfig/NginxEmitter.php +++ b/src/Service/WebserverConfig/NginxEmitter.php @@ -20,10 +20,16 @@ * sites/.conf Per-host server blocks * tls/.conf Operator-owned TLS knobs (sketch mode) * - * Ordering matters in nginx location dispatch. The emitter puts - * forbid blocks (specific paths) before the alias-based front-controller - * fallback, and the fastcgi handler goes at server scope so it applies - * across every included location block. + * Ordering matters in nginx location dispatch. Every app is wrapped + * in a single `^~` prefix location so it wins over any regex location + * at server scope. Inside it the forbid blocks (specific paths) come + * first, the php handler next, and the front-controller fallback last. + * + * The php handler is scoped PER APP rather than at server scope: an + * aliased subpath location needs SCRIPT_FILENAME derived from the + * alias ($request_filename), whereas a root-anchored app served from + * the server-level `root` uses $document_root$fastcgi_script_name. A + * single server-scope handler cannot satisfy both. */ final class NginxEmitter { @@ -40,7 +46,7 @@ public function emit(MapBuildResult $map, string $outputDir, string $phpHandlerS foreach ($map->apps as $app) { $entries[] = new EmitEntry( $outputDir . '/horde-includes/apps/' . $app->id . '.conf', - $this->renderAppSnippet($app), + $this->renderAppSnippet($app, $phpHandlerSpec), ); } $byHost = $this->groupByHost($map); @@ -74,54 +80,81 @@ private function groupByHost(MapBuildResult $map): array return $out; } - private function renderAppSnippet(AppEntry $app): string + private function renderAppSnippet(AppEntry $app, string $phpHandlerSpec): string { + [$fastcgiDirective] = $this->render->phpHandler($phpHandlerSpec, 'nginx'); + $rootAnchored = $app->isRootAnchored(); + $pathPrefix = rtrim($app->pathPrefix(), '/'); + $frontController = $this->render->frontControllerFor($app); + // For root-anchored apps the location covers `/`; for subpath + // apps the prefix path. + $locationMatch = $rootAnchored ? '/' : $pathPrefix . '/'; + // SCRIPT_FILENAME has to reflect the on-disk path php-fpm should + // execute. For a root-anchored app served from the server-level + // `root`, $document_root$fastcgi_script_name is correct. For an + // aliased subpath location that expression points at the server + // root, NOT the app's fileroot, so php-fpm would fail to find + // the script. $request_filename honours the alias and resolves + // to /