474 lines
15 KiB
Bash
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
|
|
}
|