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:
2026-09-19 00:11:33 +02:00
co-authored by Claude Opus 5
parent ffc80a8fcf
commit 6c3729bae8
17 changed files with 601 additions and 19 deletions
+34 -1
View File
@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { herkunftStimmt } from "@/app/api/auth/anmelden/route";
import { eigeneAdresse } from "@/lib/adresse";
import type { GeleseneSitzung } from "@/lib/sitzung";
import { sitzungBewerten } from "@/server/auth/sitzungspflicht";
@@ -72,3 +73,35 @@ describe("Herkunft der Anmeldung", () => {
).toBe(true);
});
});
describe("Öffentliche Adresse", () => {
// APP_URL kommt sonst aus der .env der Entwicklungsumgebung und verfälscht die Rückfallstufen.
beforeEach(() => {
vi.stubEnv("APP_URL", "");
});
afterEach(() => {
vi.unstubAllEnvs();
});
const anfrageMit = (kopfzeilen: Record<string, string>) =>
new Request("http://0a5963a591df:3000/api/auth/anmelden", { method: "POST", headers: kopfzeilen });
it("nimmt die eingetragene Adresse, nicht die interne des Containers", () => {
const adresse = eigeneAdresse(anfrageMit({}), "/hosts", "https://proxledger.example.de");
expect(adresse.toString()).toBe("https://proxledger.example.de/hosts");
});
it("fällt auf die Kopfzeilen des Reverse Proxy zurück", () => {
const anfrage = anfrageMit({ "x-forwarded-host": "dashboard.kunde.de", "x-forwarded-proto": "https" });
expect(eigeneAdresse(anfrage, "/").toString()).toBe("https://dashboard.kunde.de/");
});
it("nimmt notfalls die Anfrage selbst", () => {
expect(eigeneAdresse(anfrageMit({}), "/").host).toBe("0a5963a591df:3000");
});
it("übergeht einen unbrauchbaren Eintrag, statt zu scheitern", () => {
const anfrage = anfrageMit({ "x-forwarded-host": "dashboard.kunde.de" });
expect(eigeneAdresse(anfrage, "/", "keine-adresse").host).toBe("dashboard.kunde.de");
});
});