Skip to content

Data Storage

Apple Juicer keeps state in three places, and they fail differently. The filesystem holds the backups and is the only thing you must back up. PostgreSQL holds everything the worker derives, which you can rebuild by re-indexing. Redis holds the job queue, which you can lose without consequence.

DISK Filesystem Backups · read-only mount Temp sandbox · per request Decrypted store · plaintext Authoritative — back this up DATA PostgreSQL backups · registry + status artifact tables · 10 types artifact_search_index Rebuildable — just re-index QUEUE Redis RQ default queue job metadata + heartbeats no tokens, no payloads Ephemeral — safe to lose

The backups are the source of truth and nothing copies them wholesale. Two paths matter:

  • Backup data mounts read-only into the backend and worker (default ./data/data/ios_backups). Apple Juicer reads it and never writes to it.
  • Temporary sandbox under backup_paths.temp_path (default /tmp/apple_juicer) holds files extracted for a single download or parse. A Starlette BackgroundTask deletes each one after the response finishes.

Decrypted artifact databases are written to backup_paths.decrypted_path so the worker can index them and the UI can serve attachments without re-unlocking every time. That directory is plaintext — see Security.

Postgres is everything the worker derives from a backup:

  • backups — the canonical list of discovered backups with device metadata, size, encryption flag, and the status lifecycle (DISCOVEREDDECRYPTEDINDEXINGINDEXED).
  • Artifact tablesphoto_assets, whatsapp_*, messages, notes, calendar_*, contacts, and the rest, one set per artifact type. Every row links back to backups.id with ON DELETE CASCADE, so deleting a backup cleans up everything it produced.
  • artifact_search_index — one denormalized text row per item, which is what global search queries.

The async SQLAlchemy engine reads its DSN from APPLE_JUICER_POSTGRES__DSN, defaulting to sqlite+aiosqlite:///./temp_data/apple_juicer.db so the tool runs without Postgres for tests and quick local trials. The schema is created and kept current by Alembic, which runs upgrade head automatically on backend startup.

Redis does two jobs and stores nothing sensitive:

  • The queue. core.queue.get_queue() returns a cached connection to the default queue with a 10-minute job timeout.
  • Job bookkeeping. RQ keeps its own job metadata, heartbeats, and results here.

Session tokens and decrypted data never touch Redis; they live in memory in the backend and worker processes. Point Redis wherever you like with APPLE_JUICER_REDIS__URL.

The three layers map cleanly onto a recovery plan:

  1. Filesystem — snapshot or rsync the mounted backup directory. This is the only irreplaceable copy.
  2. PostgreSQL — use pg_dump or pg_basebackup if you want to keep the indexes. If you don’t, you can drop the database and re-index from the backups.
  3. Redis — skip it. A lost queue just means re-triggering any indexing run that was in flight.

Because decrypted payloads are never the system of record, recovery comes down to one thing: point the tool back at the backup directory and, if you didn’t keep Postgres, re-index.