From f06f5cc7c1a42025cbc14e7496c5b685d2838781 Mon Sep 17 00:00:00 2001 From: Daniel Woelfel Date: Wed, 12 Aug 2026 10:24:14 -0700 Subject: [PATCH 1/3] fix rules, add redirect origins and extra metadata about the backup --- client/www/app/intern/restore/content.tsx | 56 ++++++++++++++++++++++- server/src/instant/backup.clj | 15 +++++- server/src/instant/restore.clj | 13 +++++- 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/client/www/app/intern/restore/content.tsx b/client/www/app/intern/restore/content.tsx index 0714fde257..78559f4265 100644 --- a/client/www/app/intern/restore/content.tsx +++ b/client/www/app/intern/restore/content.tsx @@ -36,6 +36,43 @@ type RestoreJob = { const TERMINAL = new Set(['completed', 'errored', 'cancelled']); +// Reads config.json (the first entry) out of a backup zip in the browser. +// Returns the parsed config, or null if the zip can't be read/parsed (a bad zip +// is left for the server to reject on upload). +async function readBackupConfig(file: File): Promise { + try { + const { ZipReader, BlobReader, TextWriter } = await import( + '@zip.js/zip.js' + ); + const reader = new ZipReader(new BlobReader(file)); + try { + const entries = await reader.getEntries(); + const entry = entries.find((e) => e.filename === 'config.json'); + if (!entry || entry.directory) return null; + return JSON.parse(await entry.getData(new TextWriter())); + } finally { + await reader.close(); + } + } catch { + return null; + } +} + +// Backups written before we fixed a bug that dropped permission rules from the +// config lack the `appId` key (added by the same change that fixed the bug), so +// its absence means the rules may not have been captured. +function missingRulesWarning(config: any): string | null { + if (config && config.appId == null) { + return ( + 'This backup may be missing your permission rules. After the restore ' + + 'finishes, open the Permissions tab on the new app and re-add your rules ' + + 'if they’re missing.\n\n' + + 'The bug was fixed in backups created after August 12, 2026.' + ); + } + return null; +} + // A live restore bumps `updated_at` every second (see report-progress! on the // server). If a non-terminal job hasn't updated in this long, the machine // running it probably died/restarted -- the row will never finalize on its own, @@ -62,6 +99,16 @@ function RestoreDialog({ const [title, setTitle] = useState(''); const [uploading, setUploading] = useState(false); const [errorMsg, setErrorMsg] = useState(null); + const [rulesWarning, setRulesWarning] = useState(null); + + async function onFileChange(next: File | null) { + setFile(next); + setRulesWarning(null); + if (next) { + const config = await readBackupConfig(next); + setRulesWarning(missingRulesWarning(config)); + } + } async function onSubmit(e: React.FormEvent) { e.preventDefault(); @@ -94,6 +141,7 @@ function RestoreDialog({ } successToast('Restore started'); setFile(null); + setRulesWarning(null); setAppId(''); setTitle(''); dialog.onClose(); @@ -147,12 +195,18 @@ function RestoreDialog({ setFile(e.target.files?.[0] ?? null)} + onChange={(e) => onFileChange(e.target.files?.[0] ?? null)} className="hidden" /> + {rulesWarning && ( +
+ {rulesWarning} +
+ )} + From 6ec72a38a9996110e322d3f49770d00882658aaa Mon Sep 17 00:00:00 2001 From: Daniel Woelfel Date: Wed, 12 Aug 2026 11:38:08 -0700 Subject: [PATCH 3/3] webhooks were also broken --- server/src/instant/backup.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/instant/backup.clj b/server/src/instant/backup.clj index dadac2a0ec..c4c171dd0b 100644 --- a/server/src/instant/backup.clj +++ b/server/src/instant/backup.clj @@ -321,7 +321,7 @@ (rule-model/get-by-app-id conn {:app-id app-id})) (defn get-webhooks [conn app-id] - (webhook-model/get-all-by-app-id conn app-id)) + (webhook-model/get-all-by-app-id conn {:app-id app-id})) (defn get-redirect-origins [conn app-id] (redirect-origin-model/get-all-for-app conn {:app-id app-id}))