feat(installer): Laufzeitpruefung, Host-Ueberwachung, Zugang fuer Adminkonten
Drei Dinge, die beim ersten Lauf des Installers auf einer Linux-Maschine auffielen. 1. Die Release-Ablage wies Administratorkonten ab. ReleaseGuard nahm nur die Rolle 'installer' in die .htpasswd auf, waehrend Installskripte und Agent ausdruecklich sagten, ein Administratorkonto tue es auch: Anmeldung und Katalog gelangen, erst der Download endete mit 401 - und die Meldung sprach von abgelaufenen Lizenzen, die es bei einer Erstinstallation gar nicht geben kann. Adminkonten zaehlen jetzt zu den Installationskonten. FORMAT_VERSION auf 3, damit reconcile() die Dateien sofort neu schreibt statt erst beim naechsten turnusmaessigen Lauf; ein neu angelegtes Konto landet ausserdem unabhaengig von seiner Rolle sofort darin. Bei einem 401 mit Benutzerzugangsdaten nennt der Client jetzt Konto und zugangsberechtigte Rollen, und der Agent bricht ab, statt ueber die API weiterzusuchen und dieselbe Meldung ein paar Schritte spaeter ein zweites Mal zu zeigen. 2. Der Installer prueft die .NET-Laufzeit. Bisher endete eine gelungene Installation auf einer Maschine ohne .NET mit einer Anwendung, die sich nicht starten laesst - und die Fehlersuche begann beim Deploymentcenter, weil das der letzte bewusste Schritt war. Gelesen wird die runtimeconfig.json der Anwendung und mit "dotnet --list-runtimes" verglichen; fehlt etwas, nennt der Installer den Installationsbefehl fuer diese Plattform. Eigenstaendig veroeffentlichte Pakete werden nicht bemaengelt, rollForward wird beachtet. 3. Die Ueberwachung der Maschine entsteht im Installer. Zwei Fragen - Name im Dashboard und ob eingeplant werden soll - statt fuenf Schritten in der Oberflaeche an einem anderen Rechner. Monitor, Token mit genau watchdog:ping, Skript, Dateirechte, ein Heartbeat zur Probe und der Cron-Eintrag bzw. die geplante Aufgabe entstehen daraus. Fuer Maschinen ohne Installation: --action monitor. Die Agent-Skripte werden jetzt in src/Modules/Watchdog/AgentScript.php erzeugt - von Oberflaeche und Installer gemeinsam - und melden Last, Speicher, Plattenbelegung und Laufzeit mit, statt nur "status: ok". Beim Ausfuehren fielen zwei Fehler auf, die dort behoben sind: df -P verrutscht bei Geraetenamen mit Leerzeichen (gezaehlt wird jetzt von hinten), und ohne LC_ALL=C erzeugt awk auf einem deutschen System "12,5" und damit kaputtes JSON. Neu: POST /api/setup/v1/agent. SDK 2.6.0 mit SetupClient.RequestWatchdogAgentAsync(). Die OpenAPI-Beschreibung des neuen Endpunkts bleibt zunaechst aussen vor: public/api/openapi.php traegt gerade auch fremde, noch nicht committete Aenderungen aus einer parallel laufenden Arbeit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
fc9b698141
commit
687ee0cefc
@@ -132,6 +132,13 @@ namespace Deploymentcenter.UpdateAgent
|
||||
case "configure":
|
||||
return await Installation.ConfigureOnlyAsync(options, HttpClient);
|
||||
|
||||
case "monitor":
|
||||
// Fuer Maschinen, auf denen nichts zu installieren ist -
|
||||
// der Hypervisor unter den Anwendungen etwa - und zum
|
||||
// Nachholen, wenn die Frage bei der Installation verneint
|
||||
// wurde.
|
||||
return await Monitoring.RunStandaloneAsync(options, HttpClient);
|
||||
|
||||
default:
|
||||
return ShowHelp();
|
||||
}
|
||||
@@ -166,14 +173,23 @@ namespace Deploymentcenter.UpdateAgent
|
||||
};
|
||||
AnsiConsole.Write(panel);
|
||||
|
||||
ReleaseManifest? releaseManifest = null;
|
||||
ManifestLookup lookup = new ManifestLookup();
|
||||
await AnsiConsole.Status()
|
||||
.Spinner(Spinner.Known.Dots)
|
||||
.StartAsync("Lade verfuegbare Releases ...", async ctx =>
|
||||
{
|
||||
releaseManifest = await FetchManifestAsync(options);
|
||||
lookup = await FetchManifestAsync(options);
|
||||
});
|
||||
|
||||
// Der Grund steht schon im Bild - hier noch einmal nach fehlenden
|
||||
// Releases zu suchen, waere eine zweite, falsche Faehrte.
|
||||
if (lookup.Unauthorized)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
ReleaseManifest? releaseManifest = lookup.Manifest;
|
||||
|
||||
if (releaseManifest?.Latest == null)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[bold red]Fehler: Es konnte kein Release ermittelt werden.[/]");
|
||||
@@ -282,7 +298,18 @@ namespace Deploymentcenter.UpdateAgent
|
||||
|
||||
static async Task<int> DoList(AgentOptions options)
|
||||
{
|
||||
var manifest = await FetchManifestAsync(options);
|
||||
var lookup = await FetchManifestAsync(options);
|
||||
|
||||
if (lookup.Unauthorized)
|
||||
{
|
||||
// Derselbe Rueckgabewert wie bei DoCheck - wer den Agenten aus
|
||||
// einem Skript ruft, unterscheidet damit "kein Zugang" von
|
||||
// "nichts veroeffentlicht".
|
||||
Console.WriteLine("UNAUTHORIZED");
|
||||
return 2;
|
||||
}
|
||||
|
||||
var manifest = lookup.Manifest;
|
||||
if (manifest == null)
|
||||
{
|
||||
Console.WriteLine("ERROR: Kein Release-Manifest verfuegbar.");
|
||||
@@ -311,7 +338,14 @@ namespace Deploymentcenter.UpdateAgent
|
||||
|
||||
try
|
||||
{
|
||||
var releaseManifest = await FetchManifestAsync(options);
|
||||
var lookup = await FetchManifestAsync(options);
|
||||
|
||||
if (lookup.Unauthorized)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
var releaseManifest = lookup.Manifest;
|
||||
if (releaseManifest == null)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[bold red]Fehler: Release-Manifest konnte weder statisch noch ueber die API abgerufen werden.[/]");
|
||||
@@ -561,18 +595,48 @@ namespace Deploymentcenter.UpdateAgent
|
||||
/// klappte, sagte die Anwendung "Update verfuegbar" und der Agent "kein
|
||||
/// Release gefunden". Der Rueckfall auf die API stand nur im SDK.
|
||||
/// </summary>
|
||||
static async Task<ReleaseManifest?> FetchManifestAsync(AgentOptions options)
|
||||
/// <summary>
|
||||
/// Ergebnis eines Manifest-Abrufs. Ein leeres Manifest wegen 401 ist
|
||||
/// etwas anderes als ein leeres, weil nichts veroeffentlicht wurde -
|
||||
/// die Aufrufer sollen dafuer nicht dieselbe Meldung zeigen.
|
||||
/// </summary>
|
||||
internal sealed class ManifestLookup
|
||||
{
|
||||
var fromStatic = await FetchStaticManifestAsync(options);
|
||||
if (fromStatic?.Latest != null)
|
||||
{
|
||||
return fromStatic;
|
||||
}
|
||||
public ReleaseManifest? Manifest { get; init; }
|
||||
|
||||
return await FetchApiManifestAsync(options);
|
||||
/// <summary>Die Ablage hat die Zugangsdaten abgelehnt.</summary>
|
||||
public bool Unauthorized { get; init; }
|
||||
}
|
||||
|
||||
static async Task<ReleaseManifest?> FetchStaticManifestAsync(AgentOptions options)
|
||||
static async Task<ManifestLookup> FetchManifestAsync(AgentOptions options)
|
||||
{
|
||||
var (fromStatic, unauthorized) = await FetchStaticManifestAsync(options);
|
||||
|
||||
if (unauthorized)
|
||||
{
|
||||
// Ein 401 ist keine "Datei fehlt"-Lage: die Zugangsdaten
|
||||
// tragen nicht. Die API wuerde zwar antworten, sie liegt nicht
|
||||
// hinter dem Zugangsschutz - das Paket selbst aber schon. Der
|
||||
// Lauf endete also ein paar Schritte spaeter mit genau
|
||||
// derselben Meldung ein zweites Mal, dazwischen Ausgaben ueber
|
||||
// Signaturen, die den eigentlichen Grund nach oben aus dem
|
||||
// Bild schieben.
|
||||
AnsiConsole.MarkupLine("[bold red]"
|
||||
+ Markup.Escape(ReleaseCredentials.DescribeUnauthorized(options.Credentials))
|
||||
+ "[/]");
|
||||
|
||||
return new ManifestLookup { Unauthorized = true };
|
||||
}
|
||||
|
||||
if (fromStatic?.Latest != null)
|
||||
{
|
||||
return new ManifestLookup { Manifest = fromStatic };
|
||||
}
|
||||
|
||||
return new ManifestLookup { Manifest = await FetchApiManifestAsync(options) };
|
||||
}
|
||||
|
||||
static async Task<(ReleaseManifest? Manifest, bool Unauthorized)> FetchStaticManifestAsync(AgentOptions options)
|
||||
{
|
||||
// Plattformunabhaengige Releases liegen weiterhin im alten Pfad
|
||||
// ohne Zwischenebene.
|
||||
@@ -597,15 +661,12 @@ namespace Deploymentcenter.UpdateAgent
|
||||
|
||||
var resp = await HttpClient.SendAsync(request);
|
||||
|
||||
// Ein 401 ist keine "Datei fehlt"-Lage: die Lizenz traegt
|
||||
// nicht mehr. Weiterzuprobieren wuerde die Ursache nur
|
||||
// hinter einer allgemeinen Fehlermeldung verstecken.
|
||||
// Ein 401 wird nach oben durchgereicht, statt hier
|
||||
// gemeldet zu werden: sonst steht die Meldung schon im
|
||||
// Bild, waehrend der Aufrufer noch weitersucht.
|
||||
if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[bold red]"
|
||||
+ Markup.Escape(ReleaseCredentials.DescribeUnauthorized(options.Credentials))
|
||||
+ "[/]");
|
||||
return null;
|
||||
return (null, true);
|
||||
}
|
||||
|
||||
if (!resp.IsSuccessStatusCode)
|
||||
@@ -633,7 +694,7 @@ namespace Deploymentcenter.UpdateAgent
|
||||
.FirstOrDefault();
|
||||
|
||||
if (manifest.Latest != null)
|
||||
return manifest;
|
||||
return (manifest, false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -641,7 +702,7 @@ namespace Deploymentcenter.UpdateAgent
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return (null, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1140,8 +1201,8 @@ namespace Deploymentcenter.UpdateAgent
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(" --project, -p <slug> Projekt-Slug");
|
||||
Console.WriteLine(" --channel, -c <kanal> prod | beta | dev");
|
||||
Console.WriteLine(" --action, -a <aktion> interactive | install | configure | check |");
|
||||
Console.WriteLine(" update | repair | list");
|
||||
Console.WriteLine(" --action, -a <aktion> interactive | install | configure | monitor |");
|
||||
Console.WriteLine(" check | update | repair | list");
|
||||
Console.WriteLine(" --version, -v <version> Zielversion oder 'latest'");
|
||||
Console.WriteLine(" --target-dir, -t <pfad> Zu aktualisierendes Verzeichnis");
|
||||
Console.WriteLine(" --platform <rid> Laufzeitkennung (Vorgabe: die des Systems)");
|
||||
|
||||
Reference in New Issue
Block a user