265 lines
5.4 KiB
Bash
265 lines
5.4 KiB
Bash
#!/bin/sh
|
|
set -eu
|
|
|
|
node <<'NODE'
|
|
const net = require("net");
|
|
|
|
const databaseUrl = process.env.DATABASE_URL;
|
|
if (!databaseUrl) {
|
|
console.error("DATABASE_URL is not set.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const url = new URL(databaseUrl);
|
|
const host = url.hostname;
|
|
const port = Number(url.port || 5432);
|
|
const timeoutMs = 90_000;
|
|
const intervalMs = 2_000;
|
|
const startedAt = Date.now();
|
|
|
|
function check() {
|
|
const socket = net.createConnection({ host, port });
|
|
|
|
socket.once("connect", () => {
|
|
socket.end();
|
|
console.log(`Database is reachable at ${host}:${port}.`);
|
|
process.exit(0);
|
|
});
|
|
|
|
socket.once("error", (error) => {
|
|
socket.destroy();
|
|
|
|
if (Date.now() - startedAt >= timeoutMs) {
|
|
console.error(`Database is not reachable at ${host}:${port}: ${error.message}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`Waiting for database at ${host}:${port}...`);
|
|
setTimeout(check, intervalMs);
|
|
});
|
|
}
|
|
|
|
check();
|
|
NODE
|
|
|
|
if [ -d /app/prisma/migrations ] && [ "$(find /app/prisma/migrations -mindepth 1 -maxdepth 1 | wc -l)" -gt 0 ]; then
|
|
db_action="$(node <<'NODE'
|
|
const { PrismaClient } = require("@prisma/client");
|
|
|
|
const prisma = new PrismaClient();
|
|
async function main() {
|
|
const tableRows = await prisma.$queryRaw`
|
|
SELECT table_name
|
|
FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_type = 'BASE TABLE'
|
|
`;
|
|
|
|
const enumRows = await prisma.$queryRaw`
|
|
SELECT typname
|
|
FROM pg_type t
|
|
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
WHERE n.nspname = 'public'
|
|
AND t.typtype = 'e'
|
|
`;
|
|
|
|
const tables = new Set(tableRows.map((row) => row.table_name));
|
|
|
|
const hasMigrationTable = tables.has("_prisma_migrations");
|
|
|
|
const appTables = [...tables].filter(
|
|
(table) => table !== "_prisma_migrations"
|
|
);
|
|
|
|
const hasEnums = enumRows.length > 0;
|
|
|
|
let failedMigrations = [];
|
|
let migrationCount = 0;
|
|
|
|
if (hasMigrationTable) {
|
|
failedMigrations = await prisma.$queryRaw`
|
|
SELECT migration_name
|
|
FROM "_prisma_migrations"
|
|
WHERE finished_at IS NULL
|
|
AND rolled_back_at IS NULL
|
|
`;
|
|
|
|
const migrationCountResult = await prisma.$queryRaw`
|
|
SELECT COUNT(*)::int AS count
|
|
FROM "_prisma_migrations"
|
|
`;
|
|
|
|
migrationCount = migrationCountResult[0].count;
|
|
}
|
|
|
|
/**
|
|
* STATE #1
|
|
* Fresh database
|
|
*/
|
|
if (
|
|
appTables.length === 0 &&
|
|
!hasMigrationTable &&
|
|
!hasEnums
|
|
) {
|
|
console.error(
|
|
"Fresh database detected. Applying migrations."
|
|
);
|
|
|
|
console.log("migrate");
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* STATE #2
|
|
* Existing schema without Prisma migration history
|
|
*/
|
|
if (
|
|
appTables.length > 0 &&
|
|
!hasMigrationTable
|
|
) {
|
|
console.error(
|
|
"Application tables exist but Prisma migration history table is missing."
|
|
);
|
|
|
|
console.error(
|
|
"Database state is inconsistent."
|
|
);
|
|
|
|
console.log("blocked");
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* STATE #3
|
|
* Prisma migration history exists
|
|
*/
|
|
if (hasMigrationTable) {
|
|
if (
|
|
migrationCount === 0 &&
|
|
(
|
|
appTables.length > 0 ||
|
|
hasEnums
|
|
)
|
|
) {
|
|
console.error(
|
|
"Database contains schema objects but Prisma migration history is empty."
|
|
);
|
|
|
|
console.error(
|
|
"Database state is inconsistent."
|
|
);
|
|
|
|
console.error(
|
|
"Do not delete records from _prisma_migrations manually."
|
|
);
|
|
|
|
console.log("blocked");
|
|
return;
|
|
}
|
|
|
|
if (failedMigrations.length > 0) {
|
|
console.error(
|
|
`Failed Prisma migrations detected: ${failedMigrations
|
|
.map((row) => row.migration_name)
|
|
.join(", ")}`
|
|
);
|
|
|
|
console.error(
|
|
"Resolve the failed migration before starting Doktainer."
|
|
);
|
|
|
|
console.log("blocked");
|
|
return;
|
|
}
|
|
|
|
console.error(
|
|
"Prisma migration history detected. Applying pending migrations."
|
|
);
|
|
|
|
console.log("migrate");
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* STATE #4
|
|
* Partial schema / inconsistent state
|
|
*/
|
|
console.error("");
|
|
console.error(
|
|
"Database appears to be partially initialized."
|
|
);
|
|
|
|
console.error(
|
|
"Found Prisma objects without a valid schema state."
|
|
);
|
|
|
|
console.error("");
|
|
console.error(
|
|
"This may happen when:"
|
|
);
|
|
|
|
console.error(
|
|
"- a migration failed previously"
|
|
);
|
|
|
|
console.error(
|
|
"- the database was partially restored"
|
|
);
|
|
|
|
console.error(
|
|
"- schema objects were created manually"
|
|
);
|
|
|
|
console.error("");
|
|
console.error(
|
|
"Doktainer will not automatically modify the database."
|
|
);
|
|
|
|
console.error(
|
|
"Manual intervention is required."
|
|
);
|
|
|
|
console.log("blocked");
|
|
}
|
|
|
|
main()
|
|
.catch((error) => {
|
|
console.error(error);
|
|
console.log("blocked");
|
|
})
|
|
.finally(async () => {
|
|
await prisma.$disconnect();
|
|
});
|
|
NODE
|
|
)"
|
|
|
|
case "$db_action" in
|
|
migrate)
|
|
npx prisma migrate deploy
|
|
;;
|
|
push)
|
|
npx prisma db push
|
|
;;
|
|
blocked)
|
|
exit 1
|
|
;;
|
|
*)
|
|
echo "Unknown database setup action: $db_action" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
else
|
|
npx prisma db push
|
|
fi
|
|
|
|
node dist/server/index.js &
|
|
api_pid=$!
|
|
|
|
cleanup() {
|
|
kill "$api_pid" 2>/dev/null || true
|
|
}
|
|
|
|
trap cleanup INT TERM EXIT
|
|
|
|
exec node_modules/.bin/next start -p 3000 -H 0.0.0.0
|