Sign inSign up

ricariel/icloud-backup

By ricariel

•Updated 2 days ago

Image
0

468

ricariel/icloud-backup repository overview

⁠icloud-backup

One-way backup of iCloud Drive and iCloud Notes to a WebDAV server such as Nextcloud. Built to run as a Kubernetes CronJob next to icloudpd⁠, whose trusted Apple session it borrows, so there is no second two-factor login to maintain.

iCloud stays the source of truth. The tool never writes to iCloud.

⁠How it works

  1. Session. icloudpd keeps a trusted session in its cookie directory and renews it with a two-factor code about every two months. Each run copies the two session files into a private work directory under the names pyicloud⁠ expects and logs in without a password. Without a password pyicloud can only reuse the session or the trust token; a fresh login that would push a code to the Apple devices fails instead. The copy is removed at the end of the run.
  2. Drive. The whole iCloud Drive tree, including app libraries, is listed. A file is uploaded when its fingerprint (etag and size) differs from the last run. Files that disappeared from iCloud are deleted on the target.
  3. Notes. Every note is exported as a full HTML page with its images and attachments (assets/<note>/, linked relatively) into a folder named after its Notes folder. The file name carries the first eight characters of the note id, so equal titles do not collide. Changed notes are exported again, notes moved to "Recently Deleted" are removed. Locked notes cannot be read and are left as an earlier run stored them.
  4. Upload. Files go through WebDAV, never onto the server's disk, so Nextcloud keeps a version of every overwritten file and moves deleted files to its trash bin. Files above the chunk size use Nextcloud's chunked upload (v2). The modification time is passed on (X-OC-Mtime).

A SQLite file records what was uploaded, so later runs only transfer changes.

⁠Safety nets
  • If any Drive folder cannot be listed, the run deletes nothing.
  • A run refuses to delete more than MAX_DELETE_RATIO of the tracked files or notes (beyond five, which are always allowed). A nearly empty listing from iCloud therefore cannot wipe the backup. Raise the ratio for a single run when a large deletion is intended.
  • A failed upload is not recorded and is retried on the next run.
⁠Virus findings

When the server's virus scanner refuses a Drive file (Nextcloud's files_antivirus answers 415 with Virus <signature> ...), the file stays in iCloud and is not backed up, but this does not count as an error. The run mails one report listing every new finding with its signature. Once the mail is out, the file is recorded and later runs skip it without downloading it again, until it changes in iCloud or disappears from there.

If the mail cannot be sent, or MAIL_TO is not set, the run exits with code 1 and tries again next time, so a finding is never silently dropped.

⁠Configuration

All settings come from environment variables.

VariableDefaultMeaning
APPLE_IDrequiredApple ID whose session is borrowed
SOURCE_SESSION_DIR/sessionicloudpd's cookie directory, mounted read-only
WORK_DIR/tmp/icloud-backupSession copy and downloads; needs room for the largest file
STATE_FILE/state/state.sqliteUpload state, on a persistent volume
WEBDAV_URLrequiredWebDAV root, for Nextcloud https://<host>/remote.php/dav
WEBDAV_USERrequiredAccount on the WebDAV server
WEBDAV_PASSWORD_FILEFile holding the password (preferred)
WEBDAV_PASSWORDPassword, if no file is given
DRIVE_TARGETiCloud DriveTarget folder for Drive
NOTES_TARGETiCloud NotesTarget folder for Notes
DRIVE_ENABLED, NOTES_ENABLEDtrueSwitch an area off
DRY_RUNfalseLog what would happen, write nothing
MAX_DELETE_RATIO0.25Largest share of tracked items one run may delete
CHUNK_SIZE_MIB64Files larger than this are uploaded in chunks of this size
LOG_LEVELinfodebug, info, warning, error
MAIL_TORecipient of virus reports; without it a finding fails the run
SMTP_HOSTrequired with MAIL_TOMail server, implicit TLS
SMTP_PORT465Submissions port
SMTP_USERNAMErequired with MAIL_TOLogin, also the sender
SMTP_PASSWORD_FILEFile holding the SMTP password (preferred)
SMTP_PASSWORDSMTP password, if no file is given
MAIL_FROMSMTP_USERNAMESender address

For Nextcloud, use an app password (Settings, Security) rather than the account password.

⁠Exit codes

CodeMeaning
0Run completed without errors
1Run completed, but some items failed, a safety net stopped deletions, or a virus finding could not be reported
2No usable Apple session: renew it in icloudpd
3Invalid configuration

Logs are JSON lines on stderr. They never contain session tokens: for exceptions the class name goes into error, and only failed WebDAV requests add a detail with method, URL, status and the reason Nextcloud gave, such as a virus found by files_antivirus or a forbidden file name.

⁠Container image

The image runs as uid 1000 and starts icloud-backup. Tests and type checks run during the image build (stage test). It needs read access to icloudpd's cookie directory, which icloudpd also creates as uid 1000.

docker build -t icloud-backup .

⁠Development

python3.13 -m venv .venv
.venv/bin/pip install -e ".[test]"
.venv/bin/pytest
.venv/bin/mypy

Releases follow semantic versioning and are tagged automatically from Conventional Commits on main (semver-bump.yaml); a tag builds and publishes the image.

⁠Limitations

  • Shared folders that others own are included as far as pyicloud lists them.
  • Reminders are not covered.
  • Files the WebDAV server refuses for other reasons, such as forbidden names like .htaccess, are not recorded as uploaded, so every run tries them again and exits with code 1.
  • Virus findings are only handled for Drive. A refused note export counts as an ordinary failed upload.
  • Apple's web APIs are unofficial and change without notice; pyicloud is pinned and updated through Renovate.

⁠License

MIT, see LICENSE⁠.

Tag summary

Content type

Image

Digest

sha256:ec092e438…

Size

55.2 MB

Last updated

2 days ago

docker pull ricariel/icloud-backup