Include env settings snapshot in full data backup
Docker deployments have no .env file — settings live in container env vars — so the export now writes env-snapshot.env (dotenv format) into the data dir from a whitelist of portable keys (admin password, Resend keys, webhook URLs, etc.) before archiving. Machine-specific vars (PORT, NODE_ENV, SITEFORGE_DATA_DIR, ALLOW_INSECURE_COOKIES, build metadata) are excluded. Values are not applied automatically on restore; the file documents what to set on the new server. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,54 @@ import {
|
||||
refreshContentCaches,
|
||||
} from '../data.js'
|
||||
|
||||
// Portable server settings captured into env-snapshot.env at export time so a
|
||||
// backup carries the runtime configuration too (Docker deployments have no
|
||||
// .env file — settings live in container env vars). Machine-specific vars
|
||||
// (PORT, NODE_ENV, SITEFORGE_DATA_DIR, ALLOW_INSECURE_COOKIES, build info)
|
||||
// are deliberately excluded. Values are NOT applied automatically on restore.
|
||||
const ENV_SNAPSHOT_KEYS = [
|
||||
'ADMIN_PASSWORD',
|
||||
'RESEND_API_KEY',
|
||||
'RESEND_CONTACTS_API_KEY',
|
||||
'RESEND_WEBHOOK_TOKEN',
|
||||
'INBOUND_EMAIL_SECRET',
|
||||
'RESEND_FROM',
|
||||
'RESEND_TO',
|
||||
'RESEND_REPLY_TO',
|
||||
'RESEND_AUTOMATION_WELCOME',
|
||||
'RESEND_SEGMENT_ID',
|
||||
'RESEND_REMINDER_SUBJECT',
|
||||
'RESEND_WELCOME_SUBJECT',
|
||||
'RESEND_WELCOME_IMAGE_URL',
|
||||
'RESEND_WELCOME_EPISODE_URL',
|
||||
'RESEND_WELCOME_WEBSITE_URL',
|
||||
'RESEND_WELCOME_SPOTIFY_URL',
|
||||
'RESEND_WELCOME_APPLE_URL',
|
||||
'RESEND_WELCOME_AMAZON_URL',
|
||||
'CACHE_PURGE_WEBHOOK_URL',
|
||||
'DEPLOY_WEBHOOK_URL',
|
||||
'CONTACT_EMAIL_COOLDOWN_MS',
|
||||
'TRUST_PROXY_HOPS',
|
||||
'TITUS_STUDY_FILE',
|
||||
'TITUS_STUDY_DOWNLOAD_NAME',
|
||||
]
|
||||
|
||||
async function writeEnvSnapshot() {
|
||||
const lines = [
|
||||
'# Siteforge environment snapshot — regenerated on every full backup export.',
|
||||
'# Contains secrets (admin password, API keys): keep this backup private.',
|
||||
'# These values are NOT applied automatically on restore. Set them as',
|
||||
'# container environment variables (or in .env) on the new server.',
|
||||
`# Exported at ${new Date().toISOString()}`,
|
||||
'',
|
||||
]
|
||||
for (const key of ENV_SNAPSHOT_KEYS) {
|
||||
const value = process.env[key]
|
||||
if (typeof value === 'string' && value.trim() !== '') lines.push(`${key}=${value}`)
|
||||
}
|
||||
await writeFile(path.join(DATA_DIR, 'env-snapshot.env'), `${lines.join('\n')}\n`, 'utf8')
|
||||
}
|
||||
|
||||
// Files that identify an archive as a Siteforge data backup. At least one
|
||||
// must be present at the top level of an uploaded archive before we restore.
|
||||
const KNOWN_DATA_FILES = [
|
||||
@@ -106,6 +154,7 @@ export function register(app) {
|
||||
app.get('/api/admin-backup/export', requireAdminAuth, async (_req, res) => {
|
||||
try {
|
||||
await flushPendingWrites()
|
||||
await writeEnvSnapshot()
|
||||
const entries = (await readdir(DATA_DIR)).filter(name => name !== 'backups')
|
||||
if (entries.length === 0) {
|
||||
res.status(500).json({ message: 'Data directory is empty — nothing to export.' })
|
||||
|
||||
@@ -818,7 +818,7 @@ export function AnalyticsPanel({
|
||||
{/* Full Data Backup (entire /data folder — server migration) */}
|
||||
<div className="admin-stats-head admin-stats-head--visitors">
|
||||
<h2>Full Data Backup</h2>
|
||||
<p>Download every persisted file — site content, uploads, study accounts, notes, progress, questions, analytics — as one archive, or restore that archive on this or a new server.</p>
|
||||
<p>Download every persisted file — site content, uploads, study accounts, notes, progress, questions, analytics — plus a snapshot of server settings (env-snapshot.env) as one archive, or restore that archive on this or a new server. The archive contains secrets like the admin password and API keys, so keep it private. Restored settings are not applied automatically — copy them into the new server's environment.</p>
|
||||
</div>
|
||||
<div className="admin-actions admin-actions--maintenance">
|
||||
<button type="button" className="btn-admin-reset" onClick={onDownloadFullBackup} disabled={fullBackupBusy}>
|
||||
|
||||
Reference in New Issue
Block a user