parse($base."\n".$old, $legacy); if (preg_match('/^DATABASE_URL=/m', $old)) { $values['KANT_DATABASE_CONFIGURED'] = '1'; } $directory = $root.'/var/backups/environment-'.gmdate('Ymd-His').'-'.bin2hex(random_bytes(4)); if (!@mkdir($directory, 0o700, true)) { throw new \RuntimeException('Die bisherige Konfiguration konnte nicht gesichert werden. Bitte die Schreibrechte auf var/backups prüfen.'); } self::replace($directory.'/.env', $base); self::replace($directory.'/.env.local', $old); self::replace($file, self::encode($values)); if (!@unlink($legacy)) { throw new \RuntimeException('Die Einstellungen sind in .env gesichert, aber .env.local konnte nicht entfernt werden. Bitte deren Schreibrechte prüfen.'); } } }); } public static function write(string $root, array $values): void { self::locked($root, static function () use ($root, $values): void { $file = $root.'/.env'; $existing = is_file($file) ? (new Dotenv())->parse((string) file_get_contents($file), $file) : []; self::replace($file, self::encode(array_replace($existing, $values))); }); } /** A file lock serializes concurrent installer requests and secret generation. */ private static function locked(string $root, callable $operation): void { $lock = @fopen($root.'/.env.lock', 'c'); if (false === $lock || !flock($lock, LOCK_EX)) { if (is_resource($lock)) { fclose($lock); } throw new \RuntimeException('.env ist nicht beschreibbar. Bitte die Schreibrechte im Projektstamm prüfen.'); } try { $operation(); } finally { flock($lock, LOCK_UN); fclose($lock); } } public static function ensureSecret(string $root): string { $secret = ''; self::locked($root, static function () use ($root, &$secret): void { $file = $root.'/.env'; $values = (new Dotenv())->parse((string) file_get_contents($file), $file); $secret = $values['APP_SECRET'] ?? ''; if ('' === $secret) { $secret = bin2hex(random_bytes(32)); self::replace($file, self::encode([...$values, 'APP_SECRET' => $secret])); } }); return $secret; } private static function encode(array $values): string { $lines = ['# Konfiguration dieser Installation. Updates erhalten diese Datei.']; foreach ($values as $key => $value) { if (1 !== preg_match('/^[A-Z_][A-Z0-9_]*$/D', $key)) { throw new \InvalidArgumentException('Ungültiger Name einer Konfigurationseinstellung.'); } $lines[] = $key.'="'.str_replace(['\\', '"', '$', "\n", "\r"], ['\\\\', '\\"', '\\$', '\\n', '\\r'], (string) $value).'"'; } return implode("\n", $lines)."\n"; } private static function replace(string $file, string $content): void { $temporary = $file.'.'.bin2hex(random_bytes(6)).'.tmp'; if (false === @file_put_contents($temporary, $content)) { throw new \RuntimeException('.env ließ sich nicht schreiben. Bitte die Schreibrechte im Projektstamm prüfen.'); } @chmod($temporary, 0o600); if (!@rename($temporary, $file)) { @unlink($temporary); throw new \RuntimeException('.env ließ sich nicht ersetzen. Die bisherige Konfiguration bleibt erhalten.'); } } }