From 9518bb8c6a280b72f2eaa0f644280820395b2194 Mon Sep 17 00:00:00 2001 From: silisio Date: Wed, 22 Jul 2026 09:10:59 +0200 Subject: [PATCH] Load parent environment file automatically --- .env.example | 3 ++ README.md | 11 ++++++- app.php | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 6efd05e..e79187b 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,6 @@ +# Copy this file one folder above the project as .env and replace every placeholder. +# The application loads that parent .env automatically. + # Application APP_ENV=production PUBLIC_BASE_URL=https://zefix.silias.ch diff --git a/README.md b/README.md index 61b32ed..7a10db9 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,16 @@ APP_SECRET= Das bisherige, nicht versionierte `env_vars.php` wird für eine schonende Migration weiterhin geladen. Neue Installationen sollten echte Prozess-/PHP-FPM-Umgebungsvariablen verwenden. -Eine vollständige Vorlage ohne echte Zugangsdaten liegt in [`.env.example`](.env.example). Sie kann für das Deployment-Panel, Docker Compose (`env_file`) oder als Vorlage für die PHP-FPM-Konfiguration verwendet werden. Die Anwendung lädt `.env`-Dateien nicht selbstständig; die Deployment-Umgebung muss die Werte an den PHP-Prozess weiterreichen. +Eine vollständige Vorlage ohne echte Zugangsdaten liegt in [`.env.example`](.env.example). Für eine klassische Installation kann sie als `.env` **einen Ordner über dem Projektverzeichnis** abgelegt werden. Diese Datei wird beim Start automatisch geladen. Bereits gesetzte Prozessvariablen und Werte aus der bisherigen `env_vars.php` haben Vorrang. + +Beispiel bei einem Projekt unter `/var/www/zefix`: + +```text +/var/www/.env # echte, nicht versionierte Zugangsdaten +/var/www/zefix/app.php # Projekt +``` + +Alternativ kann die Vorlage weiterhin für ein Deployment-Panel, Docker Compose (`env_file`) oder die PHP-FPM-Konfiguration verwendet werden. ## Optionale Konfiguration diff --git a/app.php b/app.php index c8c117c..8165069 100644 --- a/app.php +++ b/app.php @@ -4,14 +4,92 @@ declare(strict_types=1); /** * Shared configuration, storage and abuse-protection helpers. * - * Secrets are read from environment variables. The legacy env_vars.php file is - * loaded when present so existing installations keep working during migration. + * Secrets are read from process variables or from ../.env. The legacy + * env_vars.php file is loaded when present so existing installations keep + * working during migration. */ +function app_load_env_file(string $filename): void +{ + if (!is_file($filename)) { + return; + } + + if (!is_readable($filename)) { + error_log('Environment file is not readable: ' . $filename); + return; + } + + $size = filesize($filename); + if ($size !== false && $size > 65536) { + error_log('Environment file is unexpectedly large: ' . $filename); + return; + } + + $lines = file($filename, FILE_IGNORE_NEW_LINES); + if ($lines === false) { + error_log('Environment file could not be read: ' . $filename); + return; + } + + foreach ($lines as $lineNumber => $line) { + if ($lineNumber === 0) { + $line = preg_replace('/^\xEF\xBB\xBF/', '', $line) ?? $line; + } + + $line = trim($line); + if ($line === '' || $line[0] === '#') { + continue; + } + + if (strncmp($line, 'export ', 7) === 0) { + $line = ltrim(substr($line, 7)); + } + + $separator = strpos($line, '='); + if ($separator === false) { + error_log('Ignoring invalid environment entry on line ' . ($lineNumber + 1) . '.'); + continue; + } + + $name = trim(substr($line, 0, $separator)); + if (preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $name) !== 1) { + error_log('Ignoring invalid environment variable name on line ' . ($lineNumber + 1) . '.'); + continue; + } + + // Real process variables and legacy configuration always take precedence. + if (getenv($name) !== false) { + continue; + } + + $value = trim(substr($line, $separator + 1)); + $valueLength = strlen($value); + if ($valueLength >= 2 && $value[0] === '"' && $value[$valueLength - 1] === '"') { + $value = stripcslashes(substr($value, 1, -1)); + } elseif ($valueLength >= 2 && $value[0] === "'" && $value[$valueLength - 1] === "'") { + $value = substr($value, 1, -1); + } else { + $value = preg_replace('/\s+#.*$/', '', $value) ?? $value; + $value = rtrim($value); + } + + if (strpos($value, "\0") !== false) { + error_log('Ignoring environment value containing a null byte on line ' . ($lineNumber + 1) . '.'); + continue; + } + + putenv($name . '=' . $value); + $_ENV[$name] = $value; + } +} + $legacyEnvFile = __DIR__ . '/env_vars.php'; if (is_file($legacyEnvFile)) { require_once $legacyEnvFile; } +app_load_env_file(dirname(__DIR__) . DIRECTORY_SEPARATOR . '.env'); + function app_env(string $name, string $default = ''): string { $value = getenv($name);