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,
|
refreshContentCaches,
|
||||||
} from '../data.js'
|
} 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
|
// 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.
|
// must be present at the top level of an uploaded archive before we restore.
|
||||||
const KNOWN_DATA_FILES = [
|
const KNOWN_DATA_FILES = [
|
||||||
@@ -106,6 +154,7 @@ export function register(app) {
|
|||||||
app.get('/api/admin-backup/export', requireAdminAuth, async (_req, res) => {
|
app.get('/api/admin-backup/export', requireAdminAuth, async (_req, res) => {
|
||||||
try {
|
try {
|
||||||
await flushPendingWrites()
|
await flushPendingWrites()
|
||||||
|
await writeEnvSnapshot()
|
||||||
const entries = (await readdir(DATA_DIR)).filter(name => name !== 'backups')
|
const entries = (await readdir(DATA_DIR)).filter(name => name !== 'backups')
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
res.status(500).json({ message: 'Data directory is empty — nothing to export.' })
|
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) */}
|
{/* Full Data Backup (entire /data folder — server migration) */}
|
||||||
<div className="admin-stats-head admin-stats-head--visitors">
|
<div className="admin-stats-head admin-stats-head--visitors">
|
||||||
<h2>Full Data Backup</h2>
|
<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>
|
||||||
<div className="admin-actions admin-actions--maintenance">
|
<div className="admin-actions admin-actions--maintenance">
|
||||||
<button type="button" className="btn-admin-reset" onClick={onDownloadFullBackup} disabled={fullBackupBusy}>
|
<button type="button" className="btn-admin-reset" onClick={onDownloadFullBackup} disabled={fullBackupBusy}>
|
||||||
|
|||||||
Reference in New Issue
Block a user