feat(restore): docker-logs visibility + ADMIN_CREDENTIALS.txt restore notice
Two nice-to-haves from the PR #596 review. 1. Install-from-backup logging mirrors to stdout The winston logger writes to /app/logs/combined.log and may not tee to stdout. Operators tailing `docker logs picpeak-beta-backend` after a `compose up` saw the migration sweep + npm notice and nothing about the restore. Three key events now also fire through `console.log` with a `[install-from-backup] ` prefix: - "trigger file detected → <manifest>" - "starting restore from <manifest>" - "restore completed successfully" / "FAILED — <reason>" Plus the "skipping — existing data" branch. docker-logs surface now tells the restore story without requiring an `exec into the container` step. 2. ADMIN_CREDENTIALS.txt flags stale creds when restore is queued Migration 001 detects a pending `RESTORE_ON_INSTALL` file BEFORE writing the fresh-install credentials file. If a trigger will fire on the next boot, the file now opens with a clear warning: ⚠️ RESTORE_ON_INSTALL TRIGGER DETECTED ⚠️ These credentials are temporary. An install-from-backup run is queued to fire on the next server start, which will REPLACE this admin row with the one from the backup. After the restore completes, log in with your ORIGINAL pre-disaster credentials — not the ones below. If the restore fails for some reason, the credentials below remain valid as a fallback recovery path. Doesn't skip the file (so a failed restore still has the fallback credentials), just annotates it. Closes the maintainer's "stale junk credentials" observation.
This commit is contained in:
@@ -33,11 +33,34 @@ exports.up = async function(knex) {
|
||||
// Try to save credentials to file, but don't fail if we can't
|
||||
const dataDir = path.join(__dirname, '..', '..', 'data');
|
||||
const setupInfoPath = path.join(dataDir, 'ADMIN_CREDENTIALS.txt');
|
||||
|
||||
|
||||
// Detect a pending install-from-backup trigger. If one exists,
|
||||
// these credentials are about to be obsoleted by the restore —
|
||||
// the backup's admin row replaces this fresh-install one a few
|
||||
// seconds from now. We still write the file (in case the
|
||||
// restore fails and the fresh admin is the only way in) but
|
||||
// annotate the top so admins reading the file after restore
|
||||
// don't waste time trying credentials that no longer exist.
|
||||
// Flagged on PR #596 review.
|
||||
const fsSync = require('fs');
|
||||
const backupRoot = process.env.BACKUP_ROOT || '/backup';
|
||||
const triggerWillFire = fsSync.existsSync(path.join(backupRoot, 'RESTORE_ON_INSTALL'))
|
||||
|| fsSync.existsSync(path.join(backupRoot, 'RESTORE_ON_INSTALL.txt'));
|
||||
const restoreNotice = triggerWillFire ? `
|
||||
|
||||
⚠️ RESTORE_ON_INSTALL TRIGGER DETECTED ⚠️
|
||||
These credentials are temporary. An install-from-backup run is queued
|
||||
to fire on the next server start, which will REPLACE this admin row
|
||||
with the one from the backup. After the restore completes, log in
|
||||
with your ORIGINAL pre-disaster credentials — not the ones below.
|
||||
If the restore fails for some reason, the credentials below remain
|
||||
valid as a fallback recovery path.
|
||||
` : '';
|
||||
|
||||
const setupInfo = `
|
||||
========================================
|
||||
PicPeak Admin Credentials
|
||||
========================================
|
||||
========================================${restoreNotice}
|
||||
|
||||
Your admin account has been created with these credentials:
|
||||
|
||||
|
||||
@@ -159,6 +159,17 @@ async function isDatabaseFresh(db, logger) {
|
||||
async function tryInstallFromBackup(db, logger) {
|
||||
const log = logger || { info: () => {}, warn: () => {}, error: () => {} };
|
||||
|
||||
// The container's winston logger writes to `/app/logs/combined.log`
|
||||
// by default and may not always tee to stdout, so admins running
|
||||
// `docker logs picpeak-beta-backend` after a `compose up` would
|
||||
// see no signal that a restore happened — flagged on PR #596
|
||||
// review. We mirror the key trigger / start / end lines to
|
||||
// console.log as well so the docker-logs surface tells the story
|
||||
// without needing to exec into the container.
|
||||
const announce = (msg) => {
|
||||
try { console.log(`[install-from-backup] ${msg}`); } catch (_) { /* defensive */ }
|
||||
};
|
||||
|
||||
const backupRoot = process.env.BACKUP_ROOT || '/backup';
|
||||
if (!fs.existsSync(backupRoot)) {
|
||||
return { ran: false };
|
||||
@@ -170,15 +181,18 @@ async function tryInstallFromBackup(db, logger) {
|
||||
}
|
||||
|
||||
log.info(`Install-from-backup: trigger file found at ${trigger.triggerPath}, target manifest ${trigger.manifestPath}`);
|
||||
announce(`trigger file detected → ${trigger.manifestPath}`);
|
||||
|
||||
const forceOverride = process.env.INSTALL_FROM_BACKUP_FORCE === 'true';
|
||||
const isFresh = await isDatabaseFresh(db, log);
|
||||
if (!isFresh && !forceOverride) {
|
||||
log.warn('Install-from-backup: skipping. Trigger file left in place so you can correct + retry.');
|
||||
announce('skipping — install has existing data and INSTALL_FROM_BACKUP_FORCE is not set');
|
||||
return { ran: false, error: 'Database not empty' };
|
||||
}
|
||||
|
||||
log.info(`Install-from-backup: restoring from ${trigger.manifestPath}...`);
|
||||
announce(`starting restore from ${trigger.manifestPath}`);
|
||||
|
||||
try {
|
||||
const { restoreService } = require('./restoreService');
|
||||
@@ -206,6 +220,7 @@ async function tryInstallFromBackup(db, logger) {
|
||||
}
|
||||
|
||||
log.info(`Install-from-backup: restore completed successfully from ${trigger.manifestPath}`);
|
||||
announce('restore completed successfully');
|
||||
|
||||
// Remove the trigger so the next boot doesn't redo it.
|
||||
try {
|
||||
@@ -219,6 +234,7 @@ async function tryInstallFromBackup(db, logger) {
|
||||
} catch (err) {
|
||||
log.error(`Install-from-backup: FAILED — ${err.message}`);
|
||||
log.warn('Trigger file left in place so you can fix the input and retry by restarting the container.');
|
||||
announce(`FAILED — ${err.message}. Trigger file left in place for retry.`);
|
||||
return { ran: false, error: err.message };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user