feat(deploy): Auslieferung als Docker-Stack und als Helper-Script (#026)
Docker: deploy/update.sh sichert, baut, migriert und prueft die Erreichbarkeit; der Worker hat jetzt eine Zustandspruefung. Der ganze Stapel wurde durchgespielt - bauen, starten, migrieren, anmelden, Agent-Skript abrufen. Dabei ein echter Fehler aufgefallen: Nach der Anmeldung zeigte die Weiterleitung auf den internen Containernamen. Hinter einem Reverse Proxy waere das eine tote Adresse gewesen. Weiterleitungen nehmen jetzt die eingetragene Adresse, sonst APP_URL, sonst die Proxy-Kopfzeilen. Dazu Skripte nach Art der Proxmox VE Helper-Scripts (Node und PostgreSQL direkt im LXC, ohne Docker). Vorerst intern: Eine Einreichung setzt oeffentlichen Quelltext voraus und laeuft ueber ProxmoxVED. Ausserdem docs/roadmap.md: Bis 1.0.0 fehlt kein Code mehr, sondern Erprobung. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { eigeneAdresse } from "@/lib/adresse";
|
||||
import { clientIp } from "@/lib/ratelimit";
|
||||
import { cookieOptionen, SITZUNGS_COOKIE, sitzungErzeugen } from "@/lib/sitzung";
|
||||
import { AnmeldeFehler, mitPasswortAnmelden } from "@/server/auth/benutzer";
|
||||
@@ -38,6 +39,7 @@ export function herkunftStimmt(request: Request): boolean {
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const formular = await request.formData();
|
||||
const { dashboardUrl } = await einstellungen();
|
||||
const email = formular.get("email")?.toString().trim() ?? "";
|
||||
const passwort = formular.get("passwort")?.toString() ?? "";
|
||||
const zielEingabe = formular.get("ziel")?.toString() || "/";
|
||||
@@ -45,7 +47,7 @@ export async function POST(request: Request) {
|
||||
const ziel = zielEingabe.startsWith("/") && !zielEingabe.startsWith("//") ? zielEingabe : "/";
|
||||
|
||||
function zurueckZurAnmeldung(meldung: string) {
|
||||
const adresse = new URL("/login", request.url);
|
||||
const adresse = eigeneAdresse(request, "/login", dashboardUrl);
|
||||
adresse.searchParams.set("fehler", meldung);
|
||||
if (ziel !== "/") adresse.searchParams.set("ziel", ziel);
|
||||
return Response.redirect(adresse, 303);
|
||||
@@ -75,7 +77,7 @@ export async function POST(request: Request) {
|
||||
);
|
||||
(await cookies()).set(SITZUNGS_COOKIE, token, cookieOptionen(sitzungsdauerStunden));
|
||||
|
||||
return Response.redirect(new URL(ziel, request.url), 303);
|
||||
return Response.redirect(eigeneAdresse(request, ziel, dashboardUrl), 303);
|
||||
} catch (fehler) {
|
||||
if (fehler instanceof AnmeldeFehler) return zurueckZurAnmeldung(fehler.message);
|
||||
console.error("Anmeldung fehlgeschlagen:", fehler);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { cookies } from "next/headers";
|
||||
import { eigeneAdresse } from "@/lib/adresse";
|
||||
import { cookieOptionen, SITZUNGS_COOKIE, sitzungErzeugen } from "@/lib/sitzung";
|
||||
import { mitOidcAnmelden } from "@/server/auth/benutzer";
|
||||
import { anmeldungAbschliessen, oidcEinstellungen } from "@/server/auth/oidc";
|
||||
@@ -18,8 +19,10 @@ export async function GET(request: Request) {
|
||||
speicher.delete(name);
|
||||
}
|
||||
|
||||
const { dashboardUrl } = await systemEinstellungen();
|
||||
|
||||
if (!einstellungen || !pruefwert || !zustand) {
|
||||
return Response.redirect(new URL("/login?fehler=oidc", request.url), 302);
|
||||
return Response.redirect(eigeneAdresse(request, "/login?fehler=oidc", dashboardUrl), 302);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -38,9 +41,9 @@ export async function GET(request: Request) {
|
||||
);
|
||||
speicher.set(SITZUNGS_COOKIE, token, cookieOptionen(sitzungsdauerStunden));
|
||||
|
||||
return Response.redirect(new URL(ziel.startsWith("/") ? ziel : "/", request.url), 302);
|
||||
return Response.redirect(eigeneAdresse(request, ziel.startsWith("/") ? ziel : "/", dashboardUrl), 302);
|
||||
} catch (fehler) {
|
||||
console.error("OIDC-Rückkehr fehlgeschlagen:", fehler);
|
||||
return Response.redirect(new URL("/login?fehler=oidc", request.url), 302);
|
||||
return Response.redirect(eigeneAdresse(request, "/login?fehler=oidc", dashboardUrl), 302);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Ermittelt die Adresse, unter der das Dashboard **von außen** erreichbar ist.
|
||||
*
|
||||
* Im Container ist `request.url` die interne Adresse (`http://<container-id>:3000`). Eine Weiterleitung
|
||||
* dorthin führt im Browser ins Leere – genau das passierte nach der Anmeldung im Docker-Stapel.
|
||||
*
|
||||
* Reihenfolge: eingetragene Adresse → `APP_URL` → Kopfzeilen des Reverse Proxy → Anfrage selbst.
|
||||
* Die Kopfzeilen stehen bewusst hinter den konfigurierten Werten: Sie kommen vom Client und lassen
|
||||
* sich fälschen; für eine Weiterleitung auf die eigene Seite ist das unkritisch, als erste Wahl aber
|
||||
* unnötig.
|
||||
*/
|
||||
export function oeffentlicheBasis(request: Request, eingetragen?: string): URL {
|
||||
if (eingetragen) {
|
||||
try {
|
||||
return new URL(eingetragen);
|
||||
} catch {
|
||||
// Unbrauchbarer Eintrag: weiter mit den übrigen Quellen.
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.APP_URL) {
|
||||
try {
|
||||
return new URL(process.env.APP_URL);
|
||||
} catch {
|
||||
// ebenso
|
||||
}
|
||||
}
|
||||
|
||||
const gastgeber = request.headers.get("x-forwarded-host") ?? request.headers.get("host");
|
||||
if (gastgeber) {
|
||||
const schema = request.headers.get("x-forwarded-proto") ?? new URL(request.url).protocol.replace(":", "");
|
||||
try {
|
||||
return new URL(`${schema}://${gastgeber}`);
|
||||
} catch {
|
||||
// ebenso
|
||||
}
|
||||
}
|
||||
|
||||
return new URL(request.url);
|
||||
}
|
||||
|
||||
/** Baut eine Adresse auf derselben Installation – für Weiterleitungen nach der Anmeldung. */
|
||||
export function eigeneAdresse(request: Request, pfad: string, eingetragen?: string): URL {
|
||||
return new URL(pfad, oeffentlicheBasis(request, eingetragen));
|
||||
}
|
||||
Reference in New Issue
Block a user