CluPilotCloud/app/Provisioning/Steps/Customer/ConfigureInstanceMail.php

139 lines
5.9 KiB
PHP

<?php
namespace App\Provisioning\Steps\Customer;
use App\Models\ProvisioningRun;
use App\Provisioning\StepResult;
use App\Services\Mail\GuestMailConfig;
use App\Services\Proxmox\ProxmoxClient;
use App\Support\NextcloudOcc;
use Illuminate\Support\Facades\Log;
/**
* Bringt der Nextcloud eines Kunden bei, Mail zu verschicken.
*
* Bis hierher konnte sie es NICHT — weder die Bereitstellung noch die
* Compose-Datei der Vorlage setzten je einen Versand. Damit verschickte jede
* Kundeninstanz gar nichts: keine Freigabe-Benachrichtigung, keine
* Aktivitaetsmeldung, kein "Passwort vergessen". Still, ohne Fehlermeldung.
*
* Das ist die Voraussetzung fuer die Mitarbeiterverwaltung: die Einladung an
* einen Mitarbeiter verschickt NEXTCLOUD, nicht CluPilot — nur so entsteht
* das Passwort dort, wo niemand sonst es zu sehen bekommt.
*
* Ohne Server oder Absenderpostfach wird NICHTS geschrieben. Eine halb
* eingetragene Mailkonfiguration waere schlimmer als keine.
*
* Das ist aber KEIN Grund, den Lauf scheitern zu lassen: dieser Schritt steht
* in der PFLICHT-Pipeline `customer`, und ein Kunde, der bezahlt hat, bekommt
* seine Cloud auch dann, wenn der Mailversand noch nicht eingerichtet ist —
* genau wie RegisterMonitoring die Bereitstellung nie an einer nicht
* erreichbaren Ueberwachung scheitern laesst (siehe deren Kopfkommentar). Der
* Schritt geht in diesem Fall mit `advance()` weiter; nachgeholt wird es ueber
* `clupilot:configure-instance-mail` (Aufgabe 4), sobald Server und Postfach
* stehen.
*
* Die Rueckmeldung dazu geht an ZWEI Stellen, nicht nur einer — genau wie bei
* RegisterMonitoring. Ein Lauf-Ereignis (`outcome: info`) haengt sich an den
* Lauf, den der Betreiber in der Konsole sieht; `Log::warning` bleibt daneben
* fuer die Nachschau ausserhalb der Konsole. Das Lauf-Ereignis ist hier nicht
* optional: solange der Mailserver noch nicht steht, tritt dieser Zweig bei
* JEDER einzelnen Bestellung ein, und die einzige Rueckmeldung darf dann
* nicht in einer Logdatei verschwinden, in die niemand schaut. Mailversand
* ist eine Nebenfunktion: fehlt er, funktioniert die Cloud vollstaendig, nur
* Einladungen und Freigabe-Benachrichtigungen gehen nicht.
*
* Nicht eingerichtet ist etwas anderes als kaputt: sobald Server und Postfach
* da sind, wird geschrieben, und ein Fehler beim Schreiben selbst (occ liefert
* einen Fehlercode, der Gast antwortet nicht) bleibt ein Fehlschlag wie jeder
* andere Gastbefehl in dieser Pipeline — CustomerStep::guest() wirft dafuer,
* und der Runner macht daraus einen Retry.
*
* WO DAS SMTP-PASSWORT UEBERALL SICHTBAR IST — ehrlich aufgeschrieben, weil
* ein erster Entwurf es zu verstecken versuchte und das Verstecken nichts
* brachte:
*
* - im Argv des `/bin/sh -c`, das der Gastagent fuer diesen Aufruf startet
* (guestExec baut die ganze Zeile so). Debians `dash` behaelt diesen
* Prozess fuer die volle Dauer des Aufrufs;
* - im Argv von `php occ` im Container;
* - und DAUERHAFT im Klartext in /opt/nextcloud … config/config.php, wo
* Nextcloud es selbst ablegt.
*
* Der dritte Punkt erledigt die ersten beiden: wer die Datei lesen kann, muss
* gar nicht erst in eine Prozessliste schauen. Ein Versteck, das dieselbe
* Person nicht aussperrt, ist keines — und ein Kommentar, der eines
* behauptet, haelt den Naechsten vom Nachsehen ab.
*
* Die Eingrenzung liegt deshalb in der Infrastruktur, nicht hier: das
* Versandkonto kann nur senden, der Versandport nimmt nur die eigenen
* Hostadressen an, und der Mailserver drosselt je Konto. Was hier gilt und
* geprueft wird, ist etwas anderes: der Wert wird maskiert, damit er keinen
* zweiten Befehl starten kann.
*/
class ConfigureInstanceMail extends CustomerStep
{
public function __construct(private ProxmoxClient $pve) {}
public function key(): string
{
return 'configure_instance_mail';
}
public function maxDuration(): int
{
return 120;
}
public function execute(ProvisioningRun $run): StepResult
{
$instance = $this->instance($run);
if ($instance === null) {
return StepResult::fail('instance_missing');
}
$config = GuestMailConfig::for($instance);
if (! $config->available()) {
// Nicht eingerichtet, nicht kaputt — siehe Kopfkommentar. Die Cloud
// wird trotzdem ausgeliefert. available() === false heisst hier
// immer, dass problem() einen Grund liefert (siehe GuestMailConfig)
// — kein Rueckfall auf einen Fall, der nicht eintreten kann.
$grund = $config->problem();
$run->events()->create([
'step' => $this->key(),
'attempt' => $run->attempt,
'outcome' => 'info',
'message' => 'Mailversand uebersprungen (nicht eingerichtet): '.$grund,
]);
Log::warning('Mailversand fuer Instanz uebersprungen (nicht eingerichtet)', [
'instance' => $instance->uuid,
'reason' => $grund,
]);
return StepResult::advance();
}
$pve = $this->pve->forHost($instance->host);
// Der Schluessel ist ein fester occ-Name aus GuestMailConfig::values(),
// keine Benutzereingabe — genauso unmaskiert wie 'files default_quota'
// in ApplyStorageQuota. Maskiert wird nur der WERT, siehe Kopfkommentar.
foreach ($config->values() as $schluessel => $wert) {
$this->guest($pve, $run, NextcloudOcc::command(
'config:system:set '.$schluessel.' --value='.escapeshellarg($wert)
));
}
// Das Passwort wie jeder andere Wert. Verstecken laesst es sich auf
// dieser Maschine nicht — siehe Kopfkommentar.
$this->guest($pve, $run, NextcloudOcc::command(
'config:system:set mail_smtppassword --value='.escapeshellarg($config->password())
));
return StepResult::advance();
}
}