From 2bfe1c62e03bc37f722bd757f77d5df876a16074 Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Thu, 18 Aug 2022 10:53:52 +0200 Subject: [PATCH 01/40] renamed and resorted configuration options - there is now only one identity and one remote host for all protocols - added more configuration option descriptions --- remote-backup/config.yaml | 68 +++++++++---------- remote-backup/run.sh | 103 ++++++++++++++--------------- remote-backup/translations/en.yaml | 51 +++++++++++--- 3 files changed, 122 insertions(+), 100 deletions(-) diff --git a/remote-backup/config.yaml b/remote-backup/config.yaml index 4f15198..a71a922 100644 --- a/remote-backup/config.yaml +++ b/remote-backup/config.yaml @@ -23,63 +23,59 @@ map: - share - ssl - backup:rw + options: debug: false - ssh_enabled: true - friendly_name: true - custom_prefix: Automated backup - ssh_host: "" - ssh_port: 22 - ssh_user: "" - ssh_key: "" - ssh_host_key_algorithms: "" - exclude_folders: [] - exclude_addons: [] - remote_directory: "" + remote_host: "" + remote_port: 22 + remote_user: "" + remote_password: "" + remote_key: "" + remote_host_key_algorithms: "" + backup_friendly_name: true + backup_custom_prefix: Automated backup + backup_exclude_folders: [] + backup_exclude_addons: [] + backup_keep_local: "all" backup_password: "" - keep_local_backup: "all" + ssh_enabled: true + ssh_remote_directory: "" rsync_enabled: false - rsync_host: "" rsync_rootfolder: hassio-sync rsync_exclude: - "/config/*.db-shm" - "/config/*.db-wal" - "/config/*.db" - rsync_user: "" - rsync_password: "" rclone_enabled: false + rclone_remote_directory: "" rclone_copy: false rclone_sync: false rclone_restore: false - rclone_remote: "" - rclone_remote_directory: "" + schema: debug: bool? - ssh_enabled: bool? - friendly_name: bool? - custom_prefix: str? - ssh_host: str? - ssh_port: port? - ssh_user: str? - ssh_key: str? - ssh_host_key_algorithms: str? - exclude_folders: + remote_host: str + remote_port: port + remote_user: str + remote_password: str? + remote_key: str? + remote_host_key_algorithms: str? + backup_friendly_name: bool? + backup_custom_prefix: str? + backup_exclude_folders: - match(^[A-Za-z0-9_\-\.\*\/\?\+\\]*$)? - exclude_addons: + backup_exclude_addons: - str? - remote_directory: str? + backup_keep_local: match(^(all|[+]?\d*)$) backup_password: str? - keep_local_backup: match(^(all|[+]?\d*)$) - rsync_enabled: bool? - rsync_host: str? + ssh_enabled: bool + ssh_remote_directory: str? + rsync_enabled: bool rsync_rootfolder: str? rsync_exclude: - match(^[A-Za-z0-9_\-\.\*\/\?\+\\]+$) - rsync_user: str? - rsync_password: str? - rclone_enabled: bool? + rclone_enabled: bool + rclone_remote_directory: str? rclone_copy: bool? rclone_sync: bool? rclone_restore: bool? - rclone_remote: str? - rclone_remote_directory: str? diff --git a/remote-backup/run.sh b/remote-backup/run.sh index e57a970..025f2c0 100755 --- a/remote-backup/run.sh +++ b/remote-backup/run.sh @@ -1,41 +1,38 @@ #!/command/with-contenv bashio # shellcheck shell=bash # parse inputs from options -DEBUG=$(bashio::config 'debug') -SSH_ENABLED=$(bashio::config "ssh_enabled") -FRIENDLY_NAME=$(bashio::config "friendly_name") -CUSTOM_PREFIX=$(bashio::config "custom_prefix") -SSH_HOST=$(bashio::config "ssh_host") -SSH_PORT=$(bashio::config "ssh_port") -SSH_USER=$(bashio::config "ssh_user") -SSH_KEY=$(bashio::config "ssh_key") -SSH_HOST_KEY_ALGORITHMS=$(bashio::config "ssh_host_key_algorithms") -EXCLUDE_FOLDERS=$(bashio::config "exclude_folders") -EXCLUDE_ADDONS=$(bashio::config "exclude_addons") -REMOTE_DIRECTORY=$(bashio::config "remote_directory") +DEBUG=$(bashio::config "debug") +REMOTE_HOST=$(bashio::config "remote_host") +REMOTE_PORT=$(bashio::config "remote_port") +REMOTE_USER=$(bashio::config "remote_user") +REMOTE_PASSWORD=$(bashio::config "remote_password") +REMOTE_KEY=$(bashio::config "remote_key") +REMOTE_HOST_KEY_ALGORITHMS=$(bashio::config "remote_host_key_algorithms") + +BACKUP_FRIENDLY_NAME=$(bashio::config "backup_friendly_name") +BACKUP_CUSTOM_PREFIX=$(bashio::config "backup_custom_prefix") +BACKUP_EXCLUDE_FOLDERS=$(bashio::config "backup_exclude_folders") +BACKUP_EXCLUDE_ADDONS=$(bashio::config "backup_exclude_addons") +BACKUP_KEEP_LOCAL=$(bashio::config 'backup_keep_local') BACKUP_PASSWORD=$(bashio::config 'backup_password') -KEEP_LOCAL_BACKUP=$(bashio::config 'keep_local_backup') + +SSH_ENABLED=$(bashio::config "ssh_enabled") +SSH_REMOTE_DIRECTORY=$(bashio::config "ssh_remote_directory") RSYNC_ENABLED=$(bashio::config "rsync_enabled") -RSYNC_HOST=$(bashio::config "rsync_host") RSYNC_ROOTFOLDER=$(bashio::config "rsync_rootfolder") -RSYNC_USER=$(bashio::config "rsync_user") RSYNC_EXCLUDE=$(bashio::config "rsync_exclude") -RSYNC_PASSWORD=$(bashio::config "rsync_password") + RCLONE_ENABLED=$(bashio::config "rclone_enabled") +RCLONE_REMOTE_DIRECTORY=$(bashio::config "rclone_remote_directory") RCLONE_COPY=$(bashio::config "rclone_copy") RCLONE_SYNC=$(bashio::config "rclone_sync") RCLONE_RESTORE=$(bashio::config "rclone_restore") -RCLONE_REMOTE=$(bashio::config "rclone_remote") -RCLONE_REMOTE_DIRECTORY=$(bashio::config "rclone_remote_directory") -# create variables -SSH_ID="/ssl/${SSH_KEY}" -SSH_ID=$(echo -n "${SSH_ID}") function set-debug-level { # default log level according to bashio const.sh is INFO - if [ "${DEBUG}" = true ] ; then + if bashio::var.true "${DEBUG}"; then bashio::log.level "debug" fi } @@ -44,18 +41,18 @@ function add-ssh-key { if [ "${SSH_ENABLED}" = true ] || [ "${RSYNC_ENABLED}" = true ] ; then bashio::log.info "Adding SSH key" mkdir -p ~/.ssh - cp "${SSH_ID}" "${HOME}"/.ssh/id_rsa + cp "/ssl/${REMOTE_KEY}" "${HOME}"/.ssh/id_rsa chmod 600 "${HOME}/.ssh/id_rsa" ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub ( echo "Host remote" echo " IdentityFile ${HOME}/.ssh/id_rsa" - echo " HostName ${SSH_HOST}" - echo " User ${SSH_USER}" - echo " Port ${SSH_PORT}" + echo " HostName ${REMOTE_HOST}" + echo " User ${REMOTE_USER}" + echo " Port ${REMOTE_PORT}" echo " StrictHostKeyChecking no" - if [ -n "${SSH_HOST_KEY_ALGORITHMS}" ] ; then - echo " HostKeyAlgorithms ${SSH_HOST_KEY_ALGORITHMS}" + if [ -n "${REMOTE_HOST_KEY_ALGORITHMS}" ] ; then + echo " HostKeyAlgorithms ${REMOTE_HOST_KEY_ALGORITHMS}" fi ) > "${HOME}/.ssh/config" @@ -71,11 +68,11 @@ function create-local-backup { ADDONS="" BASE_FOLDERS="addons/local homeassistant media share ssl" INSTALLED_ADDONS=$(bashio::supervisor.addons) - name="${CUSTOM_PREFIX} $(date +'%Y-%m-%d %H-%M')" + name="${BACKUP_CUSTOM_PREFIX} $(date +'%Y-%m-%d %H-%M')" bashio::log.info "Creating local backup: \"${name}\"" - if [ -n "${EXCLUDE_ADDONS}" ] || [ -n "${EXCLUDE_FOLDERS}" ] ; then - EXCLUDED_FOLDERS=$(echo "${EXCLUDE_FOLDERS}") - EXCLUDED_ADDONS=$(echo "${EXCLUDE_ADDONS}") + if [ -n "${BACKUP_EXCLUDE_ADDONS}" ] || [ -n "${BACKUP_EXCLUDE_FOLDERS}" ] ; then + EXCLUDED_FOLDERS=$(echo "${BACKUP_EXCLUDE_FOLDERS}") + EXCLUDED_ADDONS=$(echo "${BACKUP_EXCLUDE_ADDONS}") UNFORMATTED_FOLDERS="${BASE_FOLDERS}" UNFORMATTED_ADDONS="${INSTALLED_ADDONS}" if [ -n "${EXCLUDED_FOLDERS}" ] ; then @@ -112,14 +109,14 @@ function copy-backup-to-remote { if [ "${SSH_ENABLED}" = true ] ; then cd /backup/ || exit - bashio::log.info "Copying ${slug}.tar to ${REMOTE_DIRECTORY} on ${SSH_HOST} using SCP" - scp -F "${HOME}/.ssh/config" "${slug}.tar" remote:"${REMOTE_DIRECTORY}" - bashio::log.info "Backup copied to ${REMOTE_DIRECTORY}/${slug}.tar on ${SSH_HOST}" - - if [ "${FRIENDLY_NAME}" = true ] ; then - bashio::log.notice "Renaming ${slug}.tar to ${name}.tar" - ssh remote "mv \"${REMOTE_DIRECTORY}/${slug}.tar\" \"${REMOTE_DIRECTORY}/${name}.tar\"" - bashio::log.info "Backup renamed to ${REMOTE_DIRECTORY}/${name}.tar on ${SSH_HOST}" + bashio::log.info "Copying ${slug}.tar to ${SSH_REMOTE_DIRECTORY} on ${REMOTE_HOST} using SCP" + scp -F "${HOME}/.ssh/config" "${slug}.tar" remote:"${SSH_REMOTE_DIRECTORY}" + bashio::log.info "Backup copied to ${SSH_REMOTE_DIRECTORY}/${slug}.tar on ${REMOTE_HOST}" + + if [ "${BACKUP_FRIENDLY_NAME}" = true ] ; then + bashio::log.notice "Renaming ${slug}.tar to ${name}.tar" + ssh remote "mv \"${SSH_REMOTE_DIRECTORY}/${slug}.tar\" \"${SSH_REMOTE_DIRECTORY}/${name}.tar\"" + bashio::log.info "Backup renamed to ${SSH_REMOTE_DIRECTORY}/${name}.tar on ${REMOTE_HOST}" fi bashio::log.info "SCP complete" fi @@ -131,8 +128,8 @@ function rsync_folders { return fi - local FOLDERS="/config /addons /backup /share /ssl" - local RSYNC_URL="${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOTFOLDER}" + local FOLDERS="/config /addons /backup /share /ssl" # put directories without trailing slash + local RSYNC_URL="${REMOTE_USER}@${REMOTE_HOST}:${RSYNC_ROOTFOLDER}" bashio::log.info "Starting rsync" if bashio::var.true "${DEBUG}"; then @@ -149,8 +146,8 @@ function rsync_folders { fi bashio::log.debug "Syncing ${FOLDERS}" - sshpass -p "${RSYNC_PASSWORD}" \ - rsync ${FLAGS} --exclude-from='/tmp/rsync_exclude.txt' ${FOLDERS} "${RSYNC_URL}/" --delete \ + sshpass -p "${REMOTE_PASSWORD}" \ + rsync ${FLAGS} --port ${REMOTE_PORT} --exclude-from='/tmp/rsync_exclude.txt' ${FOLDERS} "${RSYNC_URL}/" --delete \ || bashio::log.fatal "Error syncing folder(s) ${FOLDERS}" bashio::log.info "Finished rsync" @@ -163,19 +160,19 @@ function rclone_backups { cp -a /ssl/rclone.conf ~/.config/rclone/rclone.conf bashio::log.info "Starting rclone" if [ "$RCLONE_COPY" = true ] ; then - if [ "$FRIENDLY_NAME" = true ] ; then + if [ "$BACKUP_FRIENDLY_NAME" = true ] ; then bashio::log.debug "Copying ${slug}.tar to ${RCLONE_REMOTE_DIRECTORY}/${name}.tar" - rclone copyto "${slug}.tar" "${RCLONE_REMOTE}:${RCLONE_REMOTE_DIRECTORY}/${name}".tar + rclone copyto "${slug}.tar" "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}/${name}".tar bashio::log.debug "Finished rclone copy" else bashio::log.debug "Copying ${slug}.tar to ${RCLONE_REMOTE_DIRECTORY}/${slug}.tar" - rclone copy "${slug}.tar" "${RCLONE_REMOTE}:${RCLONE_REMOTE_DIRECTORY}" + rclone copy "${slug}.tar" "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}" bashio::log.debug "Finished rclone copy" fi fi if [ "${RCLONE_SYNC}" = true ] ; then bashio::log.info "Syncing Backups" - rclone sync . "${RCLONE_REMOTE}:${RCLONE_REMOTE_DIRECTORY}" + rclone sync . "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}" bashio::log.info "Finished rclone sync" fi if [ "${RCLONE_RESTORE}" = true ] ; then @@ -183,7 +180,7 @@ function rclone_backups { RESTORENAME="restore-${DATEFORMAT}" mkdir -p "${RESTORENAME}" bashio::log.info "Restoring Backups to ${RESTORENAME}" - rclone copyto "${RCLONE_REMOTE}:${RCLONE_REMOTE_DIRECTORY} ${RESTORENAME}/" + rclone copyto "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY} ${RESTORENAME}/" bashio::log.info "Finished rclone restore" fi fi @@ -194,15 +191,15 @@ function delete-local-backup { ha backups reload - if [[ "${KEEP_LOCAL_BACKUP}" == "all" ]]; then + if [[ "${BACKUP_KEEP_LOCAL}" == "all" ]]; then : - elif [[ -z "${KEEP_LOCAL_BACKUP}" ]]; then + elif [[ -z "${BACKUP_KEEP_LOCAL}" ]]; then bashio::log.warning "Deleting local backup: ${slug}" ha backups remove "${slug}" else last_date_to_keep=$(ha backups list --raw-json | jq .data.backups[].date | sort -r | \ - head -n "${KEEP_LOCAL_BACKUP}" | tail -n 1 | xargs date -D "%Y-%m-%dT%T" +%s --date ) + head -n "${BACKUP_KEEP_LOCAL}" | tail -n 1 | xargs date -D "%Y-%m-%dT%T" +%s --date ) ha backups list --raw-json | jq -c .data.backups[] | while read -r backup; do if [[ $(echo "${backup}" | jq .date | xargs date -D "%Y-%m-%dT%T" +%s --date ) -lt ${last_date_to_keep} ]]; then @@ -224,4 +221,4 @@ rclone_backups delete-local-backup bashio::log.info "Backup process done!" -exit 0 +bashio::exit.ok diff --git a/remote-backup/translations/en.yaml b/remote-backup/translations/en.yaml index cb503ad..1e5006b 100644 --- a/remote-backup/translations/en.yaml +++ b/remote-backup/translations/en.yaml @@ -1,25 +1,54 @@ configuration: debug: name: Enable debugging - ssh_enabled: - name: Enable SSH - description: Copies Home Assistant backups to the remote server - friendly_name: + remote_host: + name: Remote host + description: The hostname or IP address of the remote server + remote_port: + name: Remote port + description: Port number of the remote server + remote_user: + name: Username + description: Username to be used for authentication with remote server + remote_password: + name: Password + description: Password to be used for authentication with remote server + remote_key: + name: SSH private key + description: SSH private key file to be used for authentication with remote server. This be located in the directory 'ssl' of Home Assistant. + remote_host_key_algorithms: + name: Host key algorithms + description: Can be used to enable further (legacy) algorithms for authentication + backup_friendly_name: name: Friendly name description: Rename the backup on the destination server to match the name in the Home Assistant UI - custom_prefix: + backup_custom_prefix: name: Custom backup name prefix - ssh_key: - name: SSH key - description: The filename of the SSH key, which must be located in the directory 'ssl' of Home Assistant. - exclude_folders: + backup_exclude_folders: name: Folder to exclude from backup - exclude_addons: + backup_exclude_addons: name: Addon to exclude from backup description: Give the addons slug which equals the addon hostname using '_' instead of '-', e.g. core-mariadb + backup_keep_local: + name: Local backups to keep + description: default is 'all', give a number for the last x backups to keep or empty to immediately remove created backups after copying. + backup_password: + name: Password for protected backup + ssh_enabled: + name: Enable SSH + description: Copies Home Assistant backups to the remote server + ssh_remote_directory: + name: SSH remote directory + description: Remote directory the backups are copied to rsync_enabled: name: Enable rsync description: Clones local folders to remote server (including backups) + rsync_rootfolder: + name: rsync root folder + description: Root folder to which Home Assistant directories are synchronized rsync_exclude: - name: Path patterns to exclude from sync + name: rsync path patterns to exclude from sync description: This feature uses the rsync --exclude scheme + rclone_enabled: + name: Enable rclone + description: Experimental! From b32491dafe4a0b81cbae31195580e63f9c36aecf Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Thu, 18 Aug 2022 10:57:34 +0200 Subject: [PATCH 02/40] moved host key retrieval to add-ssh-key function --- remote-backup/run.sh | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/remote-backup/run.sh b/remote-backup/run.sh index 025f2c0..85d1e89 100755 --- a/remote-backup/run.sh +++ b/remote-backup/run.sh @@ -43,6 +43,9 @@ function add-ssh-key { mkdir -p ~/.ssh cp "/ssl/${REMOTE_KEY}" "${HOME}"/.ssh/id_rsa chmod 600 "${HOME}/.ssh/id_rsa" + bashio::log.debug "Adding key of remote host ${REMOTE_HOST} to known hosts." + ssh-keyscan -t rsa ${REMOTE_HOST} >> ${HOME}/.ssh/known_hosts \ + || bashio::log.error "Failed to add ${REMOTE_HOST} host key" ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub ( echo "Host remote" @@ -137,9 +140,7 @@ function rsync_folders { else local FLAGS='-a' fi - bashio::log.debug "Adding key of remote host ${RSYNC_HOST} to known hosts." - ssh-keyscan -t rsa ${RSYNC_HOST} >> ~/.ssh/known_hosts \ - || bashio::log.error "Failed to add ${RSYNC_HOST} host key" + echo "${RSYNC_EXCLUDE}" > /tmp/rsync_exclude.txt if bashio::var.has_value "${RSYNC_EXCLUDE}"; then bashio::log.warning "File patterns that have been excluded:\n${RSYNC_EXCLUDE}" @@ -161,13 +162,13 @@ function rclone_backups { bashio::log.info "Starting rclone" if [ "$RCLONE_COPY" = true ] ; then if [ "$BACKUP_FRIENDLY_NAME" = true ] ; then - bashio::log.debug "Copying ${slug}.tar to ${RCLONE_REMOTE_DIRECTORY}/${name}.tar" - rclone copyto "${slug}.tar" "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}/${name}".tar - bashio::log.debug "Finished rclone copy" + bashio::log.debug "Copying ${slug}.tar to ${RCLONE_REMOTE_DIRECTORY}/${name}.tar" + rclone copyto "${slug}.tar" "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}/${name}".tar + bashio::log.debug "Finished rclone copy" else - bashio::log.debug "Copying ${slug}.tar to ${RCLONE_REMOTE_DIRECTORY}/${slug}.tar" - rclone copy "${slug}.tar" "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}" - bashio::log.debug "Finished rclone copy" + bashio::log.debug "Copying ${slug}.tar to ${RCLONE_REMOTE_DIRECTORY}/${slug}.tar" + rclone copy "${slug}.tar" "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}" + bashio::log.debug "Finished rclone copy" fi fi if [ "${RCLONE_SYNC}" = true ] ; then From ea9a6c59c3906ee5dc200c04f5b5e16fb1d990c5 Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Thu, 18 Aug 2022 10:58:15 +0200 Subject: [PATCH 03/40] added trailing directory slash to avoid file renaming --- remote-backup/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/remote-backup/run.sh b/remote-backup/run.sh index 85d1e89..1d6ff22 100755 --- a/remote-backup/run.sh +++ b/remote-backup/run.sh @@ -113,7 +113,7 @@ function copy-backup-to-remote { if [ "${SSH_ENABLED}" = true ] ; then cd /backup/ || exit bashio::log.info "Copying ${slug}.tar to ${SSH_REMOTE_DIRECTORY} on ${REMOTE_HOST} using SCP" - scp -F "${HOME}/.ssh/config" "${slug}.tar" remote:"${SSH_REMOTE_DIRECTORY}" + scp -F "${HOME}/.ssh/config" "${slug}.tar" remote:"${SSH_REMOTE_DIRECTORY}/" bashio::log.info "Backup copied to ${SSH_REMOTE_DIRECTORY}/${slug}.tar on ${REMOTE_HOST}" if [ "${BACKUP_FRIENDLY_NAME}" = true ] ; then From 71f2e1047acd3934af7d002b1b8c8fe39ef39f37 Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Thu, 18 Aug 2022 14:17:38 +0200 Subject: [PATCH 04/40] config description enhancements --- remote-backup/translations/en.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/remote-backup/translations/en.yaml b/remote-backup/translations/en.yaml index 1e5006b..9649712 100644 --- a/remote-backup/translations/en.yaml +++ b/remote-backup/translations/en.yaml @@ -28,7 +28,7 @@ configuration: name: Folder to exclude from backup backup_exclude_addons: name: Addon to exclude from backup - description: Give the addons slug which equals the addon hostname using '_' instead of '-', e.g. core-mariadb + description: Give the addons slug which equals the addon hostname using '_' instead of '-', e.g. core_mariadb backup_keep_local: name: Local backups to keep description: default is 'all', give a number for the last x backups to keep or empty to immediately remove created backups after copying. @@ -39,7 +39,7 @@ configuration: description: Copies Home Assistant backups to the remote server ssh_remote_directory: name: SSH remote directory - description: Remote directory the backups are copied to + description: Remote directory the backups are copied to (path must exist) rsync_enabled: name: Enable rsync description: Clones local folders to remote server (including backups) From 27a201e8890250cab06337e581754b0d12cf167f Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Thu, 18 Aug 2022 14:17:38 +0200 Subject: [PATCH 05/40] config description enhancements --- remote-backup/translations/en.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/remote-backup/translations/en.yaml b/remote-backup/translations/en.yaml index 1e5006b..acaa84c 100644 --- a/remote-backup/translations/en.yaml +++ b/remote-backup/translations/en.yaml @@ -25,10 +25,10 @@ configuration: backup_custom_prefix: name: Custom backup name prefix backup_exclude_folders: - name: Folder to exclude from backup + name: Folder to exclude from backup (valid options are addons/local, homeassistant, media, share, ssl)" backup_exclude_addons: name: Addon to exclude from backup - description: Give the addons slug which equals the addon hostname using '_' instead of '-', e.g. core-mariadb + description: Give the addons slug which equals the addon hostname using '_' instead of '-', e.g. core_mariadb backup_keep_local: name: Local backups to keep description: default is 'all', give a number for the last x backups to keep or empty to immediately remove created backups after copying. @@ -39,7 +39,7 @@ configuration: description: Copies Home Assistant backups to the remote server ssh_remote_directory: name: SSH remote directory - description: Remote directory the backups are copied to + description: Remote directory the backups are copied to (path must exist) rsync_enabled: name: Enable rsync description: Clones local folders to remote server (including backups) From 48eb5d25f29919d5f1807c1df4a48956a79170e1 Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Thu, 18 Aug 2022 15:05:08 +0200 Subject: [PATCH 06/40] cleanup backup creation --- remote-backup/run.sh | 102 +++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/remote-backup/run.sh b/remote-backup/run.sh index 1d6ff22..d0a1362 100755 --- a/remote-backup/run.sh +++ b/remote-backup/run.sh @@ -29,7 +29,6 @@ RCLONE_COPY=$(bashio::config "rclone_copy") RCLONE_SYNC=$(bashio::config "rclone_sync") RCLONE_RESTORE=$(bashio::config "rclone_restore") - function set-debug-level { # default log level according to bashio const.sh is INFO if bashio::var.true "${DEBUG}"; then @@ -67,58 +66,65 @@ function add-ssh-key { function create-local-backup { # Bind variables - FOLDERS="" - ADDONS="" - BASE_FOLDERS="addons/local homeassistant media share ssl" - INSTALLED_ADDONS=$(bashio::supervisor.addons) - name="${BACKUP_CUSTOM_PREFIX} $(date +'%Y-%m-%d %H-%M')" - bashio::log.info "Creating local backup: \"${name}\"" - if [ -n "${BACKUP_EXCLUDE_ADDONS}" ] || [ -n "${BACKUP_EXCLUDE_FOLDERS}" ] ; then - EXCLUDED_FOLDERS=$(echo "${BACKUP_EXCLUDE_FOLDERS}") - EXCLUDED_ADDONS=$(echo "${BACKUP_EXCLUDE_ADDONS}") - UNFORMATTED_FOLDERS="${BASE_FOLDERS}" - UNFORMATTED_ADDONS="${INSTALLED_ADDONS}" - if [ -n "${EXCLUDED_FOLDERS}" ] ; then - bashio::log.warning "Excluded folders:\n ${EXCLUDED_FOLDERS}" - for folder in ${EXCLUDED_FOLDERS} ; do - UNFORMATTED_FOLDERS=$(echo "${UNFORMATTED_FOLDERS}" | sed -e "s/${folder}//g") - done - fi - if [ -n "${EXCLUDED_ADDONS}" ] ; then - bashio::log.warning "Excluded addons:\n${EXCLUDED_ADDONS}" - for addon in ${EXCLUDED_ADDONS} ; do - UNFORMATTED_ADDONS="$(echo "${UNFORMATTED_ADDONS}" | sed -e "s/${addon}//g")" - done - fi - if [ -n "${UNFORMATTED_ADDONS}" ] && [ -n "${UNFORMATTED_FOLDERS}" ] ; then - for addon in ${UNFORMATTED_ADDONS} ; do - ADDONS="${ADDONS}--addons ${addon} " - done - for folder in ${UNFORMATTED_FOLDERS} ; do - FOLDERS="${FOLDERS}--folders ${folder} " - done + local base_folders="addons/local homeassistant media share ssl" + local installed_addons=$(bashio::supervisor.addons) + local name="${BACKUP_CUSTOM_PREFIX} $(date +'%Y-%m-%d %H-%M')" + local data="{\"name\":\"${name}\" \"password\": \"${BACKUP_PASSWORD}\"}" + + if bashio::var.has_value "${BACKUP_EXCLUDE_ADDONS}" ] || bashio::var.has_value "${BACKUP_EXCLUDE_FOLDERS}"; then + bashio::log.info "Creating partial backup: \"${name}\"" + + local excluded_folders=$(echo "${BACKUP_EXCLUDE_FOLDERS}") + local excluded_addons=$(echo "${BACKUP_EXCLUDE_ADDONS}") + local unformatted_folders="${base_folders}" + local unformatted_addons="${installed_addons}" + + if bashio::var.has_value "${excluded_folders}"; then + bashio::log.warning "Excluded folder(s):\n ${excluded_folders}" + for folder in ${excluded_folders} ; do + unformatted_folders=$(echo "${unformatted_folders}" | sed -e "s/${folder}//g") + done + fi + if bashio::var.has_value "${excluded_addons}"; then + bashio::log.warning "Excluded addon(s):\n${excluded_addons}" + for addon in ${excluded_addons} ; do + unformatted_addons="$(echo "${unformatted_addons}" | sed -e "s/${addon}//g")" + done + fi + + local addons=$(echo ${unformatted_addons} | sed "s/ /\", \"/g") + local folders=$(echo "${unformatted_folders}" | sed "s/ /\", \"/g" | sed "s/, \"\"//g") + bashio::log.debug "Including folder(s) \"${folders}\"" + bashio::log.debug "Including addon(s) \"${addons}\"" + + local data="$(echo $data | tr -d '}'), \"addons\": [\"${addons}\"], \"folders\": [\"${folders}\"]}" # append addon and folder set + if ! SLUG=$(bashio::api.supervisor "POST" "/backups/new/partial" "${data}" ".slug"); then + bashio::exit.nok "Error creating partial backup!" fi - bashio::log.info "Creating partial backup" - bashio::log.debug "Including ${FOLDERS} and ${ADDONS}" - slug=$(ha backups new --raw-json --name="${name}" ${ADDONS} ${FOLDERS} --password="${BACKUP_PASSWORD}" | jq --raw-output '.data.slug') else - bashio::log.info "Creating full backup" - slug=$(ha backups new --raw-json --name="${name}" --password="${BACKUP_PASSWORD}" | jq --raw-output '.data.slug') + bashio::log.info "Creating full backup: \"${name}\"" + + if ! SLUG=$(bashio::api.supervisor "POST" "/backups/new/full" "${data}" ".slug"); then + bashio::exit.nok "Error creating full backup!" + fi + fi - bashio::log.info "Backup created: ${slug}" + + bashio::log.info "Backup created: ${SLUG}" + return "${__BASHIO_EXIT_OK}" } function copy-backup-to-remote { if [ "${SSH_ENABLED}" = true ] ; then cd /backup/ || exit - bashio::log.info "Copying ${slug}.tar to ${SSH_REMOTE_DIRECTORY} on ${REMOTE_HOST} using SCP" - scp -F "${HOME}/.ssh/config" "${slug}.tar" remote:"${SSH_REMOTE_DIRECTORY}/" - bashio::log.info "Backup copied to ${SSH_REMOTE_DIRECTORY}/${slug}.tar on ${REMOTE_HOST}" + bashio::log.info "Copying ${SLUG}.tar to ${SSH_REMOTE_DIRECTORY} on ${REMOTE_HOST} using SCP" + scp -F "${HOME}/.ssh/config" "${SLUG}.tar" remote:"${SSH_REMOTE_DIRECTORY}/" + bashio::log.info "Backup copied to ${SSH_REMOTE_DIRECTORY}/${SLUG}.tar on ${REMOTE_HOST}" if [ "${BACKUP_FRIENDLY_NAME}" = true ] ; then - bashio::log.notice "Renaming ${slug}.tar to ${name}.tar" - ssh remote "mv \"${SSH_REMOTE_DIRECTORY}/${slug}.tar\" \"${SSH_REMOTE_DIRECTORY}/${name}.tar\"" + bashio::log.notice "Renaming ${SLUG}.tar to ${name}.tar" + ssh remote "mv \"${SSH_REMOTE_DIRECTORY}/${SLUG}.tar\" \"${SSH_REMOTE_DIRECTORY}/${name}.tar\"" bashio::log.info "Backup renamed to ${SSH_REMOTE_DIRECTORY}/${name}.tar on ${REMOTE_HOST}" fi bashio::log.info "SCP complete" @@ -162,12 +168,12 @@ function rclone_backups { bashio::log.info "Starting rclone" if [ "$RCLONE_COPY" = true ] ; then if [ "$BACKUP_FRIENDLY_NAME" = true ] ; then - bashio::log.debug "Copying ${slug}.tar to ${RCLONE_REMOTE_DIRECTORY}/${name}.tar" - rclone copyto "${slug}.tar" "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}/${name}".tar + bashio::log.debug "Copying ${SLUG}.tar to ${RCLONE_REMOTE_DIRECTORY}/${name}.tar" + rclone copyto "${SLUG}.tar" "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}/${name}".tar bashio::log.debug "Finished rclone copy" else - bashio::log.debug "Copying ${slug}.tar to ${RCLONE_REMOTE_DIRECTORY}/${slug}.tar" - rclone copy "${slug}.tar" "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}" + bashio::log.debug "Copying ${SLUG}.tar to ${RCLONE_REMOTE_DIRECTORY}/${SLUG}.tar" + rclone copy "${SLUG}.tar" "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}" bashio::log.debug "Finished rclone copy" fi fi @@ -195,8 +201,8 @@ function delete-local-backup { if [[ "${BACKUP_KEEP_LOCAL}" == "all" ]]; then : elif [[ -z "${BACKUP_KEEP_LOCAL}" ]]; then - bashio::log.warning "Deleting local backup: ${slug}" - ha backups remove "${slug}" + bashio::log.warning "Deleting local backup: ${SLUG}" + ha backups remove "${SLUG}" else last_date_to_keep=$(ha backups list --raw-json | jq .data.backups[].date | sort -r | \ From 0ddcda973b81fbc5c9d6736bb9ac3dd3bf91d7a1 Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Thu, 18 Aug 2022 18:16:42 +0200 Subject: [PATCH 07/40] enhanced error handling for SCP and rsync - removed rw permission for backup folder (unnecessary) - added permission for core API - added function to die with persistent notification --- remote-backup/config.yaml | 3 +- remote-backup/run.sh | 83 ++++++++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 32 deletions(-) diff --git a/remote-backup/config.yaml b/remote-backup/config.yaml index a71a922..a68b008 100644 --- a/remote-backup/config.yaml +++ b/remote-backup/config.yaml @@ -17,12 +17,13 @@ arch: homeassistant: "2021.9.0" hassio_api: true hassio_role: manager +homeassistant_api: true map: - config - addons - share - ssl - - backup:rw + - backup options: debug: false diff --git a/remote-backup/run.sh b/remote-backup/run.sh index d0a1362..27302e5 100755 --- a/remote-backup/run.sh +++ b/remote-backup/run.sh @@ -29,6 +29,8 @@ RCLONE_COPY=$(bashio::config "rclone_copy") RCLONE_SYNC=$(bashio::config "rclone_sync") RCLONE_RESTORE=$(bashio::config "rclone_restore") +BACKUP_NAME="${BACKUP_CUSTOM_PREFIX} $(date +'%Y-%m-%d %H-%M')" + function set-debug-level { # default log level according to bashio const.sh is INFO if bashio::var.true "${DEBUG}"; then @@ -36,6 +38,14 @@ function set-debug-level { fi } +function die { + local message=${1:-'no message'} + local title=${2:-'Addon: Remote Backup'} + + bashio::api.supervisor POST /core/api/services/persistent_notification/create "{\"message\":\"${message}\", \"title\":\"${title}\"}" + bashio::exit.nok "${message}" +} + function add-ssh-key { if [ "${SSH_ENABLED}" = true ] || [ "${RSYNC_ENABLED}" = true ] ; then bashio::log.info "Adding SSH key" @@ -60,7 +70,6 @@ function add-ssh-key { chmod 600 "${HOME}/.ssh/config" chmod 644 "${HOME}/.ssh/id_rsa.pub" - bashio::log.info "SSH key added" fi } @@ -68,11 +77,10 @@ function create-local-backup { # Bind variables local base_folders="addons/local homeassistant media share ssl" local installed_addons=$(bashio::supervisor.addons) - local name="${BACKUP_CUSTOM_PREFIX} $(date +'%Y-%m-%d %H-%M')" - local data="{\"name\":\"${name}\" \"password\": \"${BACKUP_PASSWORD}\"}" + local data="{\"name\":\"${BACKUP_NAME}\", \"password\": \"${BACKUP_PASSWORD}\"}" - if bashio::var.has_value "${BACKUP_EXCLUDE_ADDONS}" ] || bashio::var.has_value "${BACKUP_EXCLUDE_FOLDERS}"; then - bashio::log.info "Creating partial backup: \"${name}\"" + if bashio::var.has_value "${BACKUP_EXCLUDE_ADDONS}" || bashio::var.has_value "${BACKUP_EXCLUDE_FOLDERS}"; then + bashio::log.info "Creating partial backup: \"${BACKUP_NAME}\"" local excluded_folders=$(echo "${BACKUP_EXCLUDE_FOLDERS}") local excluded_addons=$(echo "${BACKUP_EXCLUDE_ADDONS}") @@ -98,14 +106,14 @@ function create-local-backup { bashio::log.debug "Including addon(s) \"${addons}\"" local data="$(echo $data | tr -d '}'), \"addons\": [\"${addons}\"], \"folders\": [\"${folders}\"]}" # append addon and folder set - if ! SLUG=$(bashio::api.supervisor "POST" "/backups/new/partial" "${data}" ".slug"); then - bashio::exit.nok "Error creating partial backup!" + if ! SLUG=$(bashio::api.supervisor POST /backups/new/partial "${data}" .slug); then + die "Error creating partial backup!" fi else - bashio::log.info "Creating full backup: \"${name}\"" + bashio::log.info "Creating full backup: \"${BACKUP_NAME}\"" - if ! SLUG=$(bashio::api.supervisor "POST" "/backups/new/full" "${data}" ".slug"); then - bashio::exit.nok "Error creating full backup!" + if ! SLUG=$(bashio::api.supervisor POST /backups/new/full "${data}" .slug); then + die "Error creating full backup!" fi fi @@ -115,26 +123,34 @@ function create-local-backup { } function copy-backup-to-remote { + if bashio::var.false "${SSH_ENABLED}"; then + bashio::log.debug "SCP disabled." + return "${__BASHIO_EXIT_OK}" + fi - if [ "${SSH_ENABLED}" = true ] ; then - cd /backup/ || exit - bashio::log.info "Copying ${SLUG}.tar to ${SSH_REMOTE_DIRECTORY} on ${REMOTE_HOST} using SCP" - scp -F "${HOME}/.ssh/config" "${SLUG}.tar" remote:"${SSH_REMOTE_DIRECTORY}/" - bashio::log.info "Backup copied to ${SSH_REMOTE_DIRECTORY}/${SLUG}.tar on ${REMOTE_HOST}" - - if [ "${BACKUP_FRIENDLY_NAME}" = true ] ; then - bashio::log.notice "Renaming ${SLUG}.tar to ${name}.tar" - ssh remote "mv \"${SSH_REMOTE_DIRECTORY}/${SLUG}.tar\" \"${SSH_REMOTE_DIRECTORY}/${name}.tar\"" - bashio::log.info "Backup renamed to ${SSH_REMOTE_DIRECTORY}/${name}.tar on ${REMOTE_HOST}" + cd /backup/ \ + || bashio::log.fatal "Error accessing directory /backup" \ + && return "${__BASHIO_EXIT_NOK}" + bashio::log.info "Copying backup using SCP" + if ! scp -F "${HOME}/.ssh/config" "/backup/${SLUG}.tar" remote:"${SSH_REMOTE_DIRECTORY}/"; then + bashio::log.error "Error copying backup ${SLUG}.tar to ${SSH_REMOTE_DIRECTORY} on ${REMOTE_HOST}" + return "${__BASHIO_EXIT_NOK}" + fi + + if bashio::var.true "${BACKUP_FRIENDLY_NAME}"; then + bashio::log.info "Renaming ${SLUG}.tar to ${BACKUP_NAME}.tar" + if ! ssh remote "mv \"${SSH_REMOTE_DIRECTORY}/${SLUG}.tar\" \"${SSH_REMOTE_DIRECTORY}/${BACKUP_NAME}.tar\""; then + bashio::log.error "Error renaming backup to ${SSH_REMOTE_DIRECTORY}/${BACKUP_NAME}.tar on ${REMOTE_HOST}" + return "${__BASHIO_EXIT_NOK}" fi - bashio::log.info "SCP complete" fi + return "${__BASHIO_EXIT_OK}" } function rsync_folders { if bashio::var.false "${RSYNC_ENABLED}"; then bashio::log.debug "rsync disabled." - return + return "${__BASHIO_EXIT_OK}" fi local FOLDERS="/config /addons /backup /share /ssl" # put directories without trailing slash @@ -153,11 +169,12 @@ function rsync_folders { fi bashio::log.debug "Syncing ${FOLDERS}" - sshpass -p "${REMOTE_PASSWORD}" \ - rsync ${FLAGS} --port ${REMOTE_PORT} --exclude-from='/tmp/rsync_exclude.txt' ${FOLDERS} "${RSYNC_URL}/" --delete \ - || bashio::log.fatal "Error syncing folder(s) ${FOLDERS}" + if ! sshpass -p "${REMOTE_PASSWORD}" rsync ${FLAGS} --port ${REMOTE_PORT} --exclude-from='/tmp/rsync_exclude.txt' ${FOLDERS} "${RSYNC_URL}/" --delete; then + bashio::log.error "Error syncing folder(s) ${FOLDERS}" + return "${__BASHIO_EXIT_NOK}" + fi - bashio::log.info "Finished rsync" + return "${__BASHIO_EXIT_OK}" } function rclone_backups { @@ -168,8 +185,8 @@ function rclone_backups { bashio::log.info "Starting rclone" if [ "$RCLONE_COPY" = true ] ; then if [ "$BACKUP_FRIENDLY_NAME" = true ] ; then - bashio::log.debug "Copying ${SLUG}.tar to ${RCLONE_REMOTE_DIRECTORY}/${name}.tar" - rclone copyto "${SLUG}.tar" "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}/${name}".tar + bashio::log.debug "Copying ${SLUG}.tar to ${RCLONE_REMOTE_DIRECTORY}/${BACKUP_NAME}.tar" + rclone copyto "${SLUG}.tar" "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}/${BACKUP_NAME}.tar" bashio::log.debug "Finished rclone copy" else bashio::log.debug "Copying ${SLUG}.tar to ${RCLONE_REMOTE_DIRECTORY}/${SLUG}.tar" @@ -193,7 +210,6 @@ function rclone_backups { fi } - function delete-local-backup { ha backups reload @@ -219,12 +235,17 @@ function delete-local-backup { fi } +# general setup and backup set-debug-level add-ssh-key create-local-backup -copy-backup-to-remote -rsync_folders + +# different remote cloning modes +copy-backup-to-remote || bashio::log.fatal "SCP failed." +rsync_folders || bashio::log.fatal "rsync failed." rclone_backups + +# cleanup delete-local-backup bashio::log.info "Backup process done!" From 27504c146cb3cebf94a64a0ac10c94d07145e559 Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Fri, 19 Aug 2022 13:01:10 +0200 Subject: [PATCH 08/40] adaoted folder exclude description --- remote-backup/translations/en.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/remote-backup/translations/en.yaml b/remote-backup/translations/en.yaml index acaa84c..10512c8 100644 --- a/remote-backup/translations/en.yaml +++ b/remote-backup/translations/en.yaml @@ -25,7 +25,8 @@ configuration: backup_custom_prefix: name: Custom backup name prefix backup_exclude_folders: - name: Folder to exclude from backup (valid options are addons/local, homeassistant, media, share, ssl)" + name: Folder to exclude from backup + description: valid options are addons/local, homeassistant, media, share, ssl backup_exclude_addons: name: Addon to exclude from backup description: Give the addons slug which equals the addon hostname using '_' instead of '-', e.g. core_mariadb From a3e8f3ea2d61412f3d4a9443593b4a8f02a01570 Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Sat, 20 Aug 2022 11:02:22 +0200 Subject: [PATCH 09/40] set empty config values to null Empty string is a valid imput, thus mandatory parameters are accepted as if they were filled out. --- remote-backup/config.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/remote-backup/config.yaml b/remote-backup/config.yaml index a68b008..cdcd76f 100644 --- a/remote-backup/config.yaml +++ b/remote-backup/config.yaml @@ -24,23 +24,23 @@ map: - share - ssl - backup - + options: debug: false - remote_host: "" + remote_host: null remote_port: 22 - remote_user: "" - remote_password: "" - remote_key: "" - remote_host_key_algorithms: "" + remote_user: null + remote_password: null + remote_key: null + remote_host_key_algorithms: null backup_friendly_name: true backup_custom_prefix: Automated backup backup_exclude_folders: [] backup_exclude_addons: [] backup_keep_local: "all" - backup_password: "" + backup_password: null ssh_enabled: true - ssh_remote_directory: "" + ssh_remote_directory: null rsync_enabled: false rsync_rootfolder: hassio-sync rsync_exclude: @@ -48,7 +48,7 @@ options: - "/config/*.db-wal" - "/config/*.db" rclone_enabled: false - rclone_remote_directory: "" + rclone_remote_directory: null rclone_copy: false rclone_sync: false rclone_restore: false @@ -67,7 +67,7 @@ schema: - match(^[A-Za-z0-9_\-\.\*\/\?\+\\]*$)? backup_exclude_addons: - str? - backup_keep_local: match(^(all|[+]?\d*)$) + backup_keep_local: match(^(all|[+]?\d*)$)? backup_password: str? ssh_enabled: bool ssh_remote_directory: str? From 86d63486161f5bd80138e32408fdbece31eecf24 Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Sat, 20 Aug 2022 11:21:05 +0200 Subject: [PATCH 10/40] detailed error handling - fixed die function (remaining issue in comment) - error handling of add-ssh-key function - added clone-to-remote function to try multiple methods in sequence - changed delete-local-backup to supervisor API and added error handling --- remote-backup/run.sh | 154 ++++++++++++++++++++++++++----------------- 1 file changed, 92 insertions(+), 62 deletions(-) diff --git a/remote-backup/run.sh b/remote-backup/run.sh index 27302e5..e0af5c4 100755 --- a/remote-backup/run.sh +++ b/remote-backup/run.sh @@ -40,22 +40,27 @@ function set-debug-level { function die { local message=${1:-'no message'} - local title=${2:-'Addon: Remote Backup'} + local title=${2:-'Addon: Remote Backup Failed!'} - bashio::api.supervisor POST /core/api/services/persistent_notification/create "{\"message\":\"${message}\", \"title\":\"${title}\"}" + # catch return code which is always false, see https://github.com/hassio-addons/bashio/issues/31 + local ret=$(bashio::api.supervisor POST /core/api/services/persistent_notification/create "{\"message\":\"${message}\", \"title\":\"${title}\"}") bashio::exit.nok "${message}" } +# prepare SSH environment/configuration +# function does never fail to continue with further commands function add-ssh-key { - if [ "${SSH_ENABLED}" = true ] || [ "${RSYNC_ENABLED}" = true ] ; then - bashio::log.info "Adding SSH key" - mkdir -p ~/.ssh - cp "/ssl/${REMOTE_KEY}" "${HOME}"/.ssh/id_rsa - chmod 600 "${HOME}/.ssh/id_rsa" - bashio::log.debug "Adding key of remote host ${REMOTE_HOST} to known hosts." + if bashio::var.true "${SSH_ENABLED}" || bashio::var.true "${RSYNC_ENABLED}"; then + bashio::log.info "Adding SSH key." + ( + mkdir -p ${HOME}/.ssh + cp "/ssl/${REMOTE_KEY}" "${HOME}/.ssh/id_rsa" + ssh-keygen -y -f ${HOME}/.ssh/id_rsa > ${HOME}/.ssh/id_rsa.pub + ) || bashio::log.error "Failed to create SSH key pair!" + + bashio::log.debug "Adding public key of remote host ${REMOTE_HOST} to known hosts." ssh-keyscan -t rsa ${REMOTE_HOST} >> ${HOME}/.ssh/known_hosts \ - || bashio::log.error "Failed to add ${REMOTE_HOST} host key" - ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub + || bashio::log.error "Failed to add public key for remote host ${REMOTE_HOST}!" ( echo "Host remote" echo " IdentityFile ${HOME}/.ssh/id_rsa" @@ -63,18 +68,23 @@ function add-ssh-key { echo " User ${REMOTE_USER}" echo " Port ${REMOTE_PORT}" echo " StrictHostKeyChecking no" - if [ -n "${REMOTE_HOST_KEY_ALGORITHMS}" ] ; then + if bashio::var.has_value "${REMOTE_HOST_KEY_ALGORITHMS}"; then echo " HostKeyAlgorithms ${REMOTE_HOST_KEY_ALGORITHMS}" fi ) > "${HOME}/.ssh/config" - chmod 600 "${HOME}/.ssh/config" - chmod 644 "${HOME}/.ssh/id_rsa.pub" + ( + chmod 600 "${HOME}/.ssh/id_rsa" + chmod 600 "${HOME}/.ssh/config" + chmod 644 "${HOME}/.ssh/id_rsa.pub" + ) || bashio::log.error "Failed to set SSH file permissions!" fi } +# call Home Assistant to create a local backup +# function fails in case local backup is not created function create-local-backup { - # Bind variables + # Bind local variables local base_folders="addons/local homeassistant media share ssl" local installed_addons=$(bashio::supervisor.addons) local data="{\"name\":\"${BACKUP_NAME}\", \"password\": \"${BACKUP_PASSWORD}\"}" @@ -88,16 +98,16 @@ function create-local-backup { local unformatted_addons="${installed_addons}" if bashio::var.has_value "${excluded_folders}"; then - bashio::log.warning "Excluded folder(s):\n ${excluded_folders}" - for folder in ${excluded_folders} ; do - unformatted_folders=$(echo "${unformatted_folders}" | sed -e "s/${folder}//g") - done + bashio::log.warning "Excluded folder(s):\n ${excluded_folders}" + for folder in ${excluded_folders} ; do + unformatted_folders=$(echo "${unformatted_folders}" | sed -e "s/${folder}//g") + done fi if bashio::var.has_value "${excluded_addons}"; then - bashio::log.warning "Excluded addon(s):\n${excluded_addons}" - for addon in ${excluded_addons} ; do - unformatted_addons="$(echo "${unformatted_addons}" | sed -e "s/${addon}//g")" - done + bashio::log.warning "Excluded addon(s):\n${excluded_addons}" + for addon in ${excluded_addons} ; do + unformatted_addons="$(echo "${unformatted_addons}" | sed -e "s/${addon}//g")" + done fi local addons=$(echo ${unformatted_addons} | sed "s/ /\", \"/g") @@ -107,13 +117,15 @@ function create-local-backup { local data="$(echo $data | tr -d '}'), \"addons\": [\"${addons}\"], \"folders\": [\"${folders}\"]}" # append addon and folder set if ! SLUG=$(bashio::api.supervisor POST /backups/new/partial "${data}" .slug); then - die "Error creating partial backup!" + bashio::log.fatal "Error creating partial backup!" + return "${__BASHIO_EXIT_NOK}" fi else bashio::log.info "Creating full backup: \"${BACKUP_NAME}\"" if ! SLUG=$(bashio::api.supervisor POST /backups/new/full "${data}" .slug); then - die "Error creating full backup!" + bashio::log.fatal "Error creating full backup!" + return "${__BASHIO_EXIT_NOK}" fi fi @@ -128,12 +140,9 @@ function copy-backup-to-remote { return "${__BASHIO_EXIT_OK}" fi - cd /backup/ \ - || bashio::log.fatal "Error accessing directory /backup" \ - && return "${__BASHIO_EXIT_NOK}" - bashio::log.info "Copying backup using SCP" + bashio::log.info "Copying backup using SCP." if ! scp -F "${HOME}/.ssh/config" "/backup/${SLUG}.tar" remote:"${SSH_REMOTE_DIRECTORY}/"; then - bashio::log.error "Error copying backup ${SLUG}.tar to ${SSH_REMOTE_DIRECTORY} on ${REMOTE_HOST}" + bashio::log.error "Error copying backup ${SLUG}.tar to ${SSH_REMOTE_DIRECTORY} on ${REMOTE_HOST}." return "${__BASHIO_EXIT_NOK}" fi @@ -147,20 +156,19 @@ function copy-backup-to-remote { return "${__BASHIO_EXIT_OK}" } -function rsync_folders { +function rsync-folders { if bashio::var.false "${RSYNC_ENABLED}"; then - bashio::log.debug "rsync disabled." + bashio::log.debug "Rsync disabled." return "${__BASHIO_EXIT_OK}" fi - local FOLDERS="/config /addons /backup /share /ssl" # put directories without trailing slash - local RSYNC_URL="${REMOTE_USER}@${REMOTE_HOST}:${RSYNC_ROOTFOLDER}" + local folders="/config /addons /backup /share /ssl" # put directories without trailing slash + local rsync_url="${REMOTE_USER}@${REMOTE_HOST}:${RSYNC_ROOTFOLDER}" + local flags='-a -r' bashio::log.info "Starting rsync" if bashio::var.true "${DEBUG}"; then - local FLAGS='-av' - else - local FLAGS='-a' + local flags="${flags} -v" fi echo "${RSYNC_EXCLUDE}" > /tmp/rsync_exclude.txt @@ -168,16 +176,16 @@ function rsync_folders { bashio::log.warning "File patterns that have been excluded:\n${RSYNC_EXCLUDE}" fi - bashio::log.debug "Syncing ${FOLDERS}" - if ! sshpass -p "${REMOTE_PASSWORD}" rsync ${FLAGS} --port ${REMOTE_PORT} --exclude-from='/tmp/rsync_exclude.txt' ${FOLDERS} "${RSYNC_URL}/" --delete; then - bashio::log.error "Error syncing folder(s) ${FOLDERS}" + bashio::log.debug "Syncing ${folders}" + if ! sshpass -p "${REMOTE_PASSWORD}" rsync ${flags} --port ${REMOTE_PORT} --exclude-from='/tmp/rsync_exclude.txt' ${folders} "${rsync_url}/" --delete; then + bashio::log.error "Error syncing folder(s) ${folders}." return "${__BASHIO_EXIT_NOK}" fi return "${__BASHIO_EXIT_OK}" } -function rclone_backups { +function rclone-backups { if [ "${RCLONE_ENABLED}" = true ] ; then cd /backup/ || exit mkdir -p ~/.config/rclone/ @@ -210,43 +218,65 @@ function rclone_backups { fi } +function clone-to-remote { + local ret="${__BASHIO_EXIT_OK}" + + copy-backup-to-remote || ret="${__BASHIO_EXIT_NOK}" + rsync-folders || ret="${__BASHIO_EXIT_NOK}" + rclone-backups || ret="${__BASHIO_EXIT_NOK}" + + return "${ret}" +} + function delete-local-backup { + if bashio::var.equals "${BACKUP_KEEP_LOCAL}" "all"; then + bashio::log.debug "Keep all backups." + return "${__BASHIO_EXIT_OK}" + fi - ha backups reload + if ! bashio::api.supervisor POST /backups/reload; then + bashio::log.warning "Failed to reload backups!" + fi - if [[ "${BACKUP_KEEP_LOCAL}" == "all" ]]; then - : - elif [[ -z "${BACKUP_KEEP_LOCAL}" ]]; then - bashio::log.warning "Deleting local backup: ${SLUG}" - ha backups remove "${SLUG}" + if bashio::var.is_empty "${BACKUP_KEEP_LOCAL}"; then + if bashio::var.has_value "$SLUG"; then + bashio::log.warning "Deleting local backup: ${SLUG}" + if ! bashio::api.supervisor DELETE /backups/${SLUG}; then + bashio::log.error "Failed to delete backup: ${SLUG}" + return "${__BASHIO_EXIT_NOK}" + fi + else + bashio::log.debug "No current backup to delete." + fi else - - last_date_to_keep=$(ha backups list --raw-json | jq .data.backups[].date | sort -r | \ + local ret="${__BASHIO_EXIT_OK}" + local backup_list=$(bashio::api.supervisor GET /backups) + local last_date_to_keep=$(echo "${backup_list}" | jq ".backups[].date" | sort -r | \ head -n "${BACKUP_KEEP_LOCAL}" | tail -n 1 | xargs date -D "%Y-%m-%dT%T" +%s --date ) - ha backups list --raw-json | jq -c .data.backups[] | while read -r backup; do - if [[ $(echo "${backup}" | jq .date | xargs date -D "%Y-%m-%dT%T" +%s --date ) -lt ${last_date_to_keep} ]]; then - bashio::log.warning "Deleting local backup: $(echo "${backup}" | jq -r .slug)" - ha backups remove "$(echo "${backup}" | jq -r .slug)" - bashio::log.info "Finished deleting local backup: $(echo "${backup}" | jq -r .slug)" + echo "${backup_list}" | jq -c ".backups[]" | while read -r backup; do + if [[ $(echo "${backup}" | jq ".date" | xargs date -D "%Y-%m-%dT%T" +%s --date ) -lt ${last_date_to_keep} ]]; then + local backup_slug=$(echo "${backup}" | jq -r .slug) + bashio::log.warning "Deleting local backup: ${backup_slug}" + if ! bashio::api.supervisor DELETE /backups/${backup_slug}; then + bashio::log.error "Failed to delete backup: ${backup_slug}" + ret="${__BASHIO_EXIT_NOK}" + fi fi done - + return "${ret}" fi + + return "${__BASHIO_EXIT_OK}" } # general setup and backup set-debug-level add-ssh-key -create-local-backup - -# different remote cloning modes -copy-backup-to-remote || bashio::log.fatal "SCP failed." -rsync_folders || bashio::log.fatal "rsync failed." -rclone_backups -# cleanup -delete-local-backup +create-local-backup || die "Local backup process failed! See log for details." +clone-to-remote || die "Cloning backup(s) to remote host ${REMOTE_HOST} failed! See log for details." +delete-local-backup || die "Removing local backup(s) failed! See log for details." bashio::log.info "Backup process done!" bashio::exit.ok From fb70041dfa21e43aa41525824a3131f3bb7ad744 Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Sat, 20 Aug 2022 11:40:20 +0200 Subject: [PATCH 11/40] removed unneeded variables --- remote-backup/run.sh | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/remote-backup/run.sh b/remote-backup/run.sh index e0af5c4..2a3cf75 100755 --- a/remote-backup/run.sh +++ b/remote-backup/run.sh @@ -92,20 +92,18 @@ function create-local-backup { if bashio::var.has_value "${BACKUP_EXCLUDE_ADDONS}" || bashio::var.has_value "${BACKUP_EXCLUDE_FOLDERS}"; then bashio::log.info "Creating partial backup: \"${BACKUP_NAME}\"" - local excluded_folders=$(echo "${BACKUP_EXCLUDE_FOLDERS}") - local excluded_addons=$(echo "${BACKUP_EXCLUDE_ADDONS}") local unformatted_folders="${base_folders}" local unformatted_addons="${installed_addons}" - if bashio::var.has_value "${excluded_folders}"; then - bashio::log.warning "Excluded folder(s):\n ${excluded_folders}" - for folder in ${excluded_folders} ; do + if bashio::var.has_value "${BACKUP_EXCLUDE_FOLDERS}"; then + bashio::log.notice "Excluded folder(s):\n${BACKUP_EXCLUDE_FOLDERS}" + for folder in ${BACKUP_EXCLUDE_FOLDERS} ; do unformatted_folders=$(echo "${unformatted_folders}" | sed -e "s/${folder}//g") done fi - if bashio::var.has_value "${excluded_addons}"; then - bashio::log.warning "Excluded addon(s):\n${excluded_addons}" - for addon in ${excluded_addons} ; do + if bashio::var.has_value "${BACKUP_EXCLUDE_ADDONS}"; then + bashio::log.notice "Excluded addon(s):\n${BACKUP_EXCLUDE_ADDONS}" + for addon in ${BACKUP_EXCLUDE_ADDONS} ; do unformatted_addons="$(echo "${unformatted_addons}" | sed -e "s/${addon}//g")" done fi From b85d393910ae6c8d42e628107b13f76f87a99678 Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Sat, 20 Aug 2022 11:47:26 +0200 Subject: [PATCH 12/40] changed log level, cleaned messages --- remote-backup/run.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/remote-backup/run.sh b/remote-backup/run.sh index 2a3cf75..a73858e 100755 --- a/remote-backup/run.sh +++ b/remote-backup/run.sh @@ -164,19 +164,19 @@ function rsync-folders { local rsync_url="${REMOTE_USER}@${REMOTE_HOST}:${RSYNC_ROOTFOLDER}" local flags='-a -r' - bashio::log.info "Starting rsync" + bashio::log.info "Copying backup using rsync." if bashio::var.true "${DEBUG}"; then local flags="${flags} -v" fi echo "${RSYNC_EXCLUDE}" > /tmp/rsync_exclude.txt if bashio::var.has_value "${RSYNC_EXCLUDE}"; then - bashio::log.warning "File patterns that have been excluded:\n${RSYNC_EXCLUDE}" + bashio::log.notice "Excluded rsync file patterns:\n${RSYNC_EXCLUDE}" fi bashio::log.debug "Syncing ${folders}" if ! sshpass -p "${REMOTE_PASSWORD}" rsync ${flags} --port ${REMOTE_PORT} --exclude-from='/tmp/rsync_exclude.txt' ${folders} "${rsync_url}/" --delete; then - bashio::log.error "Error syncing folder(s) ${folders}." + bashio::log.error "Error rsyncing folder(s) ${folders} to ${rsync_url}!" return "${__BASHIO_EXIT_NOK}" fi @@ -188,7 +188,7 @@ function rclone-backups { cd /backup/ || exit mkdir -p ~/.config/rclone/ cp -a /ssl/rclone.conf ~/.config/rclone/rclone.conf - bashio::log.info "Starting rclone" + bashio::log.info "Copying backup using rclone." if [ "$RCLONE_COPY" = true ] ; then if [ "$BACKUP_FRIENDLY_NAME" = true ] ; then bashio::log.debug "Copying ${SLUG}.tar to ${RCLONE_REMOTE_DIRECTORY}/${BACKUP_NAME}.tar" @@ -238,7 +238,7 @@ function delete-local-backup { if bashio::var.is_empty "${BACKUP_KEEP_LOCAL}"; then if bashio::var.has_value "$SLUG"; then - bashio::log.warning "Deleting local backup: ${SLUG}" + bashio::log.notice "Deleting local backup: ${SLUG}" if ! bashio::api.supervisor DELETE /backups/${SLUG}; then bashio::log.error "Failed to delete backup: ${SLUG}" return "${__BASHIO_EXIT_NOK}" @@ -255,7 +255,7 @@ function delete-local-backup { echo "${backup_list}" | jq -c ".backups[]" | while read -r backup; do if [[ $(echo "${backup}" | jq ".date" | xargs date -D "%Y-%m-%dT%T" +%s --date ) -lt ${last_date_to_keep} ]]; then local backup_slug=$(echo "${backup}" | jq -r .slug) - bashio::log.warning "Deleting local backup: ${backup_slug}" + bashio::log.notice "Deleting local backup: ${backup_slug}" if ! bashio::api.supervisor DELETE /backups/${backup_slug}; then bashio::log.error "Failed to delete backup: ${backup_slug}" ret="${__BASHIO_EXIT_NOK}" From c477cdaa68494e17dfda8c80df4ac68e33691b39 Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Sat, 20 Aug 2022 12:20:32 +0200 Subject: [PATCH 13/40] added port to ssh-keyscan --- remote-backup/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/remote-backup/run.sh b/remote-backup/run.sh index a73858e..2c02d99 100755 --- a/remote-backup/run.sh +++ b/remote-backup/run.sh @@ -59,7 +59,7 @@ function add-ssh-key { ) || bashio::log.error "Failed to create SSH key pair!" bashio::log.debug "Adding public key of remote host ${REMOTE_HOST} to known hosts." - ssh-keyscan -t rsa ${REMOTE_HOST} >> ${HOME}/.ssh/known_hosts \ + ssh-keyscan -t rsa -p ${REMOTE_PORT} ${REMOTE_HOST} >> ${HOME}/.ssh/known_hosts \ || bashio::log.error "Failed to add public key for remote host ${REMOTE_HOST}!" ( echo "Host remote" From e6502e613bbc1e489838cf7da6cd5422314ab25e Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Sat, 20 Aug 2022 16:40:34 +0200 Subject: [PATCH 14/40] added status event --- remote-backup/DOCS.md | 29 +++++++++++++++++++++++++++++ remote-backup/run.sh | 31 ++++++++++++++++++++++++------- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/remote-backup/DOCS.md b/remote-backup/DOCS.md index 50cfaed..e090aac 100644 --- a/remote-backup/DOCS.md +++ b/remote-backup/DOCS.md @@ -2,6 +2,35 @@ Please visit the documentation at [addons.mathesonsteplock.ca](https://addons.mathesonsteplock.ca/docs/addons/remote-backup/basic-config) +# Persistent Notification + +In case of an error, a persistent notification with the error message is created. Please see the logs in to find out what happend (you might also want to enable debugging in the configuration). + +# Using Events + +The add-on creates an event each time it is has been executed. + +| Field | Description | +| ------------ | -------------------------------------------- | +| `event_type` | `remote_backup_status` | +| `result` | Backup result status, can be `ok` or `error` | +| `message` | Human readable message for the notification | + +Here is an example automation on how to use it: +
+alias: Backup check +description: This automation creates an persistent notification in case the backup fails. +trigger: + - platform: event + event_type: remote_backup_status + event_data: + result: error +action: + - service: persistent_notification.create + data: + message: Backup failed +mode: single +# Support Me [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/mathesonsteplock) diff --git a/remote-backup/run.sh b/remote-backup/run.sh index 2c02d99..9eb9299 100755 --- a/remote-backup/run.sh +++ b/remote-backup/run.sh @@ -33,18 +33,34 @@ BACKUP_NAME="${BACKUP_CUSTOM_PREFIX} $(date +'%Y-%m-%d %H-%M')" function set-debug-level { # default log level according to bashio const.sh is INFO - if bashio::var.true "${DEBUG}"; then + if bashio::var.true "${DEBUG}"; then bashio::log.level "debug" fi } -function die { - local message=${1:-'no message'} - local title=${2:-'Addon: Remote Backup Failed!'} +# Arguments: +# $1 result should be ok or error +# $2 message message to send with the event +function fire_event { + local result=${1} + local message=${2:-} + + if bashio::var.has_value "${message}"; then + message=",\"message:\":\"${message}\"" + fi - # catch return code which is always false, see https://github.com/hassio-addons/bashio/issues/31 - local ret=$(bashio::api.supervisor POST /core/api/services/persistent_notification/create "{\"message\":\"${message}\", \"title\":\"${title}\"}") - bashio::exit.nok "${message}" + # catch return code which is always false, see https://github.com/hassio-addons/bashio/issues/31 + local ret=$(bashio::api.supervisor POST /core/api/events/remote_backup_status "{\"result\":\"${result}\"${message}}") +} +function die { + local message=${1:-'no message'} + local title=${2:-'Addon: Remote Backup Failed!'} + + # catch return code which is always false, see https://github.com/hassio-addons/bashio/issues/31 + local ret=$(bashio::api.supervisor POST /core/api/services/persistent_notification/create \ + "{\"message\":\"${message}\", \"title\":\"${title}\", \"notification_id\":\"addon-remote-backup\"}") + fire_event "error" "${message}" + bashio::exit.nok "${message}" } # prepare SSH environment/configuration @@ -277,4 +293,5 @@ clone-to-remote || die "Cloning backup(s) to remote host ${REMOTE_HOST} failed! delete-local-backup || die "Removing local backup(s) failed! See log for details." bashio::log.info "Backup process done!" +fire_event "ok" "Backup ${BACKUP_NAME} created." bashio::exit.ok From fd68747d0ea3b8ae84a07207af842849d6af6afb Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Sun, 21 Aug 2022 10:09:57 +0200 Subject: [PATCH 15/40] cleaned up rclone-backups --- remote-backup/run.sh | 55 ++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/remote-backup/run.sh b/remote-backup/run.sh index 9eb9299..3a1d547 100755 --- a/remote-backup/run.sh +++ b/remote-backup/run.sh @@ -200,36 +200,45 @@ function rsync-folders { } function rclone-backups { - if [ "${RCLONE_ENABLED}" = true ] ; then - cd /backup/ || exit + if bashio::var.false "${RCLONE_ENABLED}"; then + bashio::log.debug "Rclone disabled." + return "${__BASHIO_EXIT_OK}" + fi + + ( + cd /backup/ mkdir -p ~/.config/rclone/ cp -a /ssl/rclone.conf ~/.config/rclone/rclone.conf + ) || bashio::log.error "Failed to prepare rclone configuration!" + + if bashio::var.true "${RCLONE_COPY}"; then + local remote_name=$SLUG + if bashio::var.true "${BACKUP_FRIENDLY_NAME}"; then + remote_name=$BACKUP_NAME + fi bashio::log.info "Copying backup using rclone." - if [ "$RCLONE_COPY" = true ] ; then - if [ "$BACKUP_FRIENDLY_NAME" = true ] ; then - bashio::log.debug "Copying ${SLUG}.tar to ${RCLONE_REMOTE_DIRECTORY}/${BACKUP_NAME}.tar" - rclone copyto "${SLUG}.tar" "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}/${BACKUP_NAME}.tar" - bashio::log.debug "Finished rclone copy" - else - bashio::log.debug "Copying ${SLUG}.tar to ${RCLONE_REMOTE_DIRECTORY}/${SLUG}.tar" - rclone copy "${SLUG}.tar" "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}" - bashio::log.debug "Finished rclone copy" - fi + if ! rclone copyto "${SLUG}.tar" "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}/${remote_name}.tar"; then + bashio::log.error "Error rclone ${SLUG}.tar to ${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}/${remote_name}.tar!" + return "${__BASHIO_EXIT_NOK}" fi - if [ "${RCLONE_SYNC}" = true ] ; then - bashio::log.info "Syncing Backups" - rclone sync . "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}" - bashio::log.info "Finished rclone sync" + fi + if bashio::var.true "${RCLONE_SYNC}"; then + bashio::log.info "Syncing backups using rclone" + if ! rclone sync . "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}"; then + bashio::log.error "Error syncing backups by rclone!" + return "${__BASHIO_EXIT_NOK}" fi - if [ "${RCLONE_RESTORE}" = true ] ; then - DATEFORMAT=$(date +%F) - RESTORENAME="restore-${DATEFORMAT}" - mkdir -p "${RESTORENAME}" - bashio::log.info "Restoring Backups to ${RESTORENAME}" - rclone copyto "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY} ${RESTORENAME}/" - bashio::log.info "Finished rclone restore" + fi + if bashio::var.true "${RCLONE_RESTORE}"; then + local restore_name="restore-$(date +%F)" + mkdir -p "${restore_name}" + bashio::log.info "Restoring backups to ${restore_name} using rclone" + if ! rclone copyto "${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY} ${restore_name}/"; then + bashio::log.error "Error restoring backups from ${REMOTE_HOST}:${RCLONE_REMOTE_DIRECTORY}!" + return "${__BASHIO_EXIT_NOK}" fi fi + return "${__BASHIO_EXIT_OK}" } function clone-to-remote { From f7188cede364d08fc25880a5a9a16fb1492aa4ff Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Sun, 21 Aug 2022 10:19:40 +0200 Subject: [PATCH 16/40] minor fixes to documentation --- remote-backup/DOCS.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/remote-backup/DOCS.md b/remote-backup/DOCS.md index e090aac..b30fae1 100644 --- a/remote-backup/DOCS.md +++ b/remote-backup/DOCS.md @@ -2,11 +2,11 @@ Please visit the documentation at [addons.mathesonsteplock.ca](https://addons.mathesonsteplock.ca/docs/addons/remote-backup/basic-config) -# Persistent Notification +## Persistent Notification -In case of an error, a persistent notification with the error message is created. Please see the logs in to find out what happend (you might also want to enable debugging in the configuration). +In case of an error, a persistent notification with the error message is created. Please see the logs to find out what happend (you might also want to enable debugging in the configuration). -# Using Events +## Using Events The add-on creates an event each time it is has been executed. From 74da87ee8e26f24f6725889d9801cb1084c1fe7d Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Sun, 21 Aug 2022 10:26:35 +0200 Subject: [PATCH 17/40] allow spaces in paths --- remote-backup/config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/remote-backup/config.yaml b/remote-backup/config.yaml index cdcd76f..8df716a 100644 --- a/remote-backup/config.yaml +++ b/remote-backup/config.yaml @@ -64,7 +64,7 @@ schema: backup_friendly_name: bool? backup_custom_prefix: str? backup_exclude_folders: - - match(^[A-Za-z0-9_\-\.\*\/\?\+\\]*$)? + - match(^[A-Za-z0-9_\-\.\*\/\?\+\\ ]*$)? backup_exclude_addons: - str? backup_keep_local: match(^(all|[+]?\d*)$)? @@ -74,7 +74,7 @@ schema: rsync_enabled: bool rsync_rootfolder: str? rsync_exclude: - - match(^[A-Za-z0-9_\-\.\*\/\?\+\\]+$) + - match(^[A-Za-z0-9_\-\.\*\/\?\+\\ ]+$) rclone_enabled: bool rclone_remote_directory: str? rclone_copy: bool? From a31d17f6fcc4469483cc3c1e064ed42ad11f94b8 Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Sun, 21 Aug 2022 17:52:10 +0200 Subject: [PATCH 18/40] cleanup scp - simplified remote file renaming - fixed spaces in path issue --- remote-backup/run.sh | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/remote-backup/run.sh b/remote-backup/run.sh index 3a1d547..b850581 100755 --- a/remote-backup/run.sh +++ b/remote-backup/run.sh @@ -154,19 +154,17 @@ function copy-backup-to-remote { return "${__BASHIO_EXIT_OK}" fi + local remote_name=$SLUG + if bashio::var.true "${BACKUP_FRIENDLY_NAME}"; then + remote_name=$BACKUP_NAME + fi + bashio::log.info "Copying backup using SCP." - if ! scp -F "${HOME}/.ssh/config" "/backup/${SLUG}.tar" remote:"${SSH_REMOTE_DIRECTORY}/"; then + if ! scp -F "${HOME}/.ssh/config" "/backup/${SLUG}.tar" remote:"'${SSH_REMOTE_DIRECTORY}/${remote_name}.tar'"; then bashio::log.error "Error copying backup ${SLUG}.tar to ${SSH_REMOTE_DIRECTORY} on ${REMOTE_HOST}." return "${__BASHIO_EXIT_NOK}" fi - if bashio::var.true "${BACKUP_FRIENDLY_NAME}"; then - bashio::log.info "Renaming ${SLUG}.tar to ${BACKUP_NAME}.tar" - if ! ssh remote "mv \"${SSH_REMOTE_DIRECTORY}/${SLUG}.tar\" \"${SSH_REMOTE_DIRECTORY}/${BACKUP_NAME}.tar\""; then - bashio::log.error "Error renaming backup to ${SSH_REMOTE_DIRECTORY}/${BACKUP_NAME}.tar on ${REMOTE_HOST}" - return "${__BASHIO_EXIT_NOK}" - fi - fi return "${__BASHIO_EXIT_OK}" } From 43e3bf0923e6f3cabcbc2cf11813746ecc17aa65 Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Sun, 21 Aug 2022 18:44:47 +0200 Subject: [PATCH 19/40] added apparmor profile --- remote-backup/apparmor.txt | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 remote-backup/apparmor.txt diff --git a/remote-backup/apparmor.txt b/remote-backup/apparmor.txt new file mode 100644 index 0000000..38ab13d --- /dev/null +++ b/remote-backup/apparmor.txt @@ -0,0 +1,31 @@ +#include
-alias: Backup check -description: This automation creates an persistent notification in case the backup fails. -trigger: - - platform: event - event_type: remote_backup_status - event_data: - result: error -action: - - service: persistent_notification.create - data: - message: Backup failed -mode: single -# Support Me [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/mathesonsteplock) diff --git a/remote-backup/Dockerfile b/remote-backup/Dockerfile index 4e6ee20..d45569b 100644 --- a/remote-backup/Dockerfile +++ b/remote-backup/Dockerfile @@ -26,12 +26,12 @@ ARG BUILD_VERSION # Labels LABEL \ io.hass.name="Remote Backup" \ - io.hass.description="Automatically create and backup HA backups using SFTP (SCP), rsync" \ + io.hass.description="Automatically create and transfer HA backups using SFTP (SCP), rsync." \ io.hass.arch="${BUILD_ARCH}" \ io.hass.type="addon" \ io.hass.version=${BUILD_VERSION} \ maintainer="Matheson Steplock