Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
13 contributors

Users who have contributed to this file

@boredazfcuk @eliempje @ibtvt @bauzer714 @Alano-i @kzaoaai @Sowevo @larstomas @yayanet @Xm798 @AemonCao @baccula
executable file 1657 lines (1612 sloc) 90.6 KB
#!/bin/ash
##### Functions #####
initialise_config_file(){
{
if [ "$(grep -c "albums_with_dates=" "${config_file}")" -eq 0 ]; then echo albums_with_dates="${albums_with_dates:=false}"; fi
if [ "$(grep -c "apple_id=" "${config_file}")" -eq 0 ]; then echo apple_id="${apple_id}"; fi
if [ "$(grep -c "authentication_type=" "${config_file}")" -eq 0 ]; then echo authentication_type="${authentication_type:=2FA}"; fi
if [ "$(grep -c "auth_china=" "${config_file}")" -eq 0 ]; then echo auth_china="${auth_china:=false}"; fi
if [ "$(grep -c "auto_delete=" "${config_file}")" -eq 0 ]; then echo auto_delete="${auto_delete:=false}"; fi
if [ "$(grep -c "bark_device_key=" "${config_file}")" -eq 0 ]; then echo bark_device_key="${bark_device_key}"; fi
if [ "$(grep -c "bark_server=" "${config_file}")" -eq 0 ]; then echo bark_server="${bark_server}"; fi
if [ "$(grep -c "convert_heic_to_jpeg=" "${config_file}")" -eq 0 ]; then echo convert_heic_to_jpeg="${convert_heic_to_jpeg:=false}"; fi
if [ "$(grep -c "debug_logging=" "${config_file}")" -eq 0 ]; then echo debug_logging="${debug_logging:=false}"; fi
if [ "$(grep -c "delete_accompanying=" "${config_file}")" -eq 0 ]; then echo delete_accompanying="${delete_accompanying:=false}"; fi
if [ "$(grep -c "delete_after_download=" "${config_file}")" -eq 0 ]; then echo delete_after_download="${delete_after_download:=false}"; fi
if [ "$(grep -c "delete_notifications=" "${config_file}")" -eq 0 ]; then echo delete_notifications="${delete_notifications:=true}"; fi
if [ "$(grep -c "dingtalk_token=" "${config_file}")" -eq 0 ]; then echo dingtalk_token="${dingtalk_token}"; fi
if [ "$(grep -c "directory_permissions=" "${config_file}")" -eq 0 ]; then echo directory_permissions="${directory_permissions:=750}"; fi
if [ "$(grep -c "discord_id=" "${config_file}")" -eq 0 ]; then echo discord_id="${discord_id}"; fi
if [ "$(grep -c "discord_token=" "${config_file}")" -eq 0 ]; then echo discord_token="${discord_token}"; fi
if [ "$(grep -c "download_notifications=" "${config_file}")" -eq 0 ]; then echo download_notifications="${download_notifications:=true}"; fi
if [ "$(grep -c "download_path=" "${config_file}")" -eq 0 ]; then echo download_path="${download_path}"; fi
if [ "$(grep -c "file_permissions=" "${config_file}")" -eq 0 ]; then echo file_permissions="${file_permissions:=640}"; fi
if [ "$(grep -c "folder_structure=" "${config_file}")" -eq 0 ]; then echo folder_structure="${folder_structure:={:%Y/%m/%d\}}"; fi
if [ "$(grep -c "gotify_app_token=" "${config_file}")" -eq 0 ]; then echo gotify_app_token="${gotify_app_token}"; fi
if [ "$(grep -c "group=" "${config_file}")" -eq 0 ]; then echo group="${group:=group}"; fi
if [ "$(grep -c "group_id=" "${config_file}")" -eq 0 ]; then echo group_id="${group_id:=1000}"; fi
if [ "$(grep -c "icloud_china=" "${config_file}")" -eq 0 ]; then echo icloud_china="${icloud_china}"; fi
if [ "$(grep -c "iyuu_token=" "${config_file}")" -eq 0 ]; then echo iyuu_token="${iyuu_token}"; fi
if [ "$(grep -c "jpeg_path=" "${config_file}")" -eq 0 ]; then echo jpeg_path="${jpeg_path}"; fi
if [ "$(grep -c "jpeg_quality=" "${config_file}")" -eq 0 ]; then echo jpeg_quality="${jpeg_quality:=90}"; fi
if [ "$(grep -c "notification_days=" "${config_file}")" -eq 0 ]; then echo notification_days="${notification_days:=7}"; fi
if [ "$(grep -c "notification_type=" "${config_file}")" -eq 0 ]; then echo notification_type="${notification_type}"; fi
if [ "$(grep -c "photo_album=" "${config_file}")" -eq 0 ]; then echo photo_album="${photo_album}"; fi
#if [ "$(grep -c "photo_library=" "${config_file}")" -eq 0 ]; then echo photo_library="${photo_library}"; fi
if [ "$(grep -c "photo_size=" "${config_file}")" -eq 0 ]; then echo photo_size="${photo_size:=original}"; fi
if [ "$(grep -c "prowl_api_key=" "${config_file}")" -eq 0 ]; then echo prowl_api_key="${prowl_api_key}"; fi
if [ "$(grep -c "pushover_sound=" "${config_file}")" -eq 0 ]; then echo pushover_sound="${pushover_sound}"; fi
if [ "$(grep -c "pushover_token=" "${config_file}")" -eq 0 ]; then echo pushover_token="${pushover_token}"; fi
if [ "$(grep -c "pushover_user=" "${config_file}")" -eq 0 ]; then echo pushover_user="${pushover_user}"; fi
if [ "$(grep -c "recent_only=" "${config_file}")" -eq 0 ]; then echo recent_only="${recent_only}"; fi
if [ "$(grep -c "set_exif_datetime=" "${config_file}")" -eq 0 ]; then echo set_exif_datetime="${set_exif_datetime:=false}"; fi
if [ "$(grep -c "skip_album=" "${config_file}")" -eq 0 ]; then echo skip_album="${skip_album}"; fi
if [ "$(grep -c "single_pass=" "${config_file}")" -eq 0 ]; then echo single_pass="${single_pass:=false}"; fi
if [ "$(grep -c "skip_check=" "${config_file}")" -eq 0 ]; then echo skip_check="${skip_check:=false}"; fi
if [ "$(grep -c "skip_live_photos=" "${config_file}")" -eq 0 ]; then echo skip_live_photos="${skip_live_photos:=false}"; fi
if [ "$(grep -c "synchronisation_delay=" "${config_file}")" -eq 0 ]; then echo synchronisation_delay="${synchronisation_delay:=0}"; fi
if [ "$(grep -c "synchronisation_interval=" "${config_file}")" -eq 0 ]; then echo synchronisation_interval="${synchronisation_interval:=86400}"; fi
if [ "$(grep -c "telegram_chat_id=" "${config_file}")" -eq 0 ]; then echo telegram_chat_id="${telegram_chat_id}"; fi
if [ "$(grep -c "telegram_polling=" "${config_file}")" -eq 0 ]; then echo telegram_polling="${telegram_polling:=true}"; fi
if [ "$(grep -c "telegram_server=" "${config_file}")" -eq 0 ]; then echo telegram_server="${telegram_server}"; fi
if [ "$(grep -c "telegram_silent_file_notifications=" "${config_file}")" -eq 0 ]; then echo telegram_silent_file_notifications="${telegram_silent_file_notifications}"; fi
if [ "$(grep -c "telegram_token=" "${config_file}")" -eq 0 ]; then echo telegram_token="${telegram_token}"; fi
if [ "$(grep -c "trigger_nextlcoudcli_synchronisation=" "${config_file}")" -eq 0 ]; then echo trigger_nextlcoudcli_synchronisation="${trigger_nextlcoudcli_synchronisation}"; fi
if [ "$(grep -c "until_found=" "${config_file}")" -eq 0 ]; then echo until_found="${until_found}"; fi
if [ "$(grep -c "user=" "${config_file}")" -eq 0 ]; then echo user="${user:=user}"; fi
if [ "$(grep -c "user_id=" "${config_file}")" -eq 0 ]; then echo user_id="${user_id:=1000}"; fi
if [ "$(grep -c "webhook_https=" "${config_file}")" -eq 0 ]; then echo webhook_https="${webhook_https:=false}"; fi
if [ "$(grep -c "webhook_id=" "${config_file}")" -eq 0 ]; then echo webhook_id="${webhook_id}"; fi
if [ "$(grep -c "webhook_path=" "${config_file}")" -eq 0 ]; then echo webhook_path="${webhook_path:=/api/webhook/}"; fi
if [ "$(grep -c "webhook_port=" "${config_file}")" -eq 0 ]; then echo webhook_port="${webhook_port:=8123}"; fi
if [ "$(grep -c "webhook_server=" "${config_file}")" -eq 0 ]; then echo webhook_server="${webhook_server}"; fi
if [ "$(grep -c "wecom_id=" "${config_file}")" -eq 0 ]; then echo wecom_id="${wecom_id}"; fi
if [ "$(grep -c "wecom_proxy=" "${config_file}")" -eq 0 ]; then echo wecom_proxy="${wecom_proxy}"; fi
if [ "$(grep -c "wecom_secret=" "${config_file}")" -eq 0 ]; then echo wecom_secret="${wecom_secret}"; fi
} >> "${config_file}"
if [ "${albums_with_dates}" ]; then sed -i "s%^albums_with_dates=.*%albums_with_dates=${albums_with_dates}%" "${config_file}"; fi
if [ "${apple_id}" ]; then sed -i "s%^apple_id=.*%apple_id=${apple_id}%" "${config_file}"; fi
if [ "${authentication_type}" ]; then sed -i "s%^authentication_type=.*%authentication_type=${authentication_type}%" "${config_file}"; fi
if [ "${auth_china}" ]; then sed -i "s%^auth_china=.*%auth_china=${auth_china}%" "${config_file}"; fi
if [ "${auto_delete}" ]; then sed -i "s%^auto_delete=.*%auto_delete=${auto_delete}%" "${config_file}"; fi
if [ "${bark_device_key}" ]; then sed -i "s%^bark_device_key=.*%bark_device_key=${bark_device_key}%" "${config_file}"; fi
if [ "${bark_server}" ]; then sed -i "s%^bark_server=.*%bark_server=${bark_server}%" "${config_file}"; fi
if [ "${convert_heic_to_jpeg}" ]; then sed -i "s%^convert_heic_to_jpeg=.*%convert_heic_to_jpeg=${convert_heic_to_jpeg}%" "${config_file}"; fi
if [ "${debug_logging}" ]; then sed -i "s%^debug_logging=.*%debug_logging=${debug_logging}%" "${config_file}"; fi
if [ "${delete_accompanying}" ]; then sed -i "s%^delete_accompanying=.*%delete_accompanying=${delete_accompanying}%" "${config_file}"; fi
if [ "${delete_after_download}" ]; then sed -i "s%^delete_after_download=.*%delete_after_download=${delete_after_download}%" "${config_file}"; fi
if [ "${delete_notification}" ]; then sed -i "s%^delete_notification=.*%delete_notification=${delete_notification}%" "${config_file}"; fi
if [ "${dingtalk_token}" ]; then sed -i "s%^dingtalk_token=.*%dingtalk_token=${dingtalk_token}%" "${config_file}"; fi
if [ "${directory_permissions}" ]; then sed -i "s%^directory_permissions=.*%directory_permissions=${directory_permissions}%" "${config_file}"; fi
if [ "${discord_id}" ]; then sed -i "s%^discord_id=.*%discord_id=${discord_id}%" "${config_file}"; fi
if [ "${discord_token}" ]; then sed -i "s%^discord_token=.*%discord_token=${discord_token}%" "${config_file}"; fi
if [ "${download_notifications}" ]; then sed -i "s%^download_notifications=.*%download_notifications=${download_notifications}%" "${config_file}"; fi
if [ "${download_path}" ]; then sed -i "s%^download_path=.*%download_path=${download_path}%" "${config_file}"; fi
if [ "${file_permissions}" ]; then sed -i "s%^file_permissions=.*%file_permissions=${file_permissions}%" "${config_file}"; fi
if [ "${folder_structure}" ]; then
sanitised_folder_structure="${folder_structure//\//\\/}"
sed -i "s@^folder_structure=.*@folder_structure=${sanitised_folder_structure}@" "${config_file}"
fi
if [ "${gotify_app_token}" ]; then sed -i "s%^gotify_app_token=.*%gotify_app_token=${gotify_app_token}%" "${config_file}"; fi
if [ "${group}" ]; then sed -i "s%^group=.*%group=${group}%" "${config_file}"; fi
if [ "${group_id}" ]; then sed -i "s%^group_id=.*%group_id=${group_id}%" "${config_file}"; fi
if [ "${icloud_china}" ]; then sed -i "s%^icloud_china=.*%icloud_china=${icloud_china}%" "${config_file}"; fi
if [ "${iyuu_token}" ]; then sed -i "s%^iyuu_token=.*%iyuu_token=${iyuu_token}%" "${config_file}"; fi
if [ "${jpeg_path}" ]; then sed -i "s%^jpeg_path=.*%jpeg_path=${jpeg_path}%" "${config_file}"; fi
if [ "${jpeg_quality}" ]; then sed -i "s%^jpeg_quality=.*%jpeg_quality=${jpeg_quality}%" "${config_file}"; fi
if [ "${notification_days}" ]; then sed -i "s%^notification_days=.*%notification_days=${notification_days}%" "${config_file}"; fi
if [ "${notification_type}" ]; then sed -i "s%^notification_type=.*%notification_type=${notification_type}%" "${config_file}"; fi
if [ "${photo_album}" ]; then sed -i "s%^photo_album=.*%photo_album=\"${photo_album}\"%" "${config_file}"; fi
#if [ "${photo_library}" ]; then sed -i "s%^photo_library=.*%photo_library=${photo_library}%" "${config_file}"; fi
if [ "${photo_size}" ]; then sed -i "s%^photo_size=.*%photo_size=${photo_size}%" "${config_file}"; fi
if [ "${prowl_api_key}" ]; then sed -i "s%^prowl_api_key=.*%prowl_api_key=${prowl_api_key}%" "${config_file}"; fi
if [ "${pushover_sound}" ]; then sed -i "s%^pushover_sound=.*%pushover_sound=${pushover_sound}%" "${config_file}"; fi
if [ "${pushover_token}" ]; then sed -i "s%^pushover_token=.*%pushover_token=${pushover_token}%" "${config_file}"; fi
if [ "${pushover_user}" ]; then sed -i "s%^pushover_user=.*%pushover_user=${pushover_user}%" "${config_file}"; fi
if [ "${recent_only}" ]; then sed -i "s%^recent_only=.*%recent_only=${recent_only}%" "${config_file}"; fi
if [ "${set_exif_datetime}" ]; then sed -i "s%^set_exif_datetime=.*%set_exif_datetime=${set_exif_datetime}%" "${config_file}"; fi
if [ "${single_pass}" ]; then sed -i "s%^single_pass=.*%single_pass=${single_pass}%" "${config_file}"; fi
if [ "${skip_album}" ]; then sed -i "s%^skip_album=.*%skip_album=\"${skip_album}\"%" "${config_file}"; fi
if [ "${skip_check}" ]; then sed -i "s%^skip_check=.*%skip_check=${skip_check}%" "${config_file}"; fi
if [ "${skip_live_photos}" ]; then sed -i "s%^skip_live_photos=.*%skip_live_photos=${skip_live_photos}%" "${config_file}"; fi
if [ "${synchronisation_delay}" ]; then sed -i "s%^synchronisation_delay=.*%synchronisation_delay=${synchronisation_delay}%" "${config_file}"; fi
if [ "${synchronisation_interval}" ]; then sed -i "s%^synchronisation_interval=.*%synchronisation_interval=${synchronisation_interval}%" "${config_file}"; fi
if [ "${telegram_chat_id}" ]; then sed -i "s%^telegram_chat_id=.*%telegram_chat_id=${telegram_chat_id}%" "${config_file}"; fi
if [ "${telegram_polling}" ]; then sed -i "s%^telegram_polling=.*%telegram_polling=${telegram_polling}%" "${config_file}"; fi
if [ "${telegram_server}" ]; then sed -i "s%^telegram_server=.*%telegram_server=${telegram_server}%" "${config_file}"; fi
if [ "${telegram_silent_file_notifications}" ]; then sed -i "s%^telegram_silent_file_notifications=.*%telegram_silent_file_notifications=${telegram_silent_file_notifications}%" "${config_file}"; fi
if [ "${telegram_token}" ]; then sed -i "s%^telegram_token=.*%telegram_token=${telegram_token}%" "${config_file}"; fi
if [ "${trigger_nextlcoudcli_synchronisation}" ]; then sed -i "s%^trigger_nextlcoudcli_synchronisation=.*%trigger_nextlcoudcli_synchronisation=${trigger_nextlcoudcli_synchronisation}%" "${config_file}"; fi
if [ "${until_found}" ]; then sed -i "s%^until_found=.*%until_found=${until_found}%" "${config_file}"; fi
if [ "${user}" ]; then sed -i "s%^user=.*%user=${user}%" "${config_file}"; fi
if [ "${user_id}" ]; then sed -i "s%^user_id=.*%user_id=${user_id}%" "${config_file}"; fi
if [ "${webhook_https}" ]; then sed -i "s%^webhook_https=.*%webhook_https=${webhook_https}%" "${config_file}"; fi
if [ "${webhook_id}" ]; then sed -i "s%^webhook_id=.*%webhook_id=${webhook_id}%" "${config_file}"; fi
if [ "${webhook_path}" ]; then sed -i "s%^webhook_path=.*%webhook_path=${webhook_path}%" "${config_file}"; fi
if [ "${webhook_port}" ]; then sed -i "s%^webhook_port=.*%webhook_port=${webhook_port}%" "${config_file}"; fi
if [ "${webhook_server}" ]; then sed -i "s%^webhook_server=.*%webhook_server=${webhook_server}%" "${config_file}"; fi
if [ "${wecom_id}" ]; then sed -i "s%^wecom_id=.*%wecom_id=${wecom_id}%" "${config_file}"; fi
if [ "${wecom_proxy}" ]; then sed -i "s%^wecom_proxy=.*%wecom_proxy=${wecom_proxy}%" "${config_file}"; fi
if [ "${wecom_secret}" ]; then sed -i "s%^wecom_secret=.*%wecom_secret=${wecom_secret}%" "${config_file}"; fi
mv "${config_file}" "${config_file}.tmp"
sort "${config_file}.tmp" --output="${config_file}"
chmod --reference="${config_file}.tmp" "${config_file}"
rm "${config_file}.tmp"
sed -i 's/=True/=true/g' "${config_file}"
sed -i 's/=False/=false/g' "${config_file}"
sed -i '/photo_library=/d' "${config_file}"
}
Initialise(){
echo
LogInfo "***** boredazfcuk/icloudpd container for icloud_photo_downloader v1.0.$(cat /build_version.txt) started *****"
LogInfo "***** For support, please go here: https://github.com/boredazfcuk/docker-icloudpd *****"
LogInfo "$(cat /etc/*-release | grep "^NAME" | sed 's/NAME=//g' | sed 's/"//g') $(cat /etc/*-release | grep "VERSION_ID" | sed 's/VERSION_ID=//g' | sed 's/"//g')"
LogInfo "Python version: $(python3 --version | awk '{print $2}')"
config_file="${config_dir}/icloudpd.conf"
initialise_config_file
LogInfo "Loading configuration from: ${config_file}"
source "${config_file}"
save_ifs="${IFS}"
lan_ip="$(hostname -i)"
login_counter="0"
apple_id="$(echo -n ${apple_id} | tr '[:upper:]' '[:lower:]')"
cookie_file="$(echo -n "${apple_id//[^a-z0-9_]/}")"
older_structure="${folder_structure}"
local icloud_dot_com dns_counter
if [ "${icloud_china}" ]; then
icloud_domain="icloud.com.cn"
else
icloud_domain="icloud.com"
fi
case "${synchronisation_interval:=86400}" in
21600) synchronisation_interval=21600;; # 6 hours
43200) synchronisation_interval=43200;; # 12 hours
86400) synchronisation_interval=86400;; # 24 hours
129600) synchronisation_interval=129600;; # 36 hours
172800) synchronisation_interval=172800;; # 48 hours
604800) synchronisation_interval=604800;; # 7 days
*) synchronisation_interval=86400;; # 24 hours
esac
if [ "${synchronisation_delay:=0}" -gt 60 ]; then
synchronisation_delay=60
fi
if [ ! -d "/tmp/icloudpd" ]; then mkdir --parents "/tmp/icloudpd"; fi
if [ -f "/tmp/icloudpd/icloudpd_check_exit_code" ]; then rm "/tmp/icloudpd/icloudpd_check_exit_code"; fi
if [ -f "/tmp/icloudpd/icloudpd_download_exit_code" ]; then rm "/tmp/icloudpd/icloudpd_download_exit_code"; fi
if [ -f "/tmp/icloudpd/icloudpd_check_error" ]; then rm "/tmp/icloudpd/icloudpd_check_error"; fi
if [ -f "/tmp/icloudpd/icloudpd_download_error" ]; then rm "/tmp/icloudpd/icloudpd_download_error"; fi
touch "/tmp/icloudpd/icloudpd_check_exit_code"
touch "/tmp/icloudpd/icloudpd_download_exit_code"
touch "/tmp/icloudpd/icloudpd_check_error"
touch "/tmp/icloudpd/icloudpd_download_error"
if [ -z "${apple_id}" ]; then
LogError "Apple ID not set - exiting"
sleep 120
exit 1
fi
if [ "${apple_password}" ] && [ "${apple_password}" != "usekeyring" ]; then
LogError "Apple password configured with variable which is no longer supported. Please add password to system keyring - exiting"
sleep 120
exit 1
fi
if [ "${apple_password}" = "usekeyring" ]; then
LogWarning "Apple password variable set to 'userkeyring'. This variable can now be removed as it is now the only supported option, so obsolete - continue in 2 minutes"
sleep 120
fi
LogDebug "Running user id: $(id --user)"
LogDebug "Running group id: $(id --group)"
LogDebug "Local user: ${user:=user}:${user_id:=1000}"
LogDebug "Local group: ${group:=group}:${group_id:=1000}"
LogDebug "Force GID: ${force_gid:=false}"
LogDebug "LAN IP Address: ${lan_ip}"
LogDebug "Default gateway: $(ip route | grep default | awk '{print $3}')"
LogDebug "DNS server: $(cat /etc/resolv.conf | grep nameserver | awk '{print $2}')"
icloud_dot_com="$(nslookup -type=a ${icloud_domain} | grep -v "127.0.0.1" | grep Address | tail -1 | awk '{print $2}')"
while [ -z "${icloud_dot_com}" ]; do
if [ "${dns_counter:=0}" = 0 ]; then
LogWarning "Cannot find ${icloud_domain} IP address - retrying"
fi
sleep 10
icloud_dot_com="$(nslookup -type=a ${icloud_domain} | grep -v "127.0.0.1" | grep Address | tail -1 | awk '{print $2}')"
dns_counter=$((dns_counter+1))
if [ "${dns_counter}" = 12 ]; then
LogError "Cannot find ${icloud_domain} IP address. Please check your DNS settings - exiting"
sleep 120
exit 1
fi
done
LogDebug "IP address for ${icloud_domain}: ${icloud_dot_com}"
if [ "$(traceroute -q 1 -w 1 ${icloud_domain} >/dev/null 2>/tmp/icloudpd/icloudpd_tracert.err; echo $?)" = 1 ]; then
LogError "No route to ${icloud_domain} found. Please check your container's network settings - exiting"
LogError "Error debug - $(cat /tmp/icloudpd/icloudpd_tracert.err)"
sleep 120
exit 1
else
LogDebug "Route check to ${icloud_domain} successful"
fi
if [ "${debug_logging}" = true ]; then
LogDebug "Apple ID: (hidden)"
else
LogInfo "Apple ID: ${apple_id}"
fi
LogInfo "Authentication Type: ${authentication_type:=2FA}"
if [ "${debug_logging}" = true ]; then
LogDebug "Cookie path: ${config_dir}/(hidden)"
else
LogInfo "Cookie path: ${config_dir}/${cookie_file}"
fi
LogInfo "Cookie expiry notification period: ${notification_days:=7}"
LogInfo "Download destination directory: ${download_path:=/home/${user}/iCloud}"
if [ ! -d "${download_path}" ]; then
LogInfo "Download directory does not exist."
LogInfo "Creating ${download_path} and configuring permissions."
mkdir --parents "${download_path}"
SetOwnerAndPermissionsDownloads
fi
LogInfo "Folder structure: ${folder_structure:={:%Y/%m/%d\}}"
LogDebug "Directory permissions: ${directory_permissions:=750}"
LogDebug "File permissions: ${file_permissions:=640}"
if [ "${syncronisation_interval}" ]; then
LogWarning "The syncronisation_interval variable contained a typo. This has now been corrected to synchronisation_interval. Please update your container. Defaulting to one sync per 24 hour period"
synchronisation_interval="86400"
fi
LogInfo "Synchronisation interval: ${synchronisation_interval}"
if [ "${synchronisation_interval}" -lt 43200 ]; then
LogWarning "Setting synchronisation_interval to less than 43200 (12 hours) may cause throttling by Apple."
LogWarning "If you run into the following error: "
LogWarning " - private db access disabled for this account. Please wait a few minutes then try again. The remote servers might be trying to throttle requests. (ACCESS_DENIED)"
LogWarning "then please change your synchronisation_interval to 43200 or greater and switch the container off for 6-12 hours so Apple's throttling expires. Continuing in 2 minutes"
if [ "${warnings_acknowledged:=false}" = true ]; then
LogDebug "Throttle warning acknowledged"
else
sleep 120
fi
fi
LogInfo "Synchronisation delay (minutes): ${synchronisation_delay}"
LogInfo "Set EXIF date/time: ${set_exif_datetime:=false}"
LogInfo "Auto delete: ${auto_delete:=false}"
LogInfo "Delete after download: ${delete_after_download:=false}"
if [ "${auto_delete}" != false ] && [ "${delete_after_download}" != false ]; then
LogError "The variables auto_delete and delete_after_download cannot both be configured at the same time. Please choose one or the other - exiting"
sleep 120
exit 1
fi
LogInfo "Photo size: ${photo_size:=original}"
LogInfo "Single pass mode: ${single_pass:=false}"
if [ "${single_pass}" = true ]; then
LogDebug "Single pass mode enabled. Disabling download check"
skip_check=true
fi
LogInfo "Skip download check: ${skip_check:=false}"
LogInfo "Skip live photos: ${skip_live_photos:=false}"
if [ "${recent_only}" ]; then
LogInfo "Number of most recently added photos to download: ${recent_only}"
else
LogInfo "Number of most recently added photos to download: Download All Photos"
fi
if [ "${photo_album}" ]; then
LogInfo "Downloading photos from album(s): ${photo_album}"
# elif [ "${photo_library}" ]; then
# LogInfo "Downloading photos from library: ${photo_library}"
else
LogInfo "Downloading photos from album: Download All Photos"
fi
if [ "${until_found}" ]; then
LogInfo "Stop downloading when prexisiting files count is: ${until_found}"
else
LogInfo "Stop downloading when prexisiting files count is: Download All Photos"
fi
if [ "${skip_live_photos}" = false ]; then
LogInfo "Live photo size: ${live_photo_size:=original}"
fi
LogInfo "Skip videos: ${skip_videos:=false}"
if [ "${command_line_options}" ]; then
LogWarning "Additional command line options supplied: ${command_line_options}"
LogWarning "Additional command line options are no longer supported and will be ignored. Please specify all options using the dedicated variables."
fi
LogInfo "Convert HEIC to JPEG: ${convert_heic_to_jpeg:=false}"
if [ "${convert_heic_to_jpeg}" = true ]; then
LogDebug "JPEG conversion quality: ${jpeg_quality:=90}"
fi
if [ "${jpeg_path}" ]; then
LogInfo "Converted JPEGs path: ${jpeg_path}"
fi
if [ "${delete_accompanying:=false}" = true ] && [ -z "${warnings_acknowledged}" ]; then
LogInfo "Delete accompanying files (.JPG/.HEIC.MOV)"
LogWarning "This feature deletes files from your local disk. Please use with caution. I am not responsible for any data loss."
LogWarning "This feature cannot be used if the 'folder_structure' variable is set to 'none' and also, 'set_exif_datetime' must be 'False'"
LogWarning "These two settings will increase the chances of de-duplication happening, which could result in the wrong files being removed. Continuing in 2 minutes."
if [ "${warnings_acknowledged:=false}" = true ]; then
LogInfo "File deletion warning accepted"
else
sleep 120
fi
fi
if [ "${notification_type}" ]; then
ConfigureNotifications
fi
LogInfo "Downloading from: ${icloud_domain}"
if [ "${icloud_china}" ]; then
if [ -z "${auth_china}" ] || [ "${auth_china}" = true ]; then
auth_domain="cn"
fi
if [ "${auth_china}" = false ]; then
LogWarning "You have the icloud_china variable set, but auth_china is false. Are you sure this is correct?"
fi
fi
LogInfo "Authentication domain: ${auth_domain:=com}"
if [ "${trigger_nextlcoudcli_synchronisation}" ]; then
LogDebug "Nextcloud synchronisation trigger: Enabled"
else
LogDebug "Nextcloud synchronisation trigger: Disabled"
fi
if [ ! -d "/home/${user}/.local/share/" ]; then
LogDebug "Creating directory: /home/${user}/.local/share/"
mkdir --parents "/home/${user}/.local/share/"
fi
if [ ! -d "${config_dir}/python_keyring/" ]; then
LogDebug "Creating directory: ${config_dir}/python_keyring/"
mkdir --parents "${config_dir}/python_keyring/"
fi
if [ ! -L "/home/${user}/.local/share/python_keyring" ]; then
LogDebug "Creating symbolic link: /home/${user}/.local/share/python_keyring/ to: ${config_dir}/python_keyring/ directory"
ln --symbolic --force "${config_dir}/python_keyring/" "/home/${user}/.local/share/"
fi
}
LogInfo(){
local log_message
log_message="${1}"
echo "$(date '+%Y-%m-%d %H:%M:%S') INFO ${log_message}"
}
LogWarning(){
local log_message
log_message="${1}"
echo "$(date '+%Y-%m-%d %H:%M:%S') WARNING ${log_message}"
}
LogError(){
local log_message
log_message="${1}"
echo "$(date '+%Y-%m-%d %H:%M:%S') ERROR ${log_message}"
}
LogDebug(){
if [ "${debug_logging}" = true ]; then
local log_message
log_message="${1}"
echo "$(date '+%Y-%m-%d %H:%M:%S') DEBUG ${log_message}"
fi
}
CleanNotificationTitle(){
if [ "${notification_title}" ]; then
notification_title="${notification_title//[^a-zA-Z0-9_ ]/}"
LogDebug "Cleaned notification title: ${notification_title}"
else
LogDebug "Notification title: ${notification_title:=boredazfcuk/iCloudPD}"
fi
}
ConfigureNotifications(){
if [ -z "${prowl_api_key}" ] && [ -z "${pushover_token}" ] && [ -z "${telegram_token}" ] && [ -z "${webhook_id}" ] && [ -z "${dingtalk_token}" ] && [ -z "${discord_token}" ] && [ -z "${iyuu_token}" ] && [ -z "${wecom_secret}" ] && [ -z "${gotify_app_token}" ] && [ -z "${bark_device_key}" ]; then
LogWarning "${notification_type} notifications enabled, but API key/token/secret not set - disabling notifications"
unset notification_type
else
if [ "${notification_type}" = "Prowl" ] && [ "${prowl_api_key}" ]; then
LogInfo "${notification_type} notifications enabled"
CleanNotificationTitle
if [ "${debug_logging}" = true ]; then
LogDebug "${notification_type} api key: (hidden)"
else
LogInfo "${notification_type} api key: ${prowl_api_key}"
fi
notification_url="https://api.prowlapp.com/publicapi/add"
elif [ "${notification_type}" = "Pushover" ] && [ "${pushover_user}" ] && [ "${pushover_token}" ]; then
LogInfo "${notification_type} notifications enabled"
CleanNotificationTitle
if [ "${debug_logging}" = true ]; then
LogDebug "${notification_type} user: (hidden)"
LogDebug "${notification_type} token: (hidden)"
else
LogInfo "${notification_type} user: ${pushover_user}"
LogInfo "${notification_type} token: ${pushover_token}"
fi
if [ "${pushover_sound}" ]; then
case "${pushover_sound}" in
pushover|bike|bugle|cashregister|classical|cosmic|falling|gamelan|incoming|intermission|magic|mechanical|pianobar|siren|spacealarm|tugboat|alien|climb|persistent|echo|updown|vibrate|none)
LogDebug "${notification_type} sound: ${pushover_sound}"
;;
*)
LogDebug "${notification_type} sound not recognised. Using default"
unset pushover_sound
esac
fi
notification_url="https://api.pushover.net/1/messages.json"
elif [ "${notification_type}" = "Telegram" ] && [ "${telegram_token}" ] && [ "${telegram_chat_id}" ]; then
if [ "${telegram_server}" ] ; then
notification_url="https://${telegram_server}/bot${telegram_token}/sendMessage"
else
notification_url="https://api.telegram.org/bot${telegram_token}/sendMessage"
fi
LogInfo "${notification_type} notifications enabled"
CleanNotificationTitle
if [ "${debug_logging}" = true ]; then
LogDebug "${notification_type} token: (hidden)"
LogDebug "${notification_type} chat id: (hidden)"
LogDebug "${notification_type} polling: ${telegram_polling}"
LogDebug "${notification_type} notification URL: (hidden)"
else
LogInfo "${notification_type} token: ${telegram_token}"
LogInfo "${notification_type} chat id: ${telegram_chat_id}"
LogInfo "${notification_type} polling: ${telegram_polling}"
LogInfo "${notification_type} notification URL: ${notification_url}"
fi
if [ "${telegram_polling}" = true ]; then
LogInfo "Check Telegram bot initialised..."
bot_check="$(curl --silent -X POST "https://api.telegram.org/bot${telegram_token}/getUpdates" | jq .result[-1:])"
if [ "${bot_check}" ]; then
LogInfo " - Bot has been initialised."
current_message_id="$(echo "${bot_check}" | jq .[].message.message_id)"
if [ "${current_message_id}" = "null" ]; then
current_message_id=0
fi
LogInfo "${notification_type} current message_id: ${current_message_id:=0}"
else
LogInfo " - Bot has not been initialised or needs reinitialising. Please send a message to the bot from your iDevice and restart the container. Disabling remote wake"
sleep 10
telegram_polling=false
fi
fi
if [ "${telegram_silent_file_notifications}" ]; then telegram_silent_file_notifications=true; fi
LogDebug "${notification_type} silent file notifications: ${telegram_silent_file_notifications:=false}"
elif [ "${notification_type}" = "openhab" ] && [ "${webhook_server}" ] && [ "${webhook_id}" ]; then
if [ "${webhook_https}" = true ]; then
webhook_scheme="https"
else
webhook_scheme="http"
fi
LogInfo "${notification_type} notifications enabled"
LogDebug "${notification_type} server: ${webhook_server}"
LogDebug "${notification_type} port: ${webhook_port:=8123}"
LogDebug "${notification_type} path: ${webhook_path:=/rest/items/}"
LogDebug "${notification_type} ID: ${webhook_id}"
notification_url="${webhook_scheme}://${webhook_server}:${webhook_port}${webhook_path}${webhook_id}"
LogDebug "${notification_type} notification URL: ${notification_url}"
elif [ "${notification_type}" = "Webhook" ] && [ "${webhook_server}" ] && [ "${webhook_id}" ]; then
if [ "${webhook_https}" = true ]; then
webhook_scheme="https"
else
webhook_scheme="http"
fi
LogInfo "${notification_type} notifications enabled"
CleanNotificationTitle
LogDebug "${notification_type} server: ${webhook_server}"
LogDebug "${notification_type} port: ${webhook_port:=8123}"
LogDebug "${notification_type} path: ${webhook_path:=/api/webhook/}"
LogDebug "${notification_type} ID: ${webhook_id}"
notification_url="${webhook_scheme}://${webhook_server}:${webhook_port}${webhook_path}${webhook_id}"
LogDebug "${notification_type} notification URL: ${notification_url}"
LogDebug "${notification_type} body keyword: ${webhook_body:=data}"
elif [ "${notification_type}" = "Discord" ] && [ "${discord_id}" ] && [ "${discord_token}" ]; then
LogInfo "${notification_type} notifications enabled"
CleanNotificationTitle
if [ "${debug_logging}" = true ]; then
LogDebug "${notification_type} Discord ID: (hidden)"
LogDebug "${notification_type} Discord token: (hidden)"
notification_url="https://discord.com/api/webhooks/${discord_id}/${discord_token}"
LogDebug "${notification_type} notification URL: (hidden)"
else
LogInfo "${notification_type} Discord ID: ${discord_id}"
LogInfo "${notification_type} Discord token: ${discord_token}"
notification_url="https://discord.com/api/webhooks/${discord_id}/${discord_token}"
LogInfo "${notification_type} notification URL: ${notification_url}"
fi
elif [ "${notification_type}" = "Dingtalk" ] && [ "${dingtalk_token}" ]; then
notification_url="https://oapi.dingtalk.com/robot/send?access_token=${dingtalk_token}"
LogInfo "${notification_type} notifications enabled"
if [ "${debug_logging}" = true ]; then
LogDebug "${notification_type} token: (hidden)"
LogDebug "${notification_type} notification URL: (hidden)"
else
LogInfo "${notification_type} token: ${dingtalk_token}"
LogInfo "${notification_type} notification URL: ${notification_url}"
fi
elif [ "${notification_type}" = "IYUU" ] && [ "${iyuu_token}" ]; then
notification_url="http://iyuu.cn/${iyuu_token}.send?"
LogInfo "${notification_type} notifications enabled"
if [ "${debug_logging}" = true ]; then
LogDebug "${notification_type} token: (hidden)"
LogDebug "${notification_type} notification URL: (hidden)"
else
LogInfo "${notification_type} token: ${iyuu_token}"
LogInfo "${notification_type} notification URL: ${notification_url}"
fi
elif [ "${notification_type}" = "WeCom" ] && [ "${wecom_id}" ] && [ "${wecom_secret}" ]; then
wecom_base_url="https://qyapi.weixin.qq.com"
if [ "${wecom_proxy}" ]; then
wecom_base_url="${wecom_proxy}"
LogDebug "${notification_type} notifications proxy enabled : ${wecom_proxy}"
fi
wecom_token_url="${wecom_base_url}/cgi-bin/gettoken?corpid=${wecom_id}&corpsecret=${wecom_secret}"
wecom_token="$(/usr/bin/curl -s -G "${wecom_token_url}" | awk -F\" '{print $10}')"
wecom_token_expiry="$(date --date='2 hour')"
notification_url="${wecom_base_url}/cgi-bin/message/send?access_token=${wecom_token}"
LogInfo "${notification_type} notifications enabled"
if [ "${debug_logging}" = true ]; then
LogDebug "${notification_type} token: (hidden)"
LogDebug "${notification_type} token expiry time: $(date -d "${wecom_token_expiry}")"
LogDebug "${notification_type} notification URL: (hidden)"
else
LogInfo "${notification_type} token: ${wecom_token}"
LogInfo "${notification_type} token expiry time: $(date -d "${wecom_token_expiry}")"
LogInfo "${notification_type} notification URL: ${notification_url}"
fi
elif [ "${notification_type}" = "Gotify" ] && [ "${gotify_app_token}" ] && [ "${gotify_server_url}" ]; then
LogInfo "${notification_type} notifications enabled"
CleanNotificationTitle
if [ "${debug_logging}" = true ]; then
LogDebug "${notification_type} token: (hidden)"
LogDebug "${notification_type} server URL: (hidden)"
else
LogInfo "${notification_type} token: ${gotify_app_token}"
LogInfo "${notification_type} server URL: ${gotify_server_url}"
fi
notification_url="https://${gotify_server_url}/message?token=${gotify_app_token}"
elif [ "${notification_type}" = "Bark" ] && [ "${bark_device_key}" ] && [ "${bark_server}" ]; then
LogInfo "${notification_type} notifications enabled"
CleanNotificationTitle
if [ "${debug_logging}" = true ]; then
LogDebug "${notification_type} device key: (hidden)"
LogDebug "${notification_type} server: (hidden)"
else
LogInfo "${notification_type} device key: ${bark_device_key}"
LogInfo "${notification_type} server: ${bark_server}"
fi
notification_url="http://${bark_server}/push"
else
LogWarning "$(date '+%Y-%m-%d %H:%M:%S') WARINING ${notification_type} notifications enabled, but configured incorrectly - disabling notifications"
unset notification_type prowl_api_key pushover_user pushover_token telegram_token telegram_chat_id webhook_scheme webhook_server webhook_port webhook_id dingtalk_token discord_id discord_token iyuu_token wecom_id wecom_secret gotify_app_token gotify_server_url bark_device_key bark_server
fi
if [ -z "${icloud_china}" ]; then
Notify "startup" "iCloudPD container started" "0" "iCloudPD container now starting for Apple ID: ${apple_id}"
else
Notify "startup" "iCloudPD container started" "0" "启动成功,开始同步当前 Apple ID 中的照片" "" "" "" "开始同步 ${name} 的 iCloud 图库" "Apple ID: ${apple_id}"
fi
if [ "${download_notifications:=true}" = true ]; then
LogDebug "Download notifications: Enabled"
else
LogDebug "Download notifications: Disabled"
unset download_notifications
fi
if [ "${delete_notifications:=true}" = true ]; then
LogDebug "Delete notifications: Enabled"
else
LogDebug "Delete notifications: Disabled"
unset delete_notifications
fi
fi
}
CreateGroup(){
if [ "$(grep -c "^${group}:x:${group_id}:" "/etc/group")" -eq 1 ]; then
LogDebug "Group, ${group}:${group_id}, already created"
else
if [ "$(grep -c "^${group}:" "/etc/group")" -eq 1 ]; then
LogDebug "Group name, ${group}, already in use - exiting"
sleep 120
exit 1
elif [ "$(grep -c ":x:${group_id}:" "/etc/group")" -eq 1 ]; then
if [ "${force_gid}" = true ]; then
group="$(grep ":x:${group_id}:" /etc/group | awk -F: '{print $1}')"
LogWarning "Group id, ${group_id}, already in use by the group: ${group} - continuing as force_gid variable has been set. Group name to use: ${group}"
else
LogError "Group id, ${group_id}, already in use by the group: ${group} - exiting. If you must to add your user to this pre-existing system group, please set the force_gid variable to True"
sleep 120
exit 1
fi
else
LogDebug "Creating group ${group}:${group_id}"
groupadd --gid "${group_id}" "${group}"
fi
fi
}
CreateUser(){
if [ "$(grep -c "^${user}:x:${user_id}:${group_id}" "/etc/passwd")" -eq 1 ]; then
LogDebug "User, ${user}:${user_id}, already created"
else
if [ "$(grep -c "^${user}:" "/etc/passwd")" -eq 1 ]; then
LogError "User name, ${user}, already in use - exiting"
sleep 120
exit 1
elif [ "$(grep -c ":x:${user_id}:$" "/etc/passwd")" -eq 1 ]; then
LogError "User id, ${user_id}, already in use - exiting"
sleep 120
exit 1
else
LogDebug "Creating user ${user}:${user_id}"
useradd --shell /bin/ash --gid "${group_id}" --uid "${user_id}" "${user}" --home-dir "/home/${user}" --badname
fi
fi
}
# ListLibraries(){
# LogInfo "Shared libraries available:"
# source /opt/icloudpd_latest/bin/activate
# LogDebug "Switched to icloudpd: $(icloudpd --version | awk '{print $3}')"
# shared_libraries="$(su "${user}" -c 'icloudpd --username "${0}" --cookie-directory "${1}" --domain "${2}" --directory /dev/null --list-libraries | sed "1d"' -- "${apple_id}" "${config_dir}" "${auth_domain}")"
# deactivate
# for library in ${shared_libraries}; do
# LogInfo " - ${library}"
# done
# }
ListAlbums(){
IFS=$'\n'
source /opt/icloudpd_latest/bin/activate
LogDebug "Switched to icloudpd: $(icloudpd --version | awk '{print $3}')"
available_albums="$(su "${user}" -c 'icloudpd --username "${0}" --cookie-directory "${1}" --domain "${2}" --directory /dev/null --list-albums | sed "1d"' -- "${apple_id}" "${config_dir}" "${auth_domain}")"
deactivate
LogInfo "Albums available:"
for available_album in ${available_albums}; do
LogInfo " - ${available_album}"
done
IFS="${save_ifs}"
}
DeletePassword(){
if [ -f "${config_dir}/python_keyring/keyring_pass.cfg" ]; then
LogWarning "Keyring file ${config_dir}/python_keyring/keyring_pass.cfg exists, but --RemoveKeyring command line switch has been invoked. Removing in 30 seconds"
if [ -z "${warnings_acknowledged}" ]; then
sleep 30
else
LogInfo "Warnings acknowledged, removing immediately"
fi
rm "${config_dir}/python_keyring/keyring_pass.cfg"
else
LogError "Keyring file does not exist"
fi
}
ConfigurePassword(){
LogDebug "Configure password"
if [ -f "${config_dir}/python_keyring/keyring_pass.cfg" ] && [ "$(grep -c "=" "${config_dir}/python_keyring/keyring_pass.cfg")" -eq 0 ]; then
LogDebug "Keyring file ${config_dir}/python_keyring/keyring_pass.cfg exists, but does not contain any credentials. Removing"
rm "${config_dir}/python_keyring/keyring_pass.cfg"
fi
if [ ! -f "/home/${user}/.local/share/python_keyring/keyring_pass.cfg" ]; then
if [ "${initialise_container}" ]; then
LogDebug "Adding password to keyring file: ${config_dir}/python_keyring/keyring_pass.cfg"
if [ "${icloud_china}" ]; then
source /opt/icloudpd_v1.7.2_china/bin/activate
else
source /opt/icloudpd_latest/bin/activate
fi
LogDebug "Switched to icloudpd: $(icloudpd --version | awk '{print $3}')"
su "${user}" -c 'icloud --username "${0}"' -- "${apple_id}"
deactivate
else
LogError "Keyring file ${config_dir}/python_keyring/keyring_pass.cfg does not exist"
LogError " - Please add the your password to the system keyring using the --Initialise script command line option"
LogError " - Syntax: docker exec -it <container name> sync-icloud.sh --Initialise"
LogError " - Example: docker exec -it icloudpd sync-icloud.sh --Initialise"
LogError "Waiting for keyring file to be created..."
local counter
counter="${counter:=0}"
while [ ! -f "/home/${user}/.local/share/python_keyring/keyring_pass.cfg" ]; do
sleep 5
counter=$((counter + 1))
if [ "${counter}" -eq 360 ]; then
LogError "Keyring file has not appeared within 30 minutes. Restarting container..."
exit 1
fi
done
LogDebug "Keyring file exists, continuing"
fi
else
LogDebug "Using password stored in keyring file: ${config_dir}/python_keyring/keyring_pass.cfg"
fi
if [ ! -f "/home/${user}/.local/share/python_keyring/keyring_pass.cfg" ]; then
LogError "Keyring file does not exist. Please try again."
sleep 120
exit 1
fi
}
GenerateCookie(){
LogDebug "$(date '+%Y-%m-%d %H:%M:%S') INFO Correct owner on config directory, if required"
find "${config_dir}" ! -user "${user}" -exec chown "${user_id}" {} +
LogDebug "$(date '+%Y-%m-%d %H:%M:%S') INFO Correct group on config directory, if required"
find "${config_dir}" ! -group "${group}" -exec chgrp "${group}" {} +
if [ -f "${config_dir}/${cookie_file}" ]; then
mv "${config_dir}/${cookie_file}" "${config_dir}/${cookie_file}.bak"
fi
LogDebug "Generate ${authentication_type} cookie using password stored in keyring file"
if [ "${icloud_china}" ]; then
source /opt/icloudpd_v1.7.2_china/bin/activate
else
source /opt/icloudpd_latest/bin/activate
fi
LogDebug "Switched to icloudpd: $(icloudpd --version | awk '{print $3}')"
su "${user}" -c 'icloudpd --username "${0}" --cookie-directory "${1}" --directory /dev/null --only-print-filenames --recent 0' -- "${apple_id}" "${config_dir}"
deactivate
if [ "${authentication_type}" = "2FA" ]; then
if [ "$(grep -c "X-APPLE-WEBAUTH-HSA-TRUST" "${config_dir}/${cookie_file}")" -eq 1 ]; then
LogInfo "Two factor authentication cookie generated. Sync should now be successful"
else
LogError "2FA information missing from cookie. Authentication has failed"
LogError " - Was the correct password entered?"
LogError " - Was the 2FA code mistyped?"
LogError " - Can you log into ${icloud_domain} without receiving pop-up notifications?"
if [ -z "${icloud_china}" ]; then
LogError " - Are you based in China? You will need to set the icloud_china variable"
fi
fi
else
LogDebug "Web cookie generated. Sync should now be successful"
fi
}
CheckMount(){
LogInfo "Check download directory mounted correctly..."
if [ ! -f "${download_path}/.mounted" ]; then
LogWarning "Failsafe file ${download_path}/.mounted file is not present. Waiting for failsafe file to be created..."
local counter
counter="0"
fi
while [ ! -f "${download_path}/.mounted" ]; do
sleep 5
counter=$((counter + 1))
if [ "${counter}" -eq 360 ]; then
LogError "Failsafe file has not appeared within 30 minutes. Restarting container..."
exit 1
fi
done
LogInfo "Failsafe file ${download_path}/.mounted exists, continuing"
}
SetOwnerAndPermissionsConfig(){
LogDebug "Set owner and group on icloudpd temp directory"
chown -R "${user_id}:${group}" "/tmp/icloudpd"
LogDebug "Set owner and group on config directory"
chown -R "${user_id}:${group}" "${config_dir}"
LogDebug "Set owner and group on keyring directory"
chown -R "${user_id}:${group}" "/home/${user}/.local"
}
SetOwnerAndPermissionsDownloads(){
LogDebug "Set owner on iCloud directory, if required"
find "${download_path}" ! -type l ! -user "${user_id}" -exec chown "${user_id}" {} +
LogDebug "Set group on iCloud directory, if required"
find "${download_path}" ! -type l ! -group "${group_id}" -exec chgrp "${group_id}" {} +
LogDebug "Set ${directory_permissions} permissions on iCloud directories, if required"
find "${download_path}" -type d ! -perm "${directory_permissions}" -exec chmod "${directory_permissions}" '{}' +
LogDebug "Set ${file_permissions} permissions on iCloud files, if required"
find "${download_path}" -type f ! -perm "${file_permissions}" -exec chmod "${file_permissions}" '{}' +
}
CheckWebCookie(){
if [ -f "${config_dir}/${cookie_file}" ]; then
web_cookie_expire_date="$(grep "X_APPLE_WEB_KB" "${config_dir}/${cookie_file}" | sed -e 's#.*expires="\(.*\)Z"; HttpOnly.*#\1#')"
else
LogError "Cookie does not exist"
LogError " - Please create your cookie using the --Initialise script command line option"
LogError " - Syntax: docker exec -it <container name> sync-icloud.sh --Initialise"
LogError " - Example: docker exec -it icloudpd sync-icloud.sh --Initialise"
LogError "Waiting for cookie file to be created..."
local counter
counter="${counter:=0}"
while [ ! -f "${config_dir}/${cookie_file}" ]; do
sleep 5
counter=$((counter + 1))
if [ "${counter}" -eq 360 ]; then
LogError "Cookie file has not appeared within 30 minutes. Restarting container..."
exit 1
fi
done
LogInfo "Cookie file exists, continuing"
fi
}
Check2FACookie(){
if [ -f "${config_dir}/${cookie_file}" ]; then
LogDebug "Cookie exists, check expiry date"
if [ "$(grep -c "X-APPLE-WEBAUTH-HSA-TRUST" "${config_dir}/${cookie_file}")" -eq 1 ]; then
twofa_expire_date="$(grep "X-APPLE-WEBAUTH-HSA-TRUST" "${config_dir}/${cookie_file}" | sed -e 's#.*expires="\(.*\)Z"; HttpOnly.*#\1#')"
twofa_expire_seconds="$(date -d "${twofa_expire_date}" '+%s')"
days_remaining="$(($((twofa_expire_seconds - $(date '+%s'))) / 86400))"
echo "${days_remaining}" > "${config_dir}/DAYS_REMAINING"
if [ "${days_remaining}" -gt 0 ]; then
valid_twofa_cookie=true
LogDebug "Valid two factor authentication cookie found. Days until expiration: ${days_remaining}"
else
rm -f "${config_dir}/${cookie_file}"
LogError "Cookie expired at: ${twofa_expire_date}"
LogError "Expired cookie file has been removed"
LogError " - Please recreate your cookie using the --Initialise script command line option"
LogError " - Syntax: docker exec -it <container name> sync-icloud.sh --Initialise"
LogError " - Example: docker exec -it icloudpd sync-icloud.sh --Initialise"
LogError "Waiting for cookie file to be created..."
local counter
counter="${counter:=0}"
while [ ! -f "${config_dir}/${cookie_file}" ]; do
sleep 5
counter=$((counter + 1))
if [ "${counter}" -eq 360 ]; then
LogError "Cookie file has not appeared within 30 minutes. Restarting container..."
exit 1
fi
done
LogDebug "Cookie file exists, continuing"
fi
else
LogError "Cookie is not 2FA capable, authentication type may have changed"
LogError " - Please recreate your cookie using the --Initialise script command line option"
LogError " - Syntax: docker exec -it <container name> sync-icloud.sh --Initialise"
LogError " - Example: docker exec -it icloudpd sync-icloud.sh --Initialise"
LogError "Restarting in 5 minutes..."
sleep 300
exit 1
fi
else
LogError "Cookie does not exist"
LogError " - Please create your cookie using the --Initialise script command line option"
LogError " - Syntax: docker exec -it <container name> sync-icloud.sh --Initialise"
LogError " - Example: docker exec -it icloudpd sync-icloud.sh --Initialise"
LogError "Waiting for cookie file to be created..."
local counter
counter="${counter:=0}"
while [ ! -f "${config_dir}/${cookie_file}" ]; do
sleep 5
counter=$((counter + 1))
if [ "${counter}" -eq 360 ]; then
LogError "Cookie file has not appeared within 30 minutes. Restarting container..."
exit 1
fi
done
LogDebug "Cookie file exists, continuing"
fi
}
Display2FAExpiry(){
local error_message
LogInfo "Two factor authentication cookie expires: ${twofa_expire_date/ / @ }"
LogInfo "Days remaining until expiration: ${days_remaining}"
if [ "${days_remaining}" -le "${notification_days}" ]; then
if [ "${days_remaining}" -eq 1 ]; then
cookie_status="cookie expired"
if [ -z "${icloud_china}" ]; then
error_message="Final day before two factor authentication cookie expires for Apple ID: ${apple_id} - Please reinitialise now. This is your last reminder"
else
error_message="今天是 ${name} 的 Apple ID 两步验证 cookie 到期前的最后一天 - 请立即重新初始化,这是最后的提醒"
fi
else
cookie_status="cookie expiration"
if [ -z "${icloud_china}" ]; then
error_message="Only ${days_remaining} days until two factor authentication cookie expires for Apple ID: ${apple_id} - Please reinitialise"
else
error_message="${days_remaining} 天后 ${name} 的 Apple ID 两步验证将到期 - 请立即重新初始化"
fi
fi
LogWarning "${error_message}"
if [ "${synchronisation_time:=$(date +%s -d '+15 minutes')}" -gt "${next_notification_time:=$(date +%s)}" ]; then
if [ -z "${icloud_china}" ]; then
Notify "${cookie_status}" "2FA Cookie Expiration" "2" "${error_message}"
else
Notify "${cookie_status}" "2FA Cookie Expiration" "2" "${error_message}" "" "" "" "${days_remaining} 天后,${name} 的身份验证到期" "${error_message}"
fi
next_notification_time="$(date +%s -d "+24 hour")"
LogDebug "Next notification not before: $(date +%H:%M:%S -d "${next_notification_time} seconds")"
fi
fi
}
CheckFiles(){
if [ -f "/tmp/icloudpd/icloudpd_check.log" ]; then rm "/tmp/icloudpd/icloudpd_check.log"; fi
LogInfo "Check for new files using password stored in keyring file"
LogInfo "Generating list of files in iCloud. This may take a long time if you have a large photo collection. Please be patient. Nothing is being downloaded at this time"
>/tmp/icloudpd/icloudpd_check_error
source /opt/icloudpd_latest/bin/activate
LogDebug "Switched to icloudpd: $(icloudpd --version | awk '{print $3}')"
su "${user}" -c '(icloudpd --directory "${0}" --cookie-directory "${1}" --username "${2}" --domain "${3}" --folder-structure "${4}" --only-print-filenames 2>/tmp/icloudpd/icloudpd_check_error; echo $? >/tmp/icloudpd/icloudpd_check_exit_code) | tee /tmp/icloudpd/icloudpd_check.log' -- "${download_path}" "${config_dir}" "${apple_id}" "${auth_domain}" "${folder_structure}"
check_exit_code="$(cat /tmp/icloudpd/icloudpd_check_exit_code)"
deactivate
if [ "${check_exit_code}" -ne 0 ]; then
LogError "Failed check for new files files"
LogError " - Can you log into ${icloud_domain} without receiving pop-up notifications?"
LogError "Error debugging info:"
LogError "$(cat /tmp/icloudpd/icloudpd_check_error)"
LogError "***** Please report problems here: https://github.com/boredazfcuk/docker-icloudpd/issues *****"
if [ -z "${icloud_china}" ]; then
Notify "failure" "iCloudPD container failure" "0" "iCloudPD failed check for new files for Apple ID: ${apple_id}"
else
syn_end_time="$(date '+%H:%M:%S')"
syn_next_time="$(date +%H:%M:%S -d "${synchronisation_interval} seconds")"
Notify "failure" "iCloudPD container failure" "0" "检查 iCloud 图库新照片失败,将在 ${syn_next_time} 再次尝试" "" "" "" "检查 ${name} 的 iCloud 图库新照片失败" "将在 ${syn_next_time} 再次尝试"
fi
else
LogInfo "Check successful"
check_files_count="$(grep -c ^ /tmp/icloudpd/icloudpd_check.log)"
if [ "${check_files_count}" -gt 0 ]; then
LogInfo "New files detected: ${check_files_count}"
else
LogInfo "No new files detected. Nothing to download"
fi
fi
login_counter=$((login_counter + 1))
}
DownloadedFilesNotification(){
local new_files_count new_files_preview new_files_text
new_files="$(grep "Downloading /" /tmp/icloudpd/icloudpd_sync.log)"
new_files_count="$(grep -c "Downloading /" /tmp/icloudpd/icloudpd_sync.log)"
if [ "${new_files_count:=0}" -gt 0 ]; then
LogInfo "New files downloaded: ${new_files_count}"
new_files_preview="$(echo "${new_files}" | awk '{print $5}' | sed -e "s%${download_path}/%%g" | head -10)"
new_files_preview_count="$(echo "${new_files_preview}" | wc -l)"
if [ -z "${icloud_china}" ]; then
new_files_text="Files downloaded for Apple ID ${apple_id}: ${new_files_count}"
Notify "downloaded files" "New files detected" "0" "${new_files_text}" "${new_files_preview_count}" "downloaded" "${new_files_preview}"
else
# 结束时间、下次同步时间
syn_end_time="$(date '+%H:%M:%S')"
syn_next_time="$(date +%H:%M:%S -d "${synchronisation_interval} seconds")"
new_files_text="iCloud 图库同步完成,新增 ${new_files_count} 张照片"
Notify "downloaded files" "New files detected" "0" "${new_files_text}" "${new_files_preview_count}" "下载" "${new_files_preview}" "新增 ${new_files_count} 张照片 - ${name}" "下次同步时间 ${syn_next_time}"
fi
if [ "${trigger_nextlcoudcli_synchronisation}" ]; then
touch "${download_path}/.nextcloud_sync"
fi
fi
}
DeletedFilesNotification(){
local deleted_files deleted_files_count deleted_files_preview deleted_files_text
deleted_files="$(grep "Deleting /" /tmp/icloudpd/icloudpd_sync.log)"
deleted_files_count="$(grep -c "Deleting /" /tmp/icloudpd/icloudpd_sync.log)"
if [ "${deleted_files_count:=0}" -gt 0 ]; then
LogInfo "Number of files deleted: ${deleted_files_count}"
deleted_files_preview="$(echo "${deleted_files}" | awk '{print $5}' | sed -e "s%${download_path}/%%g" -e "s%!$%%g" | tail -10)"
deleted_files_preview_count="$(echo "${deleted_files_preview}" | wc -l)"
if [ -z "${icloud_china}" ]; then
deleted_files_text="Files deleted for Apple ID ${apple_id}: ${deleted_files_count}"
Notify "deleted files" "Recently deleted files detected" "0" "${deleted_files_text}" "${deleted_files_preview_count}" "deleted" "${deleted_files_preview}"
else
# 结束时间、下次同步时间
syn_end_time="$(date '+%H:%M:%S')"
syn_next_time="$(date +%H:%M:%S -d "${synchronisation_interval} seconds")"
deleted_files_text="iCloud 图库同步完成,删除 ${deleted_files_count} 张照片"
Notify "deleted files" "Recently deleted files detected" "0" "${deleted_files_text}" "${deleted_files_preview_count}" "删除" "${deleted_files_preview}" "删除 ${deleted_files_count} 张照片 - ${name}" "下次同步时间 ${syn_next_time}"
fi
if [ "${trigger_nextlcoudcli_synchronisation}" ]; then
touch "${download_path}/.nextcloud_sync"
fi
fi
}
ConvertDownloadedHEIC2JPEG(){
IFS="$(echo -en "\n\b")"
LogInfo "Convert HEIC to JPEG..."
for heic_file in $(echo "$(grep "Downloading /" /tmp/icloudpd/icloudpd_sync.log)" | grep ".HEIC" | awk '{print $5}'); do
if [ ! -f "${heic_file}" ]; then
LogWarning "HEIC file ${heic_file} does not exist. It may exist in 'Recently Deleted' so has been removed post download"
else
jpeg_file="${heic_file%.HEIC}.JPG"
if [ "${jpeg_path}" ]; then
jpeg_file="${jpeg_file/${download_path}/${jpeg_path}}"
mkdir --parents "$(dirname "${jpeg_file}")"
fi
LogInfo "Converting ${heic_file} to ${jpeg_file}"
convert -quality "${jpeg_quality}" "${heic_file}" "${jpeg_file}"
heic_date="$(date -r "${heic_file}" +"%a %b %e %T %Y")"
LogDebug "Timestamp of HEIC file: ${heic_date}"
touch --reference="${heic_file}" "${jpeg_file}"
LogDebug "Setting timestamp of ${jpeg_file} to ${heic_date}"
LogDebug "Correct owner and group of ${jpeg_file} to ${user}:${group}"
chown "${user}:${group}" ${jpeg_file}
fi
done
IFS="${save_ifs}"
}
SynologyPhotosAppFix(){
# Works for onestix. Do not obsolete
IFS="$(echo -en "\n\b")"
LogInfo "Fixing Synology Photos App import issue..."
for heic_file in $(echo "$(grep "Downloading /" /tmp/icloudpd/icloudpd_sync.log)" | grep ".HEIC" | awk '{print $5}'); do
LogDebug "Create empty date/time reference file ${heic_file%.HEIC}.TMP"
su "${user}" -c 'touch --reference="${0}" "${1}"' -- "${heic_file}" "${heic_file%.HEIC}.TMP"
LogDebug "Set time stamp for ${heic_file} to current: $(date)"
su "${user}" -c 'touch "${0}"' -- "${heic_file}"
LogDebug "Set time stamp for ${heic_file} to original: $(date -r "${heic_file%.HEIC}.TMP" +"%a %b %e %T %Y")"
su "${user}" -c 'touch --reference="${0}" "${1}"' -- "${heic_file%.HEIC}.TMP" "${heic_file}"
LogDebug "Removing temporary file ${heic_file%.HEIC}.TMP"
if [ -z "${persist_temp_files}" ]; then
rm "${heic_file%.HEIC}.TMP"
fi
done
IFS="${save_ifs}"
}
ConvertAllHEICs(){
IFS="$(echo -en "\n\b")"
LogInfo "Convert all HEICs to JPEG, if required..."
for heic_file in $(find "${download_path}" -type f -name *.HEIC 2>/dev/null); do
LogDebug "HEIC file found: ${heic_file}"
jpeg_file="${heic_file%.HEIC}.JPG"
if [ "${jpeg_path}" ]; then
jpeg_file="${jpeg_file/${download_path}/${jpeg_path}}"
mkdir --parents "$(dirname "${jpeg_file}")"
fi
if [ ! -f "${jpeg_file}" ]; then
LogInfo "Converting ${heic_file} to ${jpeg_file}"
convert -quality "${jpeg_quality}" "${heic_file}" "${jpeg_file}"
heic_date="$(date -r "${heic_file}" +"%a %b %e %T %Y")"
LogDebug "Timestamp of HEIC file: ${heic_date}"
touch --reference="${heic_file}" "${jpeg_file}"
LogDebug "Setting timestamp of ${jpeg_file} to ${heic_date}"
LogDebug "Correct owner and group of ${jpeg_file} to ${user}:${group}"
chown "${user}:${group}" "${jpeg_file}"
fi
done
IFS="${save_ifs}"
}
RemoveAllJPGs(){
IFS="$(echo -en "\n\b")"
LogWarning "Remove all JPGs that have accompanying HEIC files. This could result in data loss if HEIC file name matches the JPG file name, but content does not."
LogInfo "Waiting for 2mins before progressing. Please stop the container now, if this is not what you want to do..."
sleep 120
for heic_file in $(find "${download_path}" -type f -name *.HEIC 2>/dev/null); do
jpeg_file="${heic_file%.HEIC}.JPG"
if [ "${jpeg_path}" ]; then
jpeg_file="${jpeg_file/${download_path}/${jpeg_path}}"
fi
LogInfo "Removing ${jpeg_file}"
if [ -f "${jpeg_file}" ]; then
rm "${jpeg_file}"
fi
done
IFS="${save_ifs}"
}
ForceConvertAllHEICs(){
IFS="$(echo -en "\n\b")"
LogWarning "Force convert all HEICs to JPEG. This could result in data loss if JPG files have been edited on disk"
LogInfo "Waiting for 2mins before progressing. Please stop the container now, if this is not what you want to do..."
sleep 120
for heic_file in $(find "${download_path}" -type f -name *.HEIC 2>/dev/null); do
jpeg_file="${heic_file%.HEIC}.JPG"
if [ "${jpeg_path}" ]; then
jpeg_file="${jpeg_file/${download_path}/${jpeg_path}}"
fi
LogInfo "Converting ${heic_file} to ${jpeg_file}"
rm "${jpeg_file}"
convert -quality "${jpeg_quality}" "${heic_file}" "${jpeg_file}"
heic_date="$(date -r "${heic_file}" +"%a %b %e %T %Y")"
LogDebug "Timestamp of HEIC file: ${heic_date}"
touch --reference="${heic_file}" "${jpeg_file}"
LogDebug "Setting timestamp of ${jpeg_file} to ${heic_date}"
LogDebug "Correct owner and group of ${jpeg_file} to ${user}:${group}"
chown "${user}:${group}" "${jpeg_file}"
done
IFS="${save_ifs}"
}
ForceConvertAllmntHEICs(){
IFS="$(echo -en "\n\b")"
LogWarning "Force convert all HEICs in /mnt directory to JPEG. This could result in data loss if JPG files have been edited on disk"
LogInfo "Waiting for 2mins before progressing. Please stop the container now, if this is not what you want to do..."
sleep 120
for heic_file in $(find "/mnt" -type f -name *.HEIC 2>/dev/null); do
jpeg_file="${heic_file%.HEIC}.JPG"
if [ "${jpeg_path}" ]; then
jpeg_file="${jpeg_file/${download_path}/${jpeg_path}}"
fi
LogInfo "Converting ${heic_file} to ${jpeg_file}"
rm "${jpeg_file}"
convert -quality "${jpeg_quality}" "${heic_file}" "${jpeg_file}"
heic_date="$(date -r "${heic_file}" +"%a %b %e %T %Y")"
LogDebug "Timestamp of HEIC file: ${heic_date}"
touch --reference="${heic_file}" "${jpeg_file}"
LogDebug "Setting timestamp of ${jpeg_file} to ${heic_date}"
LogDebug "Correct owner and group of ${jpeg_file} to ${user}:${group}"
chown "${user}:${group}" "${jpeg_file}"
done
IFS="${save_ifs}"
}
CorrectJPEGTimestamps(){
IFS="$(echo -en "\n\b")"
LogInfo "Check and correct converted HEIC timestamps..."
for heic_file in $(find "${download_path}" -type f -name *.HEIC 2>/dev/null); do
jpeg_file="${heic_file%.HEIC}.JPG"
if [ "${jpeg_path}" ]; then
jpeg_file="${jpeg_file/${download_path}/${jpeg_path}}"
fi
heic_date="$(date -r "${heic_file}" +"%a %b %e %T %Y")"
LogDebug "Timestamp of HEIC file: ${heic_date}"
if [ -f "${jpeg_file}" ]; then
LogDebug "JPEG file found: ${jpeg_file}"
jpeg_date="$(date -r "${jpeg_file}" +"%a %b %e %T %Y")"
LogDebug "Timestamp of JPEG file: ${jpeg_date}"
if [ "${heic_date}" != "${jpeg_date}" ]; then
LogInfo "Setting timestamp of ${jpeg_file} to ${heic_date}"
touch --reference="${heic_file}" "${jpeg_file}"
else
LogDebug "Time stamps match. Adjustment not required"
fi
fi
done
IFS="${save_ifs}"
}
RemoveRecentlyDeletedAccompanyingFiles(){
IFS="$(echo -en "\n\b")"
LogInfo "Deleting 'Recently Deleted' accompanying files (.JPG/_HEVC.MOV)..."
for heic_file in $(echo "$(grep "Deleting /" /tmp/icloudpd/icloudpd_sync.log)" | grep ".HEIC" | awk '{print $5}'); do
heic_file_clean="${heic_file/!/}"
jpeg_file_clean="${heic_file_clean%.HEIC}.JPG"
if [ "${jpeg_path}" ]; then
jpeg_file_clean="${jpeg_file_clean/${download_path}/${jpeg_path}}"
fi
if [ -f "${jpeg_file_clean}" ]; then
LogDebug "Deleting ${jpeg_file_clean}"
rm -f "${jpeg_file_clean}"
fi
if [ -f "${heic_file_clean%.HEIC}_HEVC.MOV" ]; then
LogDebug "Deleting ${heic_file_clean%.HEIC}_HEVC.MOV"
rm -f "${heic_file_clean%.HEIC}_HEVC.MOV"
fi
done
LogInfo "Deleting 'Recently Deleted' accompanying files complete"
IFS="${save_ifs}"
}
RemoveEmptyDirectories(){
LogInfo "Deleting empty directories from ${download_path}..."
find "${download_path}" -type d -empty -delete
LogInfo "Deleting empty directories complete"
if [ "${jpeg_path}" ]; then
LogDebug "Deleting empty directories from ${jpeg_path}..."
find "${jpeg_path}" -type d -empty -delete
LogInfo "Deleting empty directories complete"
fi
}
Notify(){
local notification_classification notification_event notification_prority notification_message notification_result notification_files_preview_count notification_files_preview_type notification_files_preview_text
notification_classification="${1}"
notification_event="${2}"
notification_prority="${3}"
notification_message="${4}"
notification_files_preview_count="${5}"
notification_files_preview_type="${6}"
notification_files_preview_text="${7}"
notification_wecom_title="${8}"
notification_wecom_digest="${9}"
if [ "${notification_classification}" = "startup" ];then
notification_icon="\xE2\x96\xB6"
# 启动成功通知封面/Image for Startup success
thumb_media_id="$media_id_startup"
elif [ "${notification_classification}" = "remotesync" ];then
notification_icon="\xE2\x96\xB6"
# 启动成功通知封面/Image for Startup success
thumb_media_id="$media_id_startup"
elif [ "${notification_classification}" = "downloaded files" ]; then
notification_icon="\xE2\x8F\xAC"
# 下载通知封面/Image for downloaded files
thumb_media_id="$media_id_download"
elif [ "${notification_classification}" = "cookie expiration" ]; then
notification_icon="\xF0\x9F\x9A\xA9"
# cookie即将过期通知封面/Image for cookie expiration
thumb_media_id="$media_id_expiration"
elif [ "${notification_classification}" = "deleted files" ]; then
notification_icon="\xE2\x9D\x8C"
# 删除文件通知封面/Image for deleted files
thumb_media_id="$media_id_delete"
elif [ "${notification_classification}" = "failure" ] || [ "${notification_classification}" = "cookie expired" ]; then
notification_icon="\xF0\x9F\x9A\xA8"
# 同步失败、cookiey已过期通知封面/Image for cookie expired or failure
thumb_media_id="$media_id_warning"
fi
if [ "${notification_type}" ]; then LogInfo "Sending ${notification_type} ${notification_classification} notification"; fi
if [ "${notification_type}" = "Prowl" ]; then
notification_result="$(curl --silent --output /dev/null --write-out "%{http_code}" "${notification_url}" \
--form apikey="${prowl_api_key}" \
--form application="${notification_title}" \
--form event="${notification_event}" \
--form priority="${notification_prority}" \
--form description="${notification_message}")"
elif [ "${notification_type}" = "Pushover" ]; then
if [ "${notification_prority}" = "2" ]; then notification_prority=1; fi
if [ "${notification_files_preview_count}" ]; then
pushover_text="$(echo -e "${notification_icon} ${notification_event}\n${notification_message}\nMost recent ${notification_files_preview_count} ${notification_files_preview_type} files:\n${notification_files_preview_text}")"
else
pushover_text="$(echo -e "${notification_icon} ${notification_event}\n${notification_message}")"
fi
notification_result="$(curl --silent --output /dev/null --write-out "%{http_code}" "${notification_url}" \
--form-string "user=${pushover_user}" \
--form-string "token=${pushover_token}" \
--form-string "title=${notification_title}" \
--form-string "sound=${pushover_sound}" \
--form-string "priority=${notification_prority}" \
--form-string "message=${pushover_text}")"
elif [ "${notification_type}" = "Telegram" ]; then
if [ "${notification_files_preview_count}" ]; then
telegram_text="$(echo -e "${notification_icon} *${notification_title}*\n${notification_message//_/\\_}\nMost recent ${notification_files_preview_count} ${notification_files_preview_type} files:\n${notification_files_preview_text//_/\\_}")"
else
telegram_text="$(echo -e "${notification_icon} *${notification_title}*\n${notification_message//_/\\_}")"
fi
notification_result="$(curl --silent --output /dev/null --write-out "%{http_code}" --request POST "${notification_url}" \
--data chat_id="${telegram_chat_id}" \
--data parse_mode="markdown" \
--data disable_notification="${telegram_disable_notification:=false}" \
--data text="${telegram_text}")"
unset telegram_disable_notification
elif [ "${notification_type}" = "openhab" ]; then
webhook_payload="$(echo -e "${notification_title} - ${notification_message}")"
notification_result="$(curl -X 'PUT' --silent --output /dev/null --write-out "%{http_code}" "${notification_url}" \
--header 'content-type: text/plain' \
--data "${webhook_payload}")"
elif [ "${notification_type}" = "Webhook" ]; then
webhook_payload="$(echo -e "${notification_title} - ${notification_message}")"
notification_result="$(curl --silent --output /dev/null --write-out "%{http_code}" "${notification_url}" \
--header 'content-type: application/json' \
--data "{ \"${webhook_body}\" : \"${webhook_payload}\" }")"
elif [ "${notification_type}" = "Discord" ]; then
if [ "${notification_files_preview_count}" ]; then
discord_text="${notification_message}\\nMost recent ${notification_files_preview_count} ${notification_files_preview_type} files:\\n${notification_files_preview_text//$'\n'/'\n'}"
else
discord_text="$(echo -e "${notification_message}")"
fi
notification_result="$(curl --silent --output /dev/null --write-out "%{http_code}" --request POST "${notification_url}" \
--header 'content-type: application/json' \
--data "{ \"username\" : \"${notification_title}\" , \"avatar_url\" : \"https://raw.githubusercontent.com/Womabre/-unraid-docker-templates/master/images/photos_icon_large.png\" , \"embeds\" : [ { \"author\" : { \"name\" : \"${notification_event}\" } , \"color\" : 2061822 , \"description\": \"${discord_text}\" } ] }")"
elif [ "${notification_type}" = "Dingtalk" ]; then
notification_result="$(curl --silent --output /dev/null --write-out "%{http_code}" --request POST "${notification_url}" \
--header 'Content-Type: application/json' \
--data "{'msgtype': 'markdown','markdown': {'title':'${notification_title}','text':'## ${notification_title}\n${notification_message}'}}")"
elif [ "${notification_type}" = "IYUU" ]; then
if [ "${notification_files_preview_count}" ]; then
iyuu_text="$(echo -e "${notification_icon} *${notification_title}*\n${notification_message}\nMost recent ${notification_files_preview_count} ${notification_files_preview_type} files:\n${notification_files_preview_text//_/\\_}")"
else
iyuu_text="$(echo -e "${notification_icon} *${notification_title}*\n${notification_message}")"
fi
notification_result="$(curl --silent --output /dev/null --write-out "%{http_code}" --request POST "${notification_url}" \
--data text="${notification_title}" \
--data desp="${iyuu_text}")"
elif [ "${notification_type}" = "WeCom" ]; then
if [ "$(date +'%s')" -ge "$(date +'%s' -d "${wecom_token_expiry}")" ]; then
unset wecom_token
fi
if [ -z "${wecom_token}" ]; then
LogWarning "${notification_type} token has expired. Retrieving new one"
wecom_token="$(/usr/bin/curl -s -G "${wecom_token_url}" | awk -F\" '{print $10}')"
wecom_token_expiry="$(date --date='2 hour')"
notification_url="${wecom_base_url}/cgi-bin/message/send?access_token=${wecom_token}"
LogInfo "${notification_type} token: ${wecom_token}"
LogInfo "${notification_type} token expiry time: $(date -d "${wecom_token_expiry}")"
LogInfo "${notification_type} notification URL: ${notification_url}"
fi
# 结束时间、下次同步时间
syn_end_time="$(date '+%H:%M:%S')"
syn_next_time="$(date +%H:%M:%S -d "${synchronisation_interval} seconds")"
if [ "${notification_files_preview_count}" ]; then
if [ -z "${icloud_china}" ]; then
wecom_text="$(echo -e "${notification_icon} *${notification_title}*\n${notification_message}\nMost recent ${notification_files_preview_count} ${notification_files_preview_type} files:\n${notification_files_preview_text//_/\\_}")"
else
notification_files_preview_text="${notification_files_preview_text//$'\n'/'<br/>'}"
wecom_text="$(echo -e "<font style="line-height:1.5"><center><b><big><big>同步日志</big></big></b></font></center><center><b>${notification_message}</b></center><center>··················· <small>最近 ${notification_files_preview_count}${notification_files_preview_type}记录如下</small> ····················</center><code><small>${notification_files_preview_text}</small></code><center>··················· <small>下次同步时间为 ${syn_next_time}</small> ··················</center>")"
fi
else
if [ -z "${icloud_china}" ]; then
wecom_text="$(echo -e "${notification_icon} *${notification_title}*\n${notification_message}")"
else
wecom_text="$(echo -e "${notification_message}")"
fi
fi
notification_result="$(curl --silent --output /dev/null --write-out "%{http_code}" --data-ascii "{\"touser\":\"${touser}\",\"msgtype\":\"mpnews\",\"agentid\":\"${agentid}\",\"mpnews\":{\"articles\":[{\"title\":\"${notification_wecom_title}\",\"thumb_media_id\":\"${thumb_media_id}\",\"author\":\"${syn_end_time}\",\"content_source_url\":\"${content_source_url}\",\"content\":\"${wecom_text}\",\"digest\":\"${notification_wecom_digest}\"}]},\"safe\":\"0\",\"enable_id_trans\":\"0\",\"enable_duplicate_check\":\"0\",\"duplicate_check_interval\":\"1800\"}" --url "${notification_url}")"
elif [ "${notification_type}" = "Gotify" ]; then
notification_result="$(curl --silent --output /dev/null --write-out "%{http_code}" "${notification_url}" \
-F "title=${notification_title}" \
-F "message=${notification_message}")"
elif [ "${notification_type}" = "Bark" ]; then
if [ "${notification_files_preview_count}" ]; then
bark_text="$(echo -e "${notification_icon} ${notification_message} Most recent ${notification_files_preview_count} ${notification_files_preview_type} files: ${notification_files_preview_text//_/\\_}")"
else
bark_text="$(echo -e "${notification_icon} ${notification_message}")"
fi
notification_result="$(curl --location --silent --output /dev/null --write-out "%{http_code}" "http://${bark_server}/push" \
-H 'Content-Type: application/json; charset=utf-8' \
-d "{ \"device_key\": \"${bark_device_key}\", \"title\": \"${notification_title}\", \"body\": \"${bark_text}\", \"category\": \"category\" }")"
fi
if [ "${notification_type}" ]; then
if [ "${notification_result:0:1}" -eq 2 ]; then
LogDebug "${notification_type} ${notification_classification} notification sent successfully"
else
LogError "${notification_type} ${notification_classification} notification failed with status code: ${notification_result}"
LogError "***** Please report problems here: https://github.com/boredazfcuk/docker-icloudpd/issues *****"
sleep 120
exit 1
fi
fi
}
CommandLineBuilder(){
command_line="--directory ${download_path} --cookie-directory ${config_dir} --domain ${auth_domain} --username ${apple_id}"
if [ "${photo_size}" != "original" ]; then
command_line="${command_line} --size ${photo_size}"
fi
if [ "${set_exif_datetime}" != false ]; then
command_line="${command_line} --set-exif-datetime"
fi
if [ "${auto_delete}" != false ]; then
command_line="${command_line} --auto-delete"
elif [ "${delete_after_download}" != false ]; then
command_line="${command_line} --delete-after-download"
fi
if [ "${skip_live_photos}" = false ]; then
if [ "${live_photo_size}" != "original" ]; then
command_line="${command_line} --live-photo-size ${live_photo_size}"
fi
else
command_line="${command_line} --skip-live-photos"
fi
if [ "${skip_videos}" != false ]; then
command_line="${command_line} --skip-videos"
fi
if [ -z "${photo_album}" ]; then
command_line="${command_line} --folder-structure ${folder_structure}"
fi
# if [ "${photo_album}" ]; then
# command_line="${command_line} --album ${photo_album}"
# elif [ "${photo_library}" ]; then
# command_line="${command_line} --library ${photo_library}"
# fi
if [ "${until_found}" ]; then
command_line="${command_line} --until-found ${until_found}"
fi
if [ "${recent_only}" ]; then
command_line="${command_line} --recent ${recent_only}"
fi
}
SyncUser(){
LogInfo "Sync user: ${user}"
if [ "${synchronisation_delay}" -ne 0 ]; then
LogInfo "Delay for ${synchronisation_delay} minutes"
sleep "${synchronisation_delay}m"
fi
while :; do
synchronisation_start_time="$(date +'%s')"
LogInfo "Synchronisation starting at $(date +%H:%M:%S -d "@${synchronisation_start_time}")"
source <(grep debug_logging "${config_file}")
chown -R "${user}":"${group}" "${config_dir}"
if [ "${authentication_type}" = "2FA" ]; then
LogDebug "Check 2FA Cookie"
valid_twofa_cookie=false
while [ "${valid_twofa_cookie}" = false ]; do Check2FACookie; done
fi
CheckMount
if [ "${skip_check}" = false ]; then
CheckFiles
else
check_exit_code=0
check_files_count=1
fi
if [ "${check_exit_code}" -eq 0 ]; then
if [ "${check_files_count}" -gt 0 ]; then
LogDebug "Starting download of new files for user: ${user}"
synchronisation_time="$(date +%s -d '+15 minutes')"
LogDebug "Downloading new files using password stored in keyring file..."
>/tmp/icloudpd/icloudpd_download_error
IFS=$'\n'
source /opt/icloudpd_latest/bin/activate
LogDebug "Switched to icloudpd: $(icloudpd --version | awk '{print $3}')"
if [ "${photo_album}" ]; then
if [ "${photo_album}" = "all albums" ]; then
all_albums="$(su "${user}" -c 'icloudpd --username "${0}" --cookie-directory "${1}" --domain "${2}" --directory /dev/null --list-albums | sed "1d"' -- "${apple_id}" "${config_dir}" "${auth_domain}")"
albums_to_download=""
for available_album in ${available_albums}; do
if [[ ! ${skip_album} =~ ${available_album} ]]; then
if [ -z "${albums_to_download}" ]; then
albums_to_download="${available_album}"
else
albums_to_download="${albums_to_download},${available_album}"
fi
fi
done
else
albums_to_download=${photo_album}
fi
IFS=","
for album in ${albums_to_download}; do
LogInfo "Downloading album: ${album}"
if [ "${albums_with_dates}" = true ]; then
LogDebug "iCloudPD launch command: icloudpd ${command_line} --folder-structure ${album}/${older_structure} --album ${album} 2>/tmp/icloudpd/icloudpd_download_error"
su "${user}" -c '(icloudpd ${0} --folder-structure "${1}" --album "${2}" 2>/tmp/icloudpd/icloudpd_download_error; echo $? >/tmp/icloudpd/icloudpd_download_exit_code) | tee /tmp/icloudpd/icloudpd_sync.log' -- "${command_line}" "${album}/${older_structure}" "${album}"
else
LogDebug "iCloudPD launch command: icloudpd ${command_line} --folder-structure ${album} --album ${album} 2>/tmp/icloudpd/icloudpd_download_error"
su "${user}" -c '(icloudpd ${0} --folder-structure "${1}" --album "${1}" 2>/tmp/icloudpd/icloudpd_download_error; echo $? >/tmp/icloudpd/icloudpd_download_exit_code) | tee /tmp/icloudpd/icloudpd_sync.log' -- "${command_line}" "${album}"
fi
if [ "$(cat /tmp/icloudpd/icloudpd_download_exit_code)" -ne 0 ]; then
LogError "Failed downloading album: ${album}"
IFS="${save_ifs}"
sleep 10
break
fi
done
IFS="${save_ifs}"
folder_structure="${older_structure}"
else
LogDebug "iCloudPD launch command: icloudpd ${command_line} 2>/tmp/icloudpd/icloudpd_download_error"
su "${user}" -c '(icloudpd ${0} 2>/tmp/icloudpd/icloudpd_download_error; echo $? >/tmp/icloudpd/icloudpd_download_exit_code) | tee /tmp/icloudpd/icloudpd_sync.log' -- "${command_line}"
fi
download_exit_code="$(cat /tmp/icloudpd/icloudpd_download_exit_code)"
deactivate
if [ "${download_exit_code}" -gt 0 ]; then
LogError "Failed to download new files"
LogError " - Can you log into ${icloud_domain} without receiving pop-up notifications?"
LogError "Error debugging info:"
LogError "$(cat /tmp/icloudpd/icloudpd_download_error)"
LogError "***** Please report problems here: https://github.com/boredazfcuk/docker-icloudpd/issues *****"
if [ -z "${icloud_china}" ]; then
Notify "failure" "iCloudPD container failure" "1" "iCloudPD failed to download new files for Apple ID: ${apple_id}"
else
# 结束时间、下次同步时间
syn_end_time="$(date '+%H:%M:%S')"
syn_next_time="$(date +%H:%M:%S -d "${synchronisation_interval} seconds")"
Notify "failure" "iCloudPD container failure" "1" "从 iCloud 图库下载新照片失败,将在 ${syn_next_time} 再次尝试" "" "" "" "下载 ${name} 的 iCloud 图库新照片失败" "将在 ${syn_next_time} 再次尝试"
fi
else
if [ "${download_notifications}" ]; then DownloadedFilesNotification; fi
if [ "${synology_photos_app_fix}" ]; then SynologyPhotosAppFix; fi
if [ "${convert_heic_to_jpeg}" != false ]; then
LogInfo "Convert HEIC files to JPEG"
ConvertDownloadedHEIC2JPEG
fi
if [ "${delete_notifications}" ]; then DeletedFilesNotification; fi
if [ "${delete_accompanying}" = true ] && [ "${folder_structure}" != "none" ] && [ "${set_exif_datetime}" = false ]; then
RemoveRecentlyDeletedAccompanyingFiles
fi
if [ "${delete_empty_directories}" = true ] && [ "${folder_structure}" != "none" ]; then
RemoveEmptyDirectories
fi
SetOwnerAndPermissionsDownloads
LogInfo "Synchronisation complete for ${user}"
fi
login_counter=$((login_counter + 1))
fi
fi
CheckWebCookie
LogInfo "Web cookie expires: ${web_cookie_expire_date/ / @ }"
if [ "${authentication_type}" = "2FA" ]; then Display2FAExpiry; fi
LogDebug "iCloud login counter = ${login_counter}"
synchronisation_end_time="$(date +'%s')"
LogInfo "Synchronisation ended at $(date +%H:%M:%S -d "@${synchronisation_end_time}")"
LogInfo "Total time taken: $(date +%H:%M:%S -u -d @$((synchronisation_end_time - synchronisation_start_time)))"
if [ "${single_pass:=false}" = true ]; then
LogDebug "Single Pass mode set, exiting"
exit 0
else
sleep_time="$((synchronisation_interval - synchronisation_end_time + synchronisation_start_time))"
LogInfo "Next synchronisation at $(date +%H:%M:%S -d "${sleep_time} seconds")"
unset check_exit_code check_files_count download_exit_code
unset new_files
if [ "${notification_type}" = "Telegram" ] && [ "${telegram_polling}" = true ]; then
LogInfo "Monitoring ${notification_type} for remote wake command: ${user}"
listen_counter=0
while [ "${listen_counter}" -lt "${sleep_time}" ]; do
if [ "${telegram_polling}" = true ]; then
latest_message="$(curl -X POST --silent "https://api.telegram.org/bot${telegram_token}/getUpdates?allowed_updates=message" | jq '.result[-1:][].message')"
latest_message_id="$(echo "${latest_message}" | jq .message_id)"
latest_message_text="$(echo "${latest_message}" | jq .text | sed 's/"//g' | tr [:upper:] [:lower:])"
if [ "${latest_message_id}" = "null" ]; then
unset latest_message_id
fi
LogDebug "Latest: ${latest_message_id:=0}. Current: ${current_message_id:=0}"
if [ "${latest_message_id}" -gt "${current_message_id}" ]; then
LogDebug "New message received: ${latest_message_text}"
if [ "${latest_message_text}" = "$(echo ${user} | tr [:upper:] [:lower:])" ]; then
LogDebug "Remote sync initiated"
if [ -z "${icloud_china}" ]; then
Notify "remotesync" "iCloudPD remote synchronisation initiated" "0" "iCloudPD has detected a remote synchronisation request for Apple ID: ${apple_id}"
else
Notify "remotesync" "iCloudPD remote synchronisation initiated" "0" "启动成功,开始同步当前 Apple ID 中的照片" "" "" "" "开始同步 ${name} 的 iCloud 图库" "Apple ID: ${apple_id}"
fi
current_message_id="${latest_message_id}"
break
fi
fi
current_message_id="${latest_message_id}"
fi
listen_counter=$((listen_counter+60))
sleep 60
done
else
sleep "${sleep_time}"
fi
fi
done
}
SanitiseLaunchParameters(){
if [ "${script_launch_parameters}" ]; then
case "$(echo ${script_launch_parameters} | tr [:upper:] [:lower:])" in
"--initialise"|"--initialize"|"--init"|"--removekeyring"|"--convertallheics"|"--removealljpgs"|"--forceconvertallheics"|"--forceconvertallmntheics"|"--correctjpegtimestamps")
LogInfo "Script launch parameters: ${script_launch_parameters}"
;;
*)
LogWarning "Ignoring invalid launch parameter specified: ${script_launch_parameters}"
LogWarning "Please do not specify the above parameter when launching the container. Continuing in 2 minutes"
sleep 120
unset script_launch_parameters
;;
esac
fi
}
enable_debug_logging(){
LogInfo "Enabling Debug Logging"
sed -i 's/debug_logging=.*/debug_logging=true/' "${config_file}"
}
disable_debug_logging(){
LogInfo "Disabling Debug Logging"
sed -i 's/debug_logging=.*/debug_logging=false/' "${config_file}"
}
##### Script #####
script_launch_parameters="${1}"
case "$(echo ${script_launch_parameters} | tr [:upper:] [:lower:])" in
"--initialise"|"--initialize"|"--init")
initialise_container=true
;;
"--removekeyring")
delete_password=true
;;
"--convertallheics")
convert_all_heics=true
;;
"--removealljpgs")
remove_all_jpgs=true
;;
"--forceconvertallheics")
force_convert_all_heics=true
;;
"--forceconvertallmntheics")
force_convert_all_mnt_heics=true
;;
"--correctjpegtimestamps")
correct_jpeg_time_stamps=true
;;
"--enabledebugging")
enable_debugging=true
;;
"--disabledebugging")
disable_debugging=true
;;
# "--listlibraries")
# list_libraries=true
# ;;
*)
;;
esac
Initialise
SanitiseLaunchParameters
CreateGroup
CreateUser
SetOwnerAndPermissionsConfig
if [ "${delete_password}" ]; then
DeletePassword
exit 0
fi
ConfigurePassword
if [ "${initialise_container}" ]; then
GenerateCookie
exit 0
elif [ "${enable_debugging}" ]; then
enable_debug_logging
exit 0
elif [ "${disable_debugging}" ]; then
disable_debug_logging
exit 0
elif [ "${convert_all_heics}" ]; then
ConvertAllHEICs
SetOwnerAndPermissionsDownloads
LogInfo "HEIC to JPG conversion complete"
exit 0
elif [ "${remove_all_jpgs}" ]; then
RemoveAllJPGs
SetOwnerAndPermissionsDownloads
LogInfo "Forced remove JPG files if accompanying HEIC exists"
exit 0
elif [ "${force_convert_all_heics}" ]; then
ForceConvertAllHEICs
SetOwnerAndPermissionsDownloads
LogInfo "Forced HEIC to JPG conversion complete"
exit 0
elif [ "${force_convert_all_mnt_heics}" ]; then
ForceConvertAllmntHEICs
SetOwnerAndPermissionsDownloads
LogInfo "Forced HEIC to JPG conversion complete"
exit 0
elif [ "${correct_jpeg_time_stamps}" ]; then
LogInfo "Correcting timestamps for JPEG files in ${download_path}"
CorrectJPEGTimestamps
LogInfo "JPEG timestamp correction complete"
exit 0
# elif [ "${list_libraries}" ];then
# ListLibraries
# exit 0
fi
CheckMount
SetOwnerAndPermissionsConfig
CommandLineBuilder
ListAlbums
SyncUser