Add passphrase-protected export/import for integrations, DNS providers, and settings
Nothing let you back up or migrate the app's own configuration short of copying the raw SQLite file. Adds Settings -> Backup: export decrypts every integration/DNS provider credential (normally encrypted at rest with this server's CREDENTIALS_ENCRYPTION_KEY) and re-encrypts the whole payload with a passphrase you choose (scrypt- derived key, AES-256-GCM), so the file is portable to a different instance with a different encryption key rather than being tied to this one. Import decrypts with that passphrase and merges settings onto the current ones; integrations/DNS providers are only added when no existing row shares their type+name, so re-running an import never duplicates or overwrites a working credential. Scope is configuration only — no DNS records, secrets, IPAM, servers, or audit/diagnostic log data. Verified end-to-end against two isolated scratch databases with different encryption keys (proving actual cross-instance portability, not just round-tripping through the same key): export -> encrypt -> write file -> decrypt on the other DB -> import -> re-decrypt the newly created integration/provider using the target's own key, confirming the plaintext credentials survived correctly; a wrong passphrase failed loudly (GCM auth failure) as expected; and re-running the same import a second time skipped both rows instead of duplicating them. Confirmed the real dev database's mtime was untouched throughout. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b5a4c6e2d9
commit
23eb7f0d70
6 files changed
+460
No files matched your search
@@ -175,6 +175,22 @@ export interface AppSettings {
|
||||
|
||||
export type AppSettingsPatch = { [K in keyof AppSettings]?: Partial<AppSettings[K]> };
|
||||
|
||||
export interface EncryptedExportFile {
|
||||
app: "homelab-manager-backup";
|
||||
version: 1;
|
||||
salt: string;
|
||||
iv: string;
|
||||
authTag: string;
|
||||
ciphertext: string;
|
||||
}
|
||||
|
||||
export interface ImportResult {
|
||||
integrationsCreated: number;
|
||||
integrationsSkipped: string[];
|
||||
dnsProvidersCreated: number;
|
||||
dnsProvidersSkipped: string[];
|
||||
}
|
||||
|
||||
export interface DnsProviderField {
|
||||
key: string;
|
||||
label: string;
|
||||
@@ -803,5 +819,9 @@ export const api = {
|
||||
request<{ ok: true }>("/api/settings/test-webhook", { method: "POST", body: JSON.stringify(data) }),
|
||||
purgeLogs: () =>
|
||||
request<{ diagDeleted: number; auditDeleted: number }>("/api/settings/purge-logs", { method: "POST" }),
|
||||
exportConfig: (passphrase: string) =>
|
||||
request<EncryptedExportFile>("/api/settings/export", { method: "POST", body: JSON.stringify({ passphrase }) }),
|
||||
importConfig: (passphrase: string, file: EncryptedExportFile) =>
|
||||
request<ImportResult>("/api/settings/import", { method: "POST", body: JSON.stringify({ passphrase, file }) }),
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user