Files
SODEW/sodew-bash-main/libs/commands/restore.sh
T
2026-08-18 09:40:08 +02:00

474 lines
15 KiB
Bash

restoreLabel="[Restore]"
# Restores site files from an archive.
#
# The archive is first extracted into a temporary directory located on the same
# filesystem as the target vhost directory. After successful extraction, the
# current vhost directory is moved to a temporary backup path and the restored
# directory is moved into place.
#
# $1 (domain): Site domain name.
# $2 (file): Source archive file path.
#
# Returns:
# 0 on success, 1 on validation or restore failure.
function restoreSiteFiles() {
local domain
domain=$(domainPrepare "$1")
domainCheck "$domain" || return 1
local target="$olsVhostsPath/$domain"
local source="$2"
if [[ -z "$source" ]]; then
appError "Source files not specified"
return 1
fi
if [[ ! -e "$source" ]]; then
appError "Source files not found: $source"
return 1
fi
local tmpDir restoreSource output rc
if [[ -f "$source" ]]; then
tmpDir=$(mktemp -d) || {
appError "Failed to create temporary restore directory"
return 1
}
archiveExtract "$source" "$tmpDir" || {
rm -rf -- "$tmpDir" &>/dev/null
return 1
}
restoreSource="$tmpDir"
else
restoreSource="$source"
fi
rm -rf -- "$target" || {
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
appError "Failed to remove target directory: $target"
return 1
}
mkdir -p -- "$target" || {
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
appError "Failed to create target directory: $target"
return 1
}
output=$(cp -a -- "$restoreSource"/. "$target/" 2>&1)
rc=$?
if [[ $rc -ne 0 ]]; then
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
appError "Failed to copy restored files: $output"
return 1
fi
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
openlitespeedVhostRebuild "$domain" || return 1
return 0
}
# Restores a site database from a SQL file or archive.
#
# If the source file is an archive, it is extracted into a temporary directory
# first and must contain exactly one SQL file. If the target database already
# exists, the SQL file is first imported into a temporary database to validate
# the restore before recreating the target database.
#
# $1 (domain): Site domain name.
# $2 (file): Source SQL file or archive file path.
#
# Returns:
# 0 on success, 1 on validation or restore failure.
function restoreSiteDatabase() {
local domain
domain=$(domainPrepare "$1")
domainCheck "$domain" || return 1
local file="$2"
if [[ -z "$file" ]]; then
appError "Source database file not specified"
return 1
fi
if [[ ! -f "$file" ]]; then
appError "Source database file not found: $file"
return 1
fi
local tmpDir importFile
importFile="$file"
case "$file" in
*.zip|*.tar.gz|*.tgz)
tmpDir=$(mktemp -d) || {
appError "Failed to create temporary database restore directory"
return 1
}
archiveExtract "$file" "$tmpDir" || {
rm -rf -- "$tmpDir" &>/dev/null
return 1
}
local sqlCount
sqlCount=$(find -- "$tmpDir" -type f -name "*.sql" | wc -l)
if [[ "$sqlCount" -ne 1 ]]; then
rm -rf -- "$tmpDir" &>/dev/null
appError "Archive must contain exactly one SQL file: $file"
return 1
fi
importFile=$(find -- "$tmpDir" -type f -name "*.sql" -print -quit)
;;
esac
local databaseName databaseUser databasePass
databaseName=$(siteConfigGetOrSet "$domain" "databaseName" "$(mariadbDomain2id "$domain")")
databaseUser=$(siteConfigGetOrSet "$domain" "databaseUser" "$(mariadbDomain2id "$domain")")
databasePass=$(siteConfigGetOrCreate "$domain" "databasePass")
local dbExists=0
if mariadbDatabaseExists "$databaseName"; then
dbExists=1
fi
local output rc
if (( dbExists == 0 )); then
if ! mariadbDatabaseUserSet "$databaseName" "$databaseUser" "$databasePass"; then
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
appError "Failed to create database/user"
return 1
fi
output=$(mariadbMasterImport "$databaseName" "$databaseUser" "$databasePass" "$importFile" 2>&1)
rc=$?
if [[ $rc -ne 0 ]]; then
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
appError "mariadbMasterImport: $output"
return 1
fi
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
return 0
fi
local ts tmpDb
ts=$(date +%Y%m%d_%H%M%S)
tmpDb="_test_${databaseName}_$ts"
if ! mariadbDatabaseUserSet "$tmpDb" "$databaseUser" "$databasePass"; then
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
appError "Failed to prepare tmp database"
return 1
fi
output=$(mariadbMasterImport "$tmpDb" "$databaseUser" "$databasePass" "$importFile" 2>&1)
rc=$?
if [[ $rc -ne 0 ]]; then
mariadbDatabaseRemove "$tmpDb"
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
appError "Failed to import tmp database: $output"
return 1
fi
if ! mariadbDatabaseRemove "$databaseName"; then
mariadbDatabaseRemove "$tmpDb"
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
appError "Failed to recreate target database: remove failed"
return 1
fi
if ! mariadbDatabaseUserSet "$databaseName" "$databaseUser" "$databasePass"; then
mariadbDatabaseRemove "$tmpDb"
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
appError "Failed to recreate target database: create failed"
return 1
fi
output=$(mariadbMasterImport "$databaseName" "$databaseUser" "$databasePass" "$importFile" 2>&1)
rc=$?
if [[ $rc -ne 0 ]]; then
mariadbDatabaseRemove "$tmpDb"
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
appError "Failed to import database: $output"
return 1
fi
mariadbDatabaseRemove "$tmpDb"
[[ -n "$tmpDir" ]] && rm -rf -- "$tmpDir" &>/dev/null
return 0
}
# Restores selected parts of a site from explicit source paths.
#
# Each source is optional. If a source path is provided, the corresponding part
# of the site is restored. If a source path is empty, that part is skipped.
#
# $1 (domain): Site domain name.
# $2 (sourceFiles): Optional source directory or archive file path with site files.
# $3 (sourceDatabase): Optional source SQL file or archive file path with database dump.
# $4 (sourceConf): Optional source OpenLiteSpeed virtual host config file path.
#
# Returns:
# 0 on success, 1 on validation or restore failure.
function restoreSite() {
local domain error
domain=$(domainPrepare "$1")
if ! runError error domainCheck "$domain"; then
printDanger "$siteLabel $error"
return 1
fi
local sourceFiles="$2"
local sourceDatabase="$3"
local sourceConf="$4"
# Files
printInfo "$restoreLabel $domain | Files: $sourceFiles"
if [[ -n "$sourceFiles" ]]; then
if ! runError error restoreSiteFiles "$domain" "$sourceFiles"; then
printDanger "$restoreLabel $domain | Files: $error"
return 1
fi
printSuccess "$restoreLabel $domain | Files: Restoration complete"
else
printWarning "$restoreLabel $domain | Files: Skip restoring"
fi
# Database
printInfo "$restoreLabel $domain | Database: $sourceDatabase"
if [[ -n "$sourceDatabase" ]]; then
if ! runError error restoreSiteDatabase "$domain" "$sourceDatabase"; then
printDanger "$restoreLabel $domain | Database: $error"
return 1
fi
printSuccess "$restoreLabel $domain | Database: Restoration complete"
else
printWarning "$restoreLabel $domain | Database: Skip restoring"
fi
# Config
printInfo "$restoreLabel $domain | Config: $sourceConf"
if [[ -n "$sourceConf" ]]; then
if ! runError error cp -f -- "$sourceConf" "$olsVhostsConfigPath/$domain.conf"; then
printDanger "$restoreLabel $domain | Config: $error"
return 1
fi
printSuccess "$restoreLabel $domain | Config: Restoration complete"
else
printWarning "$restoreLabel $domain | Config: Skip restoring"
fi
return 0
}
# Restores a site from available backup entries.
#
# The function searches short-term and long-term backup paths for files,
# database dumps, and OpenLiteSpeed virtual host configuration files matching
# the specified domain. It can list available backups or restore a selected one.
#
# Supported index values:
# empty: Show available backups and ask for a backup number.
# list: Print available backup paths.
# last: Restore the latest available backup.
# full: Restore the latest backup that contains files, database, and config.
# auto: Restore the latest full backup if available, otherwise the latest backup.
# N: Restore backup by numeric index.
#
# $1 (domain): Site domain name.
# $2 (index): Optional backup selector: list, last, full, auto, or numeric index.
#
# Returns:
# 0 on success, 1 on validation, selection, or restore failure.
function restoreRun() {
local domain error
domain=$(domainPrepare "$1")
if ! runError error domainCheck "$domain"; then
printDanger "$siteLabel $error"
return 1
fi
local index="$2"
if [[ -n "$index" && ! "$index" =~ ^[0-9]+$ && "$index" != "auto" && "$index" != "last" && "$index" != "full" && "$index" != "list" ]]; then
printDanger "Unknown value of index"
return 1
fi
local backupEntryList=()
local backupPathList=()
local path
shopt -s nullglob
local entryList=("$backupDailyPath"/*/*/"$domain"*)
shopt -u nullglob
for entry in "${entryList[@]}"; do
backupEntryList+=("$entry")
path=2:$(dirname -- "$entry")
if ! arrayContains "$path" "${backupPathList[@]}"; then
backupPathList+=("$path")
fi
done
shopt -s nullglob
local entryList=("$backupArchivePath"/*/*/"$domain"*)
shopt -u nullglob
for entry in "${entryList[@]}"; do
backupEntryList+=("$entry")
path=1:$(dirname -- "$entry")
if ! arrayContains "$path" "${backupPathList[@]}"; then
backupPathList+=("$path")
fi
done
local backupSortList=($(printf "%s\n" "${backupPathList[@]}" | \
sed "s/\/${backupFirstDir}$/\/./" | \
sort -r | \
sed "s/\/.$/\/${backupFirstDir}/"))
local bType bPath bTime bDate sourceList indexFull suffix
local counter=1
for marker in "${backupSortList[@]}"; do
bType="${marker:0:1}"
bPath="${marker:2}"
bTime=$(basename "$bPath")
bDate=$(basename -- $(dirname -- "$bPath"))
sourceList=()
if arrayContains "$bPath/$domain" "${backupEntryList[@]}"; then
sourceList+=('files:dir')
fi
if arrayContains "$bPath/$domain.tar.gz" "${backupEntryList[@]}"; then
sourceList+=('files:tar')
fi
if arrayContains "$bPath/$domain.tgz" "${backupEntryList[@]}"; then
sourceList+=('files:tar')
fi
if arrayContains "$bPath/$domain.zip" "${backupEntryList[@]}"; then
sourceList+=('files:zip')
fi
if arrayContains "$bPath/$domain.sql" "${backupEntryList[@]}"; then
sourceList+=('db:sql')
fi
if arrayContains "$bPath/$domain.sql.tar.gz" "${backupEntryList[@]}"; then
sourceList+=('db:tar')
fi
if arrayContains "$bPath/$domain.sql.tgz" "${backupEntryList[@]}"; then
sourceList+=('db:tar')
fi
if arrayContains "$bPath/$domain.sql.zip" "${backupEntryList[@]}"; then
sourceList+=('db:zip')
fi
if arrayContains "$bPath/$domain.conf" "${backupEntryList[@]}"; then
sourceList+=('conf')
fi
if arrayContains "conf" "${sourceList[@]}" && \
( arrayContains "db:sql" "${sourceList[@]}" || arrayContains "db:tar" "${sourceList[@]}" || arrayContains "db:zip" "${sourceList[@]}" ) && \
( arrayContains "files:dir" "${sourceList[@]}" || arrayContains "files:tar" "${sourceList[@]}" || arrayContains "files:zip" "${sourceList[@]}" ); then
suffix="\033[34m${sourceList[*]}\033[0m"
if [[ -z "$indexFull" ]]; then
indexFull="$counter"
fi
else
suffix="${sourceList[*]}"
fi
if [[ "$bType" == "1" ]]; then
suffix+=" | \033[33mLongTerm\033[0m"
fi
if [[ -z "$index" ]]; then
printf "%2s: %-19s | $suffix\n" "$counter" "$bDate/$bTime"
fi
((counter++))
done
local indexSelect=
case "$index" in
list)
for marker in "${backupSortList[@]}"; do
printf "%s\n" "${marker:2}"
done
return 0
;;
last)
indexSelect=1
;;
full)
indexSelect="$indexFull"
;;
auto)
if [[ -n "$indexFull" ]]; then
indexSelect="$indexFull"
else
indexSelect=1
fi
;;
[0-9]*)
indexSelect="$index"
;;
*)
printWarning "$domain" "Warning! The relevant data will be cleared before restoring."
read -p "Specify the backup number: " indexSelect
;;
esac
((indexSelect--)) 2>/dev/null
if [[ "$indexSelect" -lt 0 || "$indexSelect" -ge "${#backupSortList[@]}" ]]; then
printDanger "Unknown index number"
return 1
fi
local restorePath="${backupSortList[$indexSelect]:2}"
if [[ ! -d "$restorePath" ]]; then
printDanger "Path for restore not found"
return 1
fi
local sourceFiles sourceDatabase sourceConf
if [[ -d "$restorePath/$domain" ]]; then
sourceFiles="$restorePath/$domain"
elif [[ -f "$restorePath/$domain.tar.gz" ]]; then
sourceFiles="$restorePath/$domain.tar.gz"
elif [[ -f "$restorePath/$domain.tgz" ]]; then
sourceFiles="$restorePath/$domain.tgz"
elif [[ -f "$restorePath/$domain.zip" ]]; then
sourceFiles="$restorePath/$domain.zip"
fi
if [[ -f "$restorePath/$domain.sql" ]]; then
sourceDatabase="$restorePath/$domain.sql"
elif [[ -f "$restorePath/$domain.sql.tar.gz" ]]; then
sourceDatabase="$restorePath/$domain.sql.tar.gz"
elif [[ -f "$restorePath/$domain.sql.tgz" ]]; then
sourceDatabase="$restorePath/$domain.sql.tgz"
elif [[ -f "$restorePath/$domain.sql.zip" ]]; then
sourceDatabase="$restorePath/$domain.sql.zip"
fi
if [[ -f "$restorePath/$domain.conf" ]]; then
sourceConf="$restorePath/$domain.conf"
fi
restoreSite "$domain" "$sourceFiles" "$sourceDatabase" "$sourceConf"
# Clean Redis
redisDomainClean "$domain"
# Rebuild config site
cmdSiteConfigRebuild "$domain"
# Rebuild config Wordpress
wordpressConfigRebuild "$domain"
return 0
}