forked from boos/BoosAPI
fix: exclude runtime db config from backup and migration (#260)
This commit is contained in:
@@ -290,6 +290,52 @@ describe('backupService', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not export runtime database config in preferences backups', async () => {
|
||||
await db.insert(schema.settings).values([
|
||||
{ key: 'db_type', value: JSON.stringify('postgres') },
|
||||
{ key: 'db_url', value: JSON.stringify('postgres://metapi:secret@db.example.com:5432/metapi') },
|
||||
{ key: 'db_ssl', value: JSON.stringify(true) },
|
||||
{ key: 'routing_fallback_unit_cost', value: JSON.stringify(0.25) },
|
||||
]).run();
|
||||
|
||||
const exported = await backupService.exportBackup('preferences') as any;
|
||||
const exportedSettingKeys = exported.preferences.settings.map((row: { key: string }) => row.key);
|
||||
|
||||
expect(exportedSettingKeys).toContain('routing_fallback_unit_cost');
|
||||
expect(exportedSettingKeys).not.toContain('db_type');
|
||||
expect(exportedSettingKeys).not.toContain('db_url');
|
||||
expect(exportedSettingKeys).not.toContain('db_ssl');
|
||||
});
|
||||
|
||||
it('ignores imported runtime database config settings', async () => {
|
||||
const result = await backupService.importBackup({
|
||||
version: '2.1',
|
||||
timestamp: Date.now(),
|
||||
type: 'preferences',
|
||||
preferences: {
|
||||
settings: [
|
||||
{ key: 'db_type', value: 'postgres' },
|
||||
{ key: 'db_url', value: 'postgres://metapi:secret@db.example.com:5432/metapi' },
|
||||
{ key: 'db_ssl', value: true },
|
||||
{ key: 'routing_fallback_unit_cost', value: 0.25 },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.sections.preferences).toBe(true);
|
||||
expect(result.appliedSettings).toEqual([
|
||||
{ key: 'routing_fallback_unit_cost', value: 0.25 },
|
||||
]);
|
||||
|
||||
const settingsRows = await db.select().from(schema.settings).all();
|
||||
const savedKeys = settingsRows.map((row) => row.key);
|
||||
|
||||
expect(savedKeys).toContain('routing_fallback_unit_cost');
|
||||
expect(savedKeys).not.toContain('db_type');
|
||||
expect(savedKeys).not.toContain('db_url');
|
||||
expect(savedKeys).not.toContain('db_ssl');
|
||||
});
|
||||
|
||||
it('preserves local logs and runtime stats when importing account backups', async () => {
|
||||
const exportedAt = '2026-03-20T09:00:00.000Z';
|
||||
const localRuntimeAt = '2026-03-21T10:30:00.000Z';
|
||||
|
||||
@@ -228,6 +228,10 @@ interface BackupImportResult {
|
||||
const EXCLUDED_SETTING_KEYS = new Set<string>([
|
||||
// Keep current admin login credential unchanged to avoid accidental lock-out.
|
||||
'auth_token',
|
||||
// Runtime database selection is environment-bound and must not be propagated by backups.
|
||||
'db_type',
|
||||
'db_url',
|
||||
'db_ssl',
|
||||
]);
|
||||
const BACKUP_WEBDAV_CONFIG_SETTING_KEY = 'backup_webdav_config_v1';
|
||||
const BACKUP_WEBDAV_STATE_SETTING_KEY = 'backup_webdav_state_v1';
|
||||
|
||||
@@ -465,4 +465,46 @@ describe('databaseMigrationService', () => {
|
||||
vi.resetModules();
|
||||
}
|
||||
});
|
||||
|
||||
it('excludes runtime database config settings from migration statements', () => {
|
||||
const statements = __databaseMigrationServiceTestUtils.buildStatements({
|
||||
version: 'test',
|
||||
timestamp: Date.now(),
|
||||
accounts: {
|
||||
sites: [],
|
||||
siteAnnouncements: [],
|
||||
siteDisabledModels: [],
|
||||
accounts: [],
|
||||
accountTokens: [],
|
||||
checkinLogs: [],
|
||||
modelAvailability: [],
|
||||
tokenModelAvailability: [],
|
||||
tokenRoutes: [],
|
||||
routeChannels: [],
|
||||
routeGroupSources: [],
|
||||
proxyLogs: [],
|
||||
proxyVideoTasks: [],
|
||||
proxyFiles: [],
|
||||
downstreamApiKeys: [],
|
||||
events: [],
|
||||
},
|
||||
preferences: {
|
||||
settings: [
|
||||
{ key: 'db_type', value: 'sqlite' },
|
||||
{ key: 'db_url', value: '/app/data/hub.db' },
|
||||
{ key: 'db_ssl', value: false },
|
||||
{ key: 'routing_fallback_unit_cost', value: 0.25 },
|
||||
],
|
||||
},
|
||||
} as any);
|
||||
|
||||
const migratedSettingKeys = statements
|
||||
.filter((statement) => statement.table === 'settings')
|
||||
.map((statement) => statement.values[0]);
|
||||
|
||||
expect(migratedSettingKeys).toContain('routing_fallback_unit_cost');
|
||||
expect(migratedSettingKeys).not.toContain('db_type');
|
||||
expect(migratedSettingKeys).not.toContain('db_url');
|
||||
expect(migratedSettingKeys).not.toContain('db_ssl');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -85,6 +85,7 @@ interface InsertStatement {
|
||||
}
|
||||
|
||||
const DIALECTS: MigrationDialect[] = ['sqlite', 'mysql', 'postgres'];
|
||||
const RUNTIME_DATABASE_SETTING_KEYS = new Set(['db_type', 'db_url', 'db_ssl']);
|
||||
|
||||
function asString(value: unknown): string {
|
||||
return typeof value === 'string' ? value.trim() : '';
|
||||
@@ -628,6 +629,9 @@ function buildStatements(snapshot: BackupSnapshot): InsertStatement[] {
|
||||
}
|
||||
|
||||
for (const row of snapshot.preferences.settings) {
|
||||
if (RUNTIME_DATABASE_SETTING_KEYS.has(row.key)) {
|
||||
continue;
|
||||
}
|
||||
statements.push({
|
||||
table: 'settings',
|
||||
columns: ['key', 'value'],
|
||||
|
||||
Reference in New Issue
Block a user