added ability for env file to preserve current data setup

This commit is contained in:
John Overton
2026-03-23 21:38:06 -04:00
parent 7d61b4c71c
commit 0be2eceea6
3 changed files with 62 additions and 13 deletions
+23 -3
View File
@@ -4,7 +4,7 @@ import path from 'path';
import JSZip from 'jszip';
import prisma from '../../db';
import { withAuthContext, ApiResponse, AuthResult } from '../../utils/auth';
import { reloadEnvFile, ensureEnvDefaults } from '../../utils/env-reload';
import { reloadEnvFile, ensureEnvDefaults, parseEnvFile, replaceEnvVar } from '../../utils/env-reload';
import { isSQLite } from '../../utils/db-provider';
import { importFromSQLiteFile, importFromJSON } from '../../utils/db-backup';
@@ -99,22 +99,42 @@ async function handler(request: NextRequest, authContext: AuthResult): Promise<N
);
}
// Extract and restore .env file if it exists
// Extract and restore .env file if it exists, preserving current DB connection params
const envFiles = Object.keys(zip.files).filter(name => name.endsWith('.backup.env'));
if (envFiles.length > 0) {
const envFile = zip.file(envFiles[0]);
if (envFile) {
const envContent = await envFile.async('string');
// Backup existing .env
// Save current DB connection params before overwriting
const preserveKeys = ['DATABASE_PROVIDER', 'DATABASE_URL', 'LOG_DATABASE_URL'];
const preserved: Record<string, string> = {};
if (fs.existsSync(envPath)) {
const currentEnvContent = fs.readFileSync(envPath, 'utf-8');
const currentVars = parseEnvFile(currentEnvContent);
for (const key of preserveKeys) {
if (currentVars[key]) preserved[key] = currentVars[key];
}
// Backup existing .env
const envBackupPath = `${envPath}.backup-${dateStr}`;
await fs.promises.copyFile(envPath, envBackupPath);
console.log('✓ Existing .env backed up');
}
// Write the backup's .env
await fs.promises.writeFile(envPath, envContent);
console.log('✓ .env file restored successfully');
// Re-apply preserved DB connection params
if (Object.keys(preserved).length > 0) {
let restoredContent = await fs.promises.readFile(envPath, 'utf-8');
for (const [key, value] of Object.entries(preserved)) {
restoredContent = replaceEnvVar(restoredContent, key, value);
}
await fs.promises.writeFile(envPath, restoredContent);
console.log('✓ Database connection parameters preserved');
}
}
}
+25 -4
View File
@@ -4,7 +4,7 @@ import path from 'path';
import JSZip from 'jszip';
import prisma from '../db';
import { withSysAdminAuth, ApiResponse } from '../utils/auth';
import { reloadEnvFile, ensureEnvDefaults } from '../utils/env-reload';
import { reloadEnvFile, ensureEnvDefaults, parseEnvFile, replaceEnvVar } from '../utils/env-reload';
import { isSQLite } from '../utils/db-provider';
import { importFromSQLiteFile, importFromJSON, exportToJSON } from '../utils/db-backup';
@@ -143,20 +143,41 @@ async function postHandler(req: NextRequest): Promise<NextResponse<ApiResponse<a
);
}
// Extract and restore .env file if it exists
// Extract and restore .env file if it exists, preserving current DB connection params
const envFiles = Object.keys(zip.files).filter(name => name.endsWith('.backup.env'));
if (envFiles.length > 0) {
const envFile = zip.file(envFiles[0]);
if (envFile) {
const envContent = await envFile.async('string');
// Backup existing .env
if (await fs.promises.access(envPath).then(() => true).catch(() => false)) {
// Save current DB connection params before overwriting
const preserveKeys = ['DATABASE_PROVIDER', 'DATABASE_URL', 'LOG_DATABASE_URL'];
const preserved: Record<string, string> = {};
const currentEnvExists = await fs.promises.access(envPath).then(() => true).catch(() => false);
if (currentEnvExists) {
const currentEnvContent = await fs.promises.readFile(envPath, 'utf-8');
const currentVars = parseEnvFile(currentEnvContent);
for (const key of preserveKeys) {
if (currentVars[key]) preserved[key] = currentVars[key];
}
// Backup existing .env
const envBackupPath = `${envPath}.backup-${dateStr}`;
await fs.promises.copyFile(envPath, envBackupPath);
}
// Write the backup's .env
await fs.promises.writeFile(envPath, envContent);
// Re-apply preserved DB connection params
if (Object.keys(preserved).length > 0) {
let restoredContent = await fs.promises.readFile(envPath, 'utf-8');
for (const [key, value] of Object.entries(preserved)) {
restoredContent = replaceEnvVar(restoredContent, key, value);
}
await fs.promises.writeFile(envPath, restoredContent);
console.log('Database connection parameters preserved during .env restore');
}
}
}
+14 -6
View File
@@ -6,7 +6,7 @@ import { execSync } from 'child_process';
* Parses a .env file content and returns key-value pairs
* Handles comments, empty lines, quoted values, and unquoted values
*/
function parseEnvFile(content: string): Record<string, string> {
export function parseEnvFile(content: string): Record<string, string> {
const envVars: Record<string, string> = {};
const lines = content.split('\n');
@@ -51,12 +51,20 @@ function parseEnvFile(content: string): Record<string, string> {
}
/**
* Reloads environment variables from the .env file into process.env
* This is useful after restoring a backup that includes a new .env file
*
* @param envFilePath Optional path to the .env file. Defaults to './.env'
* @returns True if the reload was successful, false otherwise
* Replaces or appends an environment variable in .env file content.
* If the key exists, its value is updated in-place. If not, it's appended.
*/
export function replaceEnvVar(content: string, key: string, value: string): string {
const regex = new RegExp(`^${key}=.*$`, 'm');
const needsQuotes = value.includes(' ') || value.includes('://');
const newLine = needsQuotes ? `${key}="${value}"` : `${key}=${value}`;
if (regex.test(content)) {
return content.replace(regex, newLine);
}
const suffix = content.endsWith('\n') ? '' : '\n';
return content + suffix + newLine + '\n';
}
/**
* Ensures all required environment variables exist in the .env file.
* Adds missing vars with correct defaults without overwriting existing values.