159 lines
4.7 KiB
JavaScript
159 lines
4.7 KiB
JavaScript
#!/usr/bin/env node
|
|
import { execFileSync } from 'node:child_process';
|
|
import { existsSync, readFileSync } from 'node:fs';
|
|
import { dirname, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import pg from 'pg';
|
|
|
|
const projectDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const envPath = resolve(projectDir, '.env');
|
|
|
|
if (existsSync(envPath)) {
|
|
for (const line of readFileSync(envPath, 'utf8').split('\n')) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
const eq = trimmed.indexOf('=');
|
|
if (eq === -1) continue;
|
|
const key = trimmed.slice(0, eq).trim();
|
|
const value = trimmed.slice(eq + 1).trim();
|
|
if (key && process.env[key] === undefined) process.env[key] = value;
|
|
}
|
|
}
|
|
|
|
const databaseUrl = process.env.DATABASE_URL;
|
|
if (!databaseUrl) throw new Error('DATABASE_URL not set');
|
|
|
|
const recreate = process.argv.includes('--recreate');
|
|
const token = process.env.PERSISTENCE_CHECK_TOKEN ?? `persistence-${Date.now()}`;
|
|
const pgKey = 'infra.persistence_check';
|
|
const redisKey = 'infra:persistence_check';
|
|
|
|
function run(command, args, options = {}) {
|
|
return execFileSync(command, args, {
|
|
cwd: projectDir,
|
|
stdio: options.capture ? ['ignore', 'pipe', 'pipe'] : 'inherit',
|
|
encoding: 'utf8',
|
|
});
|
|
}
|
|
|
|
function dockerCompose(args, options) {
|
|
return run('docker', ['compose', ...args], options);
|
|
}
|
|
|
|
function dockerExec(container, args, options) {
|
|
return run('docker', ['exec', container, ...args], options);
|
|
}
|
|
|
|
function waitForInfrastructure() {
|
|
const deadline = Date.now() + 60_000;
|
|
while (Date.now() < deadline) {
|
|
try {
|
|
dockerExec('mdv-dev-postgres', ['pg_isready', '-U', 'mdv', '-d', 'mercadodevida'], {
|
|
capture: true,
|
|
});
|
|
dockerExec('mdv-dev-redis', ['redis-cli', 'ping'], { capture: true });
|
|
return;
|
|
} catch {
|
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000);
|
|
}
|
|
}
|
|
throw new Error('PostgreSQL/Redis did not become ready in 60s');
|
|
}
|
|
|
|
async function writePostgresMarker() {
|
|
const pool = new pg.Pool({ connectionString: databaseUrl });
|
|
try {
|
|
await pool.query(
|
|
`INSERT INTO app_meta (key, value, updated_at)
|
|
VALUES ($1, $2, now())
|
|
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = now()`,
|
|
[pgKey, token],
|
|
);
|
|
} finally {
|
|
await pool.end();
|
|
}
|
|
}
|
|
|
|
async function readPostgresMarker() {
|
|
const pool = new pg.Pool({ connectionString: databaseUrl });
|
|
try {
|
|
const result = await pool.query('SELECT value FROM app_meta WHERE key = $1', [pgKey]);
|
|
return result.rows[0]?.value;
|
|
} finally {
|
|
await pool.end();
|
|
}
|
|
}
|
|
|
|
function writeRedisMarker() {
|
|
dockerExec('mdv-dev-redis', ['redis-cli', 'SET', redisKey, token]);
|
|
}
|
|
|
|
function readRedisMarker() {
|
|
return dockerExec('mdv-dev-redis', ['redis-cli', 'GET', redisKey], { capture: true }).trim();
|
|
}
|
|
|
|
function printMounts() {
|
|
const pgMount = run(
|
|
'docker',
|
|
[
|
|
'inspect',
|
|
'mdv-dev-postgres',
|
|
'--format',
|
|
'{{range .Mounts}}{{.Name}} -> {{.Destination}}{{println}}{{end}}',
|
|
],
|
|
{ capture: true },
|
|
).trim();
|
|
const redisMount = run(
|
|
'docker',
|
|
[
|
|
'inspect',
|
|
'mdv-dev-redis',
|
|
'--format',
|
|
'{{range .Mounts}}{{.Name}} -> {{.Destination}}{{println}}{{end}}',
|
|
],
|
|
{ capture: true },
|
|
).trim();
|
|
const redisAppendOnly = dockerExec(
|
|
'mdv-dev-redis',
|
|
['redis-cli', 'CONFIG', 'GET', 'appendonly'],
|
|
{
|
|
capture: true,
|
|
},
|
|
).trim();
|
|
console.log(`PostgreSQL mount: ${pgMount}`);
|
|
console.log(`Redis mount: ${redisMount}`);
|
|
console.log(`Redis appendonly config:\n${redisAppendOnly}`);
|
|
}
|
|
|
|
console.log('[INFO] Starting infrastructure through docker compose...');
|
|
dockerCompose(['up', '-d', 'postgres', 'redis']);
|
|
waitForInfrastructure();
|
|
printMounts();
|
|
|
|
console.log(`[INFO] Writing persistence marker: ${token}`);
|
|
await writePostgresMarker();
|
|
writeRedisMarker();
|
|
|
|
if (recreate) {
|
|
console.log('[INFO] Recreating PostgreSQL and Redis containers while keeping named volumes...');
|
|
dockerCompose(['stop', 'postgres', 'redis']);
|
|
dockerCompose(['rm', '-f', 'postgres', 'redis']);
|
|
dockerCompose(['up', '-d', 'postgres', 'redis']);
|
|
} else {
|
|
console.log('[INFO] Restarting PostgreSQL and Redis containers...');
|
|
dockerCompose(['restart', 'postgres', 'redis']);
|
|
}
|
|
waitForInfrastructure();
|
|
printMounts();
|
|
|
|
const pgValue = await readPostgresMarker();
|
|
const redisValue = readRedisMarker();
|
|
if (pgValue !== token) {
|
|
throw new Error(`PostgreSQL marker mismatch: expected ${token}, got ${pgValue ?? '<missing>'}`);
|
|
}
|
|
if (redisValue !== token) {
|
|
throw new Error(`Redis marker mismatch: expected ${token}, got ${redisValue || '<missing>'}`);
|
|
}
|
|
|
|
console.log('[OK] PostgreSQL and Redis persistence verified');
|