Skip to content

Instantly share code, notes, and snippets.

@foxt
Last active September 29, 2025 13:00
Show Gist options
  • Select an option

  • Save foxt/247f9480103696e1fadc88fa1021f1f1 to your computer and use it in GitHub Desktop.

Select an option

Save foxt/247f9480103696e1fadc88fa1021f1f1 to your computer and use it in GitHub Desktop.
Node.js script to extract files from an iOS backup created by iTunes or Finder.
// / \ WARNING: This script *moves* files from the backup directory to the output directory.
// / ! \ Meaning, if you after this script is run, the backup directory will not be able to be restored.
// /_____\ If you want to keep the backup directory intact, make a copy of it first.
// Extract files from an iOS backup created by iTunes or Finder.
// Usage: node extract.js <input_backup_directory> <output_directory>
import { existsSync, mkdirSync, renameSync } from 'node:fs';
import path, { join } from 'node:path';
import { DatabaseSync } from 'node:sqlite';
// const bplist = require('bplist-parser');
if (!process.argv[2] || !process.argv[3]) {
console.error('Usage: node extract.js <input_backup_directory> <output_directory>');
process.exit(1);
}
const indir = path.resolve(process.argv[2]);
console.log('Input directory:', indir);
if (!existsSync(path.join(indir, 'Manifest.db'))) {
console.error('Manifest.db not found in', indir);
process.exit(1);
}
const outdir = path.resolve(process.argv[3]);
console.log('Output directory:', outdir);
const db = new DatabaseSync(join(indir,'Manifest.db'), { readOnly: true });
const query = db.prepare('SELECT * FROM Files');
const rows = query.all();
function swapDomain(domain) {
// e.g., 'AppDomain-com.example.app' -> 'com.example.app-AppDomain' (better experience viewing in file browser when sorted alphabetically)
let parts = domain.split('-');
parts.push(parts.shift());
return parts.join('-');
}
for (let file of rows) {
let fileId = file.domain + '-' + file.relativePath;
let swappedDomain = swapDomain(file.domain)
let tgtPath = path.join(outdir, swappedDomain, file.relativePath);
if (file.flags == 4) {
console.warn('Skipping symlink', fileId);
} else if (file.flags == 2) {
mkdirSync(tgtPath, { recursive: true });
} else if (file.flags == 1) {
let srcPath = path.join(indir, file.fileID.substring(0,2), file.fileID);
if (!existsSync(srcPath)) {
console.warn('Source file not found for', fileId, 'at', srcPath);
continue;
}
mkdirSync(path.dirname(tgtPath), { recursive: true });
renameSync(srcPath, tgtPath);
} else {
throw new Error('Unknown file flag ' + file.flags + ' for ' + fileId);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment