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.
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-Afterheaders 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.jsonsummary - 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.
--watchmakes 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.comonce to store your password in the keyring. - Repeated 2FA prompts — your session expired; run
ifetchinteractively once to re-trust the session. - 503 / rate limited — iFetch backs off automatically; lower
--max-workersif 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
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ifetch-1.0.0.tar.gz.
File metadata
- Download URL: ifetch-1.0.0.tar.gz
- Upload date:
- Size: 65.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b5e6aff82c28b8e4497922c43952447ecdf0992a37f8fef8953fdff6c8644bf0
|
|
| MD5 |
0ee93f97debfbc36c8356bdc5b326b4b
|
|
| BLAKE2b-256 |
2c1a8a57e70cc226160b6cf86301e37754c89161df6f01ba09624d710106eb1e
|
File details
Details for the file ifetch-1.0.0-py3-none-any.whl.
File metadata
- Download URL: ifetch-1.0.0-py3-none-any.whl
- Upload date:
- Size: 46.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ebd2d91b83e6af13281510e90bb29a893eacd3102544b817c5802f7baf357627
|
|
| MD5 |
4e64b7ebec00789c00e29abbd3b07867
|
|
| BLAKE2b-256 |
721a91a5042f33e4f048955f6b1a61b8450e745eb62cb4536f6e4657e97c7da6
|