SSR switch: site setting vitec.ssr.enabled gates .htaccess proxy to SSR node
- Site set Vitecset: new bool setting vitec.ssr.enabled (default off, backend switch under Site Management > Settings > VITEC Settings) - SsrFlagService derives var/vitec-ssr-on.flag from the setting; SyncSsrFlag middleware (frontend + backend) re-syncs per request because TYPO3 v14 fires no event on site settings save; vitec:ssr-flag CLI command for status/--sync - public/.htaccess: flag-gated block proxies page requests [P] to https://vitec-ssr.evomedien.de (mod_proxy incl. https target verified on this host 2026-11-09), same exclusions as the SPA fallback block; completely inert while the flag file is absent - Emergency off without TYPO3: delete var/vitec-ssr-on.flag - Tested end to end: flag on -> pages proxied to VPS Caddy (502 while the SSR node is not deployed yet), /_json untouched; flag off -> SPA shell exactly as before Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bd7a145afa
commit
2c0da281bd
6 files changed
+242
No files matched your search
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Service;
|
||||
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
|
||||
/**
|
||||
* Keeps the SSR routing flag file in sync with the site setting
|
||||
* "vitec.ssr.enabled" (Site Set switch, editable in the backend under
|
||||
* Site Management > Settings).
|
||||
*
|
||||
* The flag file is what public/.htaccess checks per request (RewriteCond
|
||||
* ... -f): if it exists, page requests are proxied to the SSR node on the
|
||||
* Solr VPS; if not, the SPA fallback serves _frontend/index.html as
|
||||
* before. TYPO3 itself never routes differently - the flag only steers
|
||||
* Apache, which is why a plain file is used: zero runtime cost in the
|
||||
* hot path and trivially inspectable on the server.
|
||||
*
|
||||
* Saving site settings in the TYPO3 v14 backend fires NO event (core
|
||||
* SiteWriter::writeSettings dispatches nothing, checked 2026-11-09), so
|
||||
* the sync runs self-healingly from the SyncSsrFlag middleware on every
|
||||
* frontend and backend request - a mismatch costs one file operation
|
||||
* once, agreement costs a file_exists(). The vitec:ssr-flag CLI command
|
||||
* offers the same sync plus a status view.
|
||||
*/
|
||||
final class SsrFlagService
|
||||
{
|
||||
public function __construct(private readonly SiteFinder $siteFinder)
|
||||
{
|
||||
}
|
||||
|
||||
public function getFlagPath(): string
|
||||
{
|
||||
return Environment::getVarPath() . '/vitec-ssr-on.flag';
|
||||
}
|
||||
|
||||
/**
|
||||
* True as soon as ANY site enables the setting (there is only the
|
||||
* "vitec" site; iterating keeps us independent of the identifier).
|
||||
*/
|
||||
public function isEnabledInSettings(): bool
|
||||
{
|
||||
try {
|
||||
foreach ($this->siteFinder->getAllSites() as $site) {
|
||||
if ((bool)$site->getSettings()->get('vitec.ssr.enabled', false)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (SiteNotFoundException) {
|
||||
// No sites configured - treat as disabled.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isFlagSet(): bool
|
||||
{
|
||||
return is_file($this->getFlagPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or removes the flag file so it matches the setting.
|
||||
*
|
||||
* @return bool|null The new state if something changed, null if the
|
||||
* flag already matched the setting.
|
||||
*/
|
||||
public function sync(): ?bool
|
||||
{
|
||||
$desired = $this->isEnabledInSettings();
|
||||
if ($desired === $this->isFlagSet()) {
|
||||
return null;
|
||||
}
|
||||
if ($desired) {
|
||||
file_put_contents(
|
||||
$this->getFlagPath(),
|
||||
"SSR routing is ON (site setting vitec.ssr.enabled).\n"
|
||||
. "Managed by SsrFlagService - do not create/delete manually,\n"
|
||||
. "flip the switch in Site Management > Settings instead.\n"
|
||||
. 'Last sync: ' . date('c') . "\n"
|
||||
);
|
||||
} else {
|
||||
@unlink($this->getFlagPath());
|
||||
}
|
||||
return $desired;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync variant for the request hot path: never lets an IO hiccup
|
||||
* (read-only FS, concurrent unlink ...) break request handling.
|
||||
*/
|
||||
public function syncQuietly(): void
|
||||
{
|
||||
try {
|
||||
$this->sync();
|
||||
} catch (\Throwable) {
|
||||
// Intentionally silent - next request retries.
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user