feat(ausrollen): Public Keys mit Trockenlauf und Backup ausrollen (#016)
Zuweisungen von Public Keys an Hosts oder Hostgruppen, ein Trockenlauf mit Diff je Ziel und das Ausrollen genau dieses Plans. Hat sich die Datei oder das Soll seit dem Trockenlauf geaendert, bricht das Ziel ab: es wird nie etwas anderes geschrieben, als jemand gesehen hat. Auf dem Host geschieht alles als Zielbenutzer, mit Sicherung, Temp- Datei, mv und 0700/0600; der Hash wird dort erneut geprueft. Der Deployment-Key gehoert immer zum Soll des Anmeldebenutzers. Integrationstests gegen echtes sshd, die Oberflaechenbausteine folgen mit der Schluesselseite aus #012. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
"use client";
|
||||
|
||||
import { Button, Feld, Formularfehler } from "@/components/ui";
|
||||
import { Auswahlliste } from "@/components/ui/Feld";
|
||||
import { useFormularAktion } from "@/components/hosts/useFormularAktion";
|
||||
import { publicZuweisenAktion } from "@/server/actions/verwendungen";
|
||||
import type { ZielAuswahl } from "@/server/queries/ziele";
|
||||
|
||||
/**
|
||||
* Public Key einem Host oder einer Hostgruppe zuweisen (#016). Die Zuweisung allein aendert
|
||||
* noch nichts auf dem Host – erst Trockenlauf und Ausrollen.
|
||||
*/
|
||||
export function PublicZuweisenFormular({
|
||||
schluesselId,
|
||||
auswahl,
|
||||
beiErfolg,
|
||||
}: {
|
||||
schluesselId: string;
|
||||
auswahl: ZielAuswahl;
|
||||
beiErfolg?: () => void;
|
||||
}) {
|
||||
const { zustand, absenden, laeuft } = useFormularAktion(async (vorher, formular) => {
|
||||
const ergebnis = await publicZuweisenAktion(vorher, formular);
|
||||
if (!ergebnis.fehler && !ergebnis.feldfehler) beiErfolg?.();
|
||||
return ergebnis;
|
||||
});
|
||||
return (
|
||||
<form onSubmit={absenden} className="flex flex-col gap-4">
|
||||
<input type="hidden" name="schluesselId" value={schluesselId} />
|
||||
<Formularfehler text={zustand.fehler} />
|
||||
<Auswahlliste beschriftung="Ziel" name="ziel" defaultValue="" required>
|
||||
<option value="" disabled>
|
||||
Host oder Hostgruppe wählen …
|
||||
</option>
|
||||
<optgroup label="Hosts">
|
||||
{auswahl.hosts.map((h) => (
|
||||
<option key={h.id} value={`host:${h.id}`}>
|
||||
{h.name}
|
||||
{h.bereit ? "" : " (noch kein Bootstrap)"}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
{auswahl.gruppen.length > 0 && (
|
||||
<optgroup label="Hostgruppen">
|
||||
{auswahl.gruppen.map((g) => (
|
||||
<option key={g.id} value={`gruppe:${g.id}`}>
|
||||
{g.name} ({g.mitglieder} Hosts)
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
)}
|
||||
</Auswahlliste>
|
||||
{zustand.feldfehler?.["ziel"] && (
|
||||
<p className="text-danger -mt-2 text-[13px]">{zustand.feldfehler["ziel"]}</p>
|
||||
)}
|
||||
<Feld
|
||||
beschriftung="Zielbenutzer"
|
||||
name="zielbenutzer"
|
||||
defaultValue="root"
|
||||
mono
|
||||
required
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Feld
|
||||
beschriftung="Einschränkungen in authorized_keys (optional)"
|
||||
name="optionen"
|
||||
mono
|
||||
autoComplete="off"
|
||||
placeholder='restrict,from="10.0.0.12"'
|
||||
hinweis='z. B. restrict, from="IP", command="…", no-pty, no-port-forwarding'
|
||||
/>
|
||||
<Button type="submit" variante="primaer" disabled={laeuft}>
|
||||
{laeuft ? "Speichere …" : "Zuweisen"}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user