550
This commit is contained in:
@@ -0,0 +1,473 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user