Add quak backup-metadata: dump all decrypted metadata to plain JSON

New command: quak backup-metadata <dir>

Dumps every piece of decrypted account metadata into a directory tree
of plain JSON files without downloading any file content. Layout:

    <dir>/
        account.json                    { email, userID }
        collections/
            <id>-<name>/
                _collection.json        { id, name, type, pubMagicMetadata?, ... }
                <fileID>.json           { id, metadata, magicMetadata?, pubMagicMetadata? }

Also adds collection-level magic metadata decryption (magicMetadata,
pubMagicMetadata, sharedMagicMetadata) to decryptCollection, which was
previously only done for files. The server sends these for visibility
settings, sort order, cover photo selection, etc.

6 new tests covering: account.json, per-collection dirs with
_collection.json, collection pubMagicMetadata decryption, per-file
JSON with all three metadata layers, graceful handling of files with
no magic metadata, and incremental re-run safety. 116 total.
This commit is contained in:
2026-06-09 12:41:34 -04:00
parent 6729e8bdc3
commit f3958e911d
5 changed files with 567 additions and 0 deletions

View File

@@ -9,6 +9,7 @@ import envPaths from "env-paths";
import { Client, type ClientSnapshot } from "../src/client.js";
import { init } from "../src/crypto/index.js";
import { runBackup } from "../src/backup.js";
import { runMetadataBackup } from "../src/metadata-backup.js";
import {
listMissingThumbnails,
fixMissingThumbnails,
@@ -333,6 +334,18 @@ program
},
);
program
.command("backup-metadata")
.description(
"Dump all decrypted account metadata (no file content) to a directory",
)
.argument("<dir>", "Output directory")
.action(async (dir: string) => {
await init();
const client = requireSession();
await runMetadataBackup(client, dir, (msg) => stderr.write(msg + "\n"));
});
program
.command("backup")
.description(