Prod env files now win over root .env so harmonize:db can open gallery_prod after gallery_dev; tour_stops gains updated_at; halls no longer block the overlay on every painting texture. Docs cover the fixes and one-artist image pull from prod; Duccio painting assets synced from TrueNAS. Co-authored-by: Cursor <cursoragent@cursor.com>
125 lines
3.5 KiB
JavaScript
125 lines
3.5 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const readline = require('readline');
|
|
|
|
const rootDir = path.resolve(__dirname, '..');
|
|
|
|
const PROD_DB_SUFFIX = '_prod';
|
|
const DEV_DB_NAME = 'gallery_dev';
|
|
const PROD_DB_NAME = 'gallery_prod';
|
|
const LEGACY_DB_NAMES = ['Gallery', 'gallery'];
|
|
|
|
function parseEnvFile(filePath) {
|
|
const out = {};
|
|
if (!fs.existsSync(filePath)) return out;
|
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
for (const line of content.split(/\r?\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();
|
|
let value = trimmed.slice(eq + 1).trim();
|
|
if (
|
|
(value.startsWith('"') && value.endsWith('"'))
|
|
|| (value.startsWith("'") && value.endsWith("'"))
|
|
) {
|
|
value = value.slice(1, -1);
|
|
}
|
|
out[key] = value;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** Load into process.env without overwriting keys already set. */
|
|
function loadEnvFile(filePath) {
|
|
const parsed = parseEnvFile(filePath);
|
|
for (const [key, value] of Object.entries(parsed)) {
|
|
if (process.env[key] === undefined) process.env[key] = value;
|
|
}
|
|
}
|
|
|
|
function pgConfigFromMap(env) {
|
|
const host = env.DB_HOST || '192.168.10.122';
|
|
const port = Number(env.DB_PORT || 5432);
|
|
const user = env.DB_USER;
|
|
const password = env.DB_PASSWORD;
|
|
const database = env.DB_NAME;
|
|
if (!user || !password || !database) {
|
|
throw new Error('DB_USER, DB_PASSWORD, and DB_NAME are required');
|
|
}
|
|
return { host, port, user, password, database };
|
|
}
|
|
|
|
function pgConfigFromEnv() {
|
|
return pgConfigFromMap(process.env);
|
|
}
|
|
|
|
function assertProdDatabase(dbName) {
|
|
if (!dbName.endsWith(PROD_DB_SUFFIX) && dbName !== PROD_DB_NAME) {
|
|
throw new Error(
|
|
`Refusing prod operation on database "${dbName}". `
|
|
+ `Target must be "${PROD_DB_NAME}" or end with "${PROD_DB_SUFFIX}".`,
|
|
);
|
|
}
|
|
return dbName;
|
|
}
|
|
|
|
function assertDevDatabase(dbName) {
|
|
if (dbName === PROD_DB_NAME || dbName.endsWith(PROD_DB_SUFFIX)) {
|
|
throw new Error(
|
|
`Refusing dev operation on production database "${dbName}". `
|
|
+ `Use "${DEV_DB_NAME}" for development.`,
|
|
);
|
|
}
|
|
return dbName;
|
|
}
|
|
|
|
/**
|
|
* File values win over process.env so loading prod after dev (or vice versa)
|
|
* in the same process cannot leave the wrong DB_NAME stuck.
|
|
*/
|
|
function loadDevPgConfig() {
|
|
const filePath = path.join(rootDir, '.env');
|
|
const fileEnv = parseEnvFile(filePath);
|
|
const config = pgConfigFromMap({ ...process.env, ...fileEnv });
|
|
assertDevDatabase(config.database);
|
|
return config;
|
|
}
|
|
|
|
function loadProdPgConfig() {
|
|
const filePath = path.join(rootDir, 'infra', 'docker', '.env.prod');
|
|
const fileEnv = parseEnvFile(filePath);
|
|
const config = pgConfigFromMap({ ...process.env, ...fileEnv });
|
|
assertProdDatabase(config.database);
|
|
return config;
|
|
}
|
|
|
|
async function confirmProdAction(message) {
|
|
if (process.env.CONFIRM_PROD === '1') return;
|
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
const answer = await new Promise((resolve) => {
|
|
rl.question(`${message}\nType "yes" to continue: `, resolve);
|
|
});
|
|
rl.close();
|
|
if (answer.trim().toLowerCase() !== 'yes') {
|
|
throw new Error('Aborted.');
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
PROD_DB_SUFFIX,
|
|
DEV_DB_NAME,
|
|
PROD_DB_NAME,
|
|
LEGACY_DB_NAMES,
|
|
rootDir,
|
|
parseEnvFile,
|
|
loadEnvFile,
|
|
pgConfigFromEnv,
|
|
assertProdDatabase,
|
|
assertDevDatabase,
|
|
loadDevPgConfig,
|
|
loadProdPgConfig,
|
|
confirmProdAction,
|
|
};
|