# Complete kei config dictionary. # # Copy this file to ~/.config/kei/config.toml, or mount it at # /config/config.toml in Docker, then edit account and path values. # # Each option block lists: # - Option: TOML key name # - Default value: value used when the key is absent # - Valid values: accepted values or range # - Description: what the option controls # # Options whose default value is unset stay commented so copying this file does # not accidentally enable a feature, write to a path, or use a secret source. # Option: data_dir # Default value: parent directory of the resolved config file; Docker sets KEI_DATA_DIR=/config # Valid values: filesystem path; ~ expands to your home directory # Description: Stores sessions, state DBs, credentials, health data, and cookies. # data_dir = "~/.config/kei" # Option: log_level # Default value: info # Valid values: debug, info, warn, error # Description: Sets the tracing log level when --log-level is not provided. log_level = "info" [auth] # Option: auth.username # Default value: unset; ICLOUD_USERNAME can provide the account for automation # Valid values: non-empty Apple account email or identifier # Description: iCloud account username. Required for commands that authenticate. # username = "you@example.com" # Option: auth.password_file # Default value: unset; --password-file or KEI_PASSWORD_FILE can also provide it # Valid values: filesystem path; ~ expands to your home directory # Description: Reads the iCloud password from a file on each auth attempt, useful for Docker secrets. # password_file = "/run/secrets/icloud_password" # Option: auth.password_command # Default value: unset; --password-command or KEI_PASSWORD_COMMAND can also provide it # Valid values: shell command string on Unix; conflicts with password_file # Description: Runs a command on each auth attempt and uses stdout as the iCloud password. # password_command = "op read 'op://vault/icloud/password'" # Option: auth.domain # Default value: com # Valid values: com, cn # Description: Apple iCloud domain. Use cn only for China-region iCloud accounts. domain = "com" # Plaintext [auth].password is not a supported config option. Use kei password set, # auth.password_file, auth.password_command, or ICLOUD_PASSWORD for automation. [download] # Option: download.directory # Default value: unset # Valid values: filesystem path that is not a protected system directory; ~ expands to your home directory # Description: Destination root for synced and imported media. Required for sync and import-existing. # directory = "/photos" # Option: download.folder_structure # Default value: %Y/%m/%d # Valid values: strftime template plus optional leading {library}; use "none" for no date folders # Description: Directory template for unfiled and library-wide files. {album} and {smart-folder} are not valid here. Date tokens render the capture-local date when Apple supplies a usable timezone offset, otherwise the backup host's local date. folder_structure = "%Y/%m/%d" # Option: download.folder_structure_albums # Default value: {album} # Valid values: strftime template plus optional leading {library} and leading {album} # Description: Directory template for album passes. Put date folders after {album}, for example {album}/%Y/%m. Date tokens render the capture-local date when Apple supplies a usable timezone offset, otherwise the backup host's local date. folder_structure_albums = "{album}" # Option: download.folder_structure_smart_folders # Default value: {smart-folder} # Valid values: strftime template plus optional leading {library} and leading {smart-folder} # Description: Directory template for smart-folder passes. Put date folders after {smart-folder}. Date tokens render the capture-local date when Apple supplies a usable timezone offset, otherwise the backup host's local date. folder_structure_smart_folders = "{smart-folder}" # Option: download.threads # Default value: 10; default becomes 1 when bandwidth_limit is set and threads is unset # Valid values: integer 1..64 # Description: Maximum parallel download workers across all active passes. Full enumeration divides this limit equally between concurrent passes, rounds down, and leaves any remainder unused. threads = 10 # Option: download.bandwidth_limit # Default value: unset # Valid values: positive byte rate; suffixes K, M, G, Ki, Mi, Gi are accepted, for example 500K or 10M # Description: Caps total download throughput. When set without threads, kei uses one worker by default. # bandwidth_limit = "10M" # Option: download.temp_suffix # Default value: .kei-tmp # Valid values: non-empty filename suffix string # Description: Suffix for partial media downloads. Metadata writers use separate unique temporary names. Publication is guarded; suffix and age alone never authorize cleanup. temp_suffix = ".kei-tmp" # Option: download.legacy_preservation_allow_hardlinks # Default value: false # Valid values: true, false; TOML only # Description: Waives link-count refusal during legacy preservation qualification and revalidation for originals and current outputs, including sidecars. Other validation remains required. # Trust requirement: Coordinate external tools so they do not change linked contents, metadata, or kei-managed names during the operation. Distinct paths can share an inode. # Limitation: This is not a read-only mode. Existing identical-file reuse can set timestamps through aliases even with metadata output disabled; repair and recovery behavior is unchanged. legacy_preservation_allow_hardlinks = false [download.retry] # Option: download.retry.per_transfer # Default value: 3 # Valid values: integer 0..100 # Description: Retries inside a single transfer. The initial retry delay is derived from this value. per_transfer = 3 # Option: download.retry.per_asset # Default value: 10 # Valid values: integer 0 or greater; 0 disables the cap # Description: Cap on recorded rendition attempts during a run; retry preparation resets eligible failed and stale pending counts on later runs. per_asset = 10 [filters] # Option: filters.libraries # Default value: ["primary"] # Valid values: array of selectors: primary, shared, all, none, zone name, !name exclusion, =literal escape # Description: Selects which iCloud Photos libraries are in scope for default library and unfiled passes. libraries = ["primary"] # Option: filters.albums # Default value: ["all"] # Valid values: array of selectors: all, none, album name, !name exclusion, =literal escape # Description: Selects user album passes. Explicit album selectors are searched across visible libraries. albums = ["all"] # Option: filters.smart_folders # Default value: ["none"] # Valid values: array of selectors: none, all, all-with-sensitive, folder name, !name exclusion, =literal escape # Description: Selects Apple smart-folder passes. all excludes Hidden and Recently Deleted unless all-with-sensitive is used. smart_folders = ["none"] # Option: filters.unfiled # Default value: true # Valid values: true, false # Description: When true, also syncs photos that are not in a user album. unfiled = true # Option: filters.media # Default value: ["photos", "videos", "live-photos"] # Valid values: non-empty array containing photos, videos, live-photos # Description: Selects media categories before download planning. media = ["photos", "videos", "live-photos"] # Option: filters.filename_exclude # Default value: [] # Valid values: array of glob patterns, for example ["*.AAE", "Screenshot*"] # Description: Skips candidate files whose derived filename matches any pattern. filename_exclude = [] # Option: filters.recent # Default value: unset # Valid values: positive integer count such as 100, or day window string such as "30d" # Description: Permanently limits sync to recent assets. Count form is a candidate cap; day form maps to skip_created_before. # recent = 100 # Option: filters.recent_scope # Default value: global # Valid values: global, per-filter # Description: Controls where count-form recent applies. Only valid when recent is set to a count. # recent_scope = "global" # Option: filters.skip_created_before # Default value: unset # Valid values: YYYY-MM-DD, YYYY-MM-DDTHH:MM:SS, or day interval such as 30d # Description: Skips assets created before this cutoff. Date-only values compare the capture-local calendar date; explicit datetimes and relative intervals compare the instant. # skip_created_before = "2024-01-01" # Option: filters.skip_created_after # Default value: unset # Valid values: YYYY-MM-DD, YYYY-MM-DDTHH:MM:SS, or day interval such as 7d # Description: Skips assets created after this cutoff. Date-only values compare the capture-local calendar date; explicit datetimes and relative intervals compare the instant. # skip_created_after = "2024-12-31" [photos] # Option: photos.resolution # Default value: original # Valid values: original, medium, thumb, none # Description: Primary still/video rendition to download. none requires edited or alternative to be true. resolution = "original" # Option: photos.live_resolution # Default value: original # Valid values: original, medium, thumb # Description: Live Photo MOV rendition to download when live-photo video is included. live_resolution = "original" # Option: photos.live_photo_mode # Default value: both # Valid values: both, image-only, video-only, skip # Description: Controls whether Live Photos download the still image, MOV, both, or neither. live_photo_mode = "both" # Option: photos.live_photo_mov_filename_policy # Default value: suffix # Valid values: suffix, original # Description: Names Live Photo MOV files with kei's paired suffix or Apple's original MOV name. live_photo_mov_filename_policy = "suffix" # Option: photos.edited # Default value: false # Valid values: true, false # Description: Also downloads adjusted or edited stills and videos when Apple provides them. edited = false # Option: photos.edited_naming # Default value: suffix # Valid values: suffix, primary # Description: primary requires edited=true; preserves owned originals and edit history before managing the ordinary filename. See docs/edited-primary.md. edited_naming = "suffix" # Option: photos.alternative # Default value: false # Valid values: true, false # Description: Also downloads alternative or RAW sibling resources when Apple provides them. alternative = false # Option: photos.raw_policy # Default value: as-is # Valid values: as-is, prefer-raw, prefer-jpeg # Description: Chooses how RAW+JPEG pairs are aligned when both forms are available. raw_policy = "as-is" # Option: photos.file_match_policy # Default value: name-size-dedup-with-suffix # Valid values: name-size-dedup-with-suffix, name-id7 # Description: Controls path matching for import-existing and skip-existing decisions. file_match_policy = "name-size-dedup-with-suffix" # Option: photos.force_resolution # Default value: false # Valid values: true, false # Description: When true, skips assets missing the selected primary resolution instead of falling back. force_resolution = false # Option: photos.keep_unicode_in_filenames # Default value: false # Valid values: true, false # Description: When false, filenames are ASCII-safe for icloudpd-style compatibility. keep_unicode_in_filenames = false [metadata] # Option: metadata.set_exif_datetime # Default value: false # Valid values: true, false # Description: Writes missing capture dates in supported embedded formats, including HEIF XMP with the xmp feature. Ordinary writes preserve native timestamps; explicit capture repair can replace them. set_exif_datetime = false # Option: metadata.set_exif_rating # Default value: false # Valid values: true, false # Description: Writes favorite/rating metadata where supported. set_exif_rating = false # Option: metadata.set_exif_gps # Default value: false # Valid values: true, false # Description: Writes GPS metadata where supported. set_exif_gps = false # Option: metadata.set_exif_description # Default value: false # Valid values: true, false # Description: Writes description/caption metadata where supported. set_exif_description = false # Option: metadata.embed_xmp # Default value: false # Valid values: true, false; available in default builds with the xmp feature # Description: Embeds XMP metadata in supported media files. embed_xmp = false # Option: metadata.xmp_sidecar # Default value: false # Valid values: true, false; available in default builds with the xmp feature # Description: Writes XMP sidecar files next to downloaded media. xmp_sidecar = false [watch] # Option: watch.interval # Default value: unset for kei sync; kei service run falls back to 86400 seconds when unset # Valid values: integer seconds 60..86400 # Description: Runs sync repeatedly with this delay between cycles. # interval = 86400 # Option: watch.notify_systemd # Default value: auto-detect; true when NOTIFY_SOCKET is set, otherwise false # Valid values: true, false # Description: Sends systemd readiness and watchdog notifications. # notify_systemd = false # Option: watch.pid_file # Default value: unset # Valid values: filesystem path; ~ expands to your home directory # Description: Writes a PID file while sync/watch/service is running. # pid_file = "/run/kei.pid" # Option: watch.reconcile_every_n_cycles # Default value: 0 # Valid values: integer 0 or greater; 0 disables periodic reconcile # Description: Runs read-only local-vs-state reconciliation every Nth watch cycle. reconcile_every_n_cycles = 0 [notifications] # Option: notifications.script # Default value: unset # Valid values: executable script path; ~ expands to your home directory # Description: Runs after sync events; receives KEI_EVENT, KEI_MESSAGE, KEI_ICLOUD_USERNAME, per-cycle KEI_* stats, and KEI_REPORT_JSON when [report].json is configured. # script = "/config/notify.sh" [report] # Option: report.json # Default value: unset # Valid values: filesystem path; ~ expands to your home directory # Description: Atomically writes the latest sync report JSON after each cycle. # json = "/config/sync_report.json" [server] # Option: server.bind # Default value: 0.0.0.0 # Valid values: IP address, for example 0.0.0.0 or 127.0.0.1 # Description: Bind address for /healthz and /metrics in watch/service mode. bind = "0.0.0.0" # Option: server.port # Default value: 9090 # Valid values: TCP port number 0..65535 # Description: Port for /healthz and /metrics in watch/service mode. port = 9090 [ui] # Option: ui.friendly # Default value: auto; on for plain interactive terminals, off for service/container/machine-output contexts # Valid values: true, false # Description: Requests friendly terminal progress and summaries when the environment allows it. # friendly = true # Option: ui.progress_bar # Default value: true # Valid values: true, false # Description: Durable progress-bar preference. --no-progress-bar can still disable it for one run. progress_bar = true [import] # Option: import.strict # Default value: false # Valid values: true, false # Description: During import-existing, verifies a cloud prefix before adopting same-name, same-size files. strict = false