mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
318 lines
13 KiB
Bash
318 lines
13 KiB
Bash
#!/bin/bash
|
|
|
|
CAN_BIND=${CAN_BIND:-1}
|
|
|
|
# Add a file or a directory to the list of paths to backup
|
|
#
|
|
# usage: ynh_backup /path/to/stuff
|
|
#
|
|
# NB : note that this helper does *NOT* perform any copy in itself, it only
|
|
# declares stuff to be backuped via a CSV which is later picked up by the core
|
|
#
|
|
# NB 2 : there is a specific behavior for $data_dir (or childs of $data_dir) and
|
|
# /var/log/$app which are *NOT* backedup during safety-backup-before-upgrade,
|
|
# OR if the setting "do_not_backup_data" is equals 1 for that app
|
|
#
|
|
# The rationale is that these directories are usually too heavy to be integrated in every backup
|
|
# (think for example about Nextcloud with quite a lot of data, or an app with a lot of media files...)
|
|
#
|
|
# This is coupled to the fact that $data_dir and the log dir won't be (and
|
|
# should NOT) be deleted during remove, unless --purge is used. Hence, if the
|
|
# upgrade fails and the script is removed prior to restoring the backup, the
|
|
# data/logs are not destroyed.
|
|
#
|
|
ynh_backup() {
|
|
|
|
local target="$1"
|
|
local is_data=false
|
|
|
|
# If the path starts with /var/log/$app or $data_dir
|
|
if ([[ -n "${app:-}" ]] && [[ "$target" == "/var/log/$app*" ]]) || ([[ -n "${data_dir:-}" ]] && [[ "$target" == "$data_dir*" ]])
|
|
then
|
|
is_data=true
|
|
fi
|
|
|
|
if [[ -n "${app:-}" ]]
|
|
then
|
|
local do_not_backup_data=$(ynh_app_setting_get --key=do_not_backup_data)
|
|
fi
|
|
|
|
# If backing up core only (used by ynh_backup_before_upgrade),
|
|
# don't backup big data items
|
|
if [[ "$is_data" == true ]] && ([[ ${do_not_backup_data:-0} -eq 1 ]] || [[ ${BACKUP_CORE_ONLY:-0} -eq 1 ]]); then
|
|
if [ $BACKUP_CORE_ONLY -eq 1 ]; then
|
|
ynh_print_info --message="$target will not be saved, because 'BACKUP_CORE_ONLY' is set."
|
|
else
|
|
ynh_print_info --message="$target will not be saved, because 'do_not_backup_data' is set."
|
|
fi
|
|
return 1
|
|
fi
|
|
|
|
# ==============================================================================
|
|
# Format correctly source and destination paths
|
|
# ==============================================================================
|
|
# Be sure the source path is not empty
|
|
if [ ! -e "$target" ]; then
|
|
ynh_print_warn --message="File or folder '${target}' to be backed up does not exist"
|
|
return 1
|
|
fi
|
|
|
|
# Transform the source path as an absolute path
|
|
# If it's a dir remove the ending /
|
|
src_path=$(realpath "$target")
|
|
|
|
# Initialize the dest path with the source path relative to "/".
|
|
# eg: src_path=/etc/yunohost -> dest_path=etc/yunohost
|
|
dest_path="${src_path#/}"
|
|
|
|
# Check if dest_path already exists in tmp archive
|
|
if [[ -e "${dest_path}" ]]; then
|
|
ynh_print_warn --message="Destination path '${dest_path}' already exist"
|
|
return 1
|
|
fi
|
|
|
|
# Add the relative current working directory to the destination path
|
|
local rel_dir="${YNH_CWD#$YNH_BACKUP_DIR}"
|
|
rel_dir="${rel_dir%/}/"
|
|
dest_path="${rel_dir}${dest_path}"
|
|
dest_path="${dest_path#/}"
|
|
# ==============================================================================
|
|
|
|
# ==============================================================================
|
|
# Write file to backup into backup_list
|
|
# ==============================================================================
|
|
local src=$(echo "${src_path}" | sed --regexp-extended 's/"/\"\"/g')
|
|
local dest=$(echo "${dest_path}" | sed --regexp-extended 's/"/\"\"/g')
|
|
echo "\"${src}\",\"${dest}\"" >>"${YNH_BACKUP_CSV}"
|
|
|
|
# ==============================================================================
|
|
|
|
# Create the parent dir of the destination path
|
|
# It's for retro compatibility, some script consider ynh_backup creates this dir
|
|
mkdir --parents $(dirname "$YNH_BACKUP_DIR/${dest_path}")
|
|
}
|
|
|
|
# Return the path in the archive where has been stocked the origin path
|
|
#
|
|
# [internal]
|
|
#
|
|
# usage: _get_archive_path ORIGIN_PATH
|
|
_get_archive_path() {
|
|
# For security reasons we use csv python library to read the CSV
|
|
python3 -c "
|
|
import sys
|
|
import csv
|
|
with open(sys.argv[1], 'r') as backup_file:
|
|
backup_csv = csv.DictReader(backup_file, fieldnames=['source', 'dest'])
|
|
for row in backup_csv:
|
|
if row['source']==sys.argv[2].strip('\"'):
|
|
print(row['dest'])
|
|
sys.exit(0)
|
|
raise Exception('Original path for %s not found' % sys.argv[2])
|
|
" "${YNH_BACKUP_CSV}" "$1"
|
|
return $?
|
|
}
|
|
|
|
# Restore a file or a directory from the backup archive
|
|
#
|
|
# usage: ynh_restore /path/to/stuff
|
|
#
|
|
# examples:
|
|
# ynh_restore "/etc/nginx/conf.d/$domain.d/$app.conf"
|
|
#
|
|
# If the file or dir to be restored already exists on the system and is lighter
|
|
# than 500 Mo, it is backed up in `/var/cache/yunohost/appconfbackup/`.
|
|
# Otherwise, the existing file or dir is removed.
|
|
#
|
|
# if `apps/$app/etc/nginx/conf.d/$domain.d/$app.conf` exists, restore it into
|
|
# `/etc/nginx/conf.d/$domain.d/$app.conf`
|
|
# otheriwse, search for a match in the csv (eg: conf/nginx.conf) and restore it into
|
|
# `/etc/nginx/conf.d/$domain.d/$app.conf`
|
|
#
|
|
# Requires YunoHost version 2.6.4 or higher.
|
|
ynh_restore() {
|
|
target="$1"
|
|
|
|
local archive_path="$YNH_CWD${target}"
|
|
|
|
# If the path starts with /var/log/$app or $data_dir
|
|
local is_data=false
|
|
if ([[ -n "${app:-}" ]] && [[ "$target" == "/var/log/$app*" ]]) || ([[ -n "${data_dir:-}" ]] && [[ "$target" == "$data_dir*" ]])
|
|
then
|
|
is_data=true
|
|
fi
|
|
|
|
# If archive_path doesn't exist, search for a corresponding path in CSV
|
|
if [ ! -d "$archive_path" ] && [ ! -f "$archive_path" ] && [ ! -L "$archive_path" ]; then
|
|
if [[ "$is_data" == true ]]
|
|
then
|
|
ynh_print_info --message="Skipping $target which doesn't exists in the archive, probably because restoring from a safety-backup-before-upgrade"
|
|
# Assume it's not a big deal, we may be restoring a safety-backup-before-upgrade which doesnt contain those
|
|
return 0
|
|
else
|
|
# (get_archive_path will raise an exception if no match found)
|
|
archive_path="$YNH_BACKUP_DIR/$(_get_archive_path \"$target\")"
|
|
fi
|
|
fi
|
|
|
|
# Move the old directory if it already exists
|
|
if [[ -e "${target}" ]]; then
|
|
# Check if the file/dir size is less than 500 Mo
|
|
if [[ $(du --summarize --bytes ${target} | cut --delimiter="/" --fields=1) -le "500000000" ]]; then
|
|
local backup_file="/var/cache/yunohost/appconfbackup/${target}.backup.$(date '+%Y%m%d.%H%M%S')"
|
|
mkdir --parents "$(dirname "$backup_file")"
|
|
mv "${target}" "$backup_file" # Move the current file or directory
|
|
else
|
|
ynh_safe_rm "${target}"
|
|
fi
|
|
fi
|
|
|
|
# Restore target into target
|
|
mkdir --parents $(dirname "$target")
|
|
|
|
# Do a copy if it's just a mounting point
|
|
if mountpoint --quiet $YNH_BACKUP_DIR; then
|
|
if [[ -d "${archive_path}" ]]; then
|
|
archive_path="${archive_path}/."
|
|
mkdir --parents "$target"
|
|
fi
|
|
cp --archive "$archive_path" "${target}"
|
|
# Do a move if YNH_BACKUP_DIR is already a copy
|
|
else
|
|
mv "$archive_path" "${target}"
|
|
fi
|
|
}
|
|
|
|
# Restore all files that were previously backuped in an app backup script
|
|
#
|
|
# usage: ynh_restore_everything
|
|
#
|
|
# Requires YunoHost version 2.6.4 or higher.
|
|
ynh_restore_everything() {
|
|
# Deduce the relative path of $YNH_CWD
|
|
local REL_DIR="${YNH_CWD#$YNH_BACKUP_DIR/}"
|
|
REL_DIR="${REL_DIR%/}/"
|
|
|
|
# For each destination path begining by $REL_DIR
|
|
cat ${YNH_BACKUP_CSV} | tr --delete $'\r' | grep --only-matching --no-filename --perl-regexp "^\".*\",\"$REL_DIR.*\"$" \
|
|
| while read line; do
|
|
local ARCHIVE_PATH=$(echo "$line" | grep --only-matching --no-filename --perl-regexp "^\".*\",\"$REL_DIR\K.*(?=\"$)")
|
|
ynh_restore "$ARCHIVE_PATH"
|
|
done
|
|
}
|
|
|
|
# Calculate and store a file checksum into the app settings
|
|
#
|
|
# usage: ynh_store_file_checksum --file=file
|
|
# | arg: -f, --file= - The file on which the checksum will performed, then stored.
|
|
#
|
|
# $app should be defined when calling this helper
|
|
#
|
|
# Requires YunoHost version 2.6.4 or higher.
|
|
ynh_store_file_checksum() {
|
|
# ============ Argument parsing =============
|
|
local -A args_array=([f]=file= [u]=update_only)
|
|
local file
|
|
local update_only
|
|
update_only="${update_only:-0}"
|
|
ynh_handle_getopts_args "$@"
|
|
# ===========================================
|
|
|
|
local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_'
|
|
|
|
# If update only, we don't save the new checksum if no old checksum exist
|
|
if [ $update_only -eq 1 ]; then
|
|
local checksum_value=$(ynh_app_setting_get --key=$checksum_setting_name)
|
|
if [ -z "${checksum_value}" ]; then
|
|
unset backup_file_checksum
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
ynh_app_setting_set --key=$checksum_setting_name --value=$(md5sum "$file" | cut --delimiter=' ' --fields=1)
|
|
|
|
if [ ${PACKAGE_CHECK_EXEC:-0} -eq 1 ]; then
|
|
# Using a base64 is in fact more reversible than "replace / and space by _" ... So we can in fact obtain the original file path in an easy reliable way ...
|
|
local file_path_base64=$(echo "$file" | base64 -w0)
|
|
mkdir -p /var/cache/yunohost/appconfbackup/
|
|
cat $file > /var/cache/yunohost/appconfbackup/original_${file_path_base64}
|
|
fi
|
|
|
|
# If backup_file_checksum isn't empty, ynh_backup_if_checksum_is_different has made a backup
|
|
if [ -n "${backup_file_checksum-}" ]; then
|
|
# Print the diff between the previous file and the new one.
|
|
# diff return 1 if the files are different, so the || true
|
|
diff --report-identical-files --unified --color=always $backup_file_checksum $file >&2 || true
|
|
fi
|
|
# Unset the variable, so it wouldn't trig a ynh_store_file_checksum without a ynh_backup_if_checksum_is_different before it.
|
|
unset backup_file_checksum
|
|
}
|
|
|
|
# Verify the checksum and backup the file if it's different
|
|
#
|
|
# usage: ynh_backup_if_checksum_is_different --file=file
|
|
# | arg: -f, --file= - The file on which the checksum test will be perfomed.
|
|
# | ret: the name of a backup file, or nothing
|
|
#
|
|
# This helper is primarily meant to allow to easily backup personalised/manually
|
|
# modified config files.
|
|
#
|
|
# Requires YunoHost version 2.6.4 or higher.
|
|
ynh_backup_if_checksum_is_different() {
|
|
# ============ Argument parsing =============
|
|
local -A args_array=([f]=file=)
|
|
local file
|
|
ynh_handle_getopts_args "$@"
|
|
# ===========================================
|
|
|
|
local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_'
|
|
local checksum_value=$(ynh_app_setting_get --key=$checksum_setting_name)
|
|
# backup_file_checksum isn't declare as local, so it can be reuse by ynh_store_file_checksum
|
|
backup_file_checksum=""
|
|
if [ -n "$checksum_value" ]; then # Proceed only if a value was stored into the app settings
|
|
if [ -e $file ] && ! echo "$checksum_value $file" | md5sum --check --status; then # If the checksum is now different
|
|
|
|
backup_file_checksum="/var/cache/yunohost/appconfbackup/$file.backup.$(date '+%Y%m%d.%H%M%S')"
|
|
mkdir --parents "$(dirname "$backup_file_checksum")"
|
|
cp --archive "$file" "$backup_file_checksum" # Backup the current file
|
|
ynh_print_warn --message="File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file_checksum"
|
|
echo "$backup_file_checksum" # Return the name of the backup file
|
|
if [ ${PACKAGE_CHECK_EXEC:-0} -eq 1 ]; then
|
|
local file_path_base64=$(echo "$file" | base64 -w0)
|
|
if test -e /var/cache/yunohost/appconfbackup/original_${file_path_base64}
|
|
then
|
|
ynh_print_warn --message="Diff with the original file:"
|
|
diff --report-identical-files --unified --color=always /var/cache/yunohost/appconfbackup/original_${file_path_base64} $file >&2 || true
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Delete a file checksum from the app settings
|
|
#
|
|
# usage: ynh_delete_file_checksum --file=file
|
|
# | arg: -f, --file= - The file for which the checksum will be deleted
|
|
#
|
|
# $app should be defined when calling this helper
|
|
#
|
|
# Requires YunoHost version 3.3.1 or higher.
|
|
ynh_delete_file_checksum() {
|
|
# ============ Argument parsing =============
|
|
local -A args_array=([f]=file=)
|
|
local file
|
|
ynh_handle_getopts_args "$@"
|
|
# ===========================================
|
|
|
|
local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_'
|
|
ynh_app_setting_delete --key=$checksum_setting_name
|
|
}
|
|
|
|
# Checks a backup archive exists
|
|
#
|
|
# [internal]
|
|
#
|
|
ynh_backup_archive_exists() {
|
|
yunohost backup list --output-as json --quiet \
|
|
| jq -e --arg archive "$1" '.archives | index($archive)' >/dev/null
|
|
}
|