Skip to content

Commit ea4bac3

Browse files
Merge pull request #22 from move-elevator/docs/webserver-configuration
docs: add web server configuration guide for feature branch deployment
2 parents ca66943 + 425af2c commit ea4bac3

3 files changed

Lines changed: 128 additions & 32 deletions

File tree

deployer/requirements/functions.php

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ function renderRequirementsTable(): void
8181
$rows = get('requirements_rows');
8282

8383
$tableRows = array_map(
84-
static fn(array $row): array => [
84+
static fn (array $row): array => [
8585
$row['check'],
8686
formatRequirementStatus($row['status']),
8787
$row['info'],
@@ -117,7 +117,7 @@ function parsePhpBytes(string $value): int
117117
{
118118
$value = trim($value);
119119

120-
if ($value === '-1') {
120+
if ('-1' === $value) {
121121
return -1;
122122
}
123123

@@ -138,11 +138,11 @@ function meetsPhpRequirement(string $actual, string $expected, string $setting):
138138
$actualBytes = parsePhpBytes($actual);
139139
$expectedBytes = parsePhpBytes($expected);
140140

141-
if ($actualBytes === -1) {
141+
if (-1 === $actualBytes) {
142142
return true;
143143
}
144144

145-
if ($expectedBytes === -1) {
145+
if (-1 === $expectedBytes) {
146146
return false;
147147
}
148148

@@ -153,7 +153,7 @@ function meetsPhpRequirement(string $actual, string $expected, string $setting):
153153
$actualInt = (int) $actual;
154154
$expectedInt = (int) $expected;
155155

156-
if ($setting === 'max_execution_time' && $actualInt === 0) {
156+
if ('max_execution_time' === $setting && 0 === $actualInt) {
157157
return true;
158158
}
159159

@@ -176,7 +176,7 @@ function detectPackageVersion(string $command): ?string
176176
try {
177177
$output = trim(run($versionCmd));
178178

179-
if ($output !== '' && preg_match('/(\d+[\d.]*)/', $output, $matches)) {
179+
if ('' !== $output && preg_match('/(\d+[\d.]*)/', $output, $matches)) {
180180
return $matches[1];
181181
}
182182
} catch (RunException) {
@@ -197,7 +197,7 @@ function detectDatabaseProduct(): ?array
197197
try {
198198
$versionOutput = trim(run("$command --version 2>/dev/null"));
199199

200-
if ($versionOutput === '') {
200+
if ('' === $versionOutput) {
201201
continue;
202202
}
203203

@@ -239,7 +239,7 @@ function fetchEolCycles(string $product, int $timeout = 5): ?array
239239

240240
$response = @file_get_contents($url, false, $context);
241241

242-
if ($response === false) {
242+
if (false === $response) {
243243
return null;
244244
}
245245

@@ -285,7 +285,7 @@ function evaluateEolStatus(string $label, array $cycle, int $warnMonths): void
285285

286286
$eolFrom = $cycle['eolFrom'] ?? null;
287287

288-
if ($eolFrom !== null) {
288+
if (null !== $eolFrom) {
289289
try {
290290
$eolDate = new \DateTimeImmutable($eolFrom);
291291
} catch (\Exception) {
@@ -317,14 +317,14 @@ function evaluateEolStatus(string $label, array $cycle, int $warnMonths): void
317317

318318
if ($isEoas) {
319319
$info = 'Security support only';
320-
$info .= $eolFrom !== null ? ", EOL $eolFrom" : '';
320+
$info .= null !== $eolFrom ? ", EOL $eolFrom" : '';
321321
addRequirementRow("EOL: $label", REQUIREMENT_WARN, $info);
322322

323323
return;
324324
}
325325

326326
$info = 'Maintained';
327-
$info .= $eolFrom !== null ? " until $eolFrom" : '';
327+
$info .= null !== $eolFrom ? " until $eolFrom" : '';
328328
addRequirementRow("EOL: $label", REQUIREMENT_OK, $info);
329329
}
330330

@@ -335,15 +335,15 @@ function checkEolForProduct(string $label, string $product, string $cycle, int $
335335
{
336336
$cycles = fetchEolCycles($product, $timeout);
337337

338-
if ($cycles === null) {
338+
if (null === $cycles) {
339339
addRequirementRow("EOL: $label", REQUIREMENT_SKIP, 'Could not reach endoflife.date API');
340340

341341
return;
342342
}
343343

344344
$match = findEolCycle($cycles, $cycle);
345345

346-
if ($match === null) {
346+
if (null === $match) {
347347
addRequirementRow("EOL: $label", REQUIREMENT_SKIP, "Cycle $cycle not found in API");
348348

349349
return;
@@ -366,7 +366,7 @@ function isServiceActive(string ...$names): ?string
366366
if ($hasSystemctl) {
367367
$status = trim(run("systemctl is-active $name 2>/dev/null || true"));
368368

369-
if ($status === 'active') {
369+
if ('active' === $status) {
370370
return $name;
371371
}
372372
}
@@ -401,7 +401,7 @@ function getSharedEnvVars(): array
401401
foreach ($lines as $line) {
402402
$line = trim($line);
403403

404-
if ($line === '' || str_starts_with($line, '#')) {
404+
if ('' === $line || str_starts_with($line, '#')) {
405405
continue;
406406
}
407407

@@ -442,59 +442,59 @@ function resolveDatabaseCredentials(): ?array
442442
$host = has('database_host') ? (string) get('database_host') : '127.0.0.1';
443443
$port = has('database_port') ? (int) get('database_port') : 3306;
444444

445-
if ($password === '') {
445+
if ('' === $password) {
446446
$envPassword = getenv('DEPLOYER_CONFIG_DATABASE_PASSWORD');
447447

448-
if (is_string($envPassword) && $envPassword !== '') {
448+
if (is_string($envPassword) && '' !== $envPassword) {
449449
$password = $envPassword;
450450
}
451451
}
452452

453-
if ($user === '' || $password === '') {
453+
if ('' === $user || '' === $password) {
454454
$envVars = getSharedEnvVars();
455455

456-
if (has('app_type') && get('app_type') === 'typo3') {
457-
if ($user === '') {
456+
if (has('app_type') && 'typo3' === get('app_type')) {
457+
if ('' === $user) {
458458
$user = $envVars['TYPO3_CONF_VARS__DB__Connections__Default__user'] ?? '';
459459
}
460460

461-
if ($password === '') {
461+
if ('' === $password) {
462462
$key = has('env_key_database_passwort')
463463
? get('env_key_database_passwort')
464464
: 'TYPO3_CONF_VARS__DB__Connections__Default__password';
465465
$password = $envVars[$key] ?? '';
466466
}
467467

468-
if ($host === '127.0.0.1') {
468+
if ('127.0.0.1' === $host) {
469469
$envHost = $envVars['TYPO3_CONF_VARS__DB__Connections__Default__host'] ?? '';
470470

471-
if ($envHost !== '') {
471+
if ('' !== $envHost) {
472472
$host = $envHost;
473473
}
474474
}
475-
} elseif (has('app_type') && get('app_type') === 'symfony') {
475+
} elseif (has('app_type') && 'symfony' === get('app_type')) {
476476
$databaseUrl = $envVars['DATABASE_URL'] ?? '';
477477

478-
if ($databaseUrl !== '') {
479-
if ($user === '') {
478+
if ('' !== $databaseUrl) {
479+
if ('' === $user) {
480480
$parsed = parse_url($databaseUrl, PHP_URL_USER);
481481
$user = is_string($parsed) ? urldecode($parsed) : '';
482482
}
483483

484-
if ($password === '') {
484+
if ('' === $password) {
485485
$parsed = parse_url($databaseUrl, PHP_URL_PASS);
486486
$password = is_string($parsed) ? urldecode($parsed) : '';
487487
}
488488

489-
if ($host === '127.0.0.1') {
489+
if ('127.0.0.1' === $host) {
490490
$parsed = parse_url($databaseUrl, PHP_URL_HOST);
491491

492-
if (is_string($parsed) && $parsed !== '') {
492+
if (is_string($parsed) && '' !== $parsed) {
493493
$host = $parsed;
494494
}
495495
}
496496

497-
if ($port === 3306) {
497+
if (3306 === $port) {
498498
$parsed = parse_url($databaseUrl, PHP_URL_PORT);
499499

500500
if (is_int($parsed)) {
@@ -505,7 +505,7 @@ function resolveDatabaseCredentials(): ?array
505505
}
506506
}
507507

508-
if ($user === '' || $password === '') {
508+
if ('' === $user || '' === $password) {
509509
return null;
510510
}
511511

@@ -536,7 +536,7 @@ function parseGlobalGrants(string $grantsOutput): array
536536

537537
$grantsStr = strtoupper(trim($matches[1]));
538538

539-
if ($grantsStr === 'ALL PRIVILEGES' || $grantsStr === 'ALL') {
539+
if ('ALL PRIVILEGES' === $grantsStr || 'ALL' === $grantsStr) {
540540
return ['ok' => true, 'missing' => []];
541541
}
542542

docs/FEATURE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ The following steps are necessary to successfully setup the deployment workflow:
3030

3131
Choose an according [database management](DATABASE.md) type for your application.
3232

33+
Configure your [web server](WEBSERVER.md) (Apache or nginx) to serve feature branch instances.
34+
3335
Add the following line to your deployer host entry, to enable the feature branch deployment for this stage:
3436

3537
```yaml

docs/WEBSERVER.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Web server
2+
3+
The feature branch deployment supports both **Apache** and **nginx** as web server. The deployment tooling itself is web server agnostic — it uses filesystem symlinks for URL shortening and does not generate or depend on any web server configuration files.
4+
5+
## Apache
6+
7+
Apache works with minimal effort because TYPO3 and Symfony ship with `.htaccess` files that handle URL rewriting per directory. When a new feature branch is deployed, the application's `.htaccess` is available immediately without any web server restart or configuration change.
8+
9+
The only requirement is that `AllowOverride All` is set for the document root in the vhost configuration.
10+
11+
## nginx
12+
13+
Since nginx does not support per-directory configuration files like `.htaccess`, the URL rewriting and PHP routing must be defined in the server block configuration. This requires a one-time setup that covers all current and future feature branch instances.
14+
15+
### Prerequisites
16+
17+
1. **Symlinks** must be followed (this is the nginx default). Ensure `disable_symlinks` is **not** set to `on`.
18+
19+
2. **PHP-FPM** must be configured to process `.php` files in subdirectories, not just the document root.
20+
21+
3. **URL rewriting** for the application (TYPO3 or Symfony) must be handled in the server block, since there is no `.htaccess` to fall back on.
22+
23+
### Example for TYPO3
24+
25+
```nginx
26+
server {
27+
listen 443 ssl;
28+
server_name demo.local;
29+
root /var/www/html;
30+
index index.php index.html;
31+
32+
# Feature branch instances and main application
33+
location / {
34+
try_files $uri $uri/ @rewrite;
35+
}
36+
37+
# Rewrite all non-file requests to the nearest index.php.
38+
# Supports both root-level and feature branch subdirectory requests.
39+
location @rewrite {
40+
rewrite ^/([^/]+)/(.*)$ /$1/index.php last;
41+
rewrite ^(.*)$ /index.php last;
42+
}
43+
44+
# Deny access to protected directories across all instances
45+
location ~ /(typo3conf|var|config)/ {
46+
return 403;
47+
}
48+
49+
# PHP-FPM for all .php files including subdirectories
50+
location ~ \.php$ {
51+
include fastcgi_params;
52+
fastcgi_pass unix:/run/php/php-fpm.sock; # adjust to match your PHP-FPM pool socket
53+
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
54+
try_files $uri =404;
55+
}
56+
}
57+
```
58+
59+
See also the official [TYPO3 nginx configuration guide](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Configuration/WebServer/Nginx.html) for application-specific details.
60+
61+
### Example for Symfony
62+
63+
```nginx
64+
server {
65+
listen 443 ssl;
66+
server_name demo.local;
67+
root /var/www/html;
68+
index index.php index.html;
69+
70+
location / {
71+
try_files $uri $uri/ @rewrite;
72+
}
73+
74+
location @rewrite {
75+
rewrite ^/([^/]+)/(.*)$ /$1/index.php last;
76+
rewrite ^(.*)$ /index.php last;
77+
}
78+
79+
location ~ \.php$ {
80+
include fastcgi_params;
81+
fastcgi_pass unix:/run/php/php-fpm.sock; # adjust to match your PHP-FPM pool socket
82+
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
83+
try_files $uri =404;
84+
}
85+
}
86+
```
87+
88+
## User group
89+
90+
The web server user group might differ between Apache (`www-data`) and nginx (`nginx` or `www-data` depending on distribution). Adjust the deployer configuration accordingly:
91+
92+
```php
93+
set('requirements_user_group', 'nginx');
94+
```

0 commit comments

Comments
 (0)