Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 32 additions & 32 deletions deployer/requirements/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function renderRequirementsTable(): void
$rows = get('requirements_rows');

$tableRows = array_map(
static fn(array $row): array => [
static fn (array $row): array => [
$row['check'],
formatRequirementStatus($row['status']),
$row['info'],
Expand Down Expand Up @@ -117,7 +117,7 @@ function parsePhpBytes(string $value): int
{
$value = trim($value);

if ($value === '-1') {
if ('-1' === $value) {
return -1;
}

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

if ($actualBytes === -1) {
if (-1 === $actualBytes) {
return true;
}

if ($expectedBytes === -1) {
if (-1 === $expectedBytes) {
return false;
}

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

if ($setting === 'max_execution_time' && $actualInt === 0) {
if ('max_execution_time' === $setting && 0 === $actualInt) {
return true;
}

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

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

if ($versionOutput === '') {
if ('' === $versionOutput) {
continue;
}

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

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

if ($response === false) {
if (false === $response) {
return null;
}

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

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

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

if ($isEoas) {
$info = 'Security support only';
$info .= $eolFrom !== null ? ", EOL $eolFrom" : '';
$info .= null !== $eolFrom ? ", EOL $eolFrom" : '';
addRequirementRow("EOL: $label", REQUIREMENT_WARN, $info);

return;
}

$info = 'Maintained';
$info .= $eolFrom !== null ? " until $eolFrom" : '';
$info .= null !== $eolFrom ? " until $eolFrom" : '';
addRequirementRow("EOL: $label", REQUIREMENT_OK, $info);
}

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

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

return;
}

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

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

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

if ($status === 'active') {
if ('active' === $status) {
return $name;
}
}
Expand Down Expand Up @@ -401,7 +401,7 @@ function getSharedEnvVars(): array
foreach ($lines as $line) {
$line = trim($line);

if ($line === '' || str_starts_with($line, '#')) {
if ('' === $line || str_starts_with($line, '#')) {
continue;
}

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

if ($password === '') {
if ('' === $password) {
$envPassword = getenv('DEPLOYER_CONFIG_DATABASE_PASSWORD');

if (is_string($envPassword) && $envPassword !== '') {
if (is_string($envPassword) && '' !== $envPassword) {
$password = $envPassword;
}
}

if ($user === '' || $password === '') {
if ('' === $user || '' === $password) {
$envVars = getSharedEnvVars();

if (has('app_type') && get('app_type') === 'typo3') {
if ($user === '') {
if (has('app_type') && 'typo3' === get('app_type')) {
if ('' === $user) {
$user = $envVars['TYPO3_CONF_VARS__DB__Connections__Default__user'] ?? '';
}

if ($password === '') {
if ('' === $password) {
$key = has('env_key_database_passwort')
? get('env_key_database_passwort')
: 'TYPO3_CONF_VARS__DB__Connections__Default__password';
$password = $envVars[$key] ?? '';
}

if ($host === '127.0.0.1') {
if ('127.0.0.1' === $host) {
$envHost = $envVars['TYPO3_CONF_VARS__DB__Connections__Default__host'] ?? '';

if ($envHost !== '') {
if ('' !== $envHost) {
$host = $envHost;
}
}
} elseif (has('app_type') && get('app_type') === 'symfony') {
} elseif (has('app_type') && 'symfony' === get('app_type')) {
$databaseUrl = $envVars['DATABASE_URL'] ?? '';

if ($databaseUrl !== '') {
if ($user === '') {
if ('' !== $databaseUrl) {
if ('' === $user) {
$parsed = parse_url($databaseUrl, PHP_URL_USER);
$user = is_string($parsed) ? urldecode($parsed) : '';
}

if ($password === '') {
if ('' === $password) {
$parsed = parse_url($databaseUrl, PHP_URL_PASS);
$password = is_string($parsed) ? urldecode($parsed) : '';
}

if ($host === '127.0.0.1') {
if ('127.0.0.1' === $host) {
$parsed = parse_url($databaseUrl, PHP_URL_HOST);

if (is_string($parsed) && $parsed !== '') {
if (is_string($parsed) && '' !== $parsed) {
$host = $parsed;
}
}

if ($port === 3306) {
if (3306 === $port) {
$parsed = parse_url($databaseUrl, PHP_URL_PORT);

if (is_int($parsed)) {
Expand All @@ -505,7 +505,7 @@ function resolveDatabaseCredentials(): ?array
}
}

if ($user === '' || $password === '') {
if ('' === $user || '' === $password) {
return null;
}

Expand Down Expand Up @@ -536,7 +536,7 @@ function parseGlobalGrants(string $grantsOutput): array

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

if ($grantsStr === 'ALL PRIVILEGES' || $grantsStr === 'ALL') {
if ('ALL PRIVILEGES' === $grantsStr || 'ALL' === $grantsStr) {
return ['ok' => true, 'missing' => []];
}

Expand Down
2 changes: 2 additions & 0 deletions docs/FEATURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ The following steps are necessary to successfully setup the deployment workflow:

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

Configure your [web server](WEBSERVER.md) (Apache or nginx) to serve feature branch instances.

Add the following line to your deployer host entry, to enable the feature branch deployment for this stage:

```yaml
Expand Down
94 changes: 94 additions & 0 deletions docs/WEBSERVER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Web server

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.

## Apache

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.

The only requirement is that `AllowOverride All` is set for the document root in the vhost configuration.

## nginx

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.

### Prerequisites

1. **Symlinks** must be followed (this is the nginx default). Ensure `disable_symlinks` is **not** set to `on`.

2. **PHP-FPM** must be configured to process `.php` files in subdirectories, not just the document root.

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.

### Example for TYPO3

```nginx
server {
listen 443 ssl;
server_name demo.local;
root /var/www/html;
index index.php index.html;

# Feature branch instances and main application
location / {
try_files $uri $uri/ @rewrite;
}

# Rewrite all non-file requests to the nearest index.php.
# Supports both root-level and feature branch subdirectory requests.
location @rewrite {
rewrite ^/([^/]+)/(.*)$ /$1/index.php last;
rewrite ^(.*)$ /index.php last;
}

# Deny access to protected directories across all instances
location ~ /(typo3conf|var|config)/ {
return 403;
}

# PHP-FPM for all .php files including subdirectories
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock; # adjust to match your PHP-FPM pool socket
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
try_files $uri =404;
}
}
```

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.

### Example for Symfony

```nginx
server {
listen 443 ssl;
server_name demo.local;
root /var/www/html;
index index.php index.html;

location / {
try_files $uri $uri/ @rewrite;
}

location @rewrite {
rewrite ^/([^/]+)/(.*)$ /$1/index.php last;
rewrite ^(.*)$ /index.php last;
}

location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock; # adjust to match your PHP-FPM pool socket
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
try_files $uri =404;
}
}
```

## User group

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:

```php
set('requirements_user_group', 'nginx');
```