Skip to content

roshanlam/iFetch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

45 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

iFetch

Bulk-download your iCloud Drive from the command line β€” with delta sync, resume, version history, and a pipeline that mirrors iCloud to your NAS and Google Drive.

CI License: MIT Python 3.10+

Apple gives you two ways to get your data out of iCloud Drive: drag files around in Finder, or wait days for a privacy export. iFetch gives you a third: a scriptable CLI that downloads exactly what you want, only re-fetches what changed, survives interruptions, and keeps a local version history so an accidental overwrite in the cloud never costs you a file.

Why iFetch?

iFetch icloudpd rclone Apple privacy export
iCloud Drive files/folders Yes No β€” Photos only No iCloud Drive backend Yes
Delta sync (only changed data) Yes, chunk-level β€” β€” No, full dump every time
Resume interrupted transfers Yes β€” β€” No
Scriptable / schedulable Yes Yes Yes No β€” manual web request, takes days
Local version history Yes No No No
Shared-with-you items Yes No β€” No
Continue to Google Drive / NAS Yes (ifetch-mirror) No Partially (no iCloud hop) No

If you want your photos, use icloudpd β€” it is excellent at that. If you want your iCloud Drive, that is what iFetch is for.

60-second quickstart

# 1. Install
pip install "ifetch[gdrive]"   # core + Google Drive export (or: pip install ifetch)

# 2. Store your iCloud password in the system keyring (one time)
icloud --username you@example.com

# 3. Download a folder (you'll be prompted for a 2FA code on first run)
ifetch Documents ~/icloud-backup

That's it. Run the same command tomorrow and iFetch only fetches the chunks that changed.

To work from source instead:

git clone https://github.com/roshanlam/iFetch.git
cd iFetch
pip install -e ".[gdrive]"

Note on pyicloud: iFetch is built on pyicloud, which is actively maintained again on PyPI (v2.5.0+ adds the shared-drive support iFetch relies on). It is installed automatically.

Features

  • Secure authentication β€” password lives in your OS keyring (via pyicloud), full 2FA/2SA support, trusted sessions so you are not re-prompted every run
  • Chunk-level delta sync β€” unchanged files are skipped; changed files only re-download the byte ranges that differ
  • Resume-capable β€” checkpointed downloads pick up where they left off after an interruption
  • Parallel downloads β€” configurable worker pool (--max-workers)
  • Retries with exponential backoff β€” transient 5xx/timeout errors are retried, Retry-After headers respected
  • Version history β€” before overwriting a changed file, the previous copy is archived to a local .versions/ directory (see Version history)
  • Shared items β€” list and download files/folders shared with you (--list-shared)
  • Profiles β€” named include/exclude pattern sets for repeatable sync jobs
  • Plugins β€” hook into auth, listing, and download lifecycle events (docs/plugins.md)
  • Structured JSON logging and a per-run download_report.json summary
  • Google Drive export (ifetch-export) and an iCloud β†’ NAS β†’ Google Drive mirror (ifetch-mirror)

CLI reference

iFetch installs three commands:

Command Purpose
ifetch Download/list iCloud Drive content locally
ifetch-export Upload local folders to Google Drive (delta-aware)
ifetch-mirror One-way pipeline: iCloud β†’ local folder/NAS β†’ Google Drive

ifetch β€” iCloud Drive downloader

ifetch <icloud_path> [local_path] [options]
Argument / flag Description Default
icloud_path Remote iCloud Drive path, e.g. Documents/MyFolder. Required unless --list-shared β€”
local_path Local destination directory current directory
--email iCloud account email (or set ICLOUD_EMAIL) env var / error
--max-workers N Concurrent download threads 4
--max-retries N Retry attempts for failed chunks (exponential backoff) 3
--chunk-size BYTES Chunk size for differential downloads 1048576 (1 MiB)
--log-file PATH Write structured JSON logs to a file console only
--list List directory contents instead of downloading off
--list-shared List top-level items shared with you (no path needed) off
--profile NAME Apply include/exclude patterns from a profile no filter
--profile-file PATH Custom profile JSON path ~/.ifetch_profiles.json

Environment variables: ICLOUD_EMAIL (account email), ICLOUD_CHINA=true (use iCloud China mainland endpoints), IFETCH_PLUGIN_PATH (extra plugin directory).

Examples:

ifetch Documents --list                          # list a folder
ifetch --list-shared --email you@example.com     # list items shared with you
ifetch Documents/Photos ~/Downloads/icloud-photos
ifetch Documents/Programming ~/Work/Code \
  --email you@example.com --max-workers 8 --max-retries 5 --log-file download.log

After each download run, a summary is printed and a detailed download_report.json is written into the destination directory.

ifetch-export β€” local folders β†’ Google Drive

ifetch-export [options]

Uploads local folders (by default ~/Documents, ~/Downloads, ~/Desktop, ~/Pictures, ~/LocalDoc β€” whichever exist) to a folder in your Google Drive, skipping anything that hasn't changed since the last run (MD5 + a local upload index). Asks for confirmation before uploading. Requires Google OAuth credentials β€” see docs/mirror.md for the setup walkthrough.

Flag Description Default
--folders PATH... Folders to export Documents, Downloads, Desktop, Pictures, LocalDoc
--gdrive-folder NAME Destination folder name in Google Drive MacOS Data
--credentials PATH Google OAuth2 client credentials JSON credentials.json
--token PATH Where the OAuth token is cached .gdrive_token.pickle
--force Re-upload everything, even unchanged files off
--include PAT... Only upload files matching patterns (e.g. *.pdf *.docx) all
--exclude PAT... Skip files matching patterns (e.g. *.tmp) none
--chunk-size MB Resumable-upload chunk size in MB 10
--upload-workers N Parallel upload workers (max recommended: 8) 4
--ignore-file PATH .gitignore-style ignore file .gdriveexportignore
--no-ignore Ignore file disabled β€” upload everything off
--index-file PATH Upload-tracking index file .gdrive_upload_index.json
--no-index Disable the index (slower; rescans everything) off
--rebuild-index Clear the index and exit off
--show-index-stats Print index statistics and exit off
--list-defaults Print the default folder list and exit off

ifetch-mirror β€” iCloud β†’ NAS β†’ Google Drive

ifetch-mirror <icloud_path> <local_path> --gdrive-folder NAME [options]
Argument / flag Description
icloud_path Source path in iCloud Drive
local_path Local staging folder β€” typically a NAS mount
--gdrive-folder NAME Destination folder in Google Drive
--watch SECONDS Keep running, repeating the pipeline on an interval
--dry-run Show what would be transferred without transferring
--email iCloud account email (or ICLOUD_EMAIL)

See the Mirror section below and the full guide in docs/mirror.md.

Mirror: iCloud β†’ NAS β†’ Google Drive

A frequently requested workflow: keep a copy of your iCloud Drive on a NAS and in Google Drive, without ever re-transferring unchanged data. ifetch-mirror chains both hops into one command, delta-aware at each stage:

# One shot: pull iCloud Documents to the NAS, then push to Google Drive
ifetch-mirror Documents /Volumes/nas/icloud-mirror --gdrive-folder "iCloud Mirror"

# Always-on: re-run the pipeline every 15 minutes
ifetch-mirror Documents /Volumes/nas/icloud-mirror \
  --gdrive-folder "iCloud Mirror" --watch 900

# Preview what would happen
ifetch-mirror Documents /Volumes/nas/icloud-mirror \
  --gdrive-folder "iCloud Mirror" --dry-run
  • Hop 1 (iCloud β†’ local) uses iFetch's chunk-level delta sync β€” only changed byte ranges cross the wire.
  • Hop 2 (local β†’ Google Drive) uses the export engine's MD5 + upload index β€” only changed files are re-uploaded.
  • --watch makes it a lightweight always-on daemon; alternatively schedule single runs with launchd/cron/systemd (docs/scheduling.md).

The pipeline is one-way (iCloud is the source of truth). Two-way sync is on the roadmap.

Profiles

Profiles are named include/exclude pattern sets stored in ~/.ifetch_profiles.json:

{
  "pdf_backup": {
    "include": ["Documents/**/*.pdf"],
    "exclude": ["Documents/Private/*"]
  }
}
ifetch Documents ~/PDFs --profile pdf_backup
ifetch Documents ~/PDFs --profile pdf_backup --profile-file ./my_profiles.json

Patterns are glob-style (fnmatch) and are matched against the remote path. An empty include list means "everything".

Plugins

Drop a Python file into the plugins/ directory next to the ifetch package (or point IFETCH_PLUGIN_PATH at any directory) and subclass BasePlugin:

from ifetch.plugin import BasePlugin

class Notify(BasePlugin):
    def after_download(self, remote_item, local_path, success, **kwargs):
        if success:
            print(f"Downloaded {remote_item.name} -> {local_path}")

Available hooks: on_authenticated, on_list_contents, before_download, after_download, and a generic on_event (fires for download_progress and download_session_completed, among others). Plugins are auto-discovered at startup, and a crashing plugin never takes down a transfer.

Full authoring guide with two complete example plugins (webhook/ntfy notifications, checksum manifest verifier): docs/plugins.md.

Version history & rollback

Every time iFetch is about to overwrite a file that changed in iCloud, it first archives your existing local copy:

~/icloud-backup/
β”œβ”€β”€ report.pdf                     # current version
β”œβ”€β”€ .ifetch_versions.json          # version metadata (checksums, timestamps)
└── .versions/
    └── report.pdf.v1_20260718T093012   # previous version, timestamped

Nothing is ever silently destroyed by a sync. To roll back, copy the archived version over the current file:

cp ~/icloud-backup/.versions/report.pdf.v1_20260718T093012 ~/icloud-backup/report.pdf

A ifetch restore convenience command is on the roadmap; today rollback is a manual copy from .versions/.

Scheduling

Run iFetch on a schedule with launchd (macOS), cron (Linux/NAS), or systemd timers β€” worked examples for all three, plus notes on keyring access in non-interactive sessions, are in docs/scheduling.md. For an always-on process instead of a scheduler, use ifetch-mirror --watch.

Troubleshooting

Common issues β€” the 2FA flow, expired sessions, per-OS keyring problems, rate limiting/503s, the Advanced Data Protection caveat, and shared-folder quirks β€” are covered in docs/troubleshooting.md. Quick hits:

  • "No stored password found" β€” run icloud --username you@example.com once to store your password in the keyring.
  • Repeated 2FA prompts β€” your session expired; run ifetch interactively once to re-trust the session.
  • 503 / rate limited β€” iFetch backs off automatically; lower --max-workers if it persists.
  • Advanced Data Protection β€” with ADP enabled, Apple blocks web/API access to Drive data unless you enable "Access iCloud Data on the Web" in your ADP settings.

Contributing

Contributions are welcome β€” bug reports, docs, and PRs alike. Please open an issue to discuss larger changes first, and make sure pytest passes.

License

MIT β€” see LICENSE.

Acknowledgments

  • timlaing/pyicloud β€” the maintained iCloud API wrapper iFetch is built on
  • tqdm β€” progress bars

About

πŸš€ Bulk download your iCloud Drive files and folders with a simple command line tool

Resources

Contributing

Stars

Watchers

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages