Fassung 1.7.0: verlässliche Installation, übersichtliche Verwaltung und bedienbare Bewegung
This commit is contained in:
1 parent
b164c4edfe
commit
495b26896e
55 files changed
+1632
-423
No files matched your search
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Ops;
|
||||
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
|
||||
/** One installation file, with an explicit, recoverable transition from older releases. */
|
||||
final class Environment
|
||||
{
|
||||
public static function prepare(string $root): void
|
||||
{
|
||||
if (is_file($root.'/.env') && !is_file($root.'/.env.local')) {
|
||||
return;
|
||||
}
|
||||
self::locked($root, static function () use ($root): void {
|
||||
$file = $root.'/.env';
|
||||
if (!is_file($file)) {
|
||||
$template = is_file($root.'/.env.example') ? (string) file_get_contents($root.'/.env.example') : '';
|
||||
self::replace($file, $template);
|
||||
}
|
||||
$legacy = $root.'/.env.local';
|
||||
if (is_file($legacy)) {
|
||||
// Preserve the previously effective configuration, not a release's default DB.
|
||||
$base = (string) file_get_contents($file);
|
||||
$old = (string) file_get_contents($legacy);
|
||||
$values = (new Dotenv())->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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user