read(); if (trim($content) === '') { $this->error("Keine .env unter {$env->path()}."); return self::FAILURE; } $wanted = [ 'APP_HOST' => (string) ($this->option('app') ?: $this->hostOf($content)), 'SITE_HOST' => (string) $this->option('site'), 'STATUS_HOST' => (string) $this->option('status'), 'FILES_HOST' => (string) $this->option('files'), ]; $missing = []; foreach ($wanted as $key => $value) { if ($value === '' || $this->valueOf($content, $key) !== '') { continue; } $missing[$key] = $value; } if ($missing === []) { $this->info('Nichts nachzutragen — jeder angegebene Hostname steht bereits in der Datei.'); return self::SUCCESS; } $this->line('In '.$env->path().':'); foreach ($missing as $key => $value) { $this->line(" {$key}={$value}"); } // Der Satz, der einen Ausfall verhindert. Ein Hostname wird durch das // Binden zur EINZIGEN Adresse, unter der diese Routen noch antworten — // steht dafür kein DNS-Eintrag und kein Block im Reverse Proxy, ist der // Bereich danach schlicht nicht mehr erreichbar. $this->newLine(); $this->warn('Jeder dieser Namen braucht einen DNS-Eintrag und einen Block im Reverse Proxy.'); $this->warn('Ohne den ist der jeweilige Bereich nach dem Neuladen der Konfiguration nicht mehr erreichbar.'); $this->newLine(); if ($this->option('dry-run')) { $this->info('--dry-run: nichts geschrieben.'); return self::SUCCESS; } if (! $this->option('force') && ! $this->confirm('Diese Namen jetzt binden?')) { $this->line('Nichts geschrieben.'); return self::FAILURE; } try { $backup = $env->write($this->apply($content, $missing)); } catch (InvalidEnvContentException $e) { // Abgelehnt heißt hier: NICHTS geschrieben, und die Sicherung ist // erst gar nicht angelegt worden — der Editor prüft vor beidem. // Der Betreiber soll das als Satz erfahren, nicht als Stapelabzug. $this->error('Die Datei wurde nicht geschrieben: '.$e->getMessage()); return self::FAILURE; } $this->info('Geschrieben. Die vorherige Fassung liegt unter '.$backup); $this->line('Danach: php artisan config:cache && php artisan route:cache'); return self::SUCCESS; } /** * Der Wert eines Schlüssels, oder '' wenn er fehlt ODER leer ist. * * Beides zusammen, absichtlich: siehe Klassenkommentar. */ private function valueOf(string $content, string $key): string { return preg_match('/^'.preg_quote($key, '/').'=(.*)$/m', $content, $matches) === 1 ? trim($matches[1]) : ''; } /** Der Hostname aus APP_URL — die Antwort für APP_HOST steht schon in der Datei. */ private function hostOf(string $content): string { return (string) parse_url($this->valueOf($content, 'APP_URL'), PHP_URL_HOST); } /** * Vorhandene Zeile ersetzen, sonst anhängen. * * Zeilenweise und ohne `preg_replace`: der Wert kommt von der Befehlszeile, * und `preg_replace` deutet `$1`, `\1` und `\\` im ERSATZ als Rückverweise. * Aus `--site 'www.example.test$1'` würde still `SITE_HOST=www.example.test` * — und diese Datei hält jedes Geheimnis dieser Installation. Ein leise * verstümmelter Wert darin ist schlimmer als eine Fehlermeldung. * * Ersetzen und nicht nur anhängen, weil ein leerer Schlüssel als fehlend * gilt: `APP_HOST=` steht dann schon da, und ein zweites `APP_HOST=…` * darunter wäre eine Datei mit zwei Antworten auf dieselbe Frage. * * @param array $values */ private function apply(string $content, array $values): string { $lines = preg_split('/\R/', rtrim($content, "\r\n")); foreach ($values as $key => $value) { $replaced = false; foreach ($lines as $index => $line) { if (str_starts_with($line, $key.'=')) { $lines[$index] = $key.'='.$value; $replaced = true; break; } } if (! $replaced) { $lines[] = $key.'='.$value; } } return implode("\n", $lines)."\n"; } }