feat(betrieb): Docker Compose und LXC-Installer
Ein Laufzeit-Paket (scripts/paket-bauen.sh) für beide Wege: Next standalone, worker als Bundle mit nur den nötigen Paketen, Migrationen ohne Prisma-CLI. Compose lokal komplett hochgefahren; der Installer auf pve-demo in einem echten Debian-13-LXC durchgelaufen. Closes #019 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,4 +52,36 @@ describe.runIf(shellcheckVerfuegbar())("shellcheck", () => {
|
||||
})();
|
||||
expect(ausgabe).toBe("");
|
||||
}, 60_000);
|
||||
|
||||
it("findet in den Betriebsskripten nichts (Installer, Paket, CLI-Build)", () => {
|
||||
const dateien = [
|
||||
"deploy/install-lxc.sh",
|
||||
"deploy/im-container.sh",
|
||||
"scripts/paket-bauen.sh",
|
||||
"scripts/cli-pruefen.sh",
|
||||
"cli/bauen.sh",
|
||||
];
|
||||
const ausgabe = (() => {
|
||||
try {
|
||||
execFileSync(
|
||||
"docker",
|
||||
[
|
||||
"run",
|
||||
"--rm",
|
||||
"-v",
|
||||
`${process.cwd()}:/mnt:ro`,
|
||||
"-w",
|
||||
"/mnt",
|
||||
"koalaman/shellcheck:stable",
|
||||
...dateien,
|
||||
],
|
||||
{ stdio: "pipe" },
|
||||
);
|
||||
return "";
|
||||
} catch (fehler) {
|
||||
return String((fehler as { stdout?: Buffer }).stdout ?? fehler);
|
||||
}
|
||||
})();
|
||||
expect(ausgabe).toBe("");
|
||||
}, 60_000);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import pg from "pg";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { migrieren } from "@/betrieb/migrieren";
|
||||
import { datenbankVerfuegbar } from "./helpers/db";
|
||||
|
||||
const NAME = "proxdeploy_migtest";
|
||||
|
||||
describe.runIf(datenbankVerfuegbar)("Migrationen im Betrieb (ohne Prisma-CLI)", () => {
|
||||
const basis = new URL(process.env["TEST_DATABASE_URL"]!);
|
||||
const ziel = new URL(basis);
|
||||
ziel.pathname = `/${NAME}`;
|
||||
const verwaltung = async (sql: string) => {
|
||||
const c = new pg.Client({ connectionString: basis.href });
|
||||
await c.connect();
|
||||
try {
|
||||
await c.query(sql);
|
||||
} finally {
|
||||
await c.end();
|
||||
}
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
await verwaltung(`DROP DATABASE IF EXISTS ${NAME} WITH (FORCE)`);
|
||||
await verwaltung(`CREATE DATABASE ${NAME}`);
|
||||
});
|
||||
afterAll(async () => {
|
||||
await verwaltung(`DROP DATABASE IF EXISTS ${NAME} WITH (FORCE)`);
|
||||
});
|
||||
|
||||
it("spielt alle ein, beim zweiten Mal nichts – und Prisma hält den Stand für aktuell", async () => {
|
||||
const erstes = await migrieren(ziel.href, "prisma/migrations");
|
||||
expect(erstes.eingespielt.length).toBeGreaterThan(5);
|
||||
expect(erstes.eingespielt[0]).toMatch(/_grundgeruest$/);
|
||||
expect(await migrieren(ziel.href, "prisma/migrations")).toEqual({
|
||||
eingespielt: [],
|
||||
vorhanden: erstes.eingespielt.length,
|
||||
});
|
||||
|
||||
const status = execFileSync("pnpm", ["exec", "prisma", "migrate", "status"], {
|
||||
env: { ...process.env, DATABASE_URL: ziel.href },
|
||||
encoding: "utf8",
|
||||
});
|
||||
expect(status).toMatch(/Database schema is up to date/);
|
||||
|
||||
// Auch die Regeln aus den Migrationen (Trigger) sind da: das Audit-Log ist nur anhängbar.
|
||||
const c = new pg.Client({ connectionString: ziel.href });
|
||||
await c.connect();
|
||||
try {
|
||||
await c.query(`INSERT INTO "Protokoll" (quelle, aktion) VALUES ('SYSTEM', 'Test')`);
|
||||
await expect(c.query(`DELETE FROM "Protokoll"`)).rejects.toThrow();
|
||||
} finally {
|
||||
await c.end();
|
||||
}
|
||||
});
|
||||
|
||||
it("verweigert, wenn eine eingespielte Migration verändert wurde", async () => {
|
||||
const c = new pg.Client({ connectionString: ziel.href });
|
||||
await c.connect();
|
||||
await c.query(`UPDATE "_prisma_migrations" SET checksum = 'x' WHERE migration_name LIKE '%_server'`);
|
||||
await c.end();
|
||||
await expect(migrieren(ziel.href, "prisma/migrations")).rejects.toThrow(/verändert/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user