112 lines
4.5 KiB
PHP
112 lines
4.5 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Services\Dns\HostDnsDirectory;
|
|
use App\Support\HostnamePattern;
|
|
use Illuminate\Console\Command;
|
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
|
|
|
/**
|
|
* Macht Portal, Website und Statusseite im Management-Tunnel auflösbar.
|
|
*
|
|
* Ohne das trägt der Tunnel nur `10.66.0.0/24`, und diese Namen zeigen auf die
|
|
* öffentliche Adresse des Servers — der Verkehr geht also am Tunnel vorbei, und
|
|
* die Anwendung sieht die öffentliche Adresse des Anrufers statt seiner
|
|
* `10.66.0.x`. Genau daran scheiterte „über VPN die verborgene Seite ansehen":
|
|
* die Konsole ging, weil ihr Name als einziger umgebogen war.
|
|
*
|
|
* `files.` steht bewusst NICHT in der Liste. Dort holt ein Server im
|
|
* Rettungssystem sein Bootstrap-Archiv, und der ist per Definition nicht im
|
|
* Tunnel — bögen wir den Namen um, holte er es nie.
|
|
*
|
|
* Die Datei landet in demselben Verzeichnis, das dnsmasq mit `--hostsdir`
|
|
* überwacht, und in dem RegisterHostDns schon die Host-Namen ablegt. Kein
|
|
* Neustart, kein Reload: das Erscheinen der Datei IST die Veröffentlichung.
|
|
*/
|
|
class PublishTunnelNames extends Command
|
|
{
|
|
/** Eine Datei für die ganze Gruppe — siehe HostDnsDirectory::writeMany(). */
|
|
private const KEY = 'platform';
|
|
|
|
protected $signature = 'clupilot:publish-tunnel-names';
|
|
|
|
protected $description = 'Macht Portal, Website und Statusseite im Management-Tunnel auflösbar';
|
|
|
|
public function handle(HostDnsDirectory $dns): int
|
|
{
|
|
// Ohne Tunnel gibt es keinen Gateway, der diese Namen bedienen könnte.
|
|
// Eine hosts-Datei für einen Resolver, den niemand startet, ist nur
|
|
// etwas, das beim späteren Einschalten falsch sein kann.
|
|
if ((string) config('admin_access.vpn_internal_host') === '') {
|
|
$dns->writeMany(self::KEY, [], '');
|
|
$this->info('Kein Tunnel konfiguriert — nichts veröffentlicht.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$candidates = array_values(array_unique(array_filter(array_merge(
|
|
[(string) config('admin_access.app_host')],
|
|
array_map('strval', (array) config('admin_access.site_hosts', [])),
|
|
[(string) config('admin_access.status_host')],
|
|
))));
|
|
|
|
// `bind-hosts` weist einen NEUEN Wert dieser Form inzwischen zurück,
|
|
// aendert aber nichts an einem, der schon in der .env steht. Auf der
|
|
// Produktivmaschine stand dort bereits ein Markdown-Link aus einem
|
|
// Kopiervorgang (`[www.clupilot.com](https://www.clupilot.com)`), von
|
|
// VOR dieser Prüfung — geschrieben, weil EnvFileEditor nur prüft, ob
|
|
// die ZEILE die Form KEY=value hat. Ungeprüft ginge so ein Wert in
|
|
// `platform.hosts`, und der Resolver böge einen Unsinns-Namen um.
|
|
$names = [];
|
|
|
|
foreach ($candidates as $candidate) {
|
|
if (HostnamePattern::matches($candidate)) {
|
|
$names[] = $candidate;
|
|
|
|
continue;
|
|
}
|
|
|
|
$this->skipped($candidate);
|
|
}
|
|
|
|
$hub = (string) config('provisioning.wireguard.hub_address');
|
|
|
|
$dns->writeMany(self::KEY, $names, $hub);
|
|
|
|
if ($names === []) {
|
|
$this->info('Keine Hostnamen konfiguriert — nichts veröffentlicht.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
foreach ($names as $name) {
|
|
$this->line(" {$hub} {$name}");
|
|
}
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Nicht still: eine ausgelassene `.env`-Altlast soll auffallen, auch wenn
|
|
* niemand die Ausgabe dieses Befehls Zeile für Zeile liest.
|
|
*
|
|
* Auf STDERR statt über `$this->error()` — das faerbt nur denselben Strom
|
|
* rot ein, den auch `$this->line()` benutzt. `deploy/update.sh` ruft
|
|
* diesen Befehl mit `2>&1` auf und sieht den Unterschied nicht, aber ein
|
|
* Betreiber, der ihn von Hand startet und nur STDOUT protokolliert, soll
|
|
* die Meldung trotzdem bekommen. `getOutput()` liefert in einem echten
|
|
* CLI-Lauf die `ConsoleOutput`, die einen echten zweiten Strom hat; im
|
|
* Testlauf ist es der gepufferte Mock aus PendingCommand, der keiner ist
|
|
* — dort faellt das auf den normalen Strom zurück, und Tests sehen die
|
|
* Meldung trotzdem.
|
|
*/
|
|
private function skipped(string $value): void
|
|
{
|
|
$stream = $this->output->getOutput();
|
|
$target = $stream instanceof ConsoleOutputInterface ? $stream->getErrorOutput() : $stream;
|
|
|
|
$target->writeln(" uebersprungen: „{$value}“ ist kein Hostname.");
|
|
}
|
|
}
|