Add dev/prod environments with TrueNAS Docker production deploy.
Split PostgreSQL into gallery_dev and gallery_prod, add Docker/Gitea deploy tooling, SMB image sync, pgAdmin split script, dev:web on Keenetic :5173, and operator docs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
02d238b043
commit
2edf577faf
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Clone gallery_prod → gallery_dev (PostgreSQL TEMPLATE).
|
||||
* Requires superuser or CREATEDB on the Postgres host.
|
||||
*/
|
||||
const path = require('path');
|
||||
const { execFileSync } = require('child_process');
|
||||
const pg = require('pg');
|
||||
const {
|
||||
DEV_DB_NAME,
|
||||
PROD_DB_NAME,
|
||||
loadProdPgConfig,
|
||||
rootDir,
|
||||
} = require('./db-env');
|
||||
|
||||
const { Client } = pg;
|
||||
|
||||
function quoteIdent(ident) {
|
||||
return `"${ident.replace(/"/g, '""')}"`;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const prodConfig = loadProdPgConfig();
|
||||
const { host, port, user, password } = prodConfig;
|
||||
|
||||
const admin = new Client({ host, port, user, password, database: 'postgres' });
|
||||
await admin.connect();
|
||||
|
||||
console.log(`Terminating connections to "${DEV_DB_NAME}"...`);
|
||||
await admin.query(
|
||||
`SELECT pg_terminate_backend(pid)
|
||||
FROM pg_stat_activity
|
||||
WHERE datname = $1 AND pid <> pg_backend_pid()`,
|
||||
[DEV_DB_NAME],
|
||||
);
|
||||
|
||||
const exists = await admin.query(
|
||||
'SELECT 1 FROM pg_database WHERE datname = $1',
|
||||
[DEV_DB_NAME],
|
||||
);
|
||||
|
||||
if (exists.rowCount > 0) {
|
||||
console.log(`Dropping "${DEV_DB_NAME}"...`);
|
||||
await admin.query(`DROP DATABASE ${quoteIdent(DEV_DB_NAME)}`);
|
||||
}
|
||||
|
||||
console.log(`Terminating connections to "${PROD_DB_NAME}"...`);
|
||||
await admin.query(
|
||||
`SELECT pg_terminate_backend(pid)
|
||||
FROM pg_stat_activity
|
||||
WHERE datname = $1 AND pid <> pg_backend_pid()`,
|
||||
[PROD_DB_NAME],
|
||||
);
|
||||
|
||||
console.log(`Cloning "${PROD_DB_NAME}" → "${DEV_DB_NAME}" (TEMPLATE)...`);
|
||||
await admin.query(
|
||||
`CREATE DATABASE ${quoteIdent(DEV_DB_NAME)} WITH TEMPLATE ${quoteIdent(PROD_DB_NAME)} OWNER ${quoteIdent(user)}`,
|
||||
);
|
||||
|
||||
await admin.end();
|
||||
|
||||
const verify = new Client({ host, port, user, password, database: DEV_DB_NAME });
|
||||
await verify.connect();
|
||||
const tables = await verify.query(
|
||||
`SELECT count(*)::int AS n FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'`,
|
||||
);
|
||||
const paintings = await verify.query('SELECT count(*)::int AS n FROM paintings');
|
||||
await verify.end();
|
||||
|
||||
console.log(`Done. "${DEV_DB_NAME}" is a copy of "${PROD_DB_NAME}" (${tables.rows[0].n} tables, ${paintings.rows[0].n} paintings).`);
|
||||
console.log('Syncing image files from production...');
|
||||
execFileSync('powershell', ['-NoProfile', '-File', path.join(rootDir, 'infra', 'scripts', 'sync-images-from-prod.ps1'), '-SkipConfirm'], {
|
||||
cwd: rootDir,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user