mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 03:15:48 -06:00
This commit introduces a comprehensive deployment strategy that unifies the frontend and backend into a single Docker container served by the Rust backend, streamlining the deployment process and improving production architecture. Key changes: - Split CI/CD workflows: separated build.yaml (for CI/PR checks) and deploy.yaml (for production deployment) - Implemented unified Docker deployment where the Axum server serves both API routes (under /api) and frontend static files - Added GitHub Container Registry integration for Docker image distribution - Updated Railway configuration to use the new healthcheck path (/api/health) - Enhanced postgres.ts script with named volumes and constants for better container management - Added API client utilities (web/lib/api.ts) and environment configuration (web/.env.example) for frontend-backend communication - Configured Vite proxy for local development while supporting same-origin requests in production - Updated Dockerfile to include frontend static files and proper environment variable handling This architecture eliminates the need for separate deployments and CORS configuration, as the frontend and API are served from the same origin.
197 lines
5.7 KiB
TypeScript
197 lines
5.7 KiB
TypeScript
import { $ } from "bun";
|
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
import { join, dirname } from "path";
|
|
import { fileURLToPath } from "url";
|
|
import { createInterface } from "readline";
|
|
|
|
// Constants for container and volume names
|
|
const CONTAINER_NAME = "pacman-server-postgres";
|
|
const VOLUME_NAME = "pacman-postgres-data";
|
|
|
|
// Helper function to get user input
|
|
async function getUserChoice(
|
|
prompt: string,
|
|
choices: string[],
|
|
defaultIndex: number = 1
|
|
): Promise<string> {
|
|
// Check if we're in an interactive TTY
|
|
if (!process.stdin.isTTY) {
|
|
console.log(
|
|
"Non-interactive environment detected; selecting default option " +
|
|
defaultIndex
|
|
);
|
|
return String(defaultIndex);
|
|
}
|
|
|
|
console.log(prompt);
|
|
choices.forEach((choice, index) => {
|
|
console.log(`${index + 1}. ${choice}`);
|
|
});
|
|
|
|
// Use readline for interactive input
|
|
const rl = createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout,
|
|
});
|
|
|
|
return new Promise((resolve) => {
|
|
const askForChoice = () => {
|
|
rl.question("Enter your choice (1-3): ", (answer) => {
|
|
const choice = answer.trim();
|
|
if (["1", "2", "3"].includes(choice)) {
|
|
rl.close();
|
|
resolve(choice);
|
|
} else {
|
|
console.log("Invalid choice. Please enter 1, 2, or 3.");
|
|
askForChoice();
|
|
}
|
|
});
|
|
};
|
|
askForChoice();
|
|
});
|
|
}
|
|
|
|
// Get repository root path from script location
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
const repoRoot = join(__dirname, "..");
|
|
const envPath = join(repoRoot, "pacman-server", ".env");
|
|
|
|
console.log("Checking for .env file...");
|
|
|
|
// Check if .env file exists and read it
|
|
let envContent = "";
|
|
let envLines: string[] = [];
|
|
let databaseUrlLine = -1;
|
|
let databaseUrlValue = "";
|
|
|
|
if (existsSync(envPath)) {
|
|
console.log("Found .env file, reading...");
|
|
envContent = readFileSync(envPath, "utf-8");
|
|
envLines = envContent.split("\n");
|
|
|
|
// Parse .env file for DATABASE_URL
|
|
for (let i = 0; i < envLines.length; i++) {
|
|
const line = envLines[i].trim();
|
|
if (line.match(/^[A-Z_][A-Z0-9_]*=.*$/)) {
|
|
if (line.startsWith("DATABASE_URL=")) {
|
|
databaseUrlLine = i;
|
|
databaseUrlValue = line.substring(13); // Remove "DATABASE_URL="
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
console.log("No .env file found, will create one");
|
|
}
|
|
|
|
// Determine user's choice
|
|
let userChoice = "2"; // Default to print
|
|
|
|
if (databaseUrlLine !== -1) {
|
|
console.log(`Found existing DATABASE_URL: ${databaseUrlValue}`);
|
|
userChoice = await getUserChoice("\nChoose an action:", [
|
|
"Quit",
|
|
"Print (create container, print DATABASE_URL)",
|
|
"Replace (update DATABASE_URL in .env)",
|
|
]);
|
|
|
|
if (userChoice === "1") {
|
|
console.log("Exiting...");
|
|
process.exit(0);
|
|
}
|
|
} else {
|
|
console.log("No existing DATABASE_URL found");
|
|
|
|
// Ask what to do when no .env file or DATABASE_URL exists
|
|
if (!existsSync(envPath)) {
|
|
userChoice = await getUserChoice(
|
|
"\nNo .env file found. What would you like to do?",
|
|
[
|
|
"Print (create container, print DATABASE_URL)",
|
|
"Create .env file and add DATABASE_URL",
|
|
"Quit",
|
|
]
|
|
);
|
|
|
|
if (userChoice === "3") {
|
|
console.log("Exiting...");
|
|
process.exit(0);
|
|
}
|
|
} else {
|
|
console.log("Will add DATABASE_URL to existing .env file");
|
|
}
|
|
}
|
|
|
|
// Check if container exists
|
|
console.log("Checking for existing container...");
|
|
const containerExists =
|
|
await $`docker ps -a --filter name=${CONTAINER_NAME} --format "{{.Names}}"`
|
|
.text()
|
|
.then((names) => names.trim() === CONTAINER_NAME)
|
|
.catch(() => false);
|
|
|
|
let shouldReplaceContainer = false;
|
|
|
|
if (containerExists) {
|
|
console.log("Container already exists");
|
|
|
|
// Always ask what to do if container exists
|
|
const replaceChoice = await getUserChoice(
|
|
"\nContainer exists. What would you like to do?",
|
|
["Use existing container", "Replace container (remove and create new)"],
|
|
1
|
|
);
|
|
shouldReplaceContainer = replaceChoice === "2";
|
|
|
|
if (shouldReplaceContainer) {
|
|
console.log("Removing existing container...");
|
|
await $`docker rm --force --volumes ${CONTAINER_NAME}`;
|
|
|
|
// Explicitly remove the named volume to ensure clean state
|
|
console.log("Removing volume...");
|
|
await $`docker volume rm ${VOLUME_NAME}`.catch(() => {
|
|
console.log("Volume doesn't exist or already removed");
|
|
});
|
|
} else {
|
|
console.log("Using existing container");
|
|
}
|
|
}
|
|
|
|
// Create container if needed
|
|
if (!containerExists || shouldReplaceContainer) {
|
|
console.log("Creating PostgreSQL container...");
|
|
await $`docker run --detach --name ${CONTAINER_NAME} --publish 5432:5432 --volume ${VOLUME_NAME}:/var/lib/postgresql/data --env POSTGRES_USER=postgres --env POSTGRES_PASSWORD=postgres --env POSTGRES_DB=pacman-server postgres:17`;
|
|
}
|
|
|
|
// Format DATABASE_URL
|
|
const databaseUrl =
|
|
"postgresql://postgres:postgres@127.0.0.1:5432/pacman-server";
|
|
|
|
// Handle the final action based on user choice
|
|
if (userChoice === "2") {
|
|
// Print option
|
|
console.log(`\nDATABASE_URL=${databaseUrl}`);
|
|
} else if (
|
|
userChoice === "3" ||
|
|
(databaseUrlLine === -1 && userChoice === "2")
|
|
) {
|
|
// Replace or add to .env file
|
|
if (databaseUrlLine !== -1) {
|
|
// Replace existing line
|
|
console.log("Updating DATABASE_URL in .env file...");
|
|
envLines[databaseUrlLine] = `DATABASE_URL=${databaseUrl}`;
|
|
writeFileSync(envPath, envLines.join("\n"));
|
|
console.log("Updated .env file");
|
|
} else {
|
|
// Add new line
|
|
console.log("Adding DATABASE_URL to .env file...");
|
|
const newContent =
|
|
envContent +
|
|
(envContent.endsWith("\n") ? "" : "\n") +
|
|
`DATABASE_URL=${databaseUrl}\n`;
|
|
writeFileSync(envPath, newContent);
|
|
console.log("Added to .env file");
|
|
}
|
|
}
|