Add full Docker deployment: setup.sh, update.sh, healthcheck, TURN support
- setup.sh: interactive/non-interactive one-shot installer (build, DB healthcheck, migrate, seed, start), idempotent secret generation, NPM reverse-proxy network auto-detection and optional join, optional AUTO_UPDATE cron install. - update.sh: release-tag-gated updates only (never bare main), DB backup with retention before every update, lock file against concurrent runs, automatic code rollback on failed post-update healthcheck. - Dockerfile: multi-stage build, non-root user, built-in HEALTHCHECK against the new /api/health route, wholesale COPY so new source dirs (e.g. scripts/) never silently go missing at runtime. - docker-compose.yml: internal anouma-network (configurable), named volume for Postgres, app depends_on postgres healthy, no unnecessary published ports; docker-compose.override.yml.example documents joining an existing NPM network without ever touching NPM itself. - Fix host-detection: isHost was Boolean(user), wrongly granting host privileges to logged-in customers; now checks user.collection === "users". - Wire configurable STUN/TURN servers through to the WebRTC client (lib/meeting/iceServers.ts) so a TURN server can be added later via env vars only, no code changes. - DEPLOYMENT.md, updated README.md and .env.example documenting the whole flow: NPM integration, env vars, WebRTC, updates, backups, rollback. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { getPayload } from "payload";
|
||||
import config from "@payload-config";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* Used by the Docker healthcheck (see docker-compose.yml) and by
|
||||
* setup.sh/update.sh to confirm the app can actually reach Postgres before
|
||||
* being considered "up" — not just that the Node process is listening.
|
||||
*/
|
||||
export async function GET() {
|
||||
try {
|
||||
const payload = await getPayload({ config });
|
||||
await payload.db.drizzle.execute(sql`SELECT 1`);
|
||||
return NextResponse.json({ status: "ok", database: "connected" });
|
||||
} catch (error) {
|
||||
console.error("healthcheck failed", error);
|
||||
return NextResponse.json(
|
||||
{ status: "error", database: "unreachable" },
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { getPayload } from "payload";
|
||||
import config from "@payload-config";
|
||||
import { createMeetingToken } from "@/lib/meeting/token";
|
||||
import { canJoinMeeting, combineDateAndTime, getMeetingStatus } from "@/lib/meeting/status";
|
||||
import { getIceServers } from "@/lib/meeting/iceServers";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -46,7 +47,9 @@ export async function POST(request: Request, { params }: Args) {
|
||||
}
|
||||
|
||||
const { user } = await payload.auth({ headers: request.headers });
|
||||
const isHost = Boolean(user);
|
||||
// Must check the collection, not just presence — a logged-in customer
|
||||
// (collection "customers") must never be treated as host.
|
||||
const isHost = user?.collection === "users";
|
||||
|
||||
if (!isHost) {
|
||||
if (!password) {
|
||||
@@ -89,6 +92,7 @@ export async function POST(request: Request, { params }: Args) {
|
||||
participantId,
|
||||
role: isHost ? "host" : "participant",
|
||||
eventTitle: event.title,
|
||||
iceServers: getIceServers(),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("meeting join failed", error);
|
||||
|
||||
Reference in New Issue
Block a user