236 lines
7.1 KiB
JavaScript
236 lines
7.1 KiB
JavaScript
import { randomBytes } from 'crypto';
|
|
|
|
const c = (colour) =>
|
|
{
|
|
if(colour === 'reset')
|
|
{
|
|
return '\x1b[0m';
|
|
}
|
|
return Bun.color(colour, 'ansi');
|
|
};
|
|
|
|
const log = (message) => console.log(message);
|
|
const logInfo = (message) => log(c('blue') + '[INFO] ' + c('reset') + message);
|
|
const logSuccess = (message) => log(c('green') + '[SUCCESS] ' + c('reset') + message);
|
|
const logWarn = (message) => log(c('yellow') + '[WARN] ' + c('reset') + message);
|
|
const logError = (message) => log(c('red') + '[ERROR] ' + c('reset') + message);
|
|
|
|
const ENV_FILE = '.env';
|
|
const ENV_EXAMPLE_FILE = '.env.example';
|
|
const DOCKER_COMPOSE_FILE = 'docker-compose.yml';
|
|
const DOCKER_COMPOSE_EXAMPLE_FILE = 'docker-compose-example.yml';
|
|
|
|
async function generateSecureRandomString(bytes = 32)
|
|
{
|
|
return randomBytes(bytes).toString('hex');
|
|
}
|
|
|
|
async function ensureFileFromExample(targetPath, examplePath)
|
|
{
|
|
logInfo(`Checking for ${targetPath}...`);
|
|
const targetExists = await Bun.file(targetPath).exists();
|
|
|
|
if (!targetExists)
|
|
{
|
|
logWarn(`${targetPath} not found. Copying from ${examplePath}...`);
|
|
const exampleExists = await Bun.file(examplePath).exists();
|
|
if (!exampleExists)
|
|
{
|
|
logError(`${examplePath} not found. Cannot create ${targetPath}. Please ensure ${examplePath} exists.`);
|
|
// Don't exit immediately, maybe other checks can proceed.
|
|
// Consider if this should be a fatal error depending on project needs.
|
|
return { copied: false, error: true };
|
|
}
|
|
try
|
|
{
|
|
const exampleContent = await Bun.file(examplePath).text();
|
|
await Bun.write(targetPath, exampleContent);
|
|
logSuccess(`Copied ${examplePath} to ${targetPath}.`);
|
|
return { copied: true, error: false }; // Indicates file was copied
|
|
}
|
|
catch (error)
|
|
{
|
|
logError(`Failed to copy ${examplePath} to ${targetPath}: ${error.message}`);
|
|
return { copied: false, error: true }; // Indicate error
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logInfo(`${targetPath} already exists.`);
|
|
return { copied: false, error: false }; // Indicates file already existed
|
|
}
|
|
}
|
|
|
|
const secrets = new Map();
|
|
async function replacePlaceholders(filePath)
|
|
{
|
|
logInfo(`Checking for placeholders in ${filePath}...`);
|
|
try
|
|
{
|
|
let content = await Bun.file(filePath).text();
|
|
const placeholderRegex = /{{\s*([A-Z0-9_]+)\s*}}/g;
|
|
const placeholders = [...content.matchAll(placeholderRegex)];
|
|
const uniquePlaceholderNames = [...new Set(placeholders.map(match => match[1]))];
|
|
|
|
if (uniquePlaceholderNames.length === 0)
|
|
{
|
|
logInfo(`No placeholders found in ${filePath}.`);
|
|
return true; // Indicate success (no action needed)
|
|
}
|
|
|
|
logInfo(`Found placeholders: ${uniquePlaceholderNames.join(', ')}. Generating secrets...`);
|
|
for (const name of uniquePlaceholderNames)
|
|
{
|
|
// Reuse existing secret if already generated for another file in this run
|
|
if (!secrets.has(name))
|
|
{
|
|
secrets.set(name, await generateSecureRandomString());
|
|
}
|
|
}
|
|
|
|
let replacementsMade = false;
|
|
content = content.replace(placeholderRegex, (match, name) =>
|
|
{
|
|
const secret = secrets.get(name);
|
|
if (secret)
|
|
{
|
|
replacementsMade = true;
|
|
return secret;
|
|
}
|
|
return match; // Return original match if name not found (shouldn't happen with current logic)
|
|
});
|
|
|
|
if (replacementsMade)
|
|
{
|
|
await Bun.write(filePath, content);
|
|
logSuccess(`Replaced placeholders in ${filePath} with generated secrets.`);
|
|
}
|
|
else
|
|
{
|
|
logInfo(`No placeholder values needed replacement in ${filePath}.`);
|
|
}
|
|
return true; // Indicate success
|
|
|
|
}
|
|
catch (error)
|
|
{
|
|
logError(`Failed to process placeholders in ${filePath}: ${error.message}`);
|
|
return false; // Indicate failure
|
|
}
|
|
}
|
|
|
|
async function ensureDockerComposeRunning()
|
|
{
|
|
logInfo('Ensuring Docker Compose services are running...');
|
|
// Check if docker-compose.yml exists first
|
|
const composeFileExists = await Bun.file(DOCKER_COMPOSE_FILE).exists();
|
|
if (!composeFileExists)
|
|
{
|
|
logWarn(`Skipping Docker Compose setup because ${DOCKER_COMPOSE_FILE} does not exist.`);
|
|
return true; // Not an error, just skipping
|
|
}
|
|
|
|
try
|
|
{
|
|
logInfo(`Running docker compose -f ${DOCKER_COMPOSE_FILE} up -d...`);
|
|
const proc = Bun.spawn({
|
|
cmd: ['docker', 'compose', '-f', DOCKER_COMPOSE_FILE, 'up', '-d'],
|
|
stdout: 'inherit', // Pipe output to the setup script's stdout
|
|
stderr: 'inherit', // Pipe errors to the setup script's stderr
|
|
});
|
|
|
|
const exitCode = await proc.exited;
|
|
|
|
if (exitCode === 0)
|
|
{
|
|
logSuccess('Docker Compose services started successfully (or were already running).');
|
|
return true; // Indicate success
|
|
}
|
|
else
|
|
{
|
|
logError(`Docker Compose command failed with exit code ${exitCode}.`);
|
|
return false; // Indicate failure
|
|
}
|
|
}
|
|
catch (error)
|
|
{
|
|
logError(`Failed to run Docker Compose: ${error.message}`);
|
|
logError('Please ensure Docker is installed, running, and you have permissions to use it.');
|
|
return false; // Indicate failure
|
|
}
|
|
}
|
|
|
|
async function main()
|
|
{
|
|
let overallSuccess = true;
|
|
console.log(c('aqua') +` _____ _ _ _____ _ _
|
|
/ ____| | | | | __ \\ (_) | |
|
|
| (___ | |_ _ _| | ___| |__) |__ _ _ __ | |_
|
|
\\___ \\| __| | | | |/ _ \\ ___/ _ \\| | '_ \\| __|
|
|
____) | |_| |_| | | __/ | | (_) | | | | | |_
|
|
|_____/ \\__|\\__, |_|\\___|_| \\___/|_|_| |_|\\__|
|
|
__/ |
|
|
|___/ ` + c('reset'));
|
|
|
|
logInfo('Starting project setup validation...');
|
|
|
|
// Ensure .env file exists and replace placeholders if copied
|
|
const envResult = await ensureFileFromExample(ENV_FILE, ENV_EXAMPLE_FILE);
|
|
if (envResult.error)
|
|
{
|
|
overallSuccess = false;
|
|
}
|
|
else if (envResult.copied)
|
|
{
|
|
if (!await replacePlaceholders(ENV_FILE))
|
|
{
|
|
overallSuccess = false;
|
|
}
|
|
}
|
|
|
|
// Ensure docker-compose.yml exists and replace placeholders if copied
|
|
const composeResult = await ensureFileFromExample(DOCKER_COMPOSE_FILE, DOCKER_COMPOSE_EXAMPLE_FILE);
|
|
if (composeResult.error)
|
|
{
|
|
overallSuccess = false;
|
|
}
|
|
else if (composeResult.copied)
|
|
{
|
|
if (!await replacePlaceholders(DOCKER_COMPOSE_FILE))
|
|
{
|
|
overallSuccess = false;
|
|
}
|
|
}
|
|
|
|
// Only attempt to run docker compose if the previous steps were generally successful
|
|
// and the compose file actually exists now.
|
|
if (overallSuccess && await Bun.file(DOCKER_COMPOSE_FILE).exists())
|
|
{
|
|
if (!await ensureDockerComposeRunning())
|
|
{
|
|
overallSuccess = false;
|
|
}
|
|
}
|
|
else if (!await Bun.file(DOCKER_COMPOSE_FILE).exists())
|
|
{
|
|
logWarn(`Skipping Docker Compose execution as ${DOCKER_COMPOSE_FILE} is missing.`);
|
|
// Not necessarily a failure if the example file was also missing.
|
|
}
|
|
|
|
if (overallSuccess)
|
|
{
|
|
logSuccess('Project setup validation completed successfully.');
|
|
}
|
|
else
|
|
{
|
|
logError('Project setup validation failed. Please check the logs above.');
|
|
process.exit(1); // Exit with error code if any step failed
|
|
}
|
|
}
|
|
|
|
main().catch(err =>
|
|
{
|
|
logError(`Unhandled error during setup: ${err.message}\n${err.stack}`);
|
|
process.exit(1);
|
|
});
|
|
|