├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── CONFIGURATION.md ├── README.md ├── authenticate.exp ├── build_version.txt ├── change.log ├── docker-compose ├── docker-compose.example.yml └── example.env ├── healthcheck.sh ├── icloudpd.dockerfile ├── init_config.sh ├── launcher.sh ├── profile ├── reauth.sh ├── sendmessage.sh └── sync-icloud.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sh text eol=lf 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: boredazfcuk 7 | 8 | --- 9 | 10 | If you aren't reporting an issue with the code, but need assistance with configuring the container, please start a discussion. 11 | 12 | If you would like to see additional features, please raise a feature request. 13 | 14 | IF YOU RAISE AN ISSUE THAT DOES NOT COMPLETE THIS TEMPLATE, IT WILL BE DELETED. 15 | 16 | **System:** 17 | - OS: [e.g. Debian, Ubuntu, Windows, Synology DSM] 18 | - Version [e.g. 12, 24, 11, 8] 19 | 20 | **Describe the bug** 21 | A clear and concise description of what the bug is. English only please. If you can't be bothered to run it through Google Translate, or whatever, neither can I. Any issues raised that ignore this template will be closed as unplanned. 22 | 23 | **Expected behaviour** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Actual behaviour** 27 | A clear and concise description of what actually happened. 28 | 29 | **Debug log file** 30 | Please add the output of a debug log file which shows the error and all configured variables. You will need to set **debug_log=true** in your /config/icloudpd.conf file and re-generate a log file. Please feel free to remove all of the irrelevant lines, such as the filenames of the downloaded files and anything that contains personally identifiable information. If your container fails without giving any output, then you may skip this section. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: boredazfcuk 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.env 2 | .env 3 | .bak 4 | -------------------------------------------------------------------------------- /CONFIGURATION.md: -------------------------------------------------------------------------------- 1 | ## Docker Environment Variables 2 | Docker environment variables can, for the time being, be used to configure the container. However, configuring containers by using Docker environment variables is deprecated and will be removed in future versions. 3 | When the container is first started, it will write a default configuration file "/config/icloudpd.conf" and the configuration items will be loaded from there. If you find that some things are still being set, even though you have removed the variables from the container, it could be that they are still located in the configuration file. 4 | 5 | Currently, I only reccomend setting one container environment variable, TZ, as the Alpine Linux operating system will use this variable to configure the time zone: 6 | 7 | **TZ**: Sets the local timezone and is required to calculate timestamps. Default: 'UTC'. If you are unsure of your timezone, the list can be found here: https://nodatime.org/TimeZones. The value you need to set is listed in the "Zone ID" column of the table. 8 | 9 | ## CONFIGURATION ITEMS 10 | **apple_id**: This is the Apple ID that will be used when downloading files. This option is mandatory 11 | 12 | **user**: This is name of the user account that you wish to create within the container. This can be anything you choose, but ideally you would set this to match the name of the user on the host system for which you want to download files for. This user will be set as the owner of all downloaded files. Default: 'user'. This option is also used as the trigger for remotly initiated sync. Simply send your user name as a message to the Telegram chat, icoudpd will see it, and start a manual sync. 13 | 14 | **user_id**: This is the User ID number of the above user account. This can be any number that isn't already in use. Ideally, you should set this to be the same ID number as the user's ID on the host system. This will avoid permissions issues if syncing to your host's home directory. Default: '1000'. 15 | 16 | **group**: This is name of the group account that you wish to create within the container. This can be anything you choose, but ideally you would set this to match the name of the user's primary group on the host system. This group will be set as the group for all downloaded files. Default: 'group'. 17 | 18 | **group_id**: This is the Group ID number of the above group. This can be any number that isn't already in use. Ideally, you should set this to be the same Group ID number as the user's primary group on the host system. Default: '1000'. 19 | 20 | **force_gid**: If this configuration option is set it will allow the group to be created with a pre-existing group id. This may be handy if your group id clashes with a system group insude the docker container, however, if may have undesired permissions issues. Please use with caution. 21 | 22 | **download_path**: This is the directory to which files will be downloaded from iCloud. Default: "/home/${user}/iCloud". 23 | 24 | **download_interval**: This is the number of seconds between downloads. It can be set to the following periods: 21600 (6hrs), 43200 (12hrs), 86400 (24hrs), 129600 (36hrs), 172800 (48hrs) and 604800 (7 days). If this configuration option is not set to one of these values, it will default to 86400 seconds. Be careful if setting a short download period. Apple have a tendency to throttle connections that are hitting their server too often. I find that every 24hrs is fine. My phone will upload files to the cloud immediately, so if I lose my phone the photos I've taken that day will still be safe in the cloud, and the container will download those photos when it runs in the evening. Setting a value less than 12 hours will display a warning as Apple may throttle you. 25 | 26 | **download_delay**: This is the number of minutes to delay the first download. This is so that you can stagger the downloads of multiple containers. Default: 0. It has a maximum setting of 60. 27 | 28 | **notification_days**: When your cookie is nearing expiration, this is the number of days in advance it should notify you. You will receive a single notification, per day, in the days running up to cookie expiration. Default: 7. 29 | 30 | **authentication_type**: This is the type of authentication that is enabled on your iCloud account. Valid values are 'MFA' if you have multifactor authentication enabled or 'Web' if you do not. If 'Web' is specified, then cookie generation is not required. Default: 'MFA'. 31 | 32 | **directory_permissions**: This specifies the permissions to set on the directories in your download destination. Default: 750. 33 | 34 | **file_permissions**: This specifies the permissions to set on the files in your download destination. Default: 640. 35 | 36 | **folder_structure**: This specifies the folder structure to use in your download destination directory. If this configuration option is not set, it will set {:%Y/%m/%d} as the default. Use **none** to download to a flat file structure. Changing this value will not re-organise your currently downloaded files. It will leave your current folder structure intact and download your entire stream again. I do not recommend using **none** or {:%Y} as these two may result in images not being downloaded. In iCloud, you can have two identically named files and it will be fine. Using this downloader, the first file downloaded will take that name and the second file will be ignored. 37 | 38 | **albums_with_dates**: When albums are downloaded, they are downloaded into directories of the same name as the album. Setting this variable to **true** will then create sub folders according to the **folder_structure** variable. Default: false 39 | 40 | **libraries_with_dates**: When libraries are downloaded, they are downloaded into directories of the same name as the library. Setting this variable to **true** will then create sub folders according to the **folder_structure** variable. Default: false 41 | 42 | **skip_check**: Set this to **true** skip the check for new files. The check can have issues with large libraries, please set to **true** if you have more than a few thousand photos. Default: false. 43 | 44 | **download_notifications**: specifies whether notifications with a short summary should be sent for file downloads. Default: true. 45 | 46 | **delete_notifications**: Specifies whether notifications with a short summary should be sent for file deletions. Default: true. 47 | 48 | **startup_notification**: Specifies whether the startup notification should be sent. Default: true. 49 | 50 | **delete_accompanying**: Tells the script to delete files which accompany the HEIC files that are downloaded. These are the JPG files which are created if you have HEIC to JPG conversion enabled. They are also the \_HEVC.MOV files which make up part of a live photo. This feature deletes files from your disk. I'm not responsible for any data loss. 51 | 52 | **delete_empty_directories**: Tells the script to delete any empty directories it finds in the download path. It will only run if **folder_structure** isn't set to 'none' 53 | 54 | **set_exif_datetime**: Write the DateTimeOriginal exif tag from file creation date. Warning: Setting this option will alter the local file and result in the original being downloaded again, with a de-duplication suffix added to the name. Default: false. 55 | 56 | **auto_delete**: Scans the "Recently Deleted" folder and deletes any files found in there. (If you restore the photo in iCloud, it will be downloaded again). Default: false. 57 | 58 | **delete_after_download**: After a file is successfully downloaded it is moved to the Recenlty Deleted folder. This configuration option cannot be used in conjunction with **auto_delete**. Default: false. 59 | 60 | **keep_icloud_recent_days**: Set this to an integer number to only keep the most recent *n* number of days. Setting this to 0 will remove all photos from iCloud. This configuration option cannot be used in conjunction with **delete_after_download**. Default: Not set (keep all). 61 | 62 | **keep_icloud_recent_only**: Set this to **true** to enable the option above. Default: false 63 | 64 | **photo_size**: Image size to download. Can be set to **original**, **medium**, **thumb**, **adjusted**, **alternative** or any combination of those five in a comma-separated string if multiple size types are to be downloaded e.g. **photo_size=original,adjusted**. Adjusted are the edited photos that can be made by using filters, or by using the markup tool in the Photos app. Alternative are RAW file types. Default: original. 65 | 66 | **skip_live_photos**: If this is set, it will skip downloading live photos. Default: false. 67 | 68 | **live_photo_size**: Live photo file size to download. Can be set to **original**, **medium** or **thumb**. If skip_live_photos is set, this setting is redundant. Default: original. 69 | 70 | **skip_videos**: If this is set, it will skip downloading videos. Default: false. 71 | 72 | **recent_only**: Set this to an integer number to only download this many recently added photos. Default: download all photos. 73 | 74 | **until_found**: Set this to an integer number to only download the most recently added photos, until *n* number of previously downloaded consecutive photos are found. Default: download all photos. 75 | 76 | **photo_album**: Set this to a comma delimited field to download photos from a photo album. Please note, if downloading from multiple albums, you need to enclose them in quotes in your /config/icloudpd.conf file e.g. photo_album="one,two,three and four" will download photos from three albums named "one", "two" and "three and four". When downloading photo albums, the folder structure will be set to be the name of the album eg "/home/boredazfcuk/iCloud/one/IMG_0001.HEIC", "/home/boredazfcuk/iCloud/two/IMG_0002.HEIC" and "/home/boredazfcuk/iCloud/three and four/IMG_0003.HEIC". Set **photo_album="all albums"** in your configuration file /config/icloudpd.conf to download all albums. Please note: Due to a limitation in an upstream package, downloading from multiple albums will trigger multiple download runs. When Apple detect this, they may force a multifactor re-authentication. 77 | 78 | **photo_library**: Set this to a comma delimited field to download photos from a shared library. Please note, if downloading from multiple libraries, you need to enclose them in quotes in your /config/icloudpd.conf file e.g. photo_library="one,two,three and four" will download photos from three libraries named "one", "two" and "three and four". When downloading photo libraries, the folder structure will be set to be the name of the library eg "/home/boredazfcuk/iCloud/one/IMG_0001.HEIC", "/home/boredazfcuk/iCloud/two/IMG_0002.HEIC" and "/home/boredazfcuk/iCloud/three and four/IMG_0003.HEIC". Set **photo_library="all libraries"** in your configuration file /config/icloudpd.conf to download all libraries. Please note: Due to a limitation in an upstream package, downloading from multiple libraries will trigger multiple download runs, and Apple may force a multifactor re-authentication. 79 | 80 | **skip_album**: Use this option in conjunction with **photo_album** to skip certain albums e.g. **skip_album="All Photos,Time-lapse,Videos,Slo-mo,Bursts,Favorites,Panoramas,Screenshots,Live,Recently Deleted,Hidden"** 81 | 82 | **skip_library**: Use this option in conjunction with **photo_library** to skip certain libraries e.g. **skip_libraries="PrimarySync,SharedSync-########-####-####-####-############"** 83 | 84 | **photo_library**: Set this to the name of an iOS 16 shared library to download photos from that shared library. 85 | 86 | **convert_heic_to_jpeg**: Set this to **true** to convert downloaded HEIC files to JPEG, while also retaining the original. 87 | 88 | **jpeg_path**: Set this configuration option to specify a different location for the converted JPEGs. Default: "/home/${user}/iCloud", or **download_path** if not set, thereby placing the JPEG files alongside the HEIC files. 89 | 90 | **jpeg_quality**: If HEIC to JPEG conversion is enabled, this configuration option will let you set the quality of the converted file by specifying a number from 0 (lowest quality) to 100 (highest quality). Default: 90. 91 | 92 | **icloud_china**: Set this to **true** to use icloud.com.cn instead of icloud.com as the download source. Default: false. 93 | 94 | **auth_china**: Set this to **true** to use icloud.com.cn instead of icloud.com for cookie generation. Default: false. 95 | 96 | **fake_user_agent**: Set this to **true** to tell curl to use a fake user agent. This is required for some notification sites which do not allow curl to send notifications, IYUU for one. Sets user agent to: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0" 97 | 98 | **synology_photos_app_fix**: Set this to **true** to touch files after download and trigger the Synology Photos app to index any newly created files. 99 | 100 | **synology_ignore_path**: Set this to **true** to avoid warnings when trying to change **@eaDir** permissions for the extended attributes directories under Synology system. 101 | 102 | **sideways_copy_videos**: Set this to **true** to have the container copy the downloaded videos to another directory. This directory must be specified in the **video_path** directory. Default: false 103 | 104 | **sideways_copy_videos_mode**: Seto this to **copy** to have the **sideways_copy_videos** function copy files, leaving the original in place. Set this to **move** to have it move files to the destination directory. If **move** is specified, then **delete_after_download** must be set to **true**. This is to avoid a situation where all video files are moved out of the download folder, then re-downloaded when the next syncronisation occurs. By having **delete_after_download** set to **true** it means that each downloaded video is removed from iCloud after it's downloaded, so it would not be possible for the container to re-download the file after it is moved from the download directory. 105 | 106 | **single_pass**: Set this to **true** to exit out after a single pass instead of looping as per the download_interval. This way, the script can be scheduled to lauch on the host system using cron or another scheduling agent. If this option is used, it will automatically disable the download check. If using this configuration option, the restart policy of the container must be set to "no". If it is set to "always" then the container will instantly relaunch after the first run and you will hammer Apple's website. 107 | 108 | **keep_unicode**: Set this to **true** to keep unicode chars in file names or set it to **false** to remove all non-ascii chars. Default: false. 109 | 110 | **live_photo_mov_filename_policy**: Set this to **suffix** to add _HEVC to the suffix of the file name. Set it to **original** to set the filename the same as the photo. Default: suffix. 111 | 112 | **align_raw**: For photo assets with raw and jpeg, treat raw always in the specified size: **original** (raw+jpeg), **alternative** (jpeg+raw) or **as-is** (unchanged). Default: as-is. 113 | 114 | **file_match_policy**: Policy to identify existing files and de-duplicate. **name-size-dedup-with-suffix** appends file size to deduplicate. **name-id7** 115 | adds asset id from iCloud to all file names and does not need de-duplication. Default: name-size-dedup-with-suffix. 116 | 117 | **video_path**: 118 | 119 | # NEXTCLOUD CONFIGURATION ITEMS 120 | 121 | **nextcloud_delete**: Set this variable to **true** if you want to remove files from Nextcloud. This setting requires **auto_delete** to also be set to true. When a file is found in the 'Recently Deleted', the **auto_delete** function will remove the local file. If **nextcloud_delete** is also set to **true**, then it will remove that file from the Nextcloud server. 122 | 123 | **nextcloud_password**: This is the password for the Nextcloud account you are syncing to 124 | 125 | **nextcloud_target_dir**: This is the name of the root folder that you want to place files in. Please note, it is not possible to upload files to the root of a Nextcloud server at this time. 126 | 127 | **nextcloud_upload**: Set this to **true** to upload files to a Nextcloud server 128 | 129 | **nextcloud_url**: This is the URL of your Nextcloud server e.g. https://my.server.local/ or https://my.server.local/nextcloud/ 130 | 131 | **nextcloud_username**: This is the user name of the account that you want to upload the files to. 132 | 133 | ## NOTIFICATION CONFIGURATION ITEMS 134 | 135 | **notification_type**: This specifies the method that is used to send notifications. These are the options available **Prowl**, **Pushover**, **Telegram**, **Webhook**, **openhab**, **Dingtalk**, **Discord**, **IYUU**, **WeCom**, **Gotify**, **Bark**, **msmtp** and **signal**. When the multifactor authentication cookie is within 7 days (default) of expiry, a notification will be sent upon download. No more than a single notification will be sent within a 24 hour period unless the container is restarted. This does not include the notification that is sent each time the container is started. 136 | 137 | **notification_title**: This allows you to change the title which is sent on the notifications. This variable will default to **boredazfcuk/iCloudPD**. 138 | 139 | **silent_file_notifications** will send low priority messages for file downloads and file deletions so that notifications are sent, but you don't get alerted with sounds/vibrations. Currently only supported for 140 | 141 | **prowl_api_key**: Mandatory if notification_type set to 'Prowl'. This is the API key for your account as generated by the Prowl website. 142 | 143 | **pushover_user**: Mandatory if notification_type set to 'Pushover'. This is the Pushover user key associated with your account. 144 | 145 | **pushover_token**: Mandatory if notification_type set to 'Pushover'. This is the application API token. You will need to create an application by logging into your Pushover account and creating an application. 146 | 147 | **pushover_sound**: Mandatory if notification_type set to 'Pushover' this variable can be set to customise the sound of the notification. Values for this variable can be found here: https://pushover.net/api#sounds 148 | 149 | **telegram_token**: Mandatory if notification_type set to 'Telegram'. This is the token that was assigned to your account by The Botfather. 150 | 151 | **telegram_chat_id**: Mandatory if notification_type set to 'Telegram'. This is the chat_id for your Telegram bot. If the bot is a standard user that messages you, the chat ID will be a positive integer number. If the bot is a member of a group, and sends messages to the group, the chat ID will be prefixed with a hyphen '-' character. 152 | 153 | **telegram_silent_file_notifications**: Optional if notification_type set to 'Telegram'. Set this to **true** for the file download notifications to be sent silently. Default = false 154 | 155 | **telegram_polling**: Optional if notification_type set to 'Telegram'. Set this to true to enable Telegram polling. This will check the Telegram chat for messages every 60 seconds. If the latest message is the user name, it will synchronise immediately 156 | 157 | **telegram_server**: Optional if notification_type set to 'Telegram'. If Telegram is blocked in your country and you need to use a proxy server to access it, put the fully qualified domain name of the server here. e.g. proxy.server.com 158 | 159 | **telegram_http**: Optional if notification_type set to 'Telegram'. If Telegram is retricted to HTTP only in your country, set this to **true** so that HTTP is used instead of HTTPS. Default = false 160 | 161 | **webhook_server**: Mandatory if notification_type set to 'Webhook' or 'openhab' then this is the name of the server to connect to when sending webhook notifications. 162 | 163 | **webhook_port**: Mandatory if notification_type set to 'Webhook' or 'openhab' then this is the port number to use when connecting to the webhook server. If this is not set, it will default to 8123. 164 | 165 | **webhook_path**: Mandatory if notification_type set to 'Webhook' or 'openhab' then this is the path to use when connectiong to the webhook server. The path must start and end with a forward slash character. If this is not set, it will default to **/api/webhook/**. Openhab uses **"/rest/items//"**. 166 | 167 | **webhook_id**: Mandatory if notification_type set to 'Webhook' or 'openhab' then this is the Webhook ID to use. Openhab uses "state". 168 | 169 | **webhook_https**: If this is set to 'True' then the Webhook or openhab notification URL will use HTTPS, otherwise it will default to HTTP. 170 | 171 | **webhook_body**: Adapt to different services. Homeassistant uses "data" in the body of the webhook request, Discord uses "content", IFTTT uses "value1", etc.. Defaults to "data". 172 | 173 | **webhook_insecure**: Set to **true** to allow insecure https certificates, such as self-signed. 174 | 175 | **dingtalk_token**: Mandatory if notification_type set to 'Dingtalk' then this is the access token generated by the Dingtalk application. In the Dingtalk application, go to 'Security Settings', select 'Custom Keywords' and set to to the same value as **notification_title**. 176 | 177 | **discord_id**: This is the first half of the URL generated by Discord's webhook integration. It will be all numbers. Do not include any / 178 | 179 | **discord_token**: This is the second half of the URL generated by Discords webhook integration. Do no include any / 180 | 181 | **iyuu_token**: Mandatory if notification_type set to 'IYUU'. This is the access token required to send messages. 182 | 183 | **wecom_id**: Mandatory if notification_type set to 'WeCom'.企业微信通知,企业微信通知,企业ID / This is the CORPID associated with your account。企业微信通知配置也可参见[此项目链接](https://github.com/Alano-i/wecom-notification/tree/main/iCloudPD) 184 | 185 | **wecom_secret**: Mandatory if notification_type set to 'WeCom'.企业微信通知,企业应用的Secret / This is the CORPSECRET associated with your account 186 | 187 | **agentid**: Mandatory if notification_type set to 'WeCom'.企业微信通知,企业应用的id / Enterprise application id 188 | 189 | **touser**: Mandatory if notification_type set to 'WeCom'.企业微信通知,接收通知的对象 / who receives notifications 190 | 191 | **content_source_url**: Mandatory if notification_type set to 'WeCom'.企业微信通知,阅读原文跳转链接 / Click on the page link after "reading the original text" 192 | 193 | **name**: Mandatory if notification_type set to 'WeCom'.企业微信通知,当前 Apple ID 所有人 / Current Apple ID owner 194 | 195 | **wecom_proxy**: Optional. 企业微信通知,企业微信的代理,非必填,用来绕过企业微信的IP白名单 / Optional,Used to bypass the ip whitelist of WeCom 196 | 197 | **media_id_startup**: Mandatory if notification_type set to 'WeCom'.企业微信通知,启动成功通知封面 / Image for Startup success 198 | 199 | **media_id_download**: Mandatory if notification_type set to 'WeCom'.企业微信通知,下载通知封面 / Image for downloaded files 200 | 201 | **media_id_delete**: Mandatory if notification_type set to 'WeCom'.企业微信通知,删除文件通知封面 / Image for deleted files 202 | 203 | **media_id_expiration**: Mandatory if notification_type set to 'WeCom'.企业微信通知,cookie即将过期通知封面 / Image for cookie expiration 204 | 205 | **media_id_warning**: Mandatory if notification_type set to 'WeCom'.企业微信通知,同步失败、cookiey已过期通知封面 / Image for cookie expired or failure 206 | 207 | **gotify_app_token**: Mandatory if notification_type set to 'Gotify'. This is the app_token associated with your account. 208 | 209 | **gotify_https**: If this is set to 'True' then the Gotify server URL will use HTTPS, otherwise it will default to HTTP. 210 | 211 | **gotify_server_url**: Mandatory if notification_type set to 'Gotify'. This is the server name of your Gotify server e.g. server.domain.tld 212 | 213 | **bark_device_key**: Mandatory if notification_type set to 'Bark'. This is the device key associated with your device 214 | 215 | **bark_server**: Mandatory if notification_type set to 'Bark'. This is the name of your Bark server. Please note that the port should not be included and currently the project only supports http. 216 | If you use the official Bark server, please fill the field with `api.day.app`. 217 | 218 | **msmtp_host**: Mandatory if notification_type set to `msmtp`. The domain of your smtp server 219 | 220 | **msmtp_port**: Mandatory if notification_type set to `msmtp`. The port of the smtp service. Normally 465 or 587 221 | 222 | **msmtp_tls**: Mandatory if notification_type set to `msmtp`. Set to `on` or `off` to enable or disable TLS encryption. 223 | 224 | **msmtp_from**: Mandatory if notification_type set to `msmtp`. The sender's email address 225 | 226 | **msmtp_user**: Mandatory if notification_type set to `msmtp`. The login username for your SMTP provider. 227 | 228 | **msmtp_pass**: Mandatory if notification_type set to `msmtp`. The password for the login user 229 | 230 | **msmtp_args**: Optional extra arguments for `msmtp` in case your mail provider has specific requirements. For example, `--tls-starttls=off`. 231 | 232 | **signal_host**: Mandatory if notification_type set to `signal`. This is the host that you want the container to connect to. This should be a sepearate Docker container bbernhard/signal-cli-rest-api which has been paired with the Signal app on your phone. Further information can be found here: https://deepwiki.com/bbernhard/signal-cli-rest-api 233 | 234 | **signal_number**: Mandatory if notification_type set to `signal`. The phone number of the signal device to send to. 235 | 236 | **signal_port**: Mandatory if notification_type set to `signal`. The port that the bbernhard/signal-cli-rest-api container is listening on. 237 | 238 | **signal_recipient**: Mandatory if notification_type set to `signal`. The recipient for the message. 239 | 240 | ## VOLUME CONFIGURATION 241 | 242 | This container requires a named volume mapped to /config. This is where is stores the authentication cookie. Without it, it will lose the cookie information each time the container is recreated. 243 | It will download the photos to the "/home/${user}/iCloud" photos directory. You need to create a bind mount into the container at this point. 244 | 245 | ## FAILSAFE FEATURE 246 | 247 | I have added a failsafe feature to this container so that it doesn't make any changes to the filesystem unless it can verify the volume it is writing to is mounted correctly. The container will look for a file called "/home/${user}/iCloud/.mounted" (please note the capitalisation of iCloud) in the download destination directory inside the container. If this file is not present, it will not download anything from iCloud. This way, if underlying disk/volume/whatever gets unmounted, sync will not occur and this prevents the script from filling up the root volume of the host. This file **MUST** be created manually and sync will not start without it. 248 | 249 | ## CREATING A CONTAINER 250 | 251 | First off, create a dedicated network for your iCloudPD conter(s) as this overcomes some DNS and routing issues may occur if you use the legacy default network bridge that Docker creates. In this example, I've have told it to use the IP address subnet 192.168.115.1 - 192.168.115.254 and configured the gateway to be 192.168.115.254. You can use any subnet you like: 252 | 253 | ``` 254 | docker network create \ 255 | --driver=bridge \ 256 | --subnet=192.168.115.0/24 \ 257 | --gateway=192.168.115.254 \ 258 | --opt com.docker.network.bridge.name=icloudpd_br0 \ 259 | icloudpd_bridge 260 | ``` 261 | Then create the container, connecting it to the new icloudpd network bridge. It should look something like this: 262 | 263 | ``` 264 | docker create \ 265 | --name iCloudPD_boredazfcuk \ 266 | --hostname icloudpd_boredazfcuk \ 267 | --network icloudpd_bridge \ 268 | --restart=always \ 269 | --env TZ=Europe/London \ 270 | --volume icloudpd_boredazfcuk_config:/config \ 271 | --volume /home/boredazfcuk/iCloud:/home/boredazfcuk/iCloud \ 272 | boredazfcuk/icloudpd 273 | ``` 274 | 275 | Note: Raspberry Pi users have reported that the container only functions correctly when the containes is created with the `--privileged` parameter. 276 | 277 | Once you have created your container. The configurable environment items will be listed in the configuration file located in `/config/icloudpd.conf` 278 | 279 | ## CONFIGURING A PASSWORD 280 | 281 | Once the container has been created, you should connect to it and run `/usr/local/bin/sync-icloud.sh --Initialise`. This will then take you through the process of adding your password to the container's keyring. It will also take you through generating a cookie that will allow the container to download the photos. 282 | 283 | If you launch a container without initialising it first, you will receive this error message: 284 | ``` 285 | ERROR Keyring file /config/python_keyring/keyring_pass.cfg does not exist. 286 | INFO - Please add the your password to the system keyring using the --Initialise script command line option. 287 | INFO - Syntax: docker exec -it sync-icloud.sh --Initialise 288 | INFO - Example: docker exec -it icloudpd sync-icloud.sh --Initialise 289 | INFO Restarting in 5 minutes... 290 | ``` 291 | 292 | As per the error, the container needs to be initialised by using the --Initialise command line option. With the first container still running, connect to it and launch the initialisation process by running the following at the terminal prompt (assuming the container name is 'icloudpd'): 293 | `docker exec -it icloudpd sync-icloud.sh --Initialise` 294 | 295 | You will then be asked to log in to icloud.com, with your current Apple ID and password, and will be prompted to enter a multifactor authentication code which will be sent via SMS. Once that is confirmed, the password will be added to the keyring. 296 | 297 | If you do not have an authentication cookie, and you have multifactor authentication enabled on your account, you will be taken to the cookie generation process immediately after. 298 | 299 | ## MULTIFACTOR AUTHENTICATION 300 | 301 | If your Apple ID account has multifactor authentication enabled, you will see that the container waits for a multifactor authentication cookie to be created: 302 | ``` 303 | ERROR Cookie does not exist." 304 | INFO - Please create your cookie using the --Initialise script command line option." 305 | INFO - Syntax: docker exec -it sync-icloud.sh --Initialise" 306 | INFO - Example: docker exec -it icloudpd sync-icloud.sh --Initialise" 307 | INFO Restarting in 5 minutes..." 308 | ``` 309 | 310 | Without this cookie, download cannot be started. 311 | 312 | As per the error, the container needs to be initialised by using the --Initialise command line option. With the first container still running, connect to it and launch the initialisation process by running the following at the terminal prompt (assuming the container name is 'icloudpd'): 313 | `docker exec -it icloudpd sync-icloud.sh --Initialise` 314 | 315 | After that, the script will log into the icloud.com website and save the MFA cookie. Your iDevice will ask you if you want to allow or deny the login. When you allow the login, you will be give a 6-digit approval code. Enter the approval code when prompted. 316 | 317 | The process should look similar to this: 318 | 319 | ``` 320 | 2020-08-06 16:45:58 INFO ***** boredazfcuk/icloudpd container for icloud_photo_downloader started ***** 321 | 2020-08-06 16:45:58 INFO Alpine Linux v3.12 322 | 2020-08-06 16:45:58 INFO Interactive session: true 323 | 2020-08-06 16:45:58 INFO Local user: user:1000 324 | 2020-08-06 16:45:58 INFO Local group: group:1000 325 | 2020-08-06 16:45:58 INFO LAN IP Address: 192.168.20.1 326 | 2020-08-06 16:45:58 INFO Apple ID: email@address.com 327 | 2020-08-06 16:45:58 INFO Authentication Type: MFA 328 | 2020-08-06 16:45:58 INFO Cookie path: /config/emailaddresscom 329 | 2020-08-06 16:45:58 INFO Cookie expiry notification period: 7 330 | 2020-08-06 16:45:58 INFO Download destination directory: /home/user/iCloud 331 | 2020-08-06 16:45:58 INFO Folder structure: {:%Y} 332 | 2020-08-06 16:45:58 INFO Directory permissions: 750 333 | 2020-08-06 16:45:58 INFO File permissions: 640 334 | 2020-08-06 16:45:58 INFO Download interval: 43200 335 | 2020-08-06 16:45:58 INFO Time zone: Europe/London 336 | 2020-08-06 16:45:58 INFO Adding password to keyring... 337 | Enter iCloud password for email@address.com: 338 | Save password in keyring? [y/N]: y 339 | Two-step authentication required. Your trusted devices are: 340 | 0: SMS to 07******** 341 | Which device would you like to use? [0]: 0 342 | Please enter validation code: 123456 343 | 2020-08-06 16:47:04 INFO Using password stored in keyring 344 | 2020-08-06 16:47:04 INFO Generate MFA cookie with password: usekeyring 345 | 2020-08-06 16:47:04 INFO Check for new files using password stored in keyring... 346 | 0: SMS to 07******** 347 | 1: Enter two-factor authentication code 348 | Please choose an option: [0]: 1 349 | Please enter two-factor authentication code: 123456 350 | 2020-08-06 16:47:30 INFO Multifactor authentication cookie generated. Sync should now be successful. 351 | ``` 352 | 353 | This will then place a multifactor authentication cookie into the /config folder of the container. This cookie will expire after three months. After it has expired, you will need to re-initialise the container again. 354 | 355 | After this, the container should start downloading your photos. 356 | 357 | Dockerfile has a health check which will change the status of the container to 'unhealthy' if the cookie is due to expire within a set number of days (notification_days) and also if the download fails. 358 | 359 | ## MULTIFACTOR RE-AUTHENTICATION 360 | Every 30 days, the cookie will expire and need to be re-authenticated. This can be done by running the re-authentication script: 361 | 362 | ``` 363 | docker container exec -it reauth.sh 364 | ``` 365 | 366 | It will then launch the re-authentication process, presenting you with an MFA code on your iDevice and asking for this new code on the command line. e.g: 367 | 368 | ``` 369 | 2024-03-27 22:21:33 DEBUG Authenticating... 370 | 2024-03-27 22:21:35 INFO Two-step/two-factor authentication is required (2fa) 371 | Please enter two-factor authentication code: 123456 372 | 2024-03-27 22:21:55 WARNING Failed to parse response with JSON mimetype 373 | 2024-03-27 22:21:57 INFO Great, you're all set up. The script can now be run without user interaction until 2SA expires. 374 | You can set up email notifications for when the two-step authentication expires. 375 | (Use --help to view information about SMTP options.) 376 | 2024-03-27 22:21:57 INFO Authentication completed successfully 377 | ``` 378 | 379 | # TELEGRAM 2-WAY COMMUNICATIONS 380 | ## Remote Download 381 | If you are using Telegram as your notification application, you can now send messages to the chat bot, which the container will read, and then take the appropriate action. If you simply message the chatbot with the user that you have configured in the **user** variable, it will pick that up and force a download. So if you're down the pub with your mates, take a bunch of pics that you really like, simply message `boredazfcuk` (or whatever you've configured your user variable to be) to the Telegram bot and it will force a download, downloading your new photos within the next few minutes. 382 | 383 | ## Remote Re-authentication 384 | Apple have recently reduced the re-authentication tim from 90 days to 30 days. This means connecting to your container, re-initialising it and completing multi-factor authentication. If I am out of the house and my cookie expires, I would need to wait until I get home, faff about with the whole process I just described. Now, you can message your container in a similar manner to the remote syncronisation, but adding `auth` to the end of the message, so for example `boredazfcuk auth`. After you have done this, I find it is best to start typing another message starting `boredazfcuk ` (note the space) and then changing the keyboard to number input. The container should pick up this instruction within a minute and it will message you back asking for the MFA code. It will start the re-authentication process and your iDevice will display a popup to `allow` or `deny` the connection. Click `allow` and you will be presented with your multi-factor authentication code. Memorise this code and add it to the end of your message, like `boredazfcuk 123456` and hit send. The container will then use this code to re-initialise your cookie and start downloading your photos again. One word of caution though... Literally every company on the planet tells you never to share this code with anyone. I put this feature in because... well... I trust me. I don't believe in putting blind faith in other though. So neither should you. Feel free to read the source code, so you can make sure it's not doing anything nefarious, by checking it yourself. I understand that not everyone can code though, so if you don't trust it, that's totally OK, probably a good choice on your behalf. To be fair, I'm just a dude with an IT hobby. I couldn't care less about your iCloud account, your contacts, or the pictures of your cat/dog. I just hope this makes you life better in some tiny way. 385 | 386 | ## COMMAND LINE PARAMETERS 387 | 388 | There are currently a number of command line parameters are available to use with the sync-icloud.sh script. These are: 389 | 390 | **--Convert-All-HEICs** 391 | This command line option will check for HEIC files that do not have an accompanying JPEG file. If it finds a HEIC that does not have an accompaying JPEG file, it will create it. This can be used to add JPEGs for previously downloaded libraries. The easiest way to run this is to connect to the running container and executing the script. 392 | To run the script inside the currently running container, issue this command (assuming the container name is 'icloudpd'): 393 | `docker exec -it icloudpd sync-icloud.sh --Convert-All-HEICs` 394 | 395 | **--Force-Convert-All-HEICs** 396 | This command line option is the same as the above option but it will overwrite any JPEG files that are already there. This will result in data loss if the downloaded JPEG files have been edited. For this reason, there is a 2 minute delay before this option runs. This gives you time to stop the container, or cancel the script, before it runs. This option is required as the heif-tools conversion utility had a bug that over-rotates the JPEG files. This means the orientation does not match the HEIC file. The heif-tools package has now been replaced by the ImageMagick package which doesn't have this problem. This command line option can be used to re-convert all your HEIC files to JPEG, overwriting the incorrectly oriented files with correctly oriented ones. 397 | To run the script inside the currently running container, issue this command (assuming the container name is 'icloudpd'): 398 | `docker exec -it icloudpd sync-icloud.sh --Force-Convert-All-HEICs` 399 | 400 | **--Force-Convert-All-mnt-HEICs** 401 | This command line option is the same as the above option but it will overwrite any JPEG files that it finds in the /mnt subdirectory. This will result in data loss if the downloaded JPEG files have been edited. For this reason, there is a 2 minute delay before this option runs. This gives you time to stop the container, or cancel the script, before it runs. This option is required as the heif-tools conversion utility had a bug that over-rotates the JPEG files. This means the orientation does not match the HEIC file. The heif-tools package has now been replaced by the ImageMagick package which doesn't have this problem. This command line option can be used to re-convert all your HEIC files to JPEG, overwriting the incorrectly oriented files with correctly oriented ones. This option can be used to correct JPG files that have been archived and removed from your iCloud photostream. Just mount the target directory (or directories) into the /mnt subdirectoy and the script with this command. 402 | To run the script inside the currently running container, issue this command (assuming the container name is 'icloudpd'): 403 | `docker exec -it icloudpd sync-icloud.sh --Force-Convert-All-mnt-HEICs` 404 | 405 | **--Remove-All-JPGs** 406 | This command line option removes all the JPEG files which have a matching HEIC. My day-2-day laptop is Windows 11 and HEIC support is now baked into the OS. I've no need for JPGs anymore as I can view the HEICs and their thumbnails without issue. I created this to just purge all the JPEGs which have a HEIC with a matching filename. This could well remove JPEGs that have matching named HEICs, but are actually different photos. Oh well. 407 | 408 | **--Correct-JPEG-Time-Stamps** 409 | This command line option will correct the timestamps of JPEG files that do not match their accompanying HEIC files. Due to an omission, previous versions of my script never set the time stamp. This command line option will correct this issue. 410 | To run the script inside the currently running container, issue this command (assuming the container name is 'icloudpd'): 411 | `docker exec -it icloudpd sync-icloud.sh --Correct-JPEG-Time-Stamps` 412 | 413 | **--Initialise** | **--Initialize** | **--init** 414 | This command line option will allow you to add your password to the system keyring. It will also force the creation of a new two-factor authentication cookie. 415 | To run the script inside the currently running container, issue this command (assuming the container name is 'icloudpd'): 416 | `docker exec -it icloudpd sync-icloud.sh --Initialise` 417 | 418 | **--Remove-Keyring** 419 | This command line option will delete the system keyring file. You will need to run this if you change your Apple ID password. 420 | To run the script inside the currently running container, issue this command (assuming the container name is 'icloudpd'): 421 | `docker exec -it icloudpd sync-icloud.sh --Remove-Keyring` 422 | 423 | **--Enable-Debugging** 424 | This command line option will edit the config file so that debugging is enabled. This will automatically be picked up the next time a download takes place. There should be no need to restart the container 425 | To run the script inside the currently running container, issue this command (assuming the container name is 'icloudpd'): 426 | `docker exec -it icloudpd sync-icloud.sh --Enable-Degugging` 427 | 428 | **--Disable-Debugging** 429 | This command line option will edit the config file so that debugging is disabled. This will automatically be picked up the next time a download takes place. There should be no need to restart the container 430 | To run the script inside the currently running container, issue this command (assuming the container name is 'icloudpd'): 431 | `docker exec -it icloudpd sync-icloud.sh --Disable-Degugging` 432 | 433 | **--Upload-Library-To-Nextcloud** 434 | This command line option will upload your entire library to the Nextcloud server. First, it will scan your download directory, then replicate the directory structure on the Nextcloud server. Once this is complete, it will proceed upload the files to these directories. 435 | To run the script inside the currently running container, issue this command (assuming the container name is 'icloudpd'): 436 | `docker exec -it icloudpd sync-icloud.sh --Upload-Library-To-Nextcloud` 437 | 438 | **--Sideways-Copy-All-Videos** 439 | This command will copy all the videos in your download path to the location specified in the **video_path** variable. It will check the value of the **sideways_copy_all_videos_mode** variable to determine the copy mode, which can be either 'copy' or 'move'. If the copy mode is set to 'move' then the **delete_after_download** vairable must also be set to **true**. This is because moving the vidoes out of the the main download location will cause icloudpd to re-download the videos from iCloud. If **delete_after_download** is set, then iCloud should be empty, so the endless loop of downloading videos should not occurr. 440 | 441 | **--List-Albums** 442 | This commmand will list the names of the albums available to download 443 | To run the script inside the currently running container, issue this command (assuming the container name is 'icloudpd'): 444 | `docker exec -it icloudpd sync-icloud.sh --List-Albums` 445 | 446 | **--List-Libraries** 447 | This command will list the names of the libraries available to download 448 | To run the script inside the currently running container, issue this command (assuming the container name is 'icloudpd'): 449 | `docker exec -it icloudpd sync-icloud.sh --List-Libraries` 450 | 451 | ## HEALTH CHECK 452 | 453 | I have built in a health check for this container. If the script detects a download error the container will be marked as unhealthy. You can then configure this container: https://hub.docker.com/r/willfarrell/autoheal/ to monitor iCloudPD and restart the unhealthy container. Please note, if your MFA cookie expires, the container will be marked as unhealthy, and will be restarted by the authoheal container every five minutes or so... This can lead to a lot of notifications if it happens while you're asleep! 454 | 455 | ## TROUBLESHOOTING 456 | 457 | The app which runs inside this container connects to iCloud.com website and downloads the files it finds in there. Basically the same way as you would if you were downloading the files in your web browser, as Apple does not provide the capability to do this via an API (gotta love Apple's walled-garden). This is problematic because if the website changes at all, it throws the downloader out due to the website is doing something unexpected. There are many reasons that the website can change, for example, if somebody attempts to brute-force your account, you will be prompted to confirm your security questions the next time you log in. Accounts without two-factor authentication enabled will periodically receive a prompt upon login about upgrading the account's security. 458 | 459 | If you have enabled Apple's Advanced Data Protection feature in iOS 16.2, you will need to disable it. This feature encrypts your photos on Apples servers and disables photo access on icloud.com which is required for this container. This results in you only being able to access your photos from your trusted Apple devices. There is an option to allow access to icloud.com for an hour at a time, but currently, an upstream dependency does not support this. 460 | 461 | If your container starts erroring out all of a sudden, the first thing to do is to log into iCloud.com and check that there isn't some pop-up notification which needs clearing. If that doens't work, then try re-initialising your container. For larger libraries with thousands of images, disabling the download check is also a requirement, so please try that. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-icloudpd 2 | An Alpine Linux Docker container for iCloud Photos Downloader. I use it for syncing the photo streams of all the iDevices in my house back to my server because it's the only way of backing up multiple devices to a single location. It uses the system keyring to securely store credentials, has HEIC to JPG conversion capability, and can send Telegram, Prowl, Pushover, WebHook, DingTalk, Discord, openhab, IYUU, WeCom, msmtp & Signal notifications. Please note, Apple's Advanced Data Protection (ADP) is not supported. ADP must be disabled for this container to work. 3 | 4 | # Now with remote re-authentication! 5 | Taking advantage of the Telegram 2-way communications, it is now possible to re-authenticate your cookie by messaging the Telegram Chat Bot! Simply send a message to Telegram telling it you want to re-authenticate (see the CONFIGURATION.MD link below for details). When it's ready, it will message you back asking for the multi-factor authentication code. Send that to Telegram and it will complete the login process. No more re-initialising from the container command line! 6 | 7 | # Now with easier local re-authentication too! 8 | Now you can simply run reauth.sh from the Docker command line to renew you Multi-Factor Authentication cookie (again, see the CONFIGURATION.MD link below for details). 9 | 10 | # Now with 2-way comms via Telegram! 11 | Just send a message to the Telegram chat and the container will pick that up and sync immediately. 12 | 13 | # Also with built-in Nextcloud upload/delete! 14 | Just configure the Nextcloud settings and every file downloaded will be uploaded to a nextcloud server. It will also upload the JPGs it creates from HSIC file conversions. It will also sync deletes too. 15 | 16 | ## CONFIGURING ICLOUDPD 17 | 18 | The README on Dockerhub has a hard limit of 25,000 characters, and I've hit this limit too many times now. All in all, I'm up at about 37k characters for the documentation, so this README is just a placeholder. Please see CONFIGURATION.md for info on how to configure this container. It is available here: https://github.com/boredazfcuk/docker-icloudpd/blob/master/CONFIGURATION.md 19 | 20 | Litecoin: LfmogjcqJXHnvqGLTYri5M8BofqqXQttk4 21 | 22 | Ethereum: 0x752F0Fc9c1D1a10Ae3ea429505a0bbe259D60C6c 23 | 24 | Bitcoin: 1E8kUsm3qouXdVYvLMjLbw7rXNmN2jZesL or bc1q7mpp4253xeqsyafl4zkak6kpnfcsslakuscrzw -------------------------------------------------------------------------------- /authenticate.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | 3 | # Enable debugging 4 | #exp_internal 1 5 | 6 | set force_conservative 0 ;# set to 1 to force conservative mode even if 7 | ;# script wasn't run conservatively originally 8 | if {$force_conservative} { 9 | set send_slow {1 .1} 10 | proc send {ignore arg} { 11 | sleep .1 12 | exp_send -s -- $arg 13 | } 14 | } 15 | 16 | set timeout -1 17 | spawn -noecho /usr/local/bin/reauth.sh 18 | log_user 0 19 | match_max 100000 20 | expect { 21 | # Activates when account has no trusted numbers 22 | "Please enter two-factor authentication code: " { 23 | exec /usr/local/bin/sendmessage.sh mfacode 24 | send_user "\nWaiting for MFA code...\n" 25 | exec >@stdout 2>@stderr /usr/bin/inotifywait -qq -t 600 -e close_write /tmp/icloudpd/expect_input.txt 26 | set MFACODE [exec cat /tmp/icloudpd/expect_input.txt] 27 | set DATE [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] 28 | send_user "${DATE} INFO MFA code received: ${MFACODE}\n" 29 | send -- "${MFACODE}\r" 30 | # Catches success/failure for received MFA code 31 | expect { 32 | "Failed to verify two-factor authentication code" { 33 | set DATE [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] 34 | send_user "${DATE} ERROR Authentication failed\n" 35 | exec /usr/local/bin/sendmessage.sh failure 36 | } 37 | "The script can now be run without user interaction until 2FA expires" { 38 | set DATE [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] 39 | send_user "${DATE} INFO Authentication successful\n" 40 | exec /usr/local/bin/sendmessage.sh success 41 | } 42 | } 43 | } 44 | "Please enter two-factor authentication code or device index " { 45 | exec /usr/local/bin/sendmessage.sh smschoice 46 | set DATE [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] 47 | send_user "${DATE} INFO Waiting for SMS choice/MFA code...\n" 48 | exec >@stdout 2>@stderr /usr/bin/inotifywait -qq -t 600 -e close_write /tmp/icloudpd/expect_input.txt 49 | set MESSAGE [exec cat /tmp/icloudpd/expect_input.txt] 50 | set DATE [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] 51 | send_user "${DATE} INFO SMS choice/MFA code received: ${MESSAGE}\n" 52 | send -- "${MESSAGE}\r" 53 | expect { 54 | # Catches failure when MFA code is sent immediately 55 | "Failed to verify two-factor authentication code" { 56 | set DATE [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] 57 | send_user "${DATE} ERROR Authentication failed\n" 58 | exec /usr/local/bin/sendmessage.sh failure 59 | } 60 | # Catches success when MFA code is sent immediately 61 | "The script can now be run without user interaction until 2FA expires" { 62 | set DATE [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] 63 | send_user "${DATE} INFO Authentication successful\n" 64 | exec /usr/local/bin/sendmessage.sh success 65 | } 66 | # Catches when index number received instead 67 | "Please enter two-factor authentication code that you received over SMS: " { 68 | exec /usr/local/bin/sendmessage.sh mfacode 69 | set DATE [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] 70 | send_user "${DATE} INFO Waiting for MFA code...\n" 71 | exec >@stdout 2>@stderr /usr/bin/inotifywait -qq -t 600 -e close_write /tmp/icloudpd/expect_input.txt 72 | set MFACODE [exec cat /tmp/icloudpd/expect_input.txt] 73 | set DATE [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] 74 | send_user "${DATE} INFO MFA code received: ${MFACODE}\n" 75 | send -- "${MFACODE}\r" 76 | expect { 77 | # Catches failure for received MFA code 78 | "Failed to verify two-factor authentication code" { 79 | set DATE [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] 80 | send_user "${DATE} ERROR Authentication failed\n" 81 | exec /usr/local/bin/sendmessage.sh failure 82 | } 83 | # Catches success for received MFA code 84 | "The script can now be run without user interaction until 2FA expires" { 85 | set DATE [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] 86 | send_user "${DATE} INFO Authentication successful\n" 87 | exec /usr/local/bin/sendmessage.sh success 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | expect eof 95 | -------------------------------------------------------------------------------- /build_version.txt: -------------------------------------------------------------------------------- 1 | 1266_11-05-2025 2 | -------------------------------------------------------------------------------- /change.log: -------------------------------------------------------------------------------- 1 | 11/05/2024 2 | 3 | - Changed Dockerfile so that it attempts to install the named version of iCloud Photos Downloader, rather than just the latest version, assuming that PyPi's working and latest verison is published correctly 4 | - Changed syncrhonisation_time so it's set at every scheduled sync, rather than every successful download, in the hopes it will make the expiration notifications sent out more reliably 5 | - Changed everything so that it refers to "download" instead of "synchronisation". Synchonisation implies that there is file tracking, or more advanced method of keeping files "in sync" rather than "syncronisation" of the "All Photos" meta-album being by removing photos found in the "Recently Deleted" folder. This seems to be adding confusion as peope are raising issues about files not being deleted hen they are removed, or moved, between albums. 6 | 7 | 10/05/2025 8 | 9 | - Upgraded icloud_photos_downloader to v1.27.5 10 | - Removed requirement for smtp username and password 11 | - Changed some logging stuff 12 | - Attempt to silence jq errors if getting Telegram updates fails 13 | 14 | 08/05/2025 15 | 16 | - New variable **silent_file_notifications** will send low priority messages for file downloads and file deletions so that notifications are sent, but you don't get alerted with sounds/vibrations. 17 | 18 | 18/03/2025 19 | 20 | - Launcher now halts if both photo_album and photo_library are configured as these options conflict with each other 21 | 22 | 20/02/2025 23 | 24 | - Launcher now checks that **download_path**, **jpeg_path** and **video_path** exist in /proc/mounts (if they are configured0. This should confirm that these directories are mounted to external locations at container launch. The .mounted file is still a requirement, as this makes sure the container is looking at the correct external mount and also that the volume hasn't dismounted due to errors. 25 | - Remove trailing slashes from variables which could have them, like download_path, jpeg_path, video_path, the Nextcloud URL and target dir 26 | - A load of stuff I lost track of regarding conversion of jpegs and stuff. I'm a bit tipsy and probably broke a bunch of stuff. Previously there was an ugly bunch of hardcoding around HEIC/JPG file extensions... case insensitive searches, but ignoring ".heic" files etc. This uncovered a bunch of other areas where I'd copy/pasted code... so for sideways copying videos and stuff. Find other things which just didn't work in testing at all (unescaped characters in bash expansion). I've no idea ho this worked previously... maybe it's a bash/ash difference which will **** things over in side the container. Yes, I'm an adult, I can put **** if I want to, so go **** yourself if you don't like it. 27 | - Rebased to Alpine Linux 3.21.3 coz apparently that's a thing now. 28 | - Aw man. This just feels really broken, but it's not chucking errors out everywhere in my testing. Sod it. I'll push it to Dockerhub and deal with the fallout tomorrow. I have stuff to do on Saturday so no drinking tomorrow for me. Can fix stuff then if everything blows up. 29 | 30 | 19/02/2025 31 | 32 | - Made notification_type case insensitive. Some dude was using "discord" instead of "Discord". Both will work now. 33 | 34 | 17/02/2025 35 | 36 | - Setting the umask via /etc/passwd didn't work as expected. Now configuring via /etc/profile as it should be consistent whether using interactive or non-interactive shells. 37 | 38 | 16/02/2025 39 | 40 | - Now setting umask based on file permissions. This should result in files being created with correct permissions, if they are created with a non-default umask (022), rather than relying on the functions to correct permissions being run. 41 | - Change the variable name save_ifs to OLDIFS. Think I like that more. 42 | 43 | 13/02/2025 44 | 45 | - Changed the launcher to check that notifications are configured correctly pre-launch. 46 | - Fixed Nextcloud URL log display which was missing a / character between the webroot and desination directory 47 | 48 | 05/02/2025 49 | 50 | - Changed the Telegram bot check so that it only occurs once, during the launch script. Once it successfully verifies the bot, it updates a new variable in the config file and will never check again. For some reason I see it failling to verify my own bot occasionally. I think the check fails randomly for some reason. I've no need to do it each time though as the update number is tracked via file in /config so it should be able to work even if verification fails. 51 | 52 | 02/02/2025 53 | 54 | - Amended initialisation script to hang if it can't create the temporary files 55 | - Changed the way it pulls info about container; using cut to return fields rather than sed to remove the thing I grep'd for 56 | - Container no longer hangs indefinitely if Internet connectivity fails during initialisation. Now waits for 5mins before restarting 57 | - Added 1min wait if version check does not succeed so that it give the end user time to notice the problem 58 | 59 | 25/01/2025 60 | 61 | - Changed the description for --keep-icloud-recent days warning. 62 | 63 | 23/01/2025 64 | 65 | - Bump to icloud-photos-downloader v1.26.1 66 | 67 | 21/01/2025 68 | 69 | - Launcher now waits for apple_id to be added to icloudpd.conf and continues once it's added. This is so I can create test containers with docker --rm and be able to add the apple_id variable and continue using the container. This means I don't have to pre populate a static volume and icloudpd.conf file 70 | 71 | 20/01/2025 72 | 73 | - New feature... Sideways copying of videos. This tells the container to copy/move all the videos it downloads to a different directory. It also has a command line switch so that it can be run against an existing library. 74 | 75 | 19/01/2025 76 | 77 | - Rebase to Alpine Linux 3.21.2 78 | - Scrapped using bash and gone back to ash. It just adds a bit of bloat and I've not used shellcheck as much as I expected to. 79 | - Moved the bits where it creates the requied directories and checks for updates to the launcher, so that it only occurrs during startup. I want the launcher to become a startup script and put all the "pre-flight check" stuff in there... such as checking the telegram bot is enabled, as this has a short pause in it and is annoying when it checks it each time you renew MFA. Same with all the DNS/route cheks. Files permissions. Also check config files variables aren't going to cause issues. 80 | - Massive re-working of initialisation portion of script. It is all performed in the launcher so it is only performed once, instead of each time the sync-icloud.sh script is run. This should make manual execution a lot quicker. 81 | - Disabled all the permissions stuff in the main script. The initialisation script should make sure the /tmp/icloudpd, /config, download and JPEG directories have the corect permissions. As long as I make sure the script switches the user correctly when creating new files, all should be good. Did this quite some time ago, but never disabled the permissions stuff. Hopefully it will work. 82 | - Massive bunch of changes in an attempt to make sensitive items partially obsucred so that they may still be verified by someone who knows the info, but still unrecognisable if it gets posted in part of a log file on GitHub 83 | 84 | 18/01/2025 85 | 86 | - Found a bug in the helthcheck code. If the container is uninitialised, then it can't read the exit code checks. If these files don't exist, then presume healthy, as the container should be waiting for user input. 87 | 88 | 14/01/2025 89 | 90 | - Something not right with the new option as it was enabling by default and configuring a value which would delete all files from icloud.com. Have changed the init_config.sh script so it doesn't set a defualt of 0, but defaults to blank and also so that it invokes the command on the presence of the variable. 91 | - Changed the command builder to use an array 92 | - Change the logging to not display the keep_icloud_recent_days variable if it is not set 93 | - Added an extra variable, keep_icloud_recent_only which has to be switched on to enable keep_icloud_recent_days. This is a dangerous feature and enabling it can just blow away entire icloud accounts. Also added two minute warning so it can be cancelled before any damage is done. 94 | 95 | 07/01/2025 96 | 97 | - Updated parameter name **keep_recent_days** to **keep_icloud_recent_days**. 98 | 99 | 06/01/2025 100 | 101 | - Changed some IFS declarations so they're all consistent (echo \b\n to $'\n') 102 | - Re-implemented Synology Photos App Fix which hadn't been brought in since the move to icloudpd.conf file configuration 103 | - Updated the bug report template 104 | 105 | 05/01/2025 106 | 107 | - Added **keep_icloud_recent_days** option. 108 | 109 | 04/01/2025 110 | 111 | - Messed about with the Telegram polling times so that when re-authenticating, the polling frequency increases so it's more responsive. Polling every 60s makes re-authentication take about 3mins. Should now be loads quicker. 112 | - Also noticed that additional sleeps in the Telegram polling sequence would mean the sync time slips by 2 seconds every 60. I've not checked logs to confirm, so this is only a suspicion. Have changed it anyway so that it should now be more accurate and slippage should only be by the amount of time it takes to process all the commands... so hopefully can be measured in miliseconds. 113 | 114 | 29/12/2024 115 | 116 | - Bump icloud-photos-downloader to v1.25.1 and pushed to GitHub. 117 | 118 | 25/12/2024 119 | 120 | - No update, just Merry Christmas! 121 | 122 | 18/12/2024 123 | 124 | - Another ImageMagick bug... grr. 125 | - Added additional checks at container first launch when creating the /config directory and /config/icloudpd.conf file. Script will now hang before initialisation if checks on these locations fail. 126 | 127 | 15/12/2024 128 | 129 | - Still seems like there is a bug in the JPG file conversion code. Doesn't seem to create the subdirectories it needs. Likely only ever worked if folder_structure was set to none. 130 | 131 | 13/12/2024 132 | 133 | - Imagemagick changed the "convert" command to "magick convert". I'm sure they must have a reason for it, but it seems a little daft... and annoying. 134 | 135 | 09/12/2024 136 | 137 | - Well that was weird. I just re-built the "upload library to nextcloud" function using the newly reworked "create directories" and "upload files" functions as a template and the function worked first time in testing. Ther'es gotta be something wrong with it, surely? 138 | 139 | 08/12/2024 140 | 141 | - A bunch more Nextcloud stuff done over the weekend. All functions now reworked except for the one that uploads the entire library. Not as much space saved as I'd liked. The upload function was actually pretty well written, plus I broke them into separate functions for creating directories and uploading files. I also found a potential issue regarding case sensitivity and the HEIC conversion tool, so added a bunch of code to handle that. Hopefully I'll get some time to finish the last function tomorrow/Tuesday. 142 | 143 | 05/12/2024 144 | 145 | - Bit of work regarding the Nextcloud stuff. Rewritten the function that creates the directories prior to uploading. It now generates the list of encoded directory paths before attempting to create them. Seems a lot cleaner. Code has gone from about 40 lines to 30 lines, a reduction of over 200 characters. Should be able to make similar gains surrounding the nextcloud_upload, nextcloud_delete and double the amoutn for the upload_library_to_nextcloud functions... iirc. 146 | - Also, Alpine Linux 3.21.0 has dropped so rebuilt with that. 147 | 148 | 03/12/2024 149 | 150 | - Bump icloud-photos-downloader to v.125.0 and pushed to GitHub. 151 | - Then found nextcloud upload handling doesn't work when the album name has a space in it. I've got directoy creation and upload working, but it's an absolute mess. I've no idea how it's actally working and seems way, way overly complicated. Needs some time spending on it. 152 | 153 | 24/11/2024 154 | 155 | - Did remember. 156 | 157 | 23/11/2024 158 | 159 | - Well, seems like remote auth was royally fucked with the switch from ash to bash due to the regex matching. Think it's fixed. Pretty sure there were PRs surrounding the photo_album and photo_lirary features that used the same regex based matches. they could be broke, but it's late, so I'm going to bed. I'm also a bit drunk so I'll probably forget to check if its broken tomorrow. 160 | 161 | 22/11/2024 162 | 163 | - Bump to iCloud Photos Downloader v1.24.4 164 | 165 | 10/11/2024 166 | 167 | - Changed the format of a lot of code. 168 | - Added bash shell and now running scripts in bash. This is because I've started using shellcheck to test the code and it doesn't support Alpine. 169 | 170 | 22/10/2024 171 | 172 | - Removed all the "Change: " prefixes in this file. I originally planned to have different stuff, and there probably are a few still in here, but they serve no value. 173 | - Changed the search for "True" and "False" in the config file replacements so that they are now case insensitive. TRUE will also now be changed to true. As FaLSe will be changed to false. 174 | 175 | 13/10/2024 176 | 177 | - Issue reported with a "] if statement preventing launch. Odd, as greppimg the main script for it returned a second result which must have been there for many, mant releases. 178 | 179 | 11/10/2024 180 | 181 | - Added the **webhook_insecure** option to allow use of self-signed certificates. 182 | - Some logging changes. 183 | 184 | 17/09/2024 185 | 186 | - Added PR from @Teknicallity which fixes a rare condition which would lead to an empty download_path variable and some other logging output changes. 187 | 188 | 16/09/2024 189 | 190 | - Rebase to Alpine Linux 3.20.3 191 | - Installed libheif 1.18.2-r0 from Edge repository which should allow for HEIC to JPEG conversion on iOS 18 192 | 193 | 11/09/2024 194 | 195 | - Merged PR submitted by RICwang which adds the Chinese translations for the remote sync feature and also enables the feature. 196 | 197 | 08/09/2024 198 | 199 | - Upload Library to Nextcloud function now checks for the existence of the file and skips if it's already there. Hopefully this wil speed-up the time it takes, at the expense of overwriting what's already on the server. 200 | - Amended Nextcloud offline notification to contain username. I've had two notifications for a possible five accounts. Not sure which accounts to look at. 201 | - Added a bit to detect whether a command line parameter has been specified. If so, it sets telegram_polling to false, so there's no delay when launching the script with command line parameters such as init and upload-library-to-nextcloud. 202 | 203 | 02/09/2024 204 | 205 | - Bump to iCloud Photos Downloader v1.23.4 206 | 207 | 01/09/2024 208 | 209 | - Bump to iCloud Photos Downloader v1.23.2 210 | - Telegram remote authentication is working perfectly now... only related issue is that it's spamming the logs. Need to figure out how to quieten it down. Task for another day, I think. I've done so many MFA re-authentications today, it won't be long before Apple block me again. 211 | - Bump to iCloud Photos Downloader v.123.3 212 | - Fixed an odd bug that when the container is waiting for authentication to complete, the cookie was being removed. Think this behaviour has changed somwhere down the line. Previously I think the file was being overwritten, no I think it removes the file at the start of the process. This resulted in a grep command not being able to find the file. Now, set the grep command to silence errors and just echo a value indicating non-detection instead. 213 | - A buncu of remote authentication tweaks. It's looking a lot nicer now. No debug info printed and logging nicely too. 214 | 215 | 31/08/2024 216 | 217 | - Fake user agent to fix IYUU notifications in China. 218 | 219 | 30/08/2024 220 | 221 | - Latest version of icloudpd changed the way that the phone numbers were output (specifically the ***** *****) part, so the remote auth feature would not fire out the Telegram notification with the number on. 222 | 223 | 26/88/2024 224 | 225 | - Reworked Telegram notifications so that they have a base URL instead of notification URL, this is to remove duplicated lines of code surrounding notifications and polling. 226 | - Amended health check to instpect the same cookie expiry value as the main script. 227 | - Changed log output so that next synchronisation dates which are more than 20 hours in the futuer display the locale's data and time. 228 | 229 | 24/08/2024 230 | 231 | - Now possible to configure Telegram to use HTTP instead of HTTPS by setting **telegram_http=true** 232 | 233 | 23/08/2024 234 | 235 | - Amended some error logs. 236 | - Amended cookie logic to pull expiry date back for X-APPLE-WEBAUTH-USER instead of X-APPLE-WEBAUTH-PCS-Photos. Seems the latter's expiry date is extended upon each sync where the former is the true expiry date of the cookie. 237 | 238 | 22/08/2024 239 | 240 | - Moved the logic for creating the files in the JPEG path. Seems it was attempting to create a blank directory for people who did not have this configured. 241 | 242 | 31/07/2024 243 | 244 | - Might have figured out the Telgram remote authentication expect scripts bollocks. 245 | 246 | 23/07/2024 247 | 248 | - Possible bug with jpeg_path veriable. Have added some stuff to check and set permissions. 249 | 250 | 21/07/2024 251 | 252 | - Got rid of the config_dir variable and hardcoded /config. Some systems dont' seem to be honoring it, even though it's specified in the container. 253 | - Bumped to v1.22.0 254 | - icloud-photos-downloader version output text has changed. Updated command so it's correctly displayed. 255 | 256 | 10/07/2024 257 | 258 | - Bug with Alternative size. 259 | 260 | 09/07/2024 261 | 262 | - Missed one of the new photo sizes. Alternative. Duh. 263 | 264 | 08/07/2024 265 | 266 | - Reworked user/group creation. As the config file is now user to specify the user/group and not variables, it would default to creating a user:1000 and group:1000 for the user and group. Then a user could come along and change say the username, but it would fail to change this due to the UID already being is use. Script has been changed to overwrite the /etc/passwd and /etc/group files with minimal ones (only a root user and only root, tty and shadow for groups). It will then create the new user. This should allow much better user switching within the container. 267 | - v1.19.0 brought in support for downloading multiple sizes of photo. Have now updated the container to support that too. 268 | 269 | 06/07/2024 270 | 271 | - Bumped to v1.21.0 272 | 273 | 03/07/2024 274 | 275 | - Seems one of the error logs was not using the error log function, but debug log function... fixed. 276 | - Got rid of full stops on ends of some log lines. What moron puts full stops at the end of lines nowadays? 277 | - Couple of issues in the init config file. debug_logging=AAAAA was one of them. No idea how that got there. Plus group_id=group. That's prob gonna break things. 278 | - Anoher config file related change. I now set the download_path variable default in the config file where previously it was blank. Now it checks for the user variable in the config, if there isn't one, it uses the user variable, if there isn't one of those either, it defaults to /home/user/iCloud. 279 | 280 | 01/07/2024 281 | 282 | - Bumped to v1.20.4 283 | - Script now checks for presence of config file before running. Exits if it doesn't exist as it indicates failure to write to /config, the directory where the container keeps its config files/cookies/keyring etc. 284 | - Removed some commented out code relating to different virtual environments when china_fix branch was also implemented. 285 | - Changed the cookie creation command from performing a dry-run and downloading 0 recent photos, to just performing the authentication only option. 286 | - Added an extra test to init_config.sh as changes made on 24th June did not account for first run. 287 | - Added an update check to startup. 288 | 289 | 30/06/2024 290 | 291 | - Added a bunch of stuff to set defaults for empty variables in icloudpd.conf. 292 | - Added force_gid to config file as it was missing. 293 | - Added live_photo_size too. 294 | - Removed some code relating to the old NextcloudCLI container I've deprecated a long while back. 295 | 296 | 29/06/2024 297 | 298 | - Bumped to v1.20.3 299 | 300 | 24/06/2024 301 | 302 | - Think I fixed an issue where files with spaces in the name won't upload to Nextcloud... edit: yeah, pretty much sorted it. 303 | - Added a test to init_config.sh to check file permissions before launch. 304 | 305 | 23/06/2024 306 | 307 | - Not uploading to Nextcloud if the filename has a space in it. Made amendments to see if I can work out why. Seems that spaces are not being changed to %20. Made some logging changes to to check. 308 | - Found and fixed the Nextcloud upload issue. 309 | - Just found that the function which deletes empty directories wasn't actually implemented correctly. Now fixed. 310 | 311 | 17/06/2024 312 | 313 | - Bumped to v1.20.0 314 | - Amended init_config.sh so that it won't match two search results when searching for variables e.g. 'name=' previously matched 'name=' and 'nextcloud_username='. 315 | - Amended the profile so that the path variable now includes the new location for icloudpd. 316 | - Amended startup part of main script to get rid of default values in script. All these should now be set by the init script and pulled from the icldoupd.conf file. 317 | - Implemented the --keep-unicode-in-filenames functionality. 318 | - Implemented the --live-photo-mov-filename-policy functionality. 319 | - Implemented the --file-match-policy functionality. 320 | - Implemented the --align-raw functionality. 321 | - Was counting lines of the download check with grep. Swapped to wc, just in case something iffy is happening with "grep -c ^". 322 | 323 | 10/06/2024 324 | 325 | - Fixed bug introduced by SMTP notifcation. 326 | - Fixed bug in config file initilisation script. 327 | 328 | 06/06/2024 329 | 330 | - Bumped to v1.19.1 331 | - Merged pull request from @klesh to add SMTP notification support. 332 | 333 | 01/06/2024 334 | 335 | - Bumped to v1.19.0 336 | 337 | 28/05/2024 338 | 339 | - Bumped to v1.18.0 340 | 341 | 26/05/2024 342 | 343 | - Just removed some obsolete stuff from the dockerfile. 344 | - Added date to build number. Some dude was running a version that was 3 years old. I removed the date from the build number a year or two ago, coz I thought most people would be fairly up to date. Obviously not, so I've put it back. Will help identifiy people doing the same thing. 345 | - Changed the date format due to the change above, and also coz it's just easier to read when units go from smallest to largest. Reverse dates work best on filenames so they are automatically stored chronoligically, but that's not really needed here. 346 | 347 | 25/05/2024 348 | 349 | - Bumped to v1.17.7 and relocated python venv from /opt/icloudpd_latest to /opt/icloudpd as I'm no longer installing two versions into the container. Also... noticed Alpine is now at version 3.20 350 | 351 | 24/05/2024 352 | 353 | - Bumped to v1.17.6 354 | 355 | 16/05/2024 356 | 357 | - Amended the log output for when an error is detected to instruct the end user to gather a debug log before posting the issue to GitHub. Too many muppets posting a single line of error message and expecting me to know what they've messed up to break it in that way. 358 | 359 | 09/05/2024 360 | 361 | - Added a check to see if the download path is writable. A lot of users have reported seeing the error: "Invalid value for '-d' / '--directory': Path '/download_path' is not readable" and although some users it appears to be due to SMB file mounts, I suspect it is the age old issue that their permissions are just inadequate... like SMB permissions don't just have file permissions, they have user permissions via the share itself, and it's likely just that. I now check that the download directory is writable. If not, then I attempt to set the permissions as root for the configured user, then re-test. If it fails, I just bail and kick out an error. 362 | 363 | 05/05/2024 364 | 365 | - Added nano, nano-syntax and findutils to the container. BusyBox fnd doesn't support -printf and I need that to add the nano syntax highlighting stuff to /etc/nanorc. 366 | - Altered the script to add some delays in where people attempting to run as root. Running as root is not supported as this script expects to switch user at times and it breaks the su command. 367 | - Added /opt/icloudpd_latest to path so icloudpd and icloud can be run without having to cd to their locations. 368 | 369 | 04/05/2024 370 | 371 | - Some stuff to make command line life easier. 372 | - Removed a bunch of redundant stuff from the Dockerfile. 373 | 374 | 28/04/2024 375 | 376 | - I got rid of the bit which attempts to detect TCP connectivity to a DNS server which curl has been found to use. It seems that for some people, curl works just fine with UDP lookups. The people who are having problems with curl can firgure out their issue instead. 377 | - The reauth script wasn't setting the cookie directory, so the re-authed cookie would land in the root user's home directory instead. Also, the re-auth command would be run as root, so permissions could get messy. Changed script to use a run_as function, so it's runs as the configured user. 378 | - Have modified the script so that it handles the new behaviour for remotely authenticating when using SMS based MFA. Haven't tested it much yet, so probably broken lol. I need to push to Dockerhub so I can test on the accounts I do SMS MFA from, as these don't run my testing builds. 379 | 380 | 05/04/2024 381 | 382 | - Amended logic for remote authentication coz the first way I did it was pretty stupid. This allows for users to have numbers in their Apple ID, which is nice. 383 | 384 | 31/03/2024 385 | 386 | - reauth.sh script now checks for auth_china variable and configures authentication domain. 387 | 388 | 27/03/2024 389 | 390 | - Telgram messaging can now re-authenticate your icloud.com account. 391 | - Added reauth.sh script so that re-initialising the cookie is easier. 392 | 393 | 20/01/2024 394 | 395 | - Gotify can now be configured for HTTP or HTTPS connections. 396 | 397 | 07/01/2024 398 | 399 | - Merged PR from @timrettop which fixes HEIC to JPEG conversion in Alpine 3.19.0 400 | - Created option to disable the startup notification. 401 | - Removed the silencing of the set_mempolicy error. The convert application does not generate these errors any more after the jpeg package was installed in the container. It seems that the convert application was just converting the HEIC file to another HEIC file, but with the JPG file extension. Installing the package means the conversion processes a lot more quickly and correctly outputs the files. 402 | - Made the functions which convert HEIC files to JPEG, remove all HEIC files, etc do case insensitive searches. This should conver *.HEIC and *.heic to *.JPG. 403 | 404 | 06/01/2024 405 | 406 | - Rebuild container to icloudpd 1.17.3 407 | 408 | 01/01/2024 409 | 410 | - Altered how Nextcloud path is built. Seems there are double trailing slashes. Now removes the one in the variable and appends one manually. 411 | - Seems like deleting from Nextcloud wasn't working correctly. Was always coming up with "File not found" which is bizarre coz it defo worked when I first implemented it. I've changed it so it uses the same encoding method as uploads, instead of doing it using a standard file path (which wouldn't work for files/paths with spaces in their names). Anyway, seems to be deleting things OK again now. 412 | - The curl command performs TCP DNS lookups, whereas everything else like ping and nslookup uses UDP. If TCP requests are blocked to the DNS server, the container will pass the DNS checks, but then the curl commands to send the startup notifications will hang/fail. Bind-tools has been installed so that the full version of nslookup can be used and force the requests over TVP (using the -vc parameter) 413 | 414 | 30/12/2023 415 | 416 | - Removed a log entry which was malformed. 417 | - Output value of bot_check into debug log. 418 | - Added random delay of 0-15 seconds (inclusive) if Telegram polling is enabled. I believe starting multiple containers at the same time and them all hitting the APi at the same time may cause some requests to be ignored. 419 | 420 | 27/12/2023 421 | 422 | - Documented the --remove-all-jpgs command line option. It's been there ages, not sure why I never documented it. Probably because it could delete data that might be needed and I don't want people losing stuff. 423 | 24/12/2023 424 | 425 | - I think the upgrade to Alpine 3.19 may have broke Imagmagick. Adding the additional package, libheif-dev, got it working again. 426 | - Also, found that due to the newer version of ImageMagick, an error is generated when converting HEIC to JPEG. The conversion still works, so I've just silenced that error, specfically. 427 | 428 | 20/12/2023 429 | 430 | - Upgraded to use Apline Linux 3.19.0 and icloudpd 1.17.0 which now includes the authentication fix. Won't work for China as they were having to use an old version of iCloudpd which is packaged into my container. New version may work ok, but script will need significant re-write for that to occur. Probably look at it over Christmas. 431 | 432 | 19/10/2023 433 | 434 | - Added a new Nextcloud URL encode function. If a user is downloading to Photo Albums, or Shared Libraries, or even just has files with special characters (like spaces) in them, then the Nextcloud URL needs to be encoded. 435 | 436 | 18/10/2023 437 | 438 | - Trailing slashes on $download_path messes up Nextcloud uploads. Added a bit into the upload function which should account for this. 439 | 440 | 10/10/2023 441 | 442 | - Downloading albums not working correctly. Fixed it up and added a bunch more logging. Probably needs sames for libraries function. 443 | - Done the libraries function too. 444 | 445 | 09/10/2023 446 | 447 | - Bug found in health check... Was using wrong filename for error checks. 448 | - Another bug in health check... Filename followed with a } making it invalid. 449 | - icloudpd seems to error with an exit code of 0 when MFA is required but aborted due to being a non-interactive session. Have now added a check to see if the error report file has size. 450 | 451 | 05/10/2023 452 | 453 | - Some users attempting to set the user for sync to root. This breaks things as it can't create the local user account or switch users correctly, so now it will default to user:1000 group:1000 if root:0 is detected. 454 | 455 | 04/10/2023 456 | 457 | - Just some logging stuff. 458 | - Found a couple of bugs surrounding downloading from photo albums/libraries. Squished. 459 | 460 | 02/10/2023 461 | 462 | - A bunch of stuff regarding the new shared library support. 463 | - Fixed missing --enable-debugging and --disabled-debugging launch parameter validation. 464 | 465 | 30/09/2023 466 | 467 | - Found a bug which was causing videos to be skipped. Fixed it. 468 | 469 | 29/09/2023 470 | 471 | - Found that the path of the python_keyring firectory can be configured by setting the XDG_DATA_HOME variable. Configured that and then removed a bunch of code relating to moving it from /home/${user}/.local. 472 | 473 | 27/09/2023 474 | 475 | - Thought I'd just commented out all the lines required for the shared library downloads. Turns out I'd removed the bit which validates command line options, so re-added those. 476 | - Also, realised that there was a mismatch between what the allowed command line variables were, and the actual variables. This affected all variables with more than one word in them, except for the latest one I added to upload Nextcloud files. 477 | 478 | 26/09/2023 479 | 480 | - Re-enabled the libraries feature now upstream code has been merged to support shared libraries. Untested though, as I don't use libraries myself. 481 | 482 | 20/08/2023 483 | 484 | - Added a bit to create some extra temporary files as some people are having issues with them. 485 | 486 | 14/08/2023 487 | 488 | - Added Nextcloud connectivity check. My Nextcloud container was paused during a large offsite backup, so Nextcloud connectivity borked. This meant fails failed to upload and will not longer be retried. I can fix by running the function to upload the entire library, it's just a bit of a ballache, so I've added a connectivity check which will wait for the Nextcloud server to come back online. It will send a notification on the first failure, then again after an hour. 489 | - Added '--help' command line option which displays the configuration information. This is coz I couldn't remember the name of the function which uploads all files to Nextcloud when I was writing the above change log entry lol. 490 | 491 | 02/08/2023 492 | 493 | - Amended the code so that it now checks for, and removes empty folders from the Nextcloud server after it's removed stuff from Recently Deleted. Works on the test uns I've done. Not nuked my photo store, which is good. 494 | - Also amended the code which parses the download logs for downloaded/deleted files. I used to extract column 5 to know what files to process. This doesn't work for file paths with spaces in them. I've now amended it so that it cuts from column 9 upwards, which should give me the whole file path, including spaces. Haven't tested anything yet. Wish me luck. Wow. It worked first time! Tested downloads, deletes, uploads to Nextcloud, deletes from Nextcloud. Life is good. 495 | - Written a function which allows uploading of the entire library to Nextcoud in one go... Invoke with sync-cloud.sh --upload-library-to-nextcloud. 496 | - Amended the command line parameters so that ones that are multiple word, have spaces separated by hyphens like the command above. 497 | - Removed a few more checks to see if variables are present, over an acual value of true/false. 498 | 499 | 01/08/2023 500 | 501 | - Well, that's the code in to delete empty folders. It's a bit of a hack. Basically I parse the XML that is returned and count the number of links it contains, if it only has a single link (a reference to the folder itself) then the directory is removed. If there is more than one link, the directory is left in place. This is probably going to delete loads of stuff by mistake, but here goes. 502 | 503 | 31/07/2023 504 | 505 | - So, it turns out that when uploading to Nextcloud via WebDAV, the file paths are not automatically created when you 'put' a file. In addition to that, when you create a pathname, you can't automatically create all the folders in the path at once (e.g. mkdir --parents /folder1/folder2/folder3). New change now creates a unique list of the directory names from the list of newly downloaded files. It then checks if each directory in turn exists, and if not, creates it. I guess I need to look at pulling the properties of the folder, checking if it is empty, and if it is, removing it during the file removal function. Otherwise I may end up with empty folders on my Nextcloud server. 506 | 507 | 24/07/2023 508 | 509 | - Simplified the logic for auth_china. I want to eliminate chcking for the presence of a variable as being **true** and absence of a variable as **false*. This results in a tendancy towards **true*, because if it contains any value at all, it's considered **true**. Someone had configured a variable to be **false**, which means it is technically **true**. Logic is now that `if [ "${auth_china:=false}" = true ]`. This kinda flips it. Now, it's only true if it's set to **true**. If it's set to **motorboat** it's now technically **false**. 510 | 511 | 23/07/2023 512 | 513 | - Changed how the icloud_china variable is processed. I was just checking if the variable was present, so setting it to **false** was equivalent to **true**. 514 | - Oops. Didn't test that one very thoroughly. Forgot that notifications check if the **icloud_china** variable is empty if they want to send English notifications. Pushed a new version to correct that. 515 | 516 | 22/07/2023 517 | 518 | - Did some stuff around cookie creation. Script would progress immediately after cookie file was created, however, this happens when the user is prompted to authenticate, so background script would continue as user is confirming 2FA. 519 | - Changed 2FA to MFA. The correct term is Multi-Factor Authentication, not two factor authenticaion. 520 | - Introduced a bug as I forgot to change the healthcheck.sh... oops. 521 | - I'm not sure when I did this, but I scrapped the older_function thing as I'd changed how this process worked and it is no longer needed. 522 | 523 | 21/07/2023 524 | 525 | - Added a function to check if the cookie exists. Two of my containers' cookies expired and the auto-heal feature kept restaring them. This meant when they were starting up they were attempting to connect to iCloud so they could list the available abums. This was causing an MFA prompt. New function just pauses the script if a cookie does not exist. 526 | 527 | 11/07/2023 528 | 529 | - Synology systems create multiple directories which contain extended attributes, such as thumbnails. These will be seen as "eaDir*" directories. New variable added to skip changing permissions on these directories. PR submitted by tsanie. 530 | 531 | 09/07/2023 532 | 533 | - Rewrite of remot sync function. Should be a little bit more reliable, as the previous one would get differeing results back from the Telegram API. Now it goes by update ID and stores it's progress to a file, so that it doesn't fail to retrive a correct value at boot. 534 | - Added function to remove deleted files from Nextcloud. 535 | 536 | 04/07/2023 537 | 538 | - Re-enabled **delete_after_download* functionality as recent fix has been merged upstream. 539 | - Altered the clean up of the *notification_title* so that it does not apply to the commonly used Chinese notification methods. 540 | - New function to Nextcloud server. This allows uploading directly to a Nextcloud server, rather than just dropping a file for a different container to monitor and then start a manual sync. 541 | 542 | 03/07/2023 543 | 544 | - Rebuilt with iCloud Photos Downloader 1.14.2 545 | 546 | 27/06/2023 547 | 548 | - Rebuilt with Alpine Linux 3.18.2 and iCloud Photos Downloader 1.13.4 549 | 550 | 26/06/2023 551 | 552 | - Just chown the tmp and configu folders as some people seem to be having issues with keyring file which is solved by moving the file from the true location to the /config dir location. Possibly a permissions issue. Not sure. 553 | - The Telegram polling is still acting weird. Sometimes, the latest message reported by the telegram API will actually be an older message, then a second call to Telegram's API will return a higher number. Also, after a while, all messages will just disappear, meaning null values are passed back. Hopfully these should be fixed now. 554 | - New variable added **albums_with_dates** which should allow albums to be downloaded to folders with the album name, and then split by date. 555 | 556 | 22/05/2023 557 | 558 | - Send notification when remote sync command received. 559 | 560 | 04/05/2023 561 | 562 | - Out of range issue polling for Telegram wake command. 563 | 564 | 26/04/2023 565 | 566 | - Added **skip_album** variable so that certain albums (such as the Apple default ones) can be excluded from the download. 567 | 568 | 25/04/2023 569 | 570 | - Unexpected behaviour when latest message is an update about group membership. This change will set ${latest_message} to ${current_message} if ${latest_message} is empty. 571 | - Now supports downloading from multiple folder albums, and saving them into folders named accordingly. 572 | 573 | 24/04/2023 574 | 575 | - Bug fix for folder structure. 576 | - Possible solution for Telegram polling issue. 577 | 578 | 23/04/2023 579 | 580 | - Set the function to configure file ownership to use user and group ids (instead of names) as this was failing to set the correct permisisons prior to the accounts being created (brand new containers). 581 | 582 | 202304022 583 | 584 | - So, I messed up and accidentally pushed a version I was working on, to Dockerhub and to Github, which contained my Telegram API token. I had to delete the latest tag from Dockerhub, reset the master branch on GitHub and rotate my Telegram token. What a pain in the arse. That means this latest version is a little rushed, so don't be surprised if it's broken. It does bring within it a cracking new feature though... I've now coded it so it polls the Telegram chat (the one it normally sends notifications to), and if the latest message is the same as the **user** variable, it will force an immediate sync. That way, if you've taken a load of photos on your phone, you can tell your NAS at home to sync the queue once you're done. 585 | - This is just a change to the documentation. There has been a variable for Telegram **telegram_silent_file_notifications** which sends the telegram notifications for download/delete previews silenetly, I just noticed it's not been documented so I thought I'd change that. 586 | - I've re-enabled the **delete_after_download** option as it's been implemented correctly upstream and newly released. 587 | - I've also removed the i386 build platform from my images. They're failing to compile as they can't build a wheel for cryptography. I'm hoping nobody complains, but it they do I'll look at trying to re-add it in. 588 | 589 | 10/04/2023 590 | 591 | - So many changes, I've forgotten most of them... Um... now I use Python virtual environments to install mutliple versions of icloud-photos-downloader... It incorporates v1.12.0, which is used for downloading photos globally. It also uses a modified version of v1.7.3 which allows authentication against the China servers. Also rebased the image to use Alpine 3.17.3. 592 | 593 | 03/04/2023 594 | 595 | - Changed any "True/False" variable to "true/false" and also find/replace title cased variables in icloudpd.conf to lower case. 596 | 597 | 01/04/2023 598 | 599 | - Some stuff around how build tags are handled in docker buildx. Mainly so that I have versioning in Dockerhub, instead of only a single "latest" container. 600 | - Delete after download option removed due to bug where it deletes files from iCloud that have not been downloaded yet. 601 | 602 | 01/04/2023 603 | 604 | - New build refers to --china-mainland rather than --domain cn. 605 | - FFS, I typo'd it as --china_mainland. 606 | 607 | 31/03/2023 608 | - Rebuilt using icloudpd and integrating PR 608 https://github.com/icloud-photos-downloader/icloud_photos_downloader/pull/608 in an attempt to fix the on going China authentication issue. 609 | 610 | 30/03/2023 611 | 612 | - Got rid of auth_china variable, as it doesn't work. 613 | 614 | 29/03/2023 615 | 616 | - Altered the way the log file creation works so that it can add missing/future variables and sort it into alphabetical order. 617 | 618 | 28/03/2023 619 | 620 | - Urgh. It's filth, but it might just work... Docker container downloads a forked copy of icloud_photos_downloader which has been customised to work with China's authentication servers. The cookie generation function will now swap out/in these versions of the files if the auth_china variable is set to True, or just set really, I'm not fussy on this one. 621 | - Added a --enabledebugging and a --disabledebugging command line option which will edit the config file to enable/disable debugging for people who may find editing a file a little trickier on some systems... I'm thinking NAS devices may not make this so easy. 622 | - Made it so that the script reads the value of the debug_logging variable prior to each sync. That way debug logging can be switched on using the command line and it will apply on the next run. 623 | 624 | 26/03/2023 625 | 626 | - Complete overhaul of logging system. A DEBUG log now introduced which removes a lot of the standard logging and puts it behind this option. When it's enabled, it will hide some of the sensitive data such as e-mail address, Apple ID and tokens. 627 | - Complete overhaul of variables. Docker variables are now pushed into the icloudpd.conf log file stored in the config directory. This allows users to toggle options by just changing the log file and restarting the container. This will allow people to switch on/off deug logging, or change the notification method without having to remove and recreate the container. 628 | - The --ConvertAllHEICs function was missing a command to create the destination folders. This broke the cuntion is being used in conjunction with the jpeg_path variable. 629 | 630 | 15/03/2023 631 | 632 | - Rebuild to Apline Linux 3.17.2 and icloudpd 1.12.0 633 | - Added delete_after_download variable. This moves all downloaded files to 'Recently Deleted'. Cannot be used with auto_delete variable. 634 | - Added support for Telgram proxy server using the variable telegram_server. 635 | 636 | 08/02/2023 637 | 638 | - Added function to delete all JPGs that have an accompanying HEIC. Use --RemoveAllJPGs to remove that data. Be warned, JPGs that have matching names with a HEIC file in the same folder will be removed. They may not necessarily be the same photo. Use with car and only if you know what you are doing. 639 | 640 | 28/12/2022 641 | 642 | - Added details in readme warning users about enabling Apple's new Advanced Data Protection feature in iOS 16.2 643 | - Changed the way permissions and ownership is applied so that it it only applies permissions to the download directory after a sync, rather than at multiple different times. 644 | - Slight change needed to user creation due to rebase to Alpine 3.17 645 | 646 | 03/12/2022 647 | - Added --Init so I'm less likely to typo it. 648 | - When downloading from libraries it now lists available libraries to the log file. 649 | - The iOS16 shared libraries patches have been moved to a new container tagged as "testing", something isn't quite right as many people are reporting errors. 650 | 651 | 21/11/2022 652 | 653 | - iOS 16 shared libaries can now be downloaded with the --library option. PR courtesy of Patrice Neff https://github.com/pneff. 654 | - Added support for WeCom proxy via PR submitted by Alano-i and Sowevo. 655 | 656 | 08/10/2022 657 | 658 | - Incorrectly named variable. changed bark_key to bark_device_key. 659 | 660 | 26/09/2022 661 | 662 | - Amended the log entries so that the warning about setting icloud_china variable only appears if it's not already set. Also changed it to prompt the user to log into iCloud.com and make sure there are no pop-up notifications. 663 | 664 | 23/09/2022 665 | 666 | - Bug in --RemoveKeyring preventing it from launching. 667 | 668 | 12/09/2022 669 | 670 | - Documentation updated, changed --DeleteKeyring to --RemoveKeyring. 671 | - Added feature to place converted JPEGs into different folder from the download location. 672 | 673 | 08/09/2022 674 | 675 | - Keyring file wasn't actually being removed during container initialisation... Bizarre. Always thought it was. Maybe I removed it while ago, but forgot. Added a --DeleteKeying command line switch to remove the keyring file, if it exists. 676 | 677 | 28/08/2022 678 | 679 | - Bark modifications. 680 | 681 | 27/08/2022 682 | 683 | - Amended error relating to unindexed an library. Error says to wait minutes, it should say hours. 684 | 685 | 24/08/2022 686 | 687 | - Added Bark notifications (untested) 688 | - Two fixes required for Python 3.10+ support. 689 | - Pull request: Merged PR from rmlanghopto to add Gotify notifications. 690 | 691 | 28/07/2022 692 | 693 | - Set the function, which corrects the owner and permissions, to run after a successful download. Now permissions are corrected immediately after files are downloaded, rather than at the start of the next scheduled synchronisation. 694 | - Moved the Nextcloud trigger to the donwloaded/deleted files notification sections (as it's essentially notifying Nextcloud that it needs to sync), but also because where it was currently executing, meant that it would only trigger a sync on successful downloads, not if files had simply been removed. Now it will trigger a Nextcloud oneither of those two events. 695 | 696 | 27/07/2022 697 | 698 | - Added trigger_nextlcoudcli_synchronisation variable. 699 | 700 | 20/07/2022 701 | 702 | - Changed the notification events so that they can have a larger range of icons and different ones too. 703 | - Gone live with some changes to WeCom notifications. 704 | - Amended the find command to ignore symbolic links when changing the group on downloaded files and their folders. This is due to the Synology Photos app creating symlinks in the download folders. 705 | 706 | 19/07/2022 707 | 708 | - Partially added some Chinese translations, still a few questions about some other changest that have been requested, so holding off on all of them. 709 | - Cookie expiration is not working for expired cookies, only the notifications about expiry. This change should change the icon in the notification to the alert symbol when cookie has actually expired. 710 | - Reverted the changes that were put in place to support root user. Running as root user is really bad, and I probably should have just sacked it off when it was requested, so I'm putting it back to how it was because some people using unRAID devices have seen a load of issues, because they're actually running the thing as root... Why even is that? 711 | 712 | 17/07/2022 713 | 714 | - Evaluate the home directory location of the user... If user is root, home is located at /root. On CoreElec/LibreElec/OpenElec, then the user's home directory is in /storage. This change should work for either of those scenarios. 715 | 716 | 16/07/2022 717 | 718 | - Allow badnames when creating user id. 719 | - UNTESTED BETA: Added WeCom, the official method of WeChat notifications. 720 | 721 | 20/05/2022 722 | 723 | - Add option to download from single album. 724 | 725 | 18/05/2022 726 | 727 | - Bug in logic about 2FA renewal reminders means they aren't being sent out daily. I've changed logging to help identify where the problem lies. 728 | 729 | 30/04/2022 730 | 731 | - Added single_pass variable which runs the sync process once before exiting out. 732 | 733 | 28/04/2022 734 | 735 | - Merged PR from eliempje (https://github.com/boredazfcuk/docker-icloudpd/pull/155) which adds Openhab functionality. 736 | 737 | 11/04/2022 738 | - Apparently Synology devices have a Photos app which doesn't update when a download takes place. The newly created files need to be touched for it to trigger a re-index. I've added a new variable which will touch a file to trigger the update, then set it back to what it was to maintain integrity. 739 | 740 | 04/03/2022 741 | 742 | - Added a check for the IP address lookup performed during initialisation. Previously this could fail if the DNS server was not available (due to container starting on same system as a DNS container). It will now retry the lookup for 2mins before failing. 743 | - Changed cookie generation name from Generate2FACookie to GenerateCookie as it generates both types of cookie and is run no matter what cookie type you use. Also, changed the log output so it doesn't say "Generating 2FA Cookie" when generating a Web cookie. 744 | - Urgh. I dunno about you, but sometimes I delete a bunch of stupid memes off my phone and it ends up leaving my folder_structure with empty directories. I've now added a variable delete_empty_directories which will tidy those up. 745 | 746 | 20/02/2022 747 | 748 | - The timezone variable is not being set globally if it's not specified, so the underlying Python script generates a warning. The TZ variable is now defaulted to UTC in the Dockerfile which should take care of this. 749 | - Added an extra synchronisation interval of 21600, which synchronises every 6 hours. This generates a warning message detailing the potential issue and fix, and includes a 2 minute wait on container start time to make the warning prominent. 750 | - Pushover notifications not showing downloaded/deleted file previews. 751 | - Added function to allow deletion of HEIC accompanying files. These would be the converted JPG files (if using the HEIC to JPG conversion feature) and the_HEVC.MOV files which come with live photos. 752 | - Bug found with sending Telegram notifications if e-mail address has an _ character in it. The _ character wasn't escaped correctly when passing the text to the Telegram API. 753 | - The Notify function is always called, even if notifications aren't configured. A log entry was still being processed and the logic for determining a success/failure of the sent message was being performed too, resulting in an 'out of range' error, as an empty variable was being processsed. 754 | 755 | 18/02/2022 756 | 757 | - Cookie expiry notification period was being defaulted in the Initialise function and also for every notification type configured in the ConfigureNotifications function. Removed these as they're just duplicates. Also corrected a bug which would override user configured period, configuring it to 7. 758 | - Logic for skipping the file check was checking if value it true, then following two if statements had to check if it was set to false. Don't really like the OR tests that needed to be done so adjusted the logic two set the check_exit code and check_files_count to values that will always perform a sync, as they should never change due to CheckFiles function never being run. 759 | - Script doesn't seem to reliably send the "once per day" notifications during the nofitication expiry period. So have added a log entry which details the next notification time. 760 | - Huge overhaul of notification system. Probably broken everything lol. I've tested Telegra, Prowl, Discord and Pushover. I don't have any of the other methods to test though, so can't guarantee they work, or are formatted correctly. I've standardised the way notifications are sent to the Notify function. This way, only one notification command is needed to send notifications for all types and the formatting of the messages is now handled by the Notify function. 761 | 762 | 11/02/2022 763 | 764 | - Introduced a bug with my error handling from the last update. Now I create the error and exit_code log files in the Initialisation function of the script so that the correct permisions are applied. Problem was that the files were being created as root, and then the python script didn't have permission to them. 765 | - Status code checking for the curl command when sending notifications would only accept 200 as a success. Discord reports 204. I've amended the checking code to just accept 2xx. 766 | 767 | 10/02/2022 768 | 769 | - Handle errors differently. Added suggestions for authentication failure issues. 770 | - Eliminated full stops from log entries. Some lines had them, others didn't, so standardised on not having them. We don't need them. They take up precious bytes. They giving me flashbacks to COBOL. 771 | 772 | 09/02/2022 773 | 774 | - Broke out logging into three separate functions. Just because. I dunno. Having tonnes of $(date '+%Y-%m-%d %H:%M:%S') commands in the script just looked a bit weird. Plus if I even need to change it, it's three lines instead of a find/replace of a bazillion. 775 | - Set ConfigureNotifications function to check for presence of Dingtalk token, instead of ID, to decide whether to fail or not. Same as the other methods (except webhook, which doesn't use tokens). 776 | - Altered priority for Pushover warning/error notifications. Previously they were set to -2, which gives no alert, only changes the icon badge number. Now it sends a standard priority alert for all notification types, except the one that alerts you that your cookie will run out that day, which is a "quiet hours breaking" notification (same for Prowl). 777 | 778 | 06/02/2022 779 | 780 | - Amended the code which sends to Pushover notifications. Originally this was two separate commands, depending on whether the pushover_sound variable was sent. This is not needed, as Pushover will use the default sound is a blank value is sent for the "sound" argument. 781 | 782 | 04/02/2022 783 | 784 | - Changed notification failure log messages to be more accurate and display http status code. 785 | - Prowl notifications logging not quite right. 786 | - Standard notification log message. 787 | - Moved some stuff to prevent duplication of code. 788 | 789 | 23/01/2022 790 | 791 | - Added variable to set custom sounds for Pushover notification type. This can be used to silence notifications. 792 | 793 | 12/12/2021 794 | 795 | - Fix bug introduced by moving to useradd/groupadd. 796 | 797 | 11/12/2021 798 | 799 | - Amended domain and route checks to be compatible with icloud_china variable. No point checking the route to icloud.com is OK when the container downloads from icloud.com.cn. 800 | - Change to IYUU notifications, which should now get these working, and integrated changes to live. 801 | - Users and Groups now created with useradd/groupadd instead of adduser/addgroup to allow the creation of users/group with an id greater than 256,000. 802 | 803 | 30/11/2021 804 | 805 | - Final text alert for cookie expiration not received. Made a couple of changes. Will check again in 3 months time. 806 | 807 | 27/11/2021 808 | 809 | - Added the IYUU notification type. This is a 3rd party service which supports forwarding messages to WeChat. 810 | 811 | 25/11/2021 812 | 813 | - Added variable icloud_china so that photos are downloaded from icloud.com.cn instead of icloud.com 814 | - Removed readme information about obsolete variable interavtive_only. The readme is nearing it's maximum size on Dockerhub, so this helps keep tthe number of characters down. 815 | 816 | 21/11/2021 817 | 818 | - Amended healthcheck so that it does not set the status to unhealthy if notification days is less than 7. This was causing my auto-heal container to automatically restart it every 5mins. This behaviour now only occurs when the cookie has actually expired. 819 | - Made script launch parameters case insensitive and also allowed for non-English spelling of Initialise. 820 | 821 | 24/00/2021 822 | 823 | - Set icloud.com DNS query type to 'a' records only so that it returns the IPv4 IP address and not IPv6. Also, removed the local loopback addresses, just to remove more unwanted data. 824 | 825 | 20/10/2021 826 | 827 | - Discord notifications added by @ibtvt. 828 | 829 | 17/10/2021 830 | 831 | - Added detection for when a file has been downloaded, but also exists in 'Recently Deleted', so gets removed post download. In these cases, it should get rid of the missing file errors. 832 | 833 | 21/09/2021 834 | 835 | - Added DNS lookup and traceroute to check network connectivity to icloud.com. 836 | 837 | 15/09/2021 838 | 839 | - Removed the code which removes hyphen characters from the Telegram chat ID. The hyphen character is used to denote the bot has been added to a group to send messages, rather than a standard user chat. 840 | 841 | 13/09/2021 842 | 843 | - Replicated recent whitespace changes to additional functions for completeness. 844 | - Force convert HEICs functions now remove pre-existing JPG files as I don't think the convertion tool is overwriting pre-existing files. 845 | 846 | 12/09/2021 847 | 848 | - Bug introduced by recent HEIC to JPEG change. Fixed. 849 | - Added set owner and permissions function to run after HEIC to JPEG conversion functions. 850 | - Added --ForceConvertAllmntHEICs. 851 | - Fixed bug in above new command line option as it wasn't handling spaces safely. 852 | 853 | 06/09/2021 854 | 855 | - HEIC to JPEG coversion utility has a bug and rotates JPEG files when it shouldn't. Replaced with ImageMagick which doesn't have this problem. 856 | - Added --ForceConvertAllHEICs command line option to overwrite JPEG files. This is so over-rotated files can be replaced with crorrectly oriented versions. 857 | 858 | 02/09/2021 859 | 860 | - Added a log entry to show the UID of the account that is running the script. It seems some users are having permissions issues and this will help with debugging. 861 | - Also changed the symbolic link command to force linking of python_keyring directory if something that isn't a link exists in its place. 862 | 863 | 01/09/2021 864 | 865 | - Added logging for --ConvertAllHEICs function. 866 | - Amended way script handles expired cookies. 867 | 868 | 30/08/2021 869 | 870 | - Amended script to wait for the cookie file to be created, then continuing once it's appeared. Container will auto restart after 30mins if file does not appear, just in case something has gone wrong. 871 | 872 | 27/08/2021 873 | 874 | - Better way of calculating elapsed time. 875 | - Amended the logging so it lists the expiry time of expired 2FA cookies for clarity. 876 | - Amended the way the script waits for the keyring and failsafe files to be created. Instead of checking every 5 minutes before proceeding, script now checks every 5 seconds. This way, script is a bit more responsive when being initialised. If the script doesn't detect the file it's checking for after 30 minutes, it will exit the container, just in case there is a detection problem. 877 | - Added logging info explaining that the download check takes a long time on large collections and that nothing is being downloaded during that phase. 878 | - PR Merge: Changed how the webhook notification is handled for compatibility across more notification platforms. 879 | - Script now records time taken per synchronisation and subtracts this from the synchronisation_interval. This means a sync with a 12 hour interval will start 12 hours later and not skew by the amount of time it takes the sync to complete. 880 | 881 | 27/08/2021 882 | 883 | - Locked version of tzlocal to 2.1 as lateat version introduces dependency which is not available in Python 3.8 884 | - Added variable manipulation on the Telegram chat ID to remove minus sign if it's included in the variable. It needs to be omitted when posting the data to the API. 885 | 886 | 01/08/2021 887 | 888 | - Added some extra logging info. 889 | 890 | 24/07/2021 891 | 892 | - Added function to sanitise command line parameters passed to script. 893 | 894 | 19/07/2021 895 | 896 | - Download and Delete notification webhook payload JSON fix. 897 | - Remove ambiguity in DownloadedFilesNotification. 898 | 899 | 16/07/2021 900 | 901 | - Sanitise notification_title variable. 902 | - Rebase to Alpine Linux 3.14.0 903 | 904 | 13/07/2021 905 | 906 | - Modify 20210709 JSON escaping and fix minor change.log text. 907 | 908 | 12/07/2021 909 | 910 | - Removed apple_password variable. Password must now be added to the system keyring. 911 | - Default mode now to download, interactive_only variable removed. This process was far more complex than it needed to be for Synology/QNAP devices, so a small change to the default operation makes things easier for everyone. 912 | - --Generate2FACookie command line option changed to --Initialise as it is also used for saving the password to the system keyring. Not just cookie generation. 913 | 914 | 09/07/2021 915 | 916 | - Changed JSON escaping to prevent further issues. 917 | 918 | 08/07/2021 919 | 920 | - Download check and actual download functions both exit with same error message. Changed for clarity about which section is failing. 921 | 922 | 05/07/2021 923 | 924 | - JPEG quality mismatch in documentation and script. Default JPEG quality now referred to correctly in README.MD as 90. 925 | 926 | 17/06/2021 927 | 928 | - Added script hash to make sure that the sync-icloud.sh file is correct file as date stamp seems to be affected by timezone. 929 | - Oddities occuring when creating password/cookie on devices which always run interactively. 930 | 931 | 14/06/2021 932 | 933 | - Accurate health check output when approaching cookie expiration date. 934 | - Cookie expiration date checking improved. 935 | - Wasn't setting notification period for some of the notification options. 936 | - Notifications weren't firing in the notification period. Should now be fixed. 937 | 938 | 07/06/2021 939 | 940 | - Code to remove empty keyring file requires logic to detect f file actually exists, otherwise brand new containers enter a file deletion loop. Logic amended. 941 | 942 | 22/05/2021 943 | 944 | - Rebase to Alpine Linux 3.13.5 945 | - Now logs full version ID rather than just major/minor version. 946 | - Reddit user Vinnipinni made a comment that the documentation was great... so I've revisited it again to make sure it's up to date, corrected some spellings and grammar, and made a few improvements for clarity. 947 | 948 | 22/05/2021 949 | 950 | - --size parameter was incorrectly set as --photo-size. 951 | 952 | 19/05/2021 953 | 954 | - Added code to remove keyring file if it doesn't contain credentials. 955 | 956 | 15/05/2021 957 | 958 | - New download link for Boost source as location changed. 959 | 960 | 15/05/2021 961 | 962 | - Amended log output for when attempting to add a user to a pre-existing system group. 963 | 964 | 22/03/2021 965 | 966 | - Amended log text after cookie generation. Script no longer claims 2FA cookie generated for all cases, but states 2FA/Web based on which was actually generated. 967 | - Chnage: Add Litecoin address. BTC network fees are expensive AF! 968 | 969 | 04/03/2021 970 | 971 | - Added notification_title variable. 972 | - Readme changes to add full stops on lines where they were missing. 973 | 974 | 28/01/2021 975 | 976 | - Added logging of $TERM variable. 977 | 978 | 27/01/2021 979 | 980 | - Modified when the script check for the the --Generate2FACookie so that it can be invoked from NAS devices such a Synology and QNAP. 981 | 982 | 19/01/2021 983 | 984 | - Added the --Generate2FACookie command line option. 985 | 986 | 18/01/2021 987 | 988 | - Rebuilt on Alpine 3.13 989 | 990 | 21/12/2020 991 | 992 | - Convert HEIC to JPEG functions now allow specifying the quality with and integer value from 0 to 100. 993 | 994 | 16/12/2020 995 | 996 | - Sanitise apple_id variable so all letters are lower case. This prevents a case mismatch between the cookie file name that is saved, and the name that the icloudpd Python script creates the cookie file with, due to it respecting case. 997 | 998 | 04/12/2020 999 | 1000 | - Removed Pushbullet notifications. The Pushbullet service has introduced version 2 of its API so notifications no longer work. Pushbullet have also removed their app from the iOS App Store, so if it's no longer supported on iOS, there's no way to realistically support it here. 1001 | 1002 | 03/12/2020 1003 | 1004 | - Upgraded pip. 1005 | - Pushover notification bugfix. PR courtesy of @larstomas. 1006 | - Webhook notification notification standardisation. PR courtesy of @larstomas again. 1007 | - Altered failure piority for pushover notifications from -2 to 2 so they become emergency level notifications rather than silent. Change recommended by @larstomas. 1008 | - Added variable to allow skipping of the new file check. On large photo collections, this can significantly increase the time taken to download. 1009 | 1010 | 28/11/2020 1011 | 1012 | - Added log line to show the exact command line used to invole iCloudPD. 1013 | 1014 | 21/11/2020 1015 | 1016 | - Due to changes in iOS14 the 2FA synchronisation interval is now limited to a maximum of once every 12 hours. It seems that 2FA authentication is now restricted to a maximum of 26 logins before re-authentication is required. Synchronisation is now restricted to specific time periods with the shortest being 12 hours. 1017 | - New variable synchronisation_delay added. This is so that the first synchronisation can be dalayed for a number of minutes. This is so that multiple containers can stagger their downloads. 1018 | 1019 | 201112 1020 | 1021 | - Bugfix. Convert HEIC to JPEG function would only run if download notifications were enabled. 1022 | 1023 | 11/11/2020 1024 | 1025 | - Additional changes required for Pushover notifications. 1026 | 1027 | 10/11/2020 1028 | 1029 | - Added Pushover notifications. 1030 | - Failsafe file mount checking changed slightly regarding logging. 1031 | - Added variable for the --until-found option as must have missed this on the 20201024 update. 1032 | - Removed: Checking downloaded files list against files stored on the server is no longer required. Issue has been fixed upstream. 1033 | 1034 | 11/04/2020 1035 | 1036 | - Bugfix on healthcheck not detecting non-zero exit code. 1037 | 1038 | 01/11/2020 1039 | 1040 | - Removed: Multi-thread capability has been removed from upstream so removed capability to specify number of threads. 1041 | - Removed: Speed test capability. Was put in to test speed differences between mutli-thread and single-thread download options. No longer possible due to previous change. 1042 | - Bugfix. Convert HEIC to JPEG function - missed a bit. 1043 | 1044 | 30/10/2020 1045 | 1046 | - Bugfix. Convert HEIC to JPEG function failing to set correct timestamp. 1047 | 1048 | 29/10/2020 1049 | 1050 | - Remove pip cache folder /root/.cache during container build. 1051 | - Removed: Recently files only doesn't work as expected. Removed. 1052 | - Added --CorrectJPEGTimestamps command line option to correct timestamps of JPEG files. 1053 | - Convert HEIC to JPEG functions don't preserve timestamps. Filestamp of JPEG now taken from HEIC file. 1054 | - Default to downloading new files only by checking what files exist vs what needs downloading. This prevents it attempting to download all files upon every synchronisation. 1055 | - Typo for Synchronisation Interval variable. This change will break users configurations that set the old variable name, so default to 24hr sync period for these users. 1056 | 1057 | 27/10/2020 1058 | 1059 | - Silence warning about depreceated command_line_options varaible if it's actually blank to begin with. 1060 | - Remove Python package Future. This is for Python 2 to Python 3 compatibility, however, upstream application now fully Python 3 compatible. 1061 | - Python keyring and keyrings.alt no longer version restricted. Upstream application is now compatible with newer Python versions than before so these older packages no longer need to be installed. 1062 | 1063 | 27/10/2020 1064 | 1065 | - Bugfix relating to Prowl & Pushbullet notifications. 1066 | 1067 | 24/10/2020 1068 | 1069 | - Added new variables: set_exif_datetime, auto_delete, photo_size, skip_live_photos, live_photo_size & skip_videos. Eventually command_line_options variable will be removed. 1070 | - Added a new "CommandLineBuilder" function to create the command line options list to be passed to the download command. 1071 | 1072 | 22/10/2020 1073 | 1074 | - Added a speed test mode which ignores the requirement for a mounted filesystem. It limits the download to last 500 files. 1075 | - Multi-threaded mode can now be enabled by setting the multi_thread variable. This will set the thread count to the number of available processors, multipled by five. If the variable is not configured, it will default to single threaded node. Please note: Multithreaded mode has known issues withe files that have duplicate file names. Use with caution. 1076 | 1077 | 12/10/2020 1078 | 1079 | - Lowered 60 second delay for warning about password not being stored in keyring to 15 seconds as 60's a bit long. 1080 | - Removed the check for files that have been downloaded already, as new icloudpd version uses photo fingerprinting to allow downloading of different pictures with identical names. 1081 | - Added two variables, download_notifications and delete_notifications. If they are not set, they will default to True. To disable, set to False. 1082 | - Dockerfile now installs pip dependencies using requirements.txt instead of having them specified in the dockerfile. 1083 | - sync-icloud.sh script now shows the version of Python, icloudpd and pyicloud-ipd that are being used. 1084 | - Noted that folder structure can now be set to 'none' to download to flat file structure. 1085 | - diff file that applies the patch to skip incorrectly named files. Pull request has now been merged. 1086 | 1087 | 09/09/2020 1088 | 1089 | - Undersocres now being used in cookie file name. Amended to reflect change. 1090 | 1091 | 05/09/2020 1092 | 1093 | - Made the webhook path configurable. 1094 | 1095 | 25/08/2020 1096 | 1097 | - Removed the Powershell script to build separate images for ARM architectures as I'm now producing a single multi-arch image without using Windows. 1098 | 1099 | 23/08/2020 1100 | 1101 | - Version is now date and time of last modification of the sync-icloud.sh script. 1102 | 1103 | 22/08/2020 1104 | 1105 | - Added force_gid variable so that user can be added to system groups to maintain consistency with host OS. 1106 | - Rewrote the functions to create the user/group as it would fails if a system reseved group id is chosen. 1107 | 1108 | 15/08/2020 1109 | 1110 | - Amended the interactive terminal check to see if /dev/stdin is a pipe. Odd issue seen on some systems. 1111 | 1112 | 08/08/2020 1113 | 1114 | - Updated READ.ME. 1115 | 1116 | 07/08/2020 1117 | 1118 | - Updated READ.ME. 1119 | - Apply diff patch to fix album naming issue raised here: https://github.com/ndbroadbent/icloud_photos_downloader/issues/150. 1120 | 1121 | 06/08/2020 1122 | 1123 | - Removed manual versioning in Dockerfile in favour of MD5 script hash. Means I don't need to track version numbers, or remember to increment them, but latest version can be confirmed quite easily from the log. 1124 | - Added owner change to /config dir at start of ConfigurePassword function so password is successfully stored first time around. 1125 | 1126 | 03/08/2020 1127 | 1128 | - Converted docker-compose.yml file to be 3.8 compatible. Removed networking configuration. 1129 | 1130 | 03/08/2020 1131 | 1132 | - When using keyring it was claiming plaintext password was in use and vice versa. 1133 | - Added a version number in Dockerfile to display when script first starts up. 1134 | 1135 | 02/08/2020 1136 | 1137 | - Found that the --directory option is required. I wasn't including it when doing the 2FA authentication cookie generation which lead to an error. 1138 | 1139 | 08/06/2020 1140 | 1141 | - Rebased to Alpine 3.12. 1142 | - Display IP Address in log. 1143 | 1144 | 28/05/2020 1145 | 1146 | - Alpha Changed Webhook notification messages. 1147 | - Alpha Forgot to add webhook_id to the check which validates that the API keys have been set. 1148 | 1149 | 27/05/2020 1150 | 1151 | - Alpha Webhook host_ip_address variable changed to webhook_server. Added webhook_port so a non-default port can be added. 1152 | 1153 | 26/05/2020 1154 | 1155 | - Alpha: Webhook notifications implemented for Home Assistant. Based on Telegram notifications, but implemented without testing. Using them will probably break things as I'm unsure as to the format of the webhook payload. 1156 | 1157 | 19/05/2020 1158 | 1159 | - Prefixed some of the files stored in /tmp/icloudpd with icloudpd_, just for consistency. Plus it's easier to remove them all with `rm icloudpd*` instead of multiple arguments to rm. 1160 | - Keyring password saving. Previously, the Apple ID user's password could be seen in the Docker logs, the variables (docker inspect) and disturbingly, if any user did `ps -ef | grep password", it would be seen in the command line of any running connection to iCloud. Script now heavily pushes users towards saving credentials into the secure encrypted keyring. 1161 | - Saving password to text file has been removed (never made it to a release, but is doumented in the change log). 1162 | - su doesn't pass variables to the account it uses. This meant that some variables that I thought were optional (user, config_dir, folder_structure) were actually mandatory. Command now built with $0 $1 $2 etc and variables passed via command line ( -- $bunch $of $options). 1163 | - Debug logging variable removed. Always logging errors to screen. 1164 | - Telegram notification success/failure messages now confirm message type (startup, download, delete, failure). 1165 | - Cookie checking function would hang system without error if 2FA cookie does not exist. 1166 | 1167 | 18/05/2020 1168 | 1169 | - Container now saves the Apple ID password to a file located in the /config volume. Once this has been done, the apple_password variable not longer needs to be passed to the container and should be removed. 1170 | 1171 | 17/05/2020 1172 | 1173 | - Moved some of the logging commands so they triggered in more appropriate places. 1174 | - File download check error notification incorrectly labelled as a startup notification. 1175 | - Script wasn't properly handling passwords with special characters. Should be OK now. 1176 | - Amended the failsafe error message so that the name and path of the file it is looking for is visible. Makes it easier for people to spot the capitalisation in iCloud. 1177 | - File check exit code grabbing logic was flawed. Was actually grabbing the exit code of su, which was always 0. 1178 | 1179 | 14/05/2020 1180 | 1181 | - Previous change didn't work. This actually fixes the issue that the list of files is no longer growing. However, it's only highlighted a different issue. A number of files are exhibiting weird behaviour. 5 files are being listed as downloaded, but only 3 filenames are unique. These files are then also being listed as having been deleted from the system. They are all stil in my photostream on iCloud.com and not in recently deleted. Coincidentally, they all start with camphoto_ but that may not be indicitive of anything. 1182 | 1183 | 08/05/2020 1184 | 1185 | - My list of downloaded/deleted files seemed to be getting longer without me actively using my phone's camera. Guessing some variable isn't being cleared, so have localised a fair few variables to the functions they run in. 1186 | 1187 | 30/04/2020 1188 | 1189 | - Added $debug_logging variable, which outputs errors to the screen instead of hiding them. 1190 | 1191 | 13/04/2020 1192 | 1193 | - Removed PR 132. PR 132 was based off PR 110, which was raised in 2018. Neither had been merged. Obviously, after I spent an hour or so chainging my Dockerfile to incorporate the patch, it was merged upstream! 1194 | 1195 | 12/04/2020 1196 | 1197 | - Added PR 132: "Multi-threaded downloads and caching" from https://github.com/ndbroadbent/icloud_photos_downloader/pull/132. 1198 | - Added quotes to variables to prevent globbing in download check and photo download functions. 1199 | 1200 | 13/03/2020 1201 | 1202 | - Added interactive_only variable. Some hosts only run containers interactively (looking at you Synology) and this means the script gets stuck attempting to create a 2FA cookie every time. Setting interactive_only will force the script to bypass the cookie generation function and sync files instead. 1203 | 1204 | 13/03/2020 1205 | 1206 | - Log now displays OS version. 1207 | 1208 | 09/03/2020 1209 | 1210 | - Removed feature to delete JPG file if the HEIC has been removed. Floored logic in that all files exist in a single location, which doesn't match their original location. Also, JPG files received by WhatsApp would be removed as would JPG files saved from Safari. Can't work in practice. 1211 | - Script now has a command line parameter that can be run from an interactive session. --ConvertAllHEICs will scan all HEIC files and convert them to JPG, provided the JPG doesn't exist already. 1212 | - Convert HEIC to JPEG not working correctly. 1213 | 1214 | 17/02/2020 1215 | 1216 | - Running the sync client with "--only-print-filenames" actually creates folders too. File check now run as user account instead of root to avoid ownership issues. 1217 | 1218 | 11/02/2020 1219 | 1220 | - Fancy Telegram notifications caused the underscore characters in the file previews to be displayed in italics. Should be correctly escaped now. 1221 | 1222 | 09/02/2020 1223 | 1224 | - Odd issue where owner on config directoy changed to root. Added chown at start of sync to fix this, and also to Owner/Permissions setting function. 1225 | - Fancy pants Telegram notifications with emojis. 1226 | - Telegram URL Encoding function. Now sending markdown text and UTF-8 for special characters. 1227 | - Renamed $syncronisation_interval variable to $synchronisation_interval. 1228 | 1229 | 23/01/2020 1230 | 1231 | - Now based on Alpine Linux 3.11. 1232 | - Apple ID displayed on push notifications. 1233 | - ARM images rebuilt to latest version. 1234 | 1235 | 22/01/2020 1236 | 1237 | - HEIC to JPEG conversion. 1238 | - JPEG removal if original HEIC removed (experimental, backup your files!). 1239 | - Code changes for readability and simplicity. 1240 | - Added example docker-compose.yaml. 1241 | 1242 | 18/01/2020 1243 | 1244 | - Variable name changes for readability and naming accuracy. 1245 | - Pre-download check for new files so a download run will only occur if new files exist. 1246 | - Telegram notifications. 1247 | - Synchronisation summary. Number of new files downloaded. Number of deleted files (if --auto-delete enabled). 1248 | - Telegram notifications only: List of downloaded filenames (10 max). List of deleted files (10 max). 1249 | - Startup notification. 1250 | - Configurable permissions on the download destination directory. 1251 | - Logic re-writes for simplicity and optimisation. 1252 | - Additional logging. 1253 | - Code clean-ups. 1254 | - Healthcheck update. 1255 | -------------------------------------------------------------------------------- /docker-compose/docker-compose.example.yml: -------------------------------------------------------------------------------- 1 | networks: 2 | icloudpd: 3 | name: icloudpd 4 | driver: bridge 5 | ipam: 6 | driver: default 7 | driver_opts: 8 | com.docker.network.bridge.name: icloudpd 9 | 10 | volumes: 11 | icloudpd_user1_config: 12 | name: icloudpd_user1_config 13 | icloudpd_user2_config: 14 | name: icloudpd_user2_config 15 | 16 | services: 17 | icloudpd_user1: 18 | hostname: icloudpd_user1 19 | networks: 20 | icloudpd: 21 | aliases: 22 | - icloudpd_user1 23 | environment: 24 | - TZ=Europe/London 25 | - user=user1 26 | image: boredazfcuk/icloudpd 27 | healthcheck: 28 | test: /usr/local/bin/healthcheck.sh 29 | start_period: 30s 30 | restart: always 31 | volumes: 32 | - icloudpd_user1_config:/config 33 | - ./iCloud/:/home/user1/iCloud/ 34 | icloudpd_user2: 35 | hostname: icloudpd_user2 36 | networks: 37 | icloudpd: 38 | aliases: 39 | - icloudpd_user2 40 | environment: 41 | - TZ=Europe/London 42 | - user=user2 43 | image: boredazfcuk/icloudpd 44 | healthcheck: 45 | test: /usr/local/bin/healthcheck.sh 46 | start_period: 30s 47 | restart: always 48 | volumes: 49 | - icloudpd_user2_config:/config 50 | - ./iCloud/:/home/user2/iCloud/ -------------------------------------------------------------------------------- /docker-compose/example.env: -------------------------------------------------------------------------------- 1 | # iCloudPD environment variables 2 | # Variables with values beneath are showing the defaults used by the script if not specified 3 | 4 | # User and group details for account 5 | #user=user 6 | #user_id=1000 7 | #group=group 8 | #group_id=1000 9 | 10 | ## Apple iCloud Photo Downloader 11 | # Apple ID credentials 12 | #apple_id= 13 | 14 | # Authentication type 2FA or Web 15 | #authentication_type=2FA 16 | 17 | # Folder structure for downloaded files 18 | #folder_structure={:%Y/%m/%d} 19 | 20 | # Permissions for directories and files 21 | #directory_permissions=750 22 | #file_permissions=640 23 | 24 | # Creates a JPEG copy of any downloaded HEIC files (set empty to disable) 25 | #convert_heic_to_jpeg= 26 | 27 | # Delete JPEG files if associated HEIC file is removed (set empty to disable) 28 | #delete_heic_jpegs= 29 | 30 | # Additional command line options to pass to iCloud Photo Downloader eg: --auto-delete --set-exif-datetime 31 | #command_line_options= 32 | 33 | # Syncronisation interval. 21600=6hr, 43200=12hr, 86400=24hr 34 | #synchronisation_interval=86400 35 | 36 | # Set the number of days before 2FA expires to start sending notifications 37 | #notification_days=7 38 | 39 | # Set notification type to Prowl, Pushbullet or Telegram 40 | #notification_type= 41 | 42 | # The API key for Prowl, Pushbullet and Telegram notifications 43 | #prowl_api_key= 44 | #pushbullet_api_key= 45 | #telegram_token= 46 | #telegram_chat_id= 47 | -------------------------------------------------------------------------------- /healthcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ash 2 | 3 | source "/config/icloudpd.conf" 4 | 5 | if [ -f "/tmp/icloudpd/icloudpd_check_exit_code" ] || [ -f "/tmp/icloudpd/icloudpd_download_exit_code" ] 6 | then 7 | if [ -f "/tmp/icloudpd/icloudpd_download_exit_code" ] 8 | then 9 | download_exit_code="$(cat /tmp/icloudpd/icloudpd_download_exit_code)" 10 | # If the value is empty, set to 0 to presume healthy. Container is likely un-initialised and waiting for user input. 11 | # This prevents the healthcheck from restarting the container when combined with autoheal. 12 | if [ "${download_exit_code:=0}" -ne 0 ] 13 | then 14 | echo "File download error: ${download_exit_code}" 15 | exit "${download_exit_code}" 16 | fi 17 | fi 18 | if [ -f "/tmp/icloudpd/icloudpd_check_exit_code" ] 19 | then 20 | check_exit_code="$(cat /tmp/icloudpd/icloudpd_check_exit_code)" 21 | # Same as before 22 | if [ "${check_exit_code:=0}" -ne 0 ] 23 | then 24 | echo "File check error: ${check_exit_code}" 25 | exit "${check_exit_code}" 26 | fi 27 | fi 28 | else 29 | echo "Error check files missing." 30 | exit 1 31 | fi 32 | 33 | if [ -s "/tmp/icloudpd/icloudpd_check_error" ] || [ -s "/tmp/icloudpd/icloudpd_download_error" ] 34 | then 35 | if [ -f "/tmp/icloudpd/icloudpd_check_error" ] 36 | then 37 | echo "Errors reported during file check" 38 | exit 1 39 | fi 40 | if [ -s "/tmp/icloudpd/icloudpd_download_error" ] 41 | then 42 | echo "Errors reported during file download" 43 | exit 1 44 | fi 45 | fi 46 | 47 | cookie="$(echo -n "${apple_id//[^a-zA-Z0-9_]}" | tr '[:upper:]' '[:lower:]')" 48 | if [ ! -f "/config/${cookie}" ] 49 | then 50 | echo "Error: Cookie does not exist. Please generate new cookie" 51 | exit 1 52 | fi 53 | 54 | if [ "${authentication_type:=MFA}" = "MFA" ] 55 | then 56 | mfa_expire_date="$(grep "X-APPLE-DS-WEB-SESSION-TOKEN" "/config/${cookie}" | sed -e 's#.*expires="\(.*\)Z"; HttpOnly.*#\1#')" 57 | mfa_expire_seconds="$(date -d "${mfa_expire_date}" '+%s')" 58 | days_remaining="$(($((mfa_expire_seconds - $(date '+%s'))) / 86400))" 59 | if [ -z "${notification_days}" ] 60 | then 61 | notification_days=7 62 | fi 63 | if [ "${days_remaining}" -le "${notification_days}" ] && [ "${days_remaining}" -ge 1 ] 64 | then 65 | echo "Warning: Multi-factor authentication cookie is due for renewal in ${notification_days} days" 66 | elif [ "${days_remaining}" -lt 1 ] 67 | then 68 | echo "Error: Multi-factor authentication cookie has expired" 69 | exit 1 70 | fi 71 | elif [ "${authentication_type}" = "Web" ] 72 | then 73 | web_cookie_expire_date="$(grep "X_APPLE_WEB_KB" "/config/${cookie}" | sed -e 's#.*expires="\(.*\)Z"; HttpOnly.*#\1#')" 74 | web_cookie_expire_seconds="$(date -d "${web_cookie_expire_date}" '+%s')" 75 | days_remaining="$(($((web_cookie_expire_seconds - $(date '+%s'))) / 86400))" 76 | else 77 | echo "Error: Authentication type not recognised" 78 | exit 1 79 | fi 80 | 81 | echo "iCloud Photos Downloader successful and ${authentication_type} cookie valid for ${days_remaining} day(s)" 82 | 83 | exit 0 -------------------------------------------------------------------------------- /icloudpd.dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | LABEL maintainer="boredazfcuk" 3 | 4 | ENV XDG_DATA_HOME="/config" TZ="UTC" ENV="/etc/profile" config_file="/config/icloudpd.conf" 5 | 6 | ARG icloudpd_version="1.27.1" 7 | ARG build_dependencies="gcc python3-dev libc-dev libffi-dev cargo openssl-dev" 8 | ARG app_dependencies="findutils nano nano-syntax py3-pip exiftool coreutils tzdata curl libheif imagemagick shadow jq jpeg bind-tools expect inotify-tools msmtp" 9 | 10 | RUN echo "$(date '+%d/%m/%Y - %H:%M:%S') | ***** Build started for boredazfcuk's docker-icloudpd *****" && \ 11 | echo "$(date '+%d/%m/%Y - %H:%M:%S') | Install requirements" && \ 12 | apk add --no-progress --no-cache --virtual build ${build_dependencies} && \ 13 | apk add --no-progress --no-cache ${app_dependencies} && \ 14 | find /usr/share/nano -name '*.nanorc' -printf "include %p\n" >>/etc/nanorc && \ 15 | echo "$(date '+%d/%m/%Y - %H:%M:%S') | Install iCloudPD latest release" && \ 16 | python -m venv /opt/icloudpd && \ 17 | source /opt/icloudpd/bin/activate && \ 18 | pip3 install --upgrade pip && \ 19 | pip3 install --no-cache-dir icloudpd=="${icloudpd_version}" && \ 20 | deactivate && \ 21 | apk del build 22 | 23 | COPY build_version.txt /opt 24 | COPY --chmod=0755 *.sh /usr/local/bin/ 25 | COPY authenticate.exp /opt/authenticate.exp 26 | COPY CONFIGURATION.md /opt 27 | COPY profile /etc/profile 28 | 29 | HEALTHCHECK --start-period=10s --interval=1m --timeout=10s CMD /usr/local/bin/healthcheck.sh 30 | 31 | VOLUME "/config" 32 | 33 | CMD /usr/local/bin/launcher.sh -------------------------------------------------------------------------------- /init_config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ash 2 | 3 | # Move from synchronisation_interval to download_interval and from synchronisation_delay to download_delay 4 | if [ -f "${config_file}" ] 5 | then 6 | sed -i 's/^synchronisation_interval=/download_interval=/g' "${config_file}" 7 | sed -i 's/^synchronisation_delay=/download_delay=/g' "${config_file}" 8 | fi 9 | 10 | # Add missing options to the config file and if a Docker variable exists, use that to set the default value 11 | { 12 | if [ "$(grep -c "^albums_with_dates=" "${config_file}")" -eq 0 ] 13 | then 14 | echo albums_with_dates="${albums_with_dates:=false}" 15 | fi 16 | if [ "$(grep -c "^align_raw=" "${config_file}")" -eq 0 ] 17 | then 18 | echo align_raw="${align_raw:=as-is}" 19 | fi 20 | if [ "$(grep -c "^apple_id=" "${config_file}")" -eq 0 ] 21 | then 22 | echo apple_id="${apple_id}" 23 | fi 24 | if [ "$(grep -c "^authentication_type=" "${config_file}")" -eq 0 ] 25 | then 26 | echo authentication_type="${authentication_type:=MFA}" 27 | fi 28 | if [ "$(grep -c "^auth_china=" "${config_file}")" -eq 0 ] 29 | then 30 | echo auth_china="${auth_china:=false}" 31 | fi 32 | if [ "$(grep -c "^auto_delete=" "${config_file}")" -eq 0 ] 33 | then 34 | echo auto_delete="${auto_delete:=false}" 35 | fi 36 | if [ "$(grep -c "^bark_device_key=" "${config_file}")" -eq 0 ] 37 | then 38 | echo bark_device_key="${bark_device_key}" 39 | fi 40 | if [ "$(grep -c "^bark_server=" "${config_file}")" -eq 0 ] 41 | then 42 | echo bark_server="${bark_server}" 43 | fi 44 | if [ "$(grep -c "^convert_heic_to_jpeg=" "${config_file}")" -eq 0 ] 45 | then 46 | echo convert_heic_to_jpeg="${convert_heic_to_jpeg:=false}" 47 | fi 48 | if [ "$(grep -c "^debug_logging=" "${config_file}")" -eq 0 ] 49 | then 50 | echo debug_logging="${debug_logging:=false}" 51 | fi 52 | if [ "$(grep -c "^delete_accompanying=" "${config_file}")" -eq 0 ] 53 | then 54 | echo delete_accompanying="${delete_accompanying:=false}" 55 | fi 56 | if [ "$(grep -c "^delete_after_download=" "${config_file}")" -eq 0 ] 57 | then 58 | echo delete_after_download="${delete_after_download:=false}" 59 | fi 60 | if [ "$(grep -c "^delete_empty_directories=" "${config_file}")" -eq 0 ] 61 | then 62 | echo delete_empty_directories="${delete_empty_directories:=false}" 63 | fi 64 | if [ "$(grep -c "^delete_notifications=" "${config_file}")" -eq 0 ] 65 | then 66 | echo delete_notifications="${delete_notifications:=true}" 67 | fi 68 | if [ "$(grep -c "^dingtalk_token=" "${config_file}")" -eq 0 ] 69 | then 70 | echo dingtalk_token="${dingtalk_token}" 71 | fi 72 | if [ "$(grep -c "^directory_permissions=" "${config_file}")" -eq 0 ] 73 | then 74 | echo directory_permissions="${directory_permissions:=750}" 75 | fi 76 | if [ "$(grep -c "^discord_id=" "${config_file}")" -eq 0 ] 77 | then 78 | echo discord_id="${discord_id}" 79 | fi 80 | if [ "$(grep -c "^discord_token=" "${config_file}")" -eq 0 ] 81 | then 82 | echo discord_token="${discord_token}" 83 | fi 84 | if [ "$(grep -c "^download_notifications=" "${config_file}")" -eq 0 ] 85 | then 86 | echo download_notifications="${download_notifications:=true}" 87 | fi 88 | if [ "$(grep -c "^download_path=" "${config_file}")" -eq 0 ] 89 | then 90 | user="$(grep "^user=" "${config_file}" | awk -F= '{print $2}')" 91 | echo download_path="${download_path:=/home/${user:=user}/iCloud}" 92 | fi 93 | if [ "$(grep -c "^fake_user_agent=" "${config_file}")" -eq 0 ] 94 | then 95 | echo fake_user_agent="${fake_user_agent}" 96 | fi 97 | if [ "$(grep -c "^file_match_policy=" "${config_file}")" -eq 0 ] 98 | then 99 | echo file_match_policy="${file_match_policy:=name-size-dedup-with-suffix}" 100 | fi 101 | if [ "$(grep -c "^file_permissions=" "${config_file}")" -eq 0 ] 102 | then 103 | echo file_permissions="${file_permissions:=640}" 104 | fi 105 | if [ "$(grep -c "^folder_structure=" "${config_file}")" -eq 0 ] 106 | then 107 | echo folder_structure="${folder_structure:={:%Y/%m/%d\}}" 108 | fi 109 | if [ "$(grep -c "^force_gid=" "${config_file}")" -eq 0 ] 110 | then 111 | echo force_gid="${force_gid:=false}" 112 | fi 113 | if [ "$(grep -c "^gotify_app_token=" "${config_file}")" -eq 0 ] 114 | then 115 | echo gotify_app_token="${gotify_app_token}" 116 | fi 117 | if [ "$(grep -c "^gotify_https=" "${config_file}")" -eq 0 ] 118 | then 119 | echo gotify_https="${gotify_https}" 120 | fi 121 | if [ "$(grep -c "^gotify_server_url=" "${config_file}")" -eq 0 ] 122 | then 123 | echo gotify_server_url="${gotify_server_url}" 124 | fi 125 | if [ "$(grep -c "^group=" "${config_file}")" -eq 0 ] 126 | then 127 | echo group="${group:=group}" 128 | fi 129 | if [ "$(grep -c "^group_id=" "${config_file}")" -eq 0 ] 130 | then 131 | echo group_id="${group_id:=1000}" 132 | fi 133 | if [ "$(grep -c "^icloud_china=" "${config_file}")" -eq 0 ] 134 | then 135 | echo icloud_china="${icloud_china:=false}" 136 | fi 137 | if [ "$(grep -c "^iyuu_token=" "${config_file}")" -eq 0 ] 138 | then 139 | echo iyuu_token="${iyuu_token}" 140 | fi 141 | if [ "$(grep -c "^jpeg_path=" "${config_file}")" -eq 0 ] 142 | then 143 | echo jpeg_path="${jpeg_path}" 144 | fi 145 | if [ "$(grep -c "^jpeg_quality=" "${config_file}")" -eq 0 ] 146 | then 147 | echo jpeg_quality="${jpeg_quality:=90}" 148 | fi 149 | if [ "$(grep -c "^keep_icloud_recent_days=" "${config_file}")" -eq 0 ] 150 | then 151 | echo keep_icloud_recent_days="${keep_icloud_recent_days}" 152 | fi 153 | if [ "$(grep -c "^keep_icloud_recent_only=" "${config_file}")" -eq 0 ] 154 | then 155 | echo keep_icloud_recent_only="${keep_icloud_recent_only}" 156 | fi 157 | if [ "$(grep -c "^keep_unicode=" "${config_file}")" -eq 0 ] 158 | then 159 | echo keep_unicode="${keep_unicode:=false}" 160 | fi 161 | if [ "$(grep -c "^libraries_with_dates=" "${config_file}")" -eq 0 ] 162 | then 163 | echo libraries_with_dates="${libraries_with_dates:=false}" 164 | fi 165 | if [ "$(grep -c "^live_photo_mov_filename_policy=" "${config_file}")" -eq 0 ] 166 | then 167 | echo live_photo_mov_filename_policy="${live_photo_mov_filename_policy:=suffix}" 168 | fi 169 | if [ "$(grep -c "^live_photo_size=" "${config_file}")" -eq 0 ] 170 | then 171 | echo live_photo_size="${live_photo_size:=original}" 172 | fi 173 | if [ "$(grep -c "^nextcloud_delete=" "${config_file}")" -eq 0 ] 174 | then 175 | echo nextcloud_delete="${nextcloud_delete:=false}" 176 | fi 177 | if [ "$(grep -c "^nextcloud_upload=" "${config_file}")" -eq 0 ] 178 | then 179 | echo nextcloud_upload="${nextcloud_upload:=false}" 180 | fi 181 | if [ "$(grep -c "^nextcloud_url=" "${config_file}")" -eq 0 ] 182 | then 183 | echo nextcloud_url="${nextcloud_url}" 184 | fi 185 | if [ "$(grep -c "^nextcloud_username=" "${config_file}")" -eq 0 ] 186 | then 187 | echo nextcloud_username="${nextcloud_username}" 188 | fi 189 | if [ "$(grep -c "^nextcloud_password=" "${config_file}")" -eq 0 ] 190 | then 191 | echo nextcloud_password="${nextcloud_password}" 192 | fi 193 | if [ "$(grep -c "^notification_days=" "${config_file}")" -eq 0 ] 194 | then 195 | echo notification_days="${notification_days:=7}" 196 | fi 197 | if [ "$(grep -c "^notification_type=" "${config_file}")" -eq 0 ] 198 | then 199 | echo notification_type="${notification_type}" 200 | fi 201 | if [ "$(grep -c "^photo_album=" "${config_file}")" -eq 0 ] 202 | then 203 | echo photo_album="${photo_album}" 204 | fi 205 | if [ "$(grep -c "^photo_library=" "${config_file}")" -eq 0 ] 206 | then 207 | echo photo_library="${photo_library}" 208 | fi 209 | if [ "$(grep -c "^photo_size=" "${config_file}")" -eq 0 ] 210 | then 211 | echo photo_size="${photo_size:=original}" 212 | fi 213 | if [ "$(grep -c "^prowl_api_key=" "${config_file}")" -eq 0 ] 214 | then 215 | echo prowl_api_key="${prowl_api_key}" 216 | fi 217 | if [ "$(grep -c "^pushover_sound=" "${config_file}")" -eq 0 ] 218 | then 219 | echo pushover_sound="${pushover_sound}" 220 | fi 221 | if [ "$(grep -c "^pushover_token=" "${config_file}")" -eq 0 ] 222 | then 223 | echo pushover_token="${pushover_token}" 224 | fi 225 | if [ "$(grep -c "^pushover_user=" "${config_file}")" -eq 0 ] 226 | then 227 | echo pushover_user="${pushover_user}" 228 | fi 229 | if [ "$(grep -c "^recent_only=" "${config_file}")" -eq 0 ] 230 | then 231 | echo recent_only="${recent_only}" 232 | fi 233 | if [ "$(grep -c "^set_exif_datetime=" "${config_file}")" -eq 0 ] 234 | then 235 | echo set_exif_datetime="${set_exif_datetime:=false}" 236 | fi 237 | if [ "$(grep -c "^sideways_copy_videos=" "${config_file}")" -eq 0 ] 238 | then 239 | echo sideways_copy_videos="${sideways_copy:=false}" 240 | fi 241 | if [ "$(grep -c "^sideways_copy_videos_mode=" "${config_file}")" -eq 0 ] 242 | then 243 | echo sideways_copy_videos_mode="${sideways_copy_mode:=copy}" 244 | fi 245 | if [ "$(grep -c "^signal_host=" "${config_file}")" -eq 0 ] 246 | then 247 | echo signal_host="${signal_host}" 248 | fi 249 | if [ "$(grep -c "^signal_port=" "${config_file}")" -eq 0 ] 250 | then 251 | echo signal_port="${signal_port}" 252 | fi 253 | if [ "$(grep -c "^signal_number=" "${config_file}")" -eq 0 ] 254 | then 255 | echo signal_number="${signal_number}" 256 | fi 257 | if [ "$(grep -c "^signal_recipient=" "${config_file}")" -eq 0 ] 258 | then 259 | echo signal_recipient="${signal_recipient}" 260 | fi 261 | if [ "$(grep -c "^silent_file_notifications=" "${config_file}")" -eq 0 ] 262 | then 263 | echo silent_file_notifications="${silent_file_notifications:=false}" 264 | fi 265 | if [ "$(grep -c "^single_pass=" "${config_file}")" -eq 0 ] 266 | then 267 | echo single_pass="${single_pass:=false}" 268 | fi 269 | if [ "$(grep -c "^skip_album=" "${config_file}")" -eq 0 ] 270 | then 271 | echo skip_album="${skip_album}" 272 | fi 273 | if [ "$(grep -c "^skip_library=" "${config_file}")" -eq 0 ] 274 | then 275 | echo skip_library="${skip_library}" 276 | fi 277 | if [ "$(grep -c "^skip_check=" "${config_file}")" -eq 0 ] 278 | then 279 | echo skip_check="${skip_check:=false}" 280 | fi 281 | if [ "$(grep -c "^skip_download=" "${config_file}")" -eq 0 ] 282 | then 283 | echo skip_download="${skip_download:=false}" 284 | fi 285 | if [ "$(grep -c "^skip_live_photos=" "${config_file}")" -eq 0 ] 286 | then 287 | echo skip_live_photos="${skip_live_photos:=false}" 288 | fi 289 | if [ "$(grep -c "^skip_videos=" "${config_file}")" -eq 0 ] 290 | then 291 | echo skip_videos="${skip_videos:=false}" 292 | fi 293 | if [ "$(grep -c "^startup_notification=" "${config_file}")" -eq 0 ] 294 | then 295 | echo startup_notification="${startup_notification:=true}" 296 | fi 297 | if [ "$(grep -c "^download_delay=" "${config_file}")" -eq 0 ] 298 | then 299 | echo download_delay="${download_delay:=0}" 300 | fi 301 | if [ "$(grep -c "^download_interval=" "${config_file}")" -eq 0 ] 302 | then 303 | echo download_interval="${download_interval:=86400}" 304 | fi 305 | if [ "$(grep -c "^synology_ignore_path=" "${config_file}")" -eq 0 ] 306 | then 307 | echo synology_ignore_path="${synology_ignore_path:=false}" 308 | fi 309 | if [ "$(grep -c "^synology_photos_app_fix=" "${config_file}")" -eq 0 ] 310 | then 311 | echo synology_photos_app_fix="${synology_photos_app_fix:=false}" 312 | fi 313 | if [ "$(grep -c "^telegram_bot_initialised=" "${config_file}")" -eq 0 ] 314 | then 315 | echo telegram_bot_initialised=false 316 | fi 317 | if [ "$(grep -c "^telegram_chat_id=" "${config_file}")" -eq 0 ] 318 | then 319 | echo telegram_chat_id="${telegram_chat_id}" 320 | fi 321 | if [ "$(grep -c "^telegram_http=" "${config_file}")" -eq 0 ] 322 | then 323 | echo telegram_http="${telegram_http}" 324 | fi 325 | if [ "$(grep -c "^telegram_polling=" "${config_file}")" -eq 0 ] 326 | then 327 | echo telegram_polling="${telegram_polling:=true}" 328 | fi 329 | if [ "$(grep -c "^telegram_server=" "${config_file}")" -eq 0 ] 330 | then 331 | echo telegram_server="${telegram_server}" 332 | fi 333 | if [ "$(grep -c "^telegram_token=" "${config_file}")" -eq 0 ] 334 | then 335 | echo telegram_token="${telegram_token}" 336 | fi 337 | if [ "$(grep -c "^trigger_nextlcoudcli_download=" "${config_file}")" -eq 0 ] 338 | then 339 | echo trigger_nextlcoudcli_download="${trigger_nextlcoudcli_download}" 340 | fi 341 | if [ "$(grep -c "^until_found=" "${config_file}")" -eq 0 ] 342 | then 343 | echo until_found="${until_found}" 344 | fi 345 | if [ "$(grep -c "^user=" "${config_file}")" -eq 0 ] 346 | then 347 | echo user="${user:=user}" 348 | fi 349 | if [ "$(grep -c "^user_id=" "${config_file}")" -eq 0 ] 350 | then 351 | echo user_id="${user_id:=1000}" 352 | fi 353 | if [ "$(grep -c "^video_path=" "${config_file}")" -eq 0 ] 354 | then 355 | echo video_path= 356 | fi 357 | if [ "$(grep -c "^webhook_https=" "${config_file}")" -eq 0 ] 358 | then 359 | echo webhook_https="${webhook_https:=false}" 360 | fi 361 | if [ "$(grep -c "^webhook_id=" "${config_file}")" -eq 0 ] 362 | then 363 | echo webhook_id="${webhook_id}" 364 | fi 365 | if [ "$(grep -c "^webhook_path=" "${config_file}")" -eq 0 ] 366 | then 367 | echo webhook_path="${webhook_path:=/api/webhook/}" 368 | fi 369 | if [ "$(grep -c "^webhook_port=" "${config_file}")" -eq 0 ] 370 | then 371 | echo webhook_port="${webhook_port:=8123}" 372 | fi 373 | if [ "$(grep -c "^webhook_server=" "${config_file}")" -eq 0 ] 374 | then 375 | echo webhook_server="${webhook_server}" 376 | fi 377 | if [ "$(grep -c "^webhook_insecure=" "${config_file}")" -eq 0 ] 378 | then 379 | echo webhook_insecure="${webhook_insecure}" 380 | fi 381 | if [ "$(grep -c "^wecom_id=" "${config_file}")" -eq 0 ] 382 | then 383 | echo wecom_id="${wecom_id}" 384 | fi 385 | if [ "$(grep -c "^wecom_proxy=" "${config_file}")" -eq 0 ] 386 | then 387 | echo wecom_proxy="${wecom_proxy}" 388 | fi 389 | if [ "$(grep -c "^wecom_secret=" "${config_file}")" -eq 0 ] 390 | then 391 | echo wecom_secret="${wecom_secret}" 392 | fi 393 | if [ "$(grep -c "^msmtp_host=" "${config_file}")" -eq 0 ] 394 | then 395 | echo msmtp_host="${msmtp_host}" 396 | fi 397 | if [ "$(grep -c "^msmtp_port=" "${config_file}")" -eq 0 ] 398 | then 399 | echo msmtp_port="${msmtp_port}" 400 | fi 401 | if [ "$(grep -c "^msmtp_user=" "${config_file}")" -eq 0 ] 402 | then 403 | echo msmtp_user="${msmtp_user}" 404 | fi 405 | if [ "$(grep -c "^msmtp_from=" "${config_file}")" -eq 0 ] 406 | then 407 | echo msmtp_from="${msmtp_from}" 408 | fi 409 | if [ "$(grep -c "^msmtp_pass=" "${config_file}")" -eq 0 ] 410 | then 411 | echo msmtp_pass="${msmtp_pass}" 412 | fi 413 | if [ "$(grep -c "^msmtp_tls=" "${config_file}")" -eq 0 ] 414 | then 415 | echo msmtp_tls="${msmtp_tls:=on}" 416 | fi 417 | if [ "$(grep -c "^msmtp_to=" "${config_file}")" -eq 0 ] 418 | then 419 | echo msmtp_to="${msmtp_to}" 420 | fi 421 | if [ "$(grep -c "^msmtp_args=" "${config_file}")" -eq 0 ] 422 | then 423 | echo msmtp_args="${msmtp_args:=--tls-starttls=off}" 424 | fi 425 | if [ "$(grep -c "^agentid=" "${config_file}")" -eq 0 ] 426 | then 427 | echo agentid="${agentid}" 428 | fi 429 | if [ "$(grep -c "^touser=" "${config_file}")" -eq 0 ] 430 | then 431 | echo touser="${touser}" 432 | fi 433 | if [ "$(grep -c "^content_source_url=" "${config_file}")" -eq 0 ] 434 | then 435 | echo content_source_url="${content_source_url}" 436 | fi 437 | if [ "$(grep -c "^name=" "${config_file}")" -eq 0 ] 438 | then 439 | echo name="${name}" 440 | fi 441 | if [ "$(grep -c "^media_id_startup=" "${config_file}")" -eq 0 ] 442 | then 443 | echo media_id_startup="${media_id_startup}" 444 | fi 445 | if [ "$(grep -c "^media_id_download=" "${config_file}")" -eq 0 ] 446 | then 447 | echo media_id_download="${media_id_download}" 448 | fi 449 | if [ "$(grep -c "^media_id_delete=" "${config_file}")" -eq 0 ] 450 | then 451 | echo media_id_delete="${media_id_delete}" 452 | fi 453 | if [ "$(grep -c "^media_id_expiration=" "${config_file}")" -eq 0 ] 454 | then 455 | echo media_id_expiration="${media_id_expiration}" 456 | fi 457 | if [ "$(grep -c "^media_id_warning=" "${config_file}")" -eq 0 ] 458 | then 459 | echo media_id_warning="${media_id_warning}" 460 | fi 461 | } > "${config_file}.add" 462 | if [ -f "${config_file}.add" ] 463 | then 464 | cat "${config_file}.add" >> "${config_file}" 465 | rm "${config_file}.add" 466 | fi 467 | 468 | # Set default values if missing from config file 469 | if [ -z "$(grep "^authentication_type=" "${config_file}" | awk -F= '{print $2}')" ] 470 | then 471 | sed -i "s%^authentication_type=$%authentication_type=MFA%" "${config_file}" 472 | fi 473 | if [ -z "$(grep "^auth_china=" "${config_file}" | awk -F= '{print $2}')" ] 474 | then 475 | sed -i "s%^auth_china=$%auth_china=false%" "${config_file}" 476 | fi 477 | if [ -z "$(grep "^auto_delete=" "${config_file}" | awk -F= '{print $2}')" ] 478 | then 479 | sed -i "s%^auto_delete=$%auto_delete=false%" "${config_file}" 480 | fi 481 | if [ -z "$(grep "^albums_with_dates=" "${config_file}" | awk -F= '{print $2}')" ] 482 | then 483 | sed -i "s%^albums_with_dates=$%albums_with_dates=false%" "${config_file}" 484 | fi 485 | if [ -z "$(grep "^align_raw=" "${config_file}" | awk -F= '{print $2}')" ] 486 | then 487 | sed -i "s%^align_raw=$%align_raw=as-is%" "${config_file}" 488 | fi 489 | if [ -z "$(grep "^convert_heic_to_jpeg=" "${config_file}" | awk -F= '{print $2}')" ] 490 | then 491 | sed -i "s%^convert_heic_to_jpeg=$%convert_heic_to_jpeg=false%" "${config_file}" 492 | fi 493 | if [ -z "$(grep "^debug_logging=" "${config_file}" | awk -F= '{print $2}')" ] 494 | then 495 | sed -i "s%^debug_logging=$%debug_logging=false%" "${config_file}" 496 | fi 497 | if [ -z "$(grep "^delete_accompanying=" "${config_file}" | awk -F= '{print $2}')" ] 498 | then 499 | sed -i "s%^delete_accompanying=$%delete_accompanying=false%" "${config_file}" 500 | fi 501 | if [ -z "$(grep "^delete_after_download=" "${config_file}" | awk -F= '{print $2}')" ] 502 | then 503 | sed -i "s%^delete_after_download=$%delete_after_download=false%" "${config_file}" 504 | fi 505 | if [ -z "$(grep "^delete_empty_directories=" "${config_file}" | awk -F= '{print $2}')" ] 506 | then 507 | sed -i "s%^delete_empty_directories=$%delete_empty_directories=false%" "${config_file}" 508 | fi 509 | if [ -z "$(grep "^delete_notifications=" "${config_file}" | awk -F= '{print $2}')" ] 510 | then 511 | sed -i "s%^delete_notifications=$%delete_notifications=true%" "${config_file}" 512 | fi 513 | if [ -z "$(grep "^directory_permissions=" "${config_file}" | awk -F= '{print $2}')" ] 514 | then 515 | sed -i "s%^directory_permissions=$%directory_permissions=750%" "${config_file}" 516 | fi 517 | if [ -z "$(grep "^download_notifications=" "${config_file}" | awk -F= '{print $2}')" ] 518 | then 519 | sed -i "s%^download_notifications=$%download_notifications=true%" "${config_file}" 520 | fi 521 | if [ -z "$(grep "^download_path=" "${config_file}" | awk -F= '{print $2}')" ] 522 | then 523 | user="$(grep "^user=" "${config_file}" | awk -F= '{print $2}')" 524 | sed -i "s%^download_path=$%download_path=${download_path:=/home/${user:=user}/iCloud}%" "${config_file}" 525 | fi 526 | if [ -z "$(grep "^fake_user_agent=" "${config_file}" | awk -F= '{print $2}')" ] 527 | then 528 | sed -i "s%^fake_user_agent=$%fake_user_agent=false%" "${config_file}" 529 | fi 530 | if [ -z "$(grep "^file_match_policy=" "${config_file}" | awk -F= '{print $2}')" ] 531 | then 532 | sed -i "s%^file_match_policy=$%file_match_policy=name-size-dedup-with-suffix%" "${config_file}" 533 | fi 534 | if [ -z "$(grep "^file_permissions=" "${config_file}" | awk -F= '{print $2}')" ] 535 | then 536 | sed -i "s%^file_permissions=$%file_permissions=640%" "${config_file}" 537 | fi 538 | if [ -z "$(grep "^folder_structure=" "${config_file}" | awk -F= '{print $2}')" ] 539 | then 540 | sed -i "s%^folder_structure=$%folder_structure={:\%Y/\%m/\%d\}%" "${config_file}" 541 | fi 542 | if [ -z "$(grep "^force_gid=" "${config_file}" | awk -F= '{print $2}')" ] 543 | then 544 | sed -i "s%^force_gid=$%force_gid=false%" "${config_file}" 545 | fi 546 | if [ -z "$(grep "^group=" "${config_file}" | awk -F= '{print $2}')" ] 547 | then 548 | sed -i "s%^group=$%group=group%" "${config_file}" 549 | fi 550 | if [ -z "$(grep "^group_id=" "${config_file}" | awk -F= '{print $2}')" ] 551 | then 552 | sed -i "s%^group_id=$%group_id=1000%" "${config_file}" 553 | fi 554 | if [ -z "$(grep "^icloud_china=" "${config_file}" | awk -F= '{print $2}')" ] 555 | then 556 | sed -i "s%^icloud_china=$%icloud_china=false%" "${config_file}" 557 | fi 558 | if [ -z "$(grep "^jpeg_quality=" "${config_file}" | awk -F= '{print $2}')" ] 559 | then 560 | sed -i "s%^jpeg_quality=$%jpeg_quality=90%" "${config_file}" 561 | fi 562 | if [ -z "$(grep "^keep_icloud_recent_only=" "${config_file}" | awk -F= '{print $2}')" ] 563 | then 564 | sed -i "s%^keep_icloud_recent_only=$%keep_icloud_recent_only=false%" "${config_file}" 565 | fi 566 | if [ -z "$(grep "^keep_unicode=" "${config_file}" | awk -F= '{print $2}')" ] 567 | then 568 | sed -i "s%^keep_unicode=$%keep_unicode=false%" "${config_file}" 569 | fi 570 | if [ -z "$(grep "^libraries_with_dates=" "${config_file}" | awk -F= '{print $2}')" ] 571 | then 572 | sed -i "s%^libraries_with_dates=$%libraries_with_dates=false%" "${config_file}" 573 | fi 574 | if [ -z "$(grep "^live_photo_mov_filename_policy=" "${config_file}" | awk -F= '{print $2}')" ] 575 | then 576 | sed -i "s%^live_photo_mov_filename_policy=$%live_photo_mov_filename_policy=suffix%" "${config_file}" 577 | fi 578 | if [ -z "$(grep "^live_photo_size=" "${config_file}" | awk -F= '{print $2}')" ] 579 | then 580 | sed -i "s%^live_photo_size=$%live_photo_size=original%" "${config_file}" 581 | fi 582 | if [ -z "$(grep "^nextcloud_delete=" "${config_file}" | awk -F= '{print $2}')" ] 583 | then 584 | sed -i "s%^nextcloud_delete=$%nextcloud_delete=false%" "${config_file}" 585 | fi 586 | if [ -z "$(grep "^nextcloud_upload=" "${config_file}" | awk -F= '{print $2}')" ] 587 | then 588 | sed -i "s%^nextcloud_upload=$%nextcloud_upload=false%" "${config_file}" 589 | fi 590 | if [ -z "$(grep "^notification_days=" "${config_file}" | awk -F= '{print $2}')" ] 591 | then 592 | sed -i "s%^notification_days=$%notification_days=7%" "${config_file}" 593 | fi 594 | if [ -z "$(grep "^photo_size=" "${config_file}" | awk -F= '{print $2}')" ] 595 | then 596 | sed -i "s%^photo_size=$%photo_size=original%" "${config_file}" 597 | fi 598 | if [ -z "$(grep "^set_exif_datetime=" "${config_file}" | awk -F= '{print $2}')" ] 599 | then 600 | sed -i "s%^set_exif_datetime=$%set_exif_datetime=false%" "${config_file}" 601 | fi 602 | if [ -z "$(grep "^sideways_copy_videos=" "${config_file}" | awk -F= '{print $2}')" ] 603 | then 604 | sed -i "s%^sideways_copy_videos=$%sideways_copy_videos=false%" "${config_file}" 605 | fi 606 | if [ -z "$(grep "^sideways_copy_videos_mode=" "${config_file}" | awk -F= '{print $2}')" ] 607 | then 608 | sed -i "s%^sideways_copy_videos_mode=$%sideways_copy_videos_mode=copy%" "${config_file}" 609 | fi 610 | if [ -z "$(grep "^single_pass=" "${config_file}" | awk -F= '{print $2}')" ] 611 | then 612 | sed -i "s%^single_pass=$%single_pass=false%" "${config_file}" 613 | fi 614 | if [ -z "$(grep "^skip_check=" "${config_file}" | awk -F= '{print $2}')" ] 615 | then 616 | sed -i "s%^skip_check=$%skip_check=false%" "${config_file}" 617 | fi 618 | if [ -z "$(grep "^skip_download=" "${config_file}" | awk -F= '{print $2}')" ] 619 | then 620 | sed -i "s%^skip_download=$%skip_download=false%" "${config_file}" 621 | fi 622 | if [ -z "$(grep "^skip_live_photos=" "${config_file}" | awk -F= '{print $2}')" ] 623 | then 624 | sed -i "s%^skip_live_photos=$%skip_live_photos=false%" "${config_file}" 625 | fi 626 | if [ -z "$(grep "^skip_videos=" "${config_file}" | awk -F= '{print $2}')" ] 627 | then 628 | sed -i "s%^skip_videos=$%skip_videos=false%" "${config_file}" 629 | fi 630 | if [ -z "$(grep "^startup_notification=" "${config_file}" | awk -F= '{print $2}')" ] 631 | then 632 | sed -i "s%^startup_notification=$%startup_notification=true%" "${config_file}" 633 | fi 634 | if [ -z "$(grep "^download_delay=" "${config_file}" | awk -F= '{print $2}')" ] 635 | then 636 | sed -i "s%^download_delay=$%download_delay=0%" "${config_file}" 637 | fi 638 | if [ -z "$(grep "^download_interval=" "${config_file}" | awk -F= '{print $2}')" ] 639 | then 640 | sed -i "s%^download_interval=$%download_interval=86400%" "${config_file}" 641 | fi 642 | if [ -z "$(grep "^synology_ignore_path=" "${config_file}" | awk -F= '{print $2}')" ] 643 | then 644 | sed -i "s%^synology_ignore_path=$%synology_ignore_path=false%" "${config_file}" 645 | fi 646 | if [ -z "$(grep "^synology_photos_app_fix=" "${config_file}" | awk -F= '{print $2}')" ] 647 | then 648 | sed -i "s%^synology_photos_app_fix=$%synology_photos_app_fix=false%" "${config_file}" 649 | fi 650 | if [ -z "$(grep "^telegram_http=" "${config_file}" | awk -F= '{print $2}')" ] 651 | then 652 | sed -i "s%^telegram_http=$%telegram_http=false%" "${config_file}" 653 | fi 654 | if [ -z "$(grep "^user=" "${config_file}" | awk -F= '{print $2}')" ] 655 | then 656 | sed -i "s%^user=$%user=user%" "${config_file}" 657 | fi 658 | if [ -z "$(grep "^user_id=" "${config_file}" | awk -F= '{print $2}')" ] 659 | then 660 | sed -i "s%^user_id=$%user_id=1000%" "${config_file}" 661 | fi 662 | if [ -z "$(grep "^webhook_https=" "${config_file}" | awk -F= '{print $2}')" ] 663 | then 664 | sed -i "s%^webhook_https=$%webhook_https=false%" "${config_file}" 665 | fi 666 | if [ -z "$(grep "^webhook_path=" "${config_file}" | awk -F= '{print $2}')" ] 667 | then 668 | sed -i "s%^webhook_path=$%webhook_path=/api/webhook/%" "${config_file}" 669 | fi 670 | if [ -z "$(grep "^webhook_port=" "${config_file}" | awk -F= '{print $2}')" ] 671 | then 672 | sed -i "s%^webhook_port=$%webhook_port=8123%" "${config_file}" 673 | fi 674 | if [ -z "$(grep "^webhook_insecure=" "${config_file}" | awk -F= '{print $2}')" ] 675 | then 676 | sed -i "s%^webhook_insecure=$%webhook_insecure=false%" "${config_file}" 677 | fi 678 | if [ -z "$(grep "^msmtp_tls=" "${config_file}" | awk -F= '{print $2}')" ] 679 | then 680 | sed -i "s%^msmtp_tls=$%msmtp_tls=on%" "${config_file}" 681 | fi 682 | if [ -z "$(grep "^msmtp_args=" "${config_file}" | awk -F= '{print $2}')" ] 683 | then 684 | sed -i "s%^msmtp_args=$%msmtp_args=--tls-starttls=off%" "${config_file}" 685 | fi 686 | 687 | # Update configuration file with with values from Docker environment variables, in case they've changed 688 | if [ "${albums_with_dates}" ] 689 | then 690 | sed -i "s%^albums_with_dates=.*%albums_with_dates=${albums_with_dates}%" "${config_file}" 691 | fi 692 | if [ "${align_raw}" ] 693 | then 694 | sed -i "s%^align_raw=.*%align_raw=${align_raw}%" "${config_file}" 695 | fi 696 | if [ "${apple_id}" ] 697 | then 698 | sed -i "s%^apple_id=.*%apple_id=${apple_id}%" "${config_file}" 699 | fi 700 | if [ "${authentication_type}" ] 701 | then 702 | sed -i "s%^authentication_type=.*%authentication_type=${authentication_type}%" "${config_file}" 703 | fi 704 | if [ "${auth_china}" ] 705 | then 706 | sed -i "s%^auth_china=.*%auth_china=${auth_china}%" "${config_file}" 707 | fi 708 | if [ "${auto_delete}" ] 709 | then 710 | sed -i "s%^auto_delete=.*%auto_delete=${auto_delete}%" "${config_file}" 711 | fi 712 | if [ "${bark_device_key}" ] 713 | then 714 | sed -i "s%^bark_device_key=.*%bark_device_key=${bark_device_key}%" "${config_file}" 715 | fi 716 | if [ "${bark_server}" ] 717 | then 718 | sed -i "s%^bark_server=.*%bark_server=${bark_server}%" "${config_file}" 719 | fi 720 | if [ "${convert_heic_to_jpeg}" ] 721 | then 722 | sed -i "s%^convert_heic_to_jpeg=.*%convert_heic_to_jpeg=${convert_heic_to_jpeg}%" "${config_file}" 723 | fi 724 | if [ "${debug_logging}" ] 725 | then 726 | sed -i "s%^debug_logging=.*%debug_logging=${debug_logging}%" "${config_file}" 727 | fi 728 | if [ "${delete_accompanying}" ] 729 | then 730 | sed -i "s%^delete_accompanying=.*%delete_accompanying=${delete_accompanying}%" "${config_file}" 731 | fi 732 | if [ "${delete_after_download}" ] 733 | then 734 | sed -i "s%^delete_after_download=.*%delete_after_download=${delete_after_download}%" "${config_file}" 735 | fi 736 | if [ "${delete_empty_directories}" ] 737 | then 738 | sed -i "s%^delete_empty_directories=.*%delete_empty_directories=${delete_empty_directories}%" "${config_file}" 739 | fi 740 | if [ "${delete_notifications}" ] 741 | then 742 | sed -i "s%^delete_notifications=.*%delete_notifications=${delete_notifications}%" "${config_file}" 743 | fi 744 | if [ "${dingtalk_token}" ] 745 | then 746 | sed -i "s%^dingtalk_token=.*%dingtalk_token=${dingtalk_token}%" "${config_file}" 747 | fi 748 | if [ "${directory_permissions}" ] 749 | then 750 | sed -i "s%^directory_permissions=.*%directory_permissions=${directory_permissions}%" "${config_file}" 751 | fi 752 | if [ "${discord_id}" ] 753 | then 754 | sed -i "s%^discord_id=.*%discord_id=${discord_id}%" "${config_file}" 755 | fi 756 | if [ "${discord_token}" ] 757 | then 758 | sed -i "s%^discord_token=.*%discord_token=${discord_token}%" "${config_file}" 759 | fi 760 | if [ "${download_notifications}" ] 761 | then 762 | sed -i "s%^download_notifications=.*%download_notifications=${download_notifications}%" "${config_file}" 763 | fi 764 | if [ "${download_path}" ] 765 | then 766 | sed -i "s%^download_path=.*%download_path=${download_path}%" "${config_file}" 767 | fi 768 | if [ "${fake_user_agent}" ] 769 | then 770 | sed -i "s%^fake_user_agent=.*%fake_user_agent=${fake_user_agent}%" "${config_file}" 771 | fi 772 | if [ "${file_match_policy}" ] 773 | then 774 | sed -i "s%^file_match_policy=.*%file_match_policy=${file_match_policy}%" "${config_file}" 775 | fi 776 | if [ "${file_permissions}" ] 777 | then 778 | sed -i "s%^file_permissions=.*%file_permissions=${file_permissions}%" "${config_file}" 779 | fi 780 | if [ "${folder_structure}" ] 781 | then 782 | sanitised_folder_structure="${folder_structure//\//\\/}" 783 | sed -i "s@^folder_structure=.*@folder_structure=${sanitised_folder_structure}@" "${config_file}" 784 | fi 785 | if [ "${force_gid}" ] 786 | then 787 | sed -i "s%^force_gid=.*%force_gid=${force_gid}%" "${config_file}" 788 | fi 789 | if [ "${gotify_app_token}" ] 790 | then 791 | sed -i "s%^gotify_app_token=.*%gotify_app_token=${gotify_app_token}%" "${config_file}" 792 | fi 793 | if [ "${gotify_https}" ] 794 | then 795 | sed -i "s%^gotify_https=.*%gotify_https=${gotify_https}%" "${config_file}" 796 | fi 797 | if [ "${gotify_server_url}" ] 798 | then 799 | sed -i "s%^gotify_server_url=.*%gotify_server_url=${gotify_server_url}%" "${config_file}" 800 | fi 801 | if [ "${group}" ] 802 | then 803 | sed -i "s%^group=.*%group=${group}%" "${config_file}" 804 | fi 805 | if [ "${group_id}" ] 806 | then 807 | sed -i "s%^group_id=.*%group_id=${group_id}%" "${config_file}" 808 | fi 809 | if [ "${icloud_china}" ] 810 | then 811 | sed -i "s%^icloud_china=.*%icloud_china=${icloud_china}%" "${config_file}" 812 | fi 813 | if [ "${iyuu_token}" ] 814 | then 815 | sed -i "s%^iyuu_token=.*%iyuu_token=${iyuu_token}%" "${config_file}" 816 | fi 817 | if [ "${jpeg_path}" ] 818 | then 819 | sed -i "s%^jpeg_path=.*%jpeg_path=${jpeg_path}%" "${config_file}" 820 | fi 821 | if [ "${jpeg_quality}" ] 822 | then 823 | sed -i "s%^jpeg_quality=.*%jpeg_quality=${jpeg_quality}%" "${config_file}" 824 | fi 825 | if [ "${keep_icloud_recent_days}" ] 826 | then 827 | sed -i "s%^keep_icloud_recent_days=.*%keep_icloud_recent_days=${keep_icloud_recent_days}%" "${config_file}" 828 | fi 829 | if [ "${keep_icloud_recent_only}" ] 830 | then 831 | sed -i "s%^keep_icloud_recent_only=.*%keep_icloud_recent_only=${keep_icloud_recent_only}%" "${config_file}" 832 | fi 833 | if [ "${keep_unicode}" ] 834 | then 835 | sed -i "s%^keep_unicode=.*%keep_unicode=${keep_unicode}%" "${config_file}" 836 | fi 837 | if [ "${libraries_with_dates}" ] 838 | then 839 | sed -i "s%^libraries_with_dates=.*%libraries_with_dates=${libraries_with_dates}%" "${config_file}" 840 | fi 841 | if [ "${live_photo_mov_filename_policy}" ] 842 | then 843 | sed -i "s%^live_photo_mov_filename_policy=.*%live_photo_mov_filename_policy=${live_photo_mov_filename_policy}%" "${config_file}" 844 | fi 845 | if [ "${live_photo_size}" ] 846 | then 847 | sed -i "s%^live_photo_size=.*%live_photo_size=${live_photo_size}%" "${config_file}" 848 | fi 849 | if [ "${nextcloud_delete}" ] 850 | then 851 | sed -i "s%^nextcloud_delete=.*%nextcloud_delete=${nextcloud_delete}%" "${config_file}" 852 | fi 853 | if [ "${nextcloud_upload}" ] 854 | then 855 | sed -i "s%^nextcloud_upload=.*%nextcloud_upload=${nextcloud_upload}%" "${config_file}" 856 | fi 857 | if [ "${nextcloud_url}" ] 858 | then 859 | sed -i "s%^nextcloud_url=.*%nextcloud_url=${nextcloud_url}%" "${config_file}" 860 | fi 861 | if [ "${nextcloud_username}" ] 862 | then 863 | sed -i "s%^nextcloud_username=.*%nextcloud_username=${nextcloud_username}%" "${config_file}" 864 | fi 865 | if [ "${nextcloud_password}" ] 866 | then 867 | sed -i "s%^nextcloud_password=.*%nextcloud_password=${nextcloud_password}%" "${config_file}" 868 | fi 869 | if [ "${notification_days}" ] 870 | then 871 | sed -i "s%^notification_days=.*%notification_days=${notification_days}%" "${config_file}" 872 | fi 873 | if [ "${notification_type}" ] 874 | then 875 | sed -i "s%^notification_type=.*%notification_type=${notification_type}%" "${config_file}" 876 | fi 877 | if [ "${photo_album}" ] 878 | then 879 | sed -i "s%^photo_album=.*%photo_album=\"${photo_album}\"%" "${config_file}" 880 | fi 881 | if [ "${photo_library}" ] 882 | then 883 | sed -i "s%^photo_library=.*%photo_library=${photo_library}%" "${config_file}" 884 | fi 885 | if [ "${photo_size}" ] 886 | then 887 | sed -i "s%^photo_size=.*%photo_size=${photo_size}%" "${config_file}" 888 | fi 889 | if [ "${prowl_api_key}" ] 890 | then 891 | sed -i "s%^prowl_api_key=.*%prowl_api_key=${prowl_api_key}%" "${config_file}" 892 | fi 893 | if [ "${pushover_sound}" ] 894 | then 895 | sed -i "s%^pushover_sound=.*%pushover_sound=${pushover_sound}%" "${config_file}" 896 | fi 897 | if [ "${pushover_token}" ] 898 | then 899 | sed -i "s%^pushover_token=.*%pushover_token=${pushover_token}%" "${config_file}" 900 | fi 901 | if [ "${pushover_user}" ] 902 | then 903 | sed -i "s%^pushover_user=.*%pushover_user=${pushover_user}%" "${config_file}" 904 | fi 905 | if [ "${recent_only}" ] 906 | then 907 | sed -i "s%^recent_only=.*%recent_only=${recent_only}%" "${config_file}" 908 | fi 909 | if [ "${set_exif_datetime}" ] 910 | then 911 | sed -i "s%^set_exif_datetime=.*%set_exif_datetime=${set_exif_datetime}%" "${config_file}" 912 | fi 913 | if [ "${sideways_copy}" ] 914 | then 915 | sed -i "s%^sideways_copy_videos=.*%sideways_copy_videos=${sideways_copy}%" "${config_file}" 916 | fi 917 | if [ "${sideways_copy_mode}" ] 918 | then 919 | sed -i "s%^sideways_copy_videos_mode=.*%sideways_copy_videos_mode=${sideways_copy_mode}%" "${config_file}" 920 | fi 921 | if [ "${silent_file_notifications}" ] 922 | then 923 | sed -i "s%^silent_file_notifications=.*%silent_file_notifications=${silent_file_notifications}%" "${config_file}" 924 | fi 925 | if [ "${single_pass}" ] 926 | then 927 | sed -i "s%^single_pass=.*%single_pass=${single_pass}%" "${config_file}" 928 | fi 929 | if [ "${skip_album}" ] 930 | then 931 | sed -i "s%^skip_album=.*%skip_album=\"${skip_album}\"%" "${config_file}" 932 | fi 933 | if [ "${skip_library}" ] 934 | then 935 | sed -i "s%^skip_library=.*%skip_library=\"${skip_library}\"%" "${config_file}" 936 | fi 937 | if [ "${skip_check}" ] 938 | then 939 | sed -i "s%^skip_check=.*%skip_check=${skip_check}%" "${config_file}" 940 | fi 941 | if [ "${skip_download}" ] 942 | then 943 | sed -i "s%^skip_download=.*%skip_download=${skip_download}%" "${config_file}" 944 | fi 945 | if [ "${skip_live_photos}" ] 946 | then 947 | sed -i "s%^skip_live_photos=.*%skip_live_photos=${skip_live_photos}%" "${config_file}" 948 | fi 949 | if [ "${skip_videos}" ] 950 | then 951 | sed -i "s%^skip_videos=.*%skip_videos=${skip_videos}%" "${config_file}" 952 | fi 953 | if [ "${startup_notification}" ] 954 | then 955 | sed -i "s%^startup_notification=.*%startup_notification=${startup_notification}%" "${config_file}" 956 | fi 957 | if [ "${download_delay}" ] 958 | then 959 | sed -i "s%^download_delay=.*%download_delay=${download_delay}%" "${config_file}" 960 | fi 961 | if [ "${download_interval}" ] 962 | then 963 | sed -i "s%^download_interval=.*%download_interval=${download_interval}%" "${config_file}" 964 | fi 965 | if [ "${synology_ignore_path}" ] 966 | then 967 | sed -i "s%^synology_ignore_path=.*%synology_ignore_path=${synology_ignore_path}%" "${config_file}" 968 | fi 969 | if [ "${synology_photos_app_fix}" ] 970 | then 971 | sed -i "s%^synology_photos_app_fix=.*%synology_photos_app_fix=${synology_photos_app_fix}%" "${config_file}" 972 | fi 973 | if [ "${telegram_chat_id}" ] 974 | then 975 | sed -i "s%^telegram_chat_id=.*%telegram_chat_id=${telegram_chat_id}%" "${config_file}" 976 | fi 977 | if [ "${telegram_http}" ] 978 | then 979 | sed -i "s%^telegram_http=.*%telegram_http=${telegram_http}%" "${config_file}" 980 | fi 981 | if [ "${telegram_polling}" ] 982 | then 983 | sed -i "s%^telegram_polling=.*%telegram_polling=${telegram_polling}%" "${config_file}" 984 | fi 985 | if [ "${telegram_server}" ] 986 | then 987 | sed -i "s%^telegram_server=.*%telegram_server=${telegram_server}%" "${config_file}" 988 | fi 989 | if [ "${telegram_token}" ] 990 | then 991 | sed -i "s%^telegram_token=.*%telegram_token=${telegram_token}%" "${config_file}" 992 | fi 993 | if [ "${trigger_nextlcoudcli_download}" ] 994 | then 995 | sed -i "s%^trigger_nextlcoudcli_download=.*%trigger_nextlcoudcli_download=${trigger_nextlcoudcli_download}%" "${config_file}" 996 | fi 997 | if [ "${until_found}" ] 998 | then 999 | sed -i "s%^until_found=.*%until_found=${until_found}%" "${config_file}" 1000 | fi 1001 | if [ "${user}" ] 1002 | then 1003 | sed -i "s%^user=.*%user=${user}%" "${config_file}" 1004 | fi 1005 | if [ "${user_id}" ] 1006 | then 1007 | sed -i "s%^user_id=.*%user_id=${user_id}%" "${config_file}" 1008 | fi 1009 | if [ "${video_path}" ] 1010 | then 1011 | sed -i "s%^video_path=.*%video_path=${video_path}%" "${config_file}" 1012 | fi 1013 | if [ "${webhook_https}" ] 1014 | then 1015 | sed -i "s%^webhook_https=.*%webhook_https=${webhook_https}%" "${config_file}" 1016 | fi 1017 | if [ "${webhook_id}" ] 1018 | then 1019 | sed -i "s%^webhook_id=.*%webhook_id=${webhook_id}%" "${config_file}" 1020 | fi 1021 | if [ "${webhook_path}" ] 1022 | then 1023 | sed -i "s%^webhook_path=.*%webhook_path=${webhook_path}%" "${config_file}" 1024 | fi 1025 | if [ "${webhook_port}" ] 1026 | then 1027 | sed -i "s%^webhook_port=.*%webhook_port=${webhook_port}%" "${config_file}" 1028 | fi 1029 | if [ "${webhook_server}" ] 1030 | then 1031 | sed -i "s%^webhook_server=.*%webhook_server=${webhook_server}%" "${config_file}" 1032 | fi 1033 | if [ "${webhook_insecure}" ] 1034 | then 1035 | sed -i "s%^webhook_insecure=.*%webhook_insecure=${webhook_insecure}%" "${config_file}" 1036 | fi 1037 | if [ "${wecom_id}" ] 1038 | then 1039 | sed -i "s%^wecom_id=.*%wecom_id=${wecom_id}%" "${config_file}" 1040 | fi 1041 | if [ "${wecom_proxy}" ] 1042 | then 1043 | sed -i "s%^wecom_proxy=.*%wecom_proxy=${wecom_proxy}%" "${config_file}" 1044 | fi 1045 | if [ "${wecom_secret}" ] 1046 | then 1047 | sed -i "s%^wecom_secret=.*%wecom_secret=${wecom_secret}%" "${config_file}" 1048 | fi 1049 | if [ "${msmtp_host}" ] 1050 | then 1051 | sed -i "s%^msmtp_host=.*%msmtp_host=${msmtp_host}%" "${config_file}" 1052 | fi 1053 | if [ "${msmtp_port}" ] 1054 | then 1055 | sed -i "s%^msmtp_port=.*%msmtp_port=${msmtp_port}%" "${config_file}" 1056 | fi 1057 | if [ "${msmtp_user}" ] 1058 | then 1059 | sed -i "s%^msmtp_user=.*%msmtp_user=${msmtp_user}%" "${config_file}" 1060 | fi 1061 | if [ "${msmtp_from}" ] 1062 | then 1063 | sed -i "s%^msmtp_from=.*%msmtp_from=${msmtp_from}%" "${config_file}" 1064 | fi 1065 | if [ "${msmtp_pass}" ] 1066 | then 1067 | sed -i "s%^msmtp_pass=.*%msmtp_pass=${msmtp_pass}%" "${config_file}" 1068 | fi 1069 | if [ "${msmtp_to}" ] 1070 | then 1071 | sed -i "s%^msmtp_to=.*%msmtp_to=${msmtp_to}%" "${config_file}" 1072 | fi 1073 | if [ "${msmtp_tls}" ] 1074 | then 1075 | sed -i "s%^msmtp_tls=.*%msmtp_tls=${msmtp_tls}%" "${config_file}" 1076 | fi 1077 | if [ "${msmtp_args}" ] 1078 | then 1079 | sed -i "s%^msmtp_args=.*%msmtp_args=${msmtp_args}%" "${config_file}" 1080 | fi 1081 | if [ "${agentid}" ] 1082 | then 1083 | sed -i "s%^agentid=.*%agentid=${agentid}%" "${config_file}" 1084 | fi 1085 | if [ "${touser}" ] 1086 | then 1087 | sed -i "s%^touser=.*%touser=${touser}%" "${config_file}" 1088 | fi 1089 | if [ "${content_source_url}" ] 1090 | then 1091 | sed -i "s%^content_source_url=.*%content_source_url=${content_source_url}%" "${config_file}" 1092 | fi 1093 | if [ "${name}" ] 1094 | then 1095 | sed -i "s%^name=.*%name=${name}%" "${config_file}" 1096 | fi 1097 | if [ "${media_id_startup}" ] 1098 | then 1099 | sed -i "s%^media_id_startup=.*%media_id_startup=${media_id_startup}%" "${config_file}" 1100 | fi 1101 | if [ "${media_id_download}" ] 1102 | then 1103 | sed -i "s%^media_id_download=.*%media_id_download=${media_id_download}%" "${config_file}" 1104 | fi 1105 | if [ "${media_id_delete}" ] 1106 | then 1107 | sed -i "s%^media_id_delete=.*%media_id_delete=${media_id_delete}%" "${config_file}" 1108 | fi 1109 | if [ "${media_id_expiration}" ] 1110 | then 1111 | sed -i "s%^media_id_expiration=.*%media_id_expiration=${media_id_expiration}%" "${config_file}" 1112 | fi 1113 | if [ "${media_id_warning}" ] 1114 | then 1115 | sed -i "s%^media_id_warning=.*%media_id_warning=${media_id_warning}%" "${config_file}" 1116 | fi 1117 | 1118 | # Set case sensitive variables to lowercase 1119 | notification_type_lc="$(grep "^notification_type=" /config/icloudpd.conf | awk -F= '{print $2}' | tr '[:upper:]' '[:lower:]')" 1120 | if [ "${notification_type_lc}" ] 1121 | then 1122 | sed -i "s%^notification_type=.*%notification_type=${notification_type_lc}%" "${config_file}" 1123 | fi 1124 | 1125 | # Remove trailing slashes on directories 1126 | download_path_temp="$(grep "^download_path=" /config/icloudpd.conf | awk -F= '{print $2}')" 1127 | if [ "${download_path_temp}" ] 1128 | then 1129 | sed -i "s#^download_path=.*#download_path=${download_path_temp%/}#" "${config_file}" 1130 | fi 1131 | jpeg_path_temp="$(grep "^jpeg_path=" /config/icloudpd.conf | awk -F= '{print $2}')" 1132 | if [ "${jpeg_path_temp}" ] 1133 | then 1134 | sed -i "s#^jpeg_path=.*#jpeg_path=${jpeg_path_temp%/}#" "${config_file}" 1135 | fi 1136 | video_path_temp="$(grep "^video_path=" /config/icloudpd.conf | awk -F= '{print $2}')" 1137 | if [ "${video_path_temp}" ] 1138 | then 1139 | sed -i "s%^video_path=.*%video_path=${video_path_temp%/}%" "${config_file}" 1140 | fi 1141 | nextcloud_url_temp="$(grep "^nextcloud_url=" /config/icloudpd.conf | awk -F= '{print $2}')" 1142 | if [ "${nextcloud_url_temp}" ] 1143 | then 1144 | sed -i "s%^nextcloud_url=.*%nextcloud_url=${nextcloud_url_temp%/}%" "${config_file}" 1145 | fi 1146 | nextcloud_target_dir_temp="$(grep "^nextcloud_target_dir=" /config/icloudpd.conf | awk -F= '{print $2}')" 1147 | if [ "${nextcloud_target_dir_temp}" ] 1148 | then 1149 | sed -i "s%^nextcloud_target_dir=.*%nextcloud_target_dir=${nextcloud_target_dir_temp%/}%" "${config_file}" 1150 | fi 1151 | 1152 | # Update config file 1153 | mv "${config_file}" "${config_file}.tmp" 1154 | sort "${config_file}.tmp" --output="${config_file}" 1155 | sed -i '/^$/d' "${config_file}.tmp" 1156 | chmod --reference="${config_file}.tmp" "${config_file}" 1157 | rm "${config_file}.tmp" 1158 | 1159 | sed -i 's/=True/=true/gI' "${config_file}" 1160 | sed -i 's/=False/=false/gI' "${config_file}" 1161 | sed -i 's/debug_logging=AAAAA/debug_logging=false/' "${config_file}" 1162 | sed -i 's/authentication_type=2FA/authentication_type=MFA/' "${config_file}" 1163 | sed -i '/keep_recent_days=/d' "${config_file}" -------------------------------------------------------------------------------- /launcher.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ash 2 | 3 | log_info() 4 | { 5 | echo "$(date '+%Y-%m-%d %H:%M:%S') INFO ${1}" 6 | } 7 | 8 | log_info_n() 9 | { 10 | echo -n "$(date '+%Y-%m-%d %H:%M:%S') INFO ${1}" 11 | } 12 | 13 | log_warning() 14 | { 15 | echo "$(date '+%Y-%m-%d %H:%M:%S') WARNING ${1}" 16 | } 17 | 18 | log_error() 19 | { 20 | echo "$(date '+%Y-%m-%d %H:%M:%S') ERROR ${1}" 21 | } 22 | 23 | log_debug() 24 | { 25 | if [ "${debug_logging}" = true ] 26 | then 27 | echo "$(date '+%Y-%m-%d %H:%M:%S') DEBUG ${1}" 28 | fi 29 | } 30 | 31 | create_group() 32 | { 33 | if [ "$(grep -c "^${group}:x:${group_id}:" "/etc/group")" -eq 0 ] 34 | then 35 | log_debug " | Creating minimal /etc/group file" 36 | { 37 | echo 'root:x:0:root' 38 | echo 'tty:x:5:' 39 | echo 'shadow:x:42:' 40 | } >/etc/group 41 | if [ "$(grep -c "^${group}:" "/etc/group")" -eq 1 ] 42 | then 43 | log_error " | Group name, ${group}, already in use. Cannot continue. Halting" 44 | sleep infinity 45 | fi 46 | log_debug " | Creating group ${group}:${group_id}" 47 | groupadd --gid "${group_id}" "${group}" 48 | fi 49 | } 50 | 51 | create_user() 52 | { 53 | if [ "$(grep -c "^${user}:x:${user_id}:${group_id}" "/etc/passwd")" -eq 0 ] 54 | then 55 | log_debug " | Creating minimal /etc/passwd file" 56 | echo "root:x:0:0:root:/root:/bin/ash" >/etc/passwd 57 | log_debug " | Creating user ${user}:x:${user_id}:${group_id}::/home/${user}:/bin/ash" 58 | useradd "${user}" --uid "${user_id}" --gid "${group_id}" --home-dir "/home/${user}" --shell /bin/ash --badname 59 | fi 60 | } 61 | 62 | set_user_mask() 63 | { 64 | case "${file_permissions}" in 65 | 600) user_mask=077;; 66 | 640) user_mask=027;; 67 | 644) user_mask=022;; 68 | 660) user_mask=007;; 69 | 664) user_mask=002;; 70 | 666) user_mask=000;; 71 | *) user_mask=077;; 72 | esac 73 | log_info " - Setting file mode creation mask from configured file permissions (${file_permissions}): ${user_mask}" 74 | sed -i "s/^umask.*/umask ${user_mask}/" /etc/profile 75 | umask "${user_mask}" 76 | } 77 | 78 | set_owner_and_permissions_downloads() 79 | { 80 | log_info " - Setting owner, group and permissions on: ${download_path}" 81 | log_debug " | Set owner" 82 | find "${download_path}" ! -type l ! -user "${user_id}" ! -path "${ignore_path}" -exec chown "${user_id}" {} + 83 | log_debug " | Set group" 84 | find "${download_path}" ! -type l ! -group "${group_id}" ! -path "${ignore_path}" -exec chgrp "${group_id}" {} + 85 | log_debug " | Set ${directory_permissions} permissions on directories" 86 | find "${download_path}" -type d ! -perm "${directory_permissions}" ! -path "${ignore_path}" -exec chmod "${directory_permissions}" '{}' + 87 | log_debug " | Set ${file_permissions} permissions on files" 88 | find "${download_path}" -type f ! -perm "${file_permissions}" ! -path "${ignore_path}" -exec chmod "${file_permissions}" '{}' + 89 | } 90 | 91 | set_owner_and_permissions_jpegs() 92 | { 93 | log_info " - Setting owner, group and permissions on: ${jpeg_path}" 94 | log_debug " | Set owner" 95 | find "${jpeg_path}" ! -type l ! -user "${user_id}" ! -path "${ignore_path}" -exec chown "${user_id}" {} + 96 | log_debug " | Set group" 97 | find "${jpeg_path}" ! -type l ! -group "${group_id}" ! -path "${ignore_path}" -exec chgrp "${group_id}" {} + 98 | log_debug " | Set ${directory_permissions} permissions on directories" 99 | find "${jpeg_path}" -type d ! -perm "${directory_permissions}" ! -path "${ignore_path}" -exec chmod "${directory_permissions}" '{}' + 100 | log_debug " | Set ${file_permissions} permissions on files" 101 | find "${jpeg_path}" -type f ! -perm "${file_permissions}" ! -path "${ignore_path}" -exec chmod "${file_permissions}" '{}' + 102 | } 103 | 104 | set_owner_and_permissions_videos() 105 | { 106 | log_info " - Setting owner, group and permissions on: ${video_path}" 107 | log_debug " | Set owner" 108 | find "${video_path}" ! -type l ! -user "${user_id}" ! -path "${ignore_path}" -exec chown "${user_id}" {} + 109 | log_debug " | Set group" 110 | find "${video_path}" ! -type l ! -group "${group_id}" ! -path "${ignore_path}" -exec chgrp "${group_id}" {} + 111 | log_debug " | Set ${directory_permissions} permissions on directories" 112 | find "${video_path}" -type d ! -perm "${directory_permissions}" ! -path "${ignore_path}" -exec chmod "${directory_permissions}" '{}' + 113 | log_debug " | Set ${file_permissions} permissions on files" 114 | find "${video_path}" -type f ! -perm "${file_permissions}" ! -path "${ignore_path}" -exec chmod "${file_permissions}" '{}' + 115 | } 116 | 117 | set_owner_and_permissions_config() 118 | { 119 | log_info " - Set owner and group on config directory: /config" 120 | chown -R "${user_id}:${group_id}" "/config" 121 | log_info " - Set owner and group on icloudpd temp directory: /tmp/icloudpd" 122 | chown -R "${user_id}:${group_id}" "/tmp/icloudpd" 123 | } 124 | 125 | run_as() 126 | { 127 | if [ "$(id -u)" = 0 ] 128 | then 129 | su "${user}" -s /bin/ash -c "${1}" 130 | else 131 | /bin/ash -c "${1}" 132 | fi 133 | } 134 | 135 | disable_notifications() 136 | { 137 | log_warning " | $(echo "${notification_type}" | cut -c1 | tr '[:lower:]' '[:upper:]')$(echo "${notification_type}" | cut -c2-) notifications enabled, but API key/token/secret/server/id not set" 138 | log_warning " ! Disabling nofifications" 139 | sed 's/notification_type=.*/notification_type=/' "${config_file}" 140 | sleep 1m 141 | } 142 | 143 | ##### Start Script ##### 144 | log_info "Initialising container..." 145 | 146 | # Create the temporary directory 147 | if [ ! -d "/tmp/icloudpd" ] 148 | then 149 | log_info " - Creating temporary directory" 150 | if ! mkdir --parents "/tmp/icloudpd" 151 | then 152 | log_error "Failed to create temporary directory" 153 | fi 154 | fi 155 | 156 | # Remove pre-existing temporary files 157 | if [ -f "/tmp/icloudpd/icloudpd_check_exit_code" ] 158 | then 159 | rm "/tmp/icloudpd/icloudpd_check_exit_code" 160 | fi 161 | if [ -f "/tmp/icloudpd/icloudpd_download_exit_code" ] 162 | then 163 | rm "/tmp/icloudpd/icloudpd_download_exit_code" 164 | fi 165 | if [ -f "/tmp/icloudpd/icloudpd_check_error" ] 166 | then 167 | rm "/tmp/icloudpd/icloudpd_check_error" 168 | fi 169 | if [ -f "/tmp/icloudpd/icloudpd_download_error" ] 170 | then 171 | rm "/tmp/icloudpd/icloudpd_download_error" 172 | fi 173 | if [ -f "/tmp/icloudpd/icloudpd_sync.log" ] 174 | then 175 | rm "/tmp/icloudpd/icloudpd_sync.log" 176 | fi 177 | if [ -f "/tmp/icloudpd/icloudpd_tracert.err" ] 178 | then 179 | rm "/tmp/icloudpd/icloudpd_tracert.err" 180 | fi 181 | 182 | # Create new temporary files 183 | log_info " - Create temporary files" 184 | if ! touch "/tmp/icloudpd/icloudpd_check_exit_code" 185 | then 186 | log_error " | Failed to create /tmp/icloudpd/icloudpd_check_exit_code" 187 | log_error " ! Cannot continue. Halting" 188 | sleep infinity 189 | fi 190 | if ! touch "/tmp/icloudpd/icloudpd_download_exit_code" 191 | then 192 | log_error " | Failed to create /tmp/icloudpd/icloudpd_download_exit_code" 193 | log_error " ! Cannot continue. Halting" 194 | sleep infinity 195 | fi 196 | if ! touch "/tmp/icloudpd/icloudpd_check_error" 197 | then 198 | log_error " | Failed to create /tmp/icloudpd/icloudpd_check_error" 199 | log_error " ! Cannot continue. Halting" 200 | sleep infinity 201 | fi 202 | if ! touch "/tmp/icloudpd/icloudpd_download_error" 203 | then 204 | log_error " | Failed to create /tmp/icloudpd/icloudpd_download_error" 205 | log_error " ! Cannot continue. Halting" 206 | sleep infinity 207 | fi 208 | if ! touch "/tmp/icloudpd/icloudpd_sync.log" 209 | then 210 | log_error " | Failed to create /tmp/icloudpd/icloudpd_sync.log" 211 | log_error " ! Cannot continue. Halting" 212 | sleep infinity 213 | fi 214 | if ! touch "/tmp/icloudpd/icloudpd_tracert.err" 215 | then 216 | log_error " | Failed to create /tmp/icloudpd/icloudpd_tracert.err" 217 | log_error " ! Cannot continue. Halting" 218 | sleep infinity 219 | fi 220 | if ! touch "/tmp/icloudpd/expect_input.txt" 221 | then 222 | log_error " | Failed to create /tmp/icloudpd/expect_input.txt" 223 | log_error " ! Cannot continue. Halting" 224 | sleep infinity 225 | fi 226 | 227 | # Push icloudpd version to file so it's a smidgen quicker to load on container restarts/syncs 228 | /opt/icloudpd/bin/icloudpd --version | awk -F, '{print $1}' | sed 's/version://' > /tmp/icloudpd/icloudpd_version 229 | python3 --version | awk '{print $2}' > /tmp/icloudpd/python_version 230 | 231 | # Check the config directory exists and create it if it does not 232 | log_info " - Checking configuration file permissions" 233 | if [ ! -d "/config" ] 234 | then 235 | if ! mkdir /config 236 | then 237 | log_error " | Failed to create configuration directory: /config" 238 | log_error " ! Check your volume mount is not read-only. Check NFS/SMB share permissions if mounting to a shared location" 239 | log_error " ! Cannot continue. Halting" 240 | sleep infinity 241 | fi 242 | fi 243 | 244 | # Check the config file exists and create it if it does not 245 | if [ ! -f "${config_file}" ] 246 | then 247 | if ! touch "${config_file}" 248 | then 249 | log_error " | Failed to create configration file: ${config_file}" 250 | log_error " ! Check your volume mount is not read-only. Check NFS/SMB share permissions if mounting to a shared location. Check you container has root permissions." 251 | log_error " ! Cannot continue. Halting" 252 | sleep infinity 253 | fi 254 | fi 255 | 256 | # Check the config file isn't actually a directory 257 | if [ ! -f "${config_file}" ] 258 | then 259 | log_error " | Config file appears to be a directory: ${config_file}" 260 | log_error " ! Check your volume mount" 261 | log_error " ! Cannot continue. Halting" 262 | sleep infinity 263 | fi 264 | 265 | # Create enty config file, populate with default variables and configure it if Docker variables are also apecified 266 | log_info " - Create/update configuration file: ${config_file}" 267 | /usr/local/bin/init_config.sh 268 | 269 | # Check config file was created and is writable 270 | if [ ! -f "${config_file}" ] 271 | then 272 | log_error " | Failed to create configuration file: ${config_file}" 273 | log_error " ! Cannot continue. Halting" 274 | sleep infinity 275 | elif [ ! -w "${config_file}" ] 276 | then 277 | log_error " | Cannot write to configuration file: ${config_file}" 278 | log_error " ! Cannot continue. Halting" 279 | sleep infinity 280 | fi 281 | 282 | # Load variables from config file 283 | log_info " - Checking ${config_file} for errors" 284 | source "${config_file}" 285 | 286 | # Check Apple ID set 287 | if [ -z "${apple_id}" ] 288 | then 289 | log_error " | Apple ID not set" 290 | log_error " ! Waiting for it to be added to: ${config_file}" 291 | while [ -z "${apple_id}" ] 292 | do 293 | sleep 10 294 | source "${config_file}" 295 | done 296 | fi 297 | 298 | # Check download interval 299 | case "${download_interval}" in 300 | 21600) download_interval=21600;; # 6 hours 301 | 43200) download_interval=43200;; # 12 hours 302 | 86400) download_interval=86400;; # 24 hours 303 | 129600) download_interval=129600;; # 36 hours 304 | 172800) download_interval=172800;; # 48 hours 305 | 604800) download_interval=604800;; # 7 days 306 | *) sed 's/download_interval=.*/download_interval=86400/' "${config_file}";; # 24 hours 307 | esac 308 | # Lower it to 60 if set higher 309 | if [ "${download_delay:=61}" -gt 60 ] 310 | then 311 | sed 's/download_delay=.*/download_delay=60/' "${config_file}" 312 | fi 313 | 314 | # Check user not attempting to configure the local user as root as this breaks the "runas" function 315 | if [ "${group}" = "root" ] 316 | then 317 | log_warning " | The local group for download cannot be root, resetting to 'group'" 318 | sed -i "s%^group=$%group=group%" "${config_file}" 319 | user_warning_displayed=true 320 | fi 321 | if [ "${group_id}" -eq 0 ] 322 | then 323 | log_warning " | The local group id for download cannot be 0, resetting to '1000'" 324 | sed -i "s%^group_id=$%group_id=1000%" "${config_file}" 325 | user_warning_displayed=true 326 | fi 327 | 328 | # Check download path variable configured 329 | if [ -z "${download_path}" ] 330 | then 331 | log_error " | Download path is not set properly in config" 332 | log_error " ! Cannot continue. Halting" 333 | sleep infinity 334 | fi 335 | 336 | # Initialise download path 337 | if [ ! -d "${download_path}" ] 338 | then 339 | log_info " | Download directory does not exist" 340 | log_info " | Creating ${download_path} and configuring permssions" 341 | 342 | if ! mkdir --parents "${download_path}" 343 | then 344 | log_error " | Failed to create download directory: '${download_path}'" 345 | log_error " ! Cannot continue. Halting" 346 | sleep infinity 347 | else 348 | chown "${user_id}:${group_id}" "${download_path}" 349 | fi 350 | fi 351 | 352 | # Initialise jpeg_path 353 | if [ "${jpeg_path}" ] && [ ! -d "${jpeg_path}" ] 354 | then 355 | log_info " | JPEG directory does not exist" 356 | log_info " | Creating ${jpeg_path}" 357 | 358 | if ! mkdir --parents "${jpeg_path}" 359 | then 360 | log_error " | Failed to create JPEG directory: '${jpeg_path}'" 361 | log_error " ! Cannot continue. Halting" 362 | sleep infinity 363 | else 364 | chown "${user_id}:${group_id}" "${jpeg_path}" 365 | fi 366 | fi 367 | 368 | # Initialise videos_path 369 | if [ "${video_path}" ] && [ ! -d "${video_path}" ] 370 | then 371 | log_info " | Video directory does not exist" 372 | log_info " | Creating ${video_path}" 373 | 374 | if ! mkdir --parents "${video_path}" 375 | then 376 | log_error " | Failed to create video directory: '${video_path}'" 377 | log_error " ! Cannot continue. Halting" 378 | sleep infinity 379 | else 380 | chown "${user_id}:${group_id}" "${video_path}" 381 | fi 382 | fi 383 | 384 | # Warn if sync interval is too short 385 | if [ "${download_interval}" -lt 43200 ] && [ "${warnings_acknowledged:=false}" = false ] 386 | then 387 | log_warning " | Setting download_interval to less than 43200 (12 hours) may cause throttling by Apple" 388 | log_warning " ! If you run into the following error:" 389 | log_warning " ! 'private db access disabled for this account. Please wait a few hours then try again. The remote servers might be trying to throttle requests. (ACCESS_DENIED)'" 390 | log_warning " ! then check your download_interval is 43200 or greater and switch the container off for 6-12 hours so Apple's throttling expires" 391 | user_warning_displayed=true 392 | fi 393 | 394 | # Warn if set_exif_datetime is enabled 395 | if [ "${set_exif_datetime}" = true ] 396 | then 397 | log_warning " | Configuring set_exif_datetime=true changes the files that are downloaded, so they will be downloaded a second time. Enabling this setting results in a lot of duplicate photos" 398 | user_warning_displayed=true 399 | fi 400 | 401 | # Halt on conflicting settings 402 | if [ "${auto_delete}" != false ] && [ "${delete_after_download}" != false ] 403 | then 404 | log_error " | The variables auto_delete and delete_after_download cannot both be configured at the same time. Please choose one or the other. Halting" 405 | sleep infinity 406 | fi 407 | if [ "${sideways_copy_videos_mode}" = "move" ] && [ "${delete_after_download}" = false ] 408 | then 409 | log_error " | The variable sideways_copy_videos_mode cannot be set to 'move' unless delete_after_download is set to 'true', otherwise all icloud videos will be downloaded every run. Please set the copy mode to 'copy' or delete_after_download to 'true'. Halting" 410 | sleep infinity 411 | fi 412 | if [ "${photo_album}" ] && [ "${photo_library}" ] 413 | then 414 | log_error " | The variables photo_album and photo_library cannot both be configured at the same time. Please configure photo_album, photo_library or neither. Halting" 415 | sleep infinity 416 | fi 417 | 418 | # Display warning when using keep_icloud_recent 419 | if [ "${keep_icloud_recent_only}" = true ] && [ "${warnings_acknowledged:=false}" = false ] 420 | then 421 | log_warning " | The 'Keep iCloud recent' feature deletes all files from iCloud which are older than this amount of days. Setting this to 0 will delete everthing" 422 | log_warning " Please use this with caution. I am not responsible for any data loss. Continuing in 2 minutes" 423 | user_warning_displayed=true 424 | fi 425 | 426 | # Display warning when deleting accompanying files 427 | if [ "${delete_accompanying}" = true ] && [ "${warnings_acknowledged:=false}" = false ] 428 | then 429 | log_info " | Delete accompanying files (.JPG/.HEIC.MOV)" 430 | log_warning " ! This feature deletes files from your local disk. Please use with caution. I am not responsible for any data loss" 431 | log_warning " ! This feature cannot be used if the 'folder_structure' variable is set to 'none' and also, 'set_exif_datetime' must be 'false'" 432 | log_warning " ! These two settings will increase the chances of de-duplication happening, which could result in the wrong files being removed" 433 | user_warning_displayed=true 434 | fi 435 | 436 | # Check China website and authentication sites are not mismatched 437 | if [ "${icloud_china}" = true ] 438 | then 439 | if [ "${auth_china}" != true ] 440 | then 441 | log_warning " | You have the icloud_china variable set to true but auth_china set to false. Are you sure this is correct?" 442 | user_warning_displayed=true 443 | fi 444 | fi 445 | 446 | # Check all Nextcloud variables are present if Nextcloud uploading 447 | if [ "${nextcloud_upload}" = true ] 448 | then 449 | if [ -z "${nextcloud_url}" ] && [ -Z "${nextcloud_username}" ] && [ -z "${nextcloud_password}" ] 450 | then 451 | log_error " | Nextcloud upload: Missing mandatory variables. Halting" 452 | sleep infinity 453 | fi 454 | fi 455 | 456 | # Skip delay when warnings are presented 457 | if [ "${user_warning_displayed:=false}" = true ] 458 | then 459 | if [ "${warnings_acknowledged:=false}" = true ] 460 | then 461 | log_debug " | Configuration warnings acknowledged" 462 | else 463 | log_warning "Non-fatal configuration options detected. Continuing in 2 minutes..." 464 | sleep 120 465 | fi 466 | fi 467 | 468 | # Check notifications 469 | if [ "${notification_type}" ] 470 | then 471 | log_info " - Check $(echo "${notification_type}" | cut -c1 | tr '[:lower:]' '[:upper:]')$(echo "${notification_type}" | cut -c2-) notifications configuration" 472 | if [ "${notification_type}" = "prowl" ] && [ -z "${prowl_api_key}" ] 473 | then 474 | disable_notifications 475 | fi 476 | if [ "${notification_type}" = "pushover" ] 477 | then 478 | if [ -z "${pushover_user}" ] || [ -z "${pushover_token}" ] 479 | then 480 | disable_notifications 481 | fi 482 | fi 483 | if [ "${notification_type}" = "telegram" ] 484 | then 485 | if [ -z "${telegram_token}" ] || [ -z "${telegram_chat_id}" ] 486 | then 487 | disable_notifications 488 | fi 489 | fi 490 | if [ "${notification_type}" = "openhab" ] 491 | then 492 | if [ -z "${webhook_server}" ] || [ -z "${webhook_id}" ] 493 | then 494 | disable_notifications 495 | fi 496 | fi 497 | if [ "${notification_type}" = "webhook" ] 498 | then 499 | if [ -z "${webhook_server}" ] || [ -z "${webhook_id}" ] 500 | then 501 | disable_notifications 502 | fi 503 | fi 504 | if [ "${notification_type}" = "discord" ] 505 | then 506 | if [ -z "${discord_id}" ] || [ -z "${discord_token}" ] 507 | then 508 | disable_notifications 509 | fi 510 | fi 511 | if [ "${notification_type}" = "dingtalk" ] && [ -z "${dingtalk_token}" ] 512 | then 513 | disable_notifications 514 | fi 515 | if [ "${notification_type}" = "iyuu" ] && [ -z "${iyuu_token}" ] 516 | then 517 | disable_notifications 518 | fi 519 | if [ "${notification_type}" = "wecom" ] 520 | then 521 | if [ -z "${wecom_id}" ] || [ -z "${wecom_secret}" ] 522 | then 523 | disable_notifications 524 | fi 525 | fi 526 | if [ "${notification_type}" = "gotify" ] 527 | then 528 | if [ -z "${gotify_app_token}" ] || [ -z "${gotify_server_url}" ] 529 | then 530 | disable_notifications 531 | fi 532 | fi 533 | if [ "${notification_type}" = "bark" ] 534 | then 535 | if [ -z "${bark_device_key}" ] || [ -z "${bark_server}" ] 536 | then 537 | disable_notifications 538 | fi 539 | fi 540 | if [ "${notification_type}" = "msmtp" ] 541 | then 542 | if [ -z "${msmtp_host}" ] || [ -z "${msmtp_port}" ] || [ -z "${msmtp_user}" ] || [ -z "${msmtp_pass}" ] 543 | then 544 | disable_notifications 545 | fi 546 | fi 547 | if [ "${notification_type}" = "signal" ] 548 | then 549 | if [ -z "${signal_host}" ] || [ -z "${signal_port}" ] || [ -z "${signal_number}" ] || [ -z "${signal_recipient}" ] 550 | then 551 | disable_notifications 552 | fi 553 | fi 554 | fi 555 | 556 | # Check download directories are mounted 557 | log_info " - Checing download locations are mounted" 558 | if [ "${download_path}" ] 559 | then 560 | if [ "$(cat /proc/mounts | cut -d' ' -f2 | grep -c "${download_path%/}")" -eq 0 ] 561 | then 562 | log_error " | Download directory is not mounted: ${download_path%/}" 563 | log_error " ! Cannot continue. Halting" 564 | sleep infinity 565 | else 566 | log_debug " | Download directory is mounted: ${download_path%/}" 567 | fi 568 | fi 569 | if [ "${jpeg_path}" ] 570 | then 571 | if [ "$(cat /proc/mounts | cut -d' ' -f2 | grep -c "${jpeg_path%/}")" -eq 0 ] 572 | then 573 | log_error " | JPEG download directory is not mounted: ${jpeg_path%/}" 574 | log_error " ! Cannot continue. Halting" 575 | sleep infinity 576 | else 577 | log_debug " | JPEG download directory is mounted: ${jpeg_path%/}" 578 | fi 579 | fi 580 | if [ "${video_path}" ] 581 | then 582 | if [ "$(cat /proc/mounts | cut -d' ' -f2 | grep -c "${video_path%/}")" -eq 0 ] 583 | then 584 | log_error " | Sideways copy video directory is not mounted: ${video_path%/}" 585 | log_error " ! Cannot continue. Halting" 586 | sleep infinity 587 | else 588 | log_debug " | Sideways copy video download directory is mounted: ${video_path%/}" 589 | fi 590 | fi 591 | 592 | # Create group/user 593 | log_info " - Checking user:group account: ${user}:${group}" 594 | create_group 595 | create_user 596 | set_user_mask 597 | 598 | # Check/Set permissions 599 | set_owner_and_permissions_config 600 | set_owner_and_permissions_downloads 601 | if [ "${jpeg_path}" ] && [ -d "${jpeg_path}" ] 602 | then 603 | set_owner_and_permissions_jpegs 604 | fi 605 | if [ "${video_path}" ] && [ -d "${video_path}" ] 606 | then 607 | set_owner_and_permissions_videos 608 | fi 609 | 610 | # Check config directory is writable by configured user 611 | log_info " - Checking directories are writable by user: ${user}" 612 | if [ "$(run_as "test -w /config; echo $?")" -ne 0 ] 613 | then 614 | log_error " | Directory is not writable: /config" 615 | log_error " ! Cannot continue. Halting" 616 | sleep infinity 617 | fi 618 | # Check keyring directory is writable by configured user 619 | if [ "$(run_as "test -w /config/python_keyring; echo $?")" -ne 0 ] 620 | then 621 | log_error " | Directory is not writable: /config/python_keyring" 622 | log_error " ! Cannot continue. Halting" 623 | sleep infinity 624 | fi 625 | # Check download directory is writable by configured user 626 | if [ "$(run_as "test -w ${download_path}; echo $?")" -ne 0 ] 627 | then 628 | log_error " | Directory is not writable: ${download_path}" 629 | log_error " ! Cannot continue. Halting" 630 | sleep infinity 631 | fi 632 | # Check JPEG directory is writable by configured user 633 | if [ "${jpeg_path}" ] && [ ! -d "${jpeg_path}" ] 634 | then 635 | log_info " - Testing JPEG directory writable by user: ${user}" 636 | if [ "$(run_as "test -w ${jpeg_path}; echo $?")" -ne 0 ] 637 | then 638 | log_error " | Directory is not writable: ${jpeg_path}" 639 | log_error " ! Cannot continue. Halting" 640 | sleep infinity 641 | fi 642 | fi 643 | 644 | # Check route to icloud web site 645 | if [ "${icloud_china:=false}" = true ] 646 | then 647 | icloud_domain="icloud.com.cn" 648 | else 649 | icloud_domain="icloud.com" 650 | fi 651 | log_info " - Checking ${icloud_domain} is accessible" 652 | if [ "$(traceroute -q 1 -w 1 ${icloud_domain} >/dev/null 2>/tmp/icloudpd/icloudpd_tracert.err; echo $?)" = 1 ] 653 | then 654 | log_error " | No route to ${icloud_domain} found. Please check your container's network settings" 655 | log_error " ! Error debug - $(cat /tmp/icloudpd/icloudpd_tracert.err)" 656 | log_error " ! Cannot continue. Restarting in 5 minutes" 657 | sleep 5m 658 | exit 1 659 | fi 660 | 661 | # Check Telegram bot initialised 662 | if [ "${notification_type}" = "telegram" ] && [ "${telegram_token}" ] && [ "${telegram_chat_id}" ] && [ "${telegram_polling}" = true ] 663 | then 664 | log_info " - Checking Telegram bot initialised" 665 | if [ "${telegram_bot_initialised}" = false ] 666 | then 667 | if [ "${telegram_http}" = true ] 668 | then 669 | telegram_protocol="http" 670 | else 671 | telegram_protocol="https" 672 | fi 673 | if [ "${telegram_server}" ] 674 | then 675 | telegram_base_url="${telegram_protocol}://${telegram_server}/bot${telegram_token}" 676 | else 677 | telegram_base_url="${telegram_protocol}://api.telegram.org/bot${telegram_token}" 678 | fi 679 | telegram_update_id_offset_file="/config/telegram_update_id.num" 680 | if [ ! -f "${telegram_update_id_offset_file}" ] 681 | then 682 | echo -n 0 > "${telegram_update_id_offset_file}" 683 | fi 684 | sleep "$((RANDOM % 15))" 685 | bot_check="$(curl --silent -X POST "${telegram_base_url}/getUpdates" | jq -r .ok)" 686 | if [ "${bot_check}" = true ] 687 | then 688 | sed -i "s%^telegram_bot_initialised=false$%telegram_bot_initialised=true%" "${config_file}" 689 | else 690 | log_warning " | Telegram bot does not appear to have been initialised or needs reinitialising. Please send a message to the bot from your iDevice and restart the container" 691 | fi 692 | fi 693 | fi 694 | 695 | # Check for updates 696 | log_info_n " - Checking for updates: " 697 | current_version="$(awk -F_ '{print $1}' /opt/build_version.txt)" 698 | latest_version="$(curl --silent --max-time 5 https://raw.githubusercontent.com/boredazfcuk/docker-icloudpd/master/build_version.txt | awk -F_ '{print $1}')" 699 | if [ "${current_version:=99}" -eq "99" ] || [ "${latest_version:=98}" -eq "98" ] 700 | then 701 | echo "Check for updates failed. Placeholder version detected. Current version: ${current_version}. Latest version: ${latest_version}" 702 | user_warning_displayed=true 703 | sleep 1m 704 | elif [ "${current_version}" -lt "${latest_version}" ] 705 | then 706 | echo "Current version (v${current_version}) is out of date. Please upgrade to latest version (v${latest_version})." 707 | user_warning_displayed=true 708 | sleep 1m 709 | elif [ "${current_version}" -gt "${latest_version}" ] 710 | then 711 | echo "Current version (v${current_version}) is newer than latest build (v${latest_version}). Good luck!" 712 | elif [ "${current_version}" -eq "${latest_version}" ] 713 | then 714 | echo "Current version (v${current_version}) is up to date" 715 | else 716 | echo "Check for updates failed. Cannot continue. Halting" 717 | sleep infinity 718 | fi 719 | 720 | log_info "Initialisation complete" 721 | exec /usr/local/bin/sync-icloud.sh -------------------------------------------------------------------------------- /profile: -------------------------------------------------------------------------------- 1 | #!/bin/ash 2 | 3 | EUID=$(id -u) 4 | export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/icloudpd/bin" 5 | export PAGER=less 6 | export LS_COLORS='no=32:fi=32:di=01;34:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.heic=01;35:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.avi=01;35:*.fli=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.ogg=035:*.mp3=01;35:*.wav=01;35:*.xml=00;31:' 7 | umask 027 8 | 9 | for script in /etc/profile.d/*.sh ; do 10 | if [ -r "$script" ] ; then 11 | . "$script" 12 | fi 13 | done 14 | 15 | dl_path="$(grep "^download_path=" /config/icloudpd.conf | awk '{print $2}')" 16 | alias bins='cd /usr/local/bin' 17 | alias opt='cd /opt' 18 | alias config='cd /config' 19 | alias cls='clear' 20 | alias dls="cd ${dl_path:=\/home\/$(id -un)\/iCloud\/}" 21 | alias ls='ls -l --color --escape --human-readable' 22 | alias sl='ls -l --color --escape --human-readable' 23 | alias rm='rm -v' 24 | alias mv='mv -v' 25 | alias cp='cp -vp' 26 | alias rcp='scp' 27 | alias dfh='df -h | grep -v "/var/lib/docker\|udev\|tmpfs"' 28 | alias duh='du -h' 29 | alias grep='grep --color' 30 | alias home='cd ~' 31 | alias sourcereload='source /etc/profile' 32 | alias logs='cd /var/log/' 33 | alias listening='netstat -lntu' 34 | alias install='apk add' 35 | alias remove='apk del' 36 | alias update='apk update' 37 | alias listupdates='apk list --upgradable' 38 | alias upgrade='apk update && apk upgrade' 39 | alias editconfig='nano /config/icloudpd.conf' 40 | alias whatsmyip='wget -qO- icanhazip.com' 41 | alias innit='/usr/local/bin/sync-icloud.sh --init' 42 | 43 | function __setprompt 44 | { 45 | 46 | # Define colors 47 | local LIGHTGREY='\[\033[0;37m\]' 48 | local WHITE='\[\033[1;37m\]' 49 | local BLACK='\[\033[0;30m\]' 50 | local DARKGRAY='\[\033[1;30m\]' 51 | local RED='\[\033[0;31m\]' 52 | local LIGHTRED='\[\033[1;31m\]' 53 | local GREEN='\[\033[0;32m\]' 54 | local LIGHTGREEN='\[\033[1;32m\]' 55 | local BROWN='\[\033[0;33m\]' 56 | local YELLOW='\[\033[1;33m\]' 57 | local BLUE='\[\033[0;34m\]' 58 | local LIGHTBLUE='\[\033[1;34m\]' 59 | local MAGENTA='\[\033[0;35m\]' 60 | local LIGHTMAGENTA='\[\033[1;35m\]' 61 | local CYAN='\[\033[0;36m\]' 62 | local LIGHTCYAN='\[\033[1;36m\]' 63 | local NOCOLOR='\[\033[0m\]' 64 | 65 | # Green prompt is user, red prompt if red 66 | if [ $EUID -eq 0 ]; then 67 | PS1="${LIGHTRED}\u${LIGHTGREY}@${LIGHTRED}\h" 68 | else 69 | PS1="${LIGHTGREEN}\u${LIGHTGREY}@${LIGHTGREEN}\h" 70 | fi 71 | 72 | # Current directory 73 | PS1="${PS1}${LIGHTGREY}:${LIGHTBLUE}\w" 74 | 75 | # Prompt end 76 | if [ $EUID -eq 0 ]; then 77 | PS1="${PS1}${RED}>${GREEN} " # Root user 78 | else 79 | PS1="${PS1}${GREEN}>${GREEN} " # Normal user 80 | fi 81 | 82 | } 83 | __setprompt 84 | PROMPT_COMMAND='echo -ne "\e]0;$USER@${HOSTNAME}: $(pwd -P)\a"' 85 | 86 | unset script dl_path -------------------------------------------------------------------------------- /reauth.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ash 2 | 3 | run_as() 4 | { 5 | local command_to_run 6 | command_to_run="${1}" 7 | if [ "$(id -u)" = 0 ] 8 | then 9 | su "${user:=user}" -s /bin/ash -c "${command_to_run}" 10 | else 11 | /bin/ash -c "${command_to_run}" 12 | fi 13 | } 14 | 15 | user="$(grep "^user=" /config/icloudpd.conf | awk -F= '{print $2}')" 16 | apple_id="$(grep apple_id /config/icloudpd.conf | awk -F= '{print $2}')" 17 | auth_china="$(grep auth_china /config/icloudpd.conf | awk -F= '{print $2}')" 18 | cookie_file="$(echo -n "${apple_id//[^a-z0-9_]/}")" 19 | 20 | if [ "${auth_china:=false}" = true ] 21 | then 22 | auth_domain="cn" 23 | fi 24 | 25 | if [ -f "/config/${cookie_file}" ] 26 | then 27 | rm "/config/${cookie_file}" 28 | fi 29 | 30 | if [ -f "/config/${cookie_file}.session" ] 31 | then 32 | rm "/config/${cookie_file}.session" 33 | fi 34 | 35 | run_as "/opt/icloudpd/bin/icloudpd --username ${apple_id} --cookie-directory /config --auth-only --domain ${auth_domain:=com} | tee /tmp/icloudpd/reauth.log" 36 | 37 | rm /tmp/icloudpd/reauth.log -------------------------------------------------------------------------------- /sendmessage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ash 2 | 3 | send_message() 4 | { 5 | local text 6 | text="$1" 7 | notification_result="$(curl --silent --output /dev/null --write-out "%{http_code}" --request POST "${notification_url}" \ 8 | --data chat_id="${telegram_chat_id}" \ 9 | --data parse_mode="markdown" \ 10 | --data disable_notification="${telegram_disable_notification:=false}" \ 11 | --data text="${text}")" 12 | } 13 | 14 | choose_sms_number() 15 | { 16 | local auth_log_numbers auth_log_text 17 | auth_log_numbers="$(grep "^ " /tmp/icloudpd/reauth.log | sed 's/\*\*\*\*\*\ \*\*\*\*/number ending in /g')" 18 | auth_index_upper="$(grep "^ " /tmp/icloudpd/reauth.log | tail -1 | awk -F: '{print $1}' | sed 's/ //g')" 19 | if [ "${auth_index_upper}" = "a" ] 20 | then 21 | option_list="a" 22 | else 23 | option_list="a-${auth_index_upper}" 24 | fi 25 | auth_log_text="Please select option to send the SMS code to:%0A${auth_log_numbers}%0AReply with '${user}