Goal and prerequisites
Section titled “Goal and prerequisites”Preserve device backups, application settings, pairing records, and keys; recover them in a separate directory, verify the result, and then decide whether it should replace the original deployment. This page recovers the application on your NAS. Writing data back to an iPhone is a separate Experimental operation.
These commands assume Linux Docker Compose, the repository’s compose.yaml, and its ./data layout. You need administrative access, Python 3, and GNU tar with ACL/xattr support. For custom mounts, external password files, or NAS shared folders, first inventory the actual paths. Do not apply the default directory list unchanged. NAS backup tools may preserve shared-folder ACLs, but must still save all the data and configuration listed below.
This procedure is based on the current configuration format and application behavior. A complete cross-host recovery following this page has not yet been validated. Rehearse on a separate copy and preserve the original deployment.
1. Save a consistent deployment copy
Section titled “1. Save a consistent deployment copy”-
In the Web UI, turn off Automatic backup for each device, save, and record the original schedules. Wait for existing backup, inspection, encryption, unpack, or restore operations to finish. Disabling a schedule does not cancel an active job. Check the UI and logs;
/healthzalone does not establish that the instance is idle. -
In the deployment directory, record the actual image, version, mounts, and Compose project name. These commands expose only the image reference, not the complete environment. Do not upload full
docker inspectordocker compose configoutput; it may contain the master key.Terminal window docker compose psdocker inspect --format '{{.Config.Image}} {{.Image}}' iosbackupcurl --user iosbackup http://127.0.0.1:9000/api/versioncurlprompts for the administrator password. Replaceiosbackupif the container has a different name, and adjust the port if necessary. Save the exact image reference/digest, version, and all Compose files in a protected record. -
Prepare separate protected storage with sufficient capacity. Replace
/mnt/protected-backupwith an already mounted, encrypted, trusted backup destination. Do not put the archive inside thedatadirectory being archived. Atar.gzfile is compressed, not encrypted.Terminal window set -euumask 077archive_dir=$(mktemp -d /mnt/protected-backup/iosbackup-state.XXXXXX)docker compose stopdocker compose ps -aConfirm that the service has stopped. Investigate any remaining related container or operation before continuing. No other instance may use these directories during migration.
-
In the same terminal and deployment directory, create the archive. This example includes Compose and all three complete data directories, adding common Compose override files only when present; do not create absent files. Add custom
-ffiles, external password/key files, and additional mounts to the inventory and protected copy separately.Terminal window set -- compose.yaml data/backups data/configs data/lockdownfor iosbk_extra in compose.override.yaml compose.override.yml docker-compose.override.yaml docker-compose.override.yml; doif [ -f "$iosbk_extra" ]; then set -- "$@" "$iosbk_extra"; fidonesudo tar --acls --xattrs --numeric-owner -czpf "$archive_dir/state.tar.gz" "$@"sudo chmod 600 "$archive_dir/state.tar.gz"sudo tar -tzf "$archive_dir/state.tar.gz" > "$archive_dir/contents.txt"sudo sha256sum "$archive_dir/state.tar.gz" > "$archive_dir/state.sha256"If any command fails, retain the error and stop the migration. Check the complete
data/configsinventory: default administrator password and digest,secret_key, CSRF state, backup settings, andsecrets.encwhen secrets have been saved, plus pairing records and actual backup files. If you use a custom password or external master key, include the actual credential files in the protected copy. File paths in the inventory are private too. -
Save the image/version record on the protected storage and confirm the archive is readable. For a backup-only operation, restart the original service with
docker compose start; verify its state before restoring the recorded schedules. For an imminent migration, leave it stopped. Keep the original directory and the only known-good copy.
Expected result: a consistent stopped-service copy, the exact image version or digest, and the original master key. A readable archive and checksum establish file readability, not a recovered instance or recoverable iPhone.
2. Recover into an empty directory and verify in isolation
Section titled “2. Recover into an empty directory and verify in isolation”-
Prepare a new empty directory on the destination host. Do not overwrite a live deployment. First compare the archive’s checksum and confirm that it contains only the expected relative paths you saved. Do not extract untrusted archives. Replace the example paths below.
Terminal window (set -euumask 077sudo mkdir /srv/iosbackup-recoverysudo chown "$(id -u):$(id -g)" /srv/iosbackup-recoverychmod 700 /srv/iosbackup-recoverycd /srv/iosbackup-recoverysudo tar --acls --xattrs --numeric-owner -xzpf /mnt/protected-backup/SELECTED/state.tar.gz)Continue only if the block above succeeds. Stop if the directory already exists or extraction fails. After success, enter the new directory in this terminal for all following commands:
Terminal window cd /srv/iosbackup-recoveryExtraction preserves numeric ownership, modes, and supported ACLs/xattrs. Check UID/GID meaning and filesystem ACLs on the destination; do not fix differences with recursive
chmod 777. Retain configuration-directory mode0700and mode0600for passwords, master keys, digests, and other secret files. Verify administrator-only access to backup and pairing directories separately instead of assuming the app tightens their permissions. -
Do not start the recovered production Compose configuration yet. Confirm that default
secret_keyand password/digest files were restored, or that original external credential sources and values are unchanged, and that mounts point to the new copies. Keep using the original login password. Migration is not initialization: recover missing keys from original copies instead of letting the app generate a new password or master key. -
Disable automatic backups and restore permissions in the recovered configuration, since an older archive may contain enabled schedules. The script below changes only the copy in the current directory: it preserves the original file, accepts only the current
schema_version=1format, preserves other fields, and rejects unknown formats. The original instance must be stopped, and this recovered instance must not have started.Terminal window sudo python3 - <<'PY'import json, os, shutil, stat, tempfilefrom pathlib import Pathp = Path('data/configs/backup_configs.json')if not p.is_file() or p.is_symlink():raise SystemExit('No regular backup configuration: check the archive and paths before starting')data = json.loads(p.read_text())if not isinstance(data, dict):raise SystemExit('Unknown configuration format')if data.get('schema_version') != 1 or not isinstance(data.get('configs'), dict):raise SystemExit('Unknown schema: stop and consult the matching release documentation')devices = data['configs']if any(not isinstance(v, dict) for v in devices.values()):raise SystemExit('Unknown device configuration format')saved = p.with_name(p.name + '.before-recovery')if saved.exists():raise SystemExit('An original copy already exists: inspect it; do not overwrite it')shutil.copy2(p, saved)for cfg in devices.values():cfg['auto_backup_enabled'] = Falsecfg['restore_enabled'] = Falseoriginal = p.stat()fd, temporary = tempfile.mkstemp(dir=p.parent, prefix='.recovery-')try:with os.fdopen(fd, 'w') as f:os.fchmod(f.fileno(), stat.S_IMODE(original.st_mode))os.fchown(f.fileno(), original.st_uid, original.st_gid)json.dump(data, f, ensure_ascii=False, indent=2)f.write('\n')f.flush()os.fsync(f.fileno())os.replace(temporary, p)finally:if os.path.exists(temporary):os.unlink(temporary)print('Disabled automatic backup and restore in the recovery copy; original file preserved')PYThe script preserves file ownership and mode. Restore any special file-level ACLs/xattrs with the relevant management tools if your deployment relies on them. A missing configuration can mean that no devices were configured, or that the archive is incomplete; establish the cause instead of creating a guessed configuration.
-
Create a separate
compose.recovery.yamland use it alone, without merging the production Compose file . This validation instance uses an internal bridge network, no USB/udev mounts, no privileged mode, and a Web port bound only to host loopback. It is not a real-device backup deployment.services:iosbackup:image: "REPLACE_WITH_RECORDED_IMAGE"restart: "no"tmpfs:- /tmp:size=64m,mode=1777- /var/run:size=16m,mode=0755ports:- "127.0.0.1:19000:9000"environment:PORT: "9000"LOG_LEVEL: "INFO"IOSBK_LISTEN_ADDR: "0.0.0.0"IOSBK_AUTH_ENABLED: "true"IOSBK_CONFIGS_DIR: "/configs"IOSBK_BACKUPS_DIR: "/backups"IOSBK_ENABLE_EXPERIMENTAL_OPERATIONS: "false"volumes:- ./data/backups:/backups- ./data/configs:/configs- ./data/lockdown:/var/lib/lockdownnetworks: [recovery]networks:recovery:internal: trueConnect only this internal network; do not mount host sockets/devices or add other networks. Confirm that the host has no custom routes connecting this network to the phone’s network. If the original configuration used an allowed path outside
/backups, map that same container path to a separate copy first. Never mount the original production directory. This configuration uses the default password and master key restored under/configs. If the original instance used a custom password or master key, add its original settings using advanced credential configuration, keeping the password and key values unchanged. Mount recovered copies of external files read-only at their original container paths; do not start without them. Do not supply both a key environment value and a key file. -
Replace
image: "REPLACE_WITH_RECORDED_IMAGE"incompose.recovery.yamlwith the recorded original image’s complete tag or digest. Do not uselatestor create another configuration file. Confirm that the image can be obtained and original credential sources are restored before starting.Terminal window docker compose -p iosbackup-recovery -f compose.recovery.yaml pulldocker compose -p iosbackup-recovery -f compose.recovery.yaml up -ddocker compose -p iosbackup-recovery -f compose.recovery.yaml logs --tail=100curl -fsS --retry 12 --retry-connrefused --retry-delay 5 --retry-max-time 90 http://127.0.0.1:19000/healthzcurl --user iosbackup http://127.0.0.1:19000/api/versionOpen
http://127.0.0.1:19000/in a browser on the destination host. For a remote host, use SSH local forwarding before opening that address:ssh -L 19000:127.0.0.1:19000 <administrator>@<destination-host>. Do not expose the validation port to the Internet. -
Check that the original password works, no new credentials were unexpectedly generated, the version matches, device settings and removed-device status remain, schedules are off, and logs contain no configuration/secret decryption errors. Offline devices and discovery failures are expected in this environment, not proof of lost pairing. Blank secret fields protect saved values and do not prove they are missing. This stage does not test real notifications or establish that encrypted manifests can be decrypted.
Expected result: the recovered copy can load application settings and accept login in isolation. The archive and original production directory remain intact. Record what passed and what remains unverified.
3. Switch to the production instance and verify a device
Section titled “3. Switch to the production instance and verify a device”-
Confirm that the original service on the old host is stopped and will not be restarted by another Compose project, system task, or restart policy. Keep its directory. Do not enable the same device’s schedules in both instances.
-
Stop/remove the isolated container and its internal network. This command does not delete bind-mounted data directories.
Terminal window docker compose -p iosbackup-recovery -f compose.recovery.yaml down -
Review the recovered production
compose.yaml: image, port, project name, USB/udev, three separate-copy mounts, and original key must be correct, with Experimental operations off. Connect only one non-critical device at first and start production using the installation guide. -
Check login/version, USB presence/pairing, existing backup information, and the file list. Do not clear lockdown on a pairing failure; inspect mounts, Trust, and device state first. For
MBErrorDomain/207, follow the encryption guide to check the original password/key. Generating a new key is not a fix. -
With schedules still off, complete one manual USB backup. Confirm that the page shows backup completed, check the last-success time, and record which backup information and lists you can read. Restore schedules one device at a time and observe a real run, then test notifications. Notification tests send real messages.
On failure, stop the new instance and preserve its logs/directory before deciding to resume the original. Confirm the new instance is stopped before switching back. Do not copy the recovered tree over the original instance or overwrite data added after migration with an older archive.
Next: record the recovery exercise’s image, date, checks, and remaining gaps; maintain versions with upgrade and rollback. Successful instance recovery is still not a completed iPhone restore exercise.