301 lines
9.0 KiB
Bash
301 lines
9.0 KiB
Bash
#!/bin/bash
|
|
set -Eeuo pipefail
|
|
|
|
ERROR_MSG=""
|
|
RESTORE_OK=0
|
|
DA_USER=""
|
|
DB_NAME=""
|
|
JOB_ID=""
|
|
LOG_FILE=""
|
|
RESTORE_MODE=""
|
|
SOURCE_DB_NAME=""
|
|
SOURCE_FILE=""
|
|
RESTORE_ROLLBACK_FILE=""
|
|
RESTORE_ROLLBACK_CREATED=0
|
|
|
|
JOB_FILE="${1:-}"
|
|
if [ -z "$JOB_FILE" ] || [ ! -f "$JOB_FILE" ]; then
|
|
echo "mysql: missing job file" >&2
|
|
exit 1
|
|
fi
|
|
|
|
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
CONFIG_FILE="${PLUGIN_DIR}/plugin-settings.conf"
|
|
LOG_DIR="${PLUGIN_DIR}/data/logs"
|
|
COMMON_FILE="${PLUGIN_DIR}/scripts/setup/mysql_common.sh"
|
|
mkdir -p "$LOG_DIR"
|
|
|
|
urlencode() {
|
|
local s="$1"
|
|
local out=""
|
|
local i c
|
|
for ((i=0; i<${#s}; i++)); do
|
|
c="${s:i:1}"
|
|
case "$c" in
|
|
[a-zA-Z0-9.~_-]) out+="$c" ;;
|
|
' ') out+='%20' ;;
|
|
*) printf -v c '%%%02X' "'$c"; out+="$c" ;;
|
|
esac
|
|
done
|
|
printf '%s' "$out"
|
|
}
|
|
|
|
notify_user() {
|
|
local code="$1"
|
|
if [ -z "${DA_USER:-}" ]; then
|
|
return 0
|
|
fi
|
|
|
|
local subject message
|
|
if [ "$code" -eq 0 ] && [ "$RESTORE_OK" -eq 1 ]; then
|
|
subject="Przywracanie bazy MySQL zakończone pomyślnie"
|
|
message="Przywracanie bazy ${DB_NAME} z pliku ${SOURCE_FILE:-nieznany} zakończone."
|
|
else
|
|
subject="Błąd przywracania bazy MySQL"
|
|
if [ -n "$ERROR_MSG" ]; then
|
|
message="Przywracanie bazy ${DB_NAME} nie powiodło się: ${ERROR_MSG}"
|
|
else
|
|
message="Przywracanie bazy ${DB_NAME} nie powiodło się. Sprawdź log zadania."
|
|
fi
|
|
fi
|
|
|
|
local task_queue="/usr/local/directadmin/data/task.queue"
|
|
local users="select1%3D$(urlencode "$DA_USER")"
|
|
local line="action=notify&value=users&subject=$(urlencode "$subject")&message=$(urlencode "$message")&users=${users}"
|
|
printf "%s\n" "$line" >> "$task_queue"
|
|
}
|
|
|
|
fail() {
|
|
local msg="$1"
|
|
ERROR_MSG="$msg"
|
|
if [ -n "${LOG_FILE:-}" ]; then
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${msg}" >> "$LOG_FILE"
|
|
fi
|
|
}
|
|
|
|
log_unexpected_error() {
|
|
local line_no="$1"
|
|
local command="$2"
|
|
local code="$3"
|
|
if [ -z "${LOG_FILE:-}" ]; then
|
|
return 0
|
|
fi
|
|
if [ -n "${ERROR_MSG:-}" ]; then
|
|
return 0
|
|
fi
|
|
|
|
ERROR_MSG="Nieoczekiwany błąd wykonywania przywracania (kod ${code}, linia ${line_no})."
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${ERROR_MSG} CMD: ${command}" >> "$LOG_FILE"
|
|
}
|
|
|
|
load_plugin_settings() {
|
|
if [ ! -f "$CONFIG_FILE" ]; then
|
|
fail "Brak pliku konfiguracyjnego pluginu: $CONFIG_FILE"
|
|
exit 1
|
|
fi
|
|
# shellcheck disable=SC1090
|
|
source "$CONFIG_FILE"
|
|
}
|
|
|
|
sanitize_mysql_dump() {
|
|
awk '
|
|
function lower(s) { return tolower(s) }
|
|
{
|
|
line = $0
|
|
l = lower(line)
|
|
if (l ~ /^[[:space:]]*create[[:space:]]+database[[:space:]]+/) { next }
|
|
if (l ~ /^[[:space:]]*drop[[:space:]]+database[[:space:]]+/) { next }
|
|
if (l ~ /^[[:space:]]*use[[:space:]]+[`"]/ ) { next }
|
|
if (l ~ /^[[:space:]]*use[[:space:]]+[a-z0-9_]+[[:space:]]*;/) { next }
|
|
print line
|
|
}
|
|
'
|
|
}
|
|
|
|
preclean_database() {
|
|
local db_name="$1"
|
|
local current_charset=""
|
|
local current_collation=""
|
|
|
|
current_charset="$(mysql_exec mysql -Nse "SELECT DEFAULT_CHARACTER_SET_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = '${db_name}'" 2>>"$LOG_FILE" || true)"
|
|
current_collation="$(mysql_exec mysql -Nse "SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = '${db_name}'" 2>>"$LOG_FILE" || true)"
|
|
[ -n "$current_charset" ] || current_charset="utf8mb4"
|
|
[ -n "$current_collation" ] || current_collation="utf8mb4_unicode_ci"
|
|
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Recreating database ${db_name} before restore" >> "$LOG_FILE"
|
|
mysql_exec mysql -e "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$db_name");" >> "$LOG_FILE" 2>&1
|
|
mysql_exec mysql -e "CREATE DATABASE $(mysql_quote_identifier "$db_name") CHARACTER SET ${current_charset} COLLATE ${current_collation};" >> "$LOG_FILE" 2>&1
|
|
}
|
|
|
|
cleanup_restore_rollback() {
|
|
if [ -n "${RESTORE_ROLLBACK_FILE:-}" ] && [ -f "$RESTORE_ROLLBACK_FILE" ]; then
|
|
rm -f "$RESTORE_ROLLBACK_FILE"
|
|
fi
|
|
}
|
|
|
|
backup_alt_database_for_rollback() {
|
|
local db_name="$1"
|
|
RESTORE_ROLLBACK_FILE="$(mktemp)"
|
|
RESTORE_ROLLBACK_CREATED=0
|
|
|
|
if [ -z "$(mysql_exec mysql -Nse "SELECT 1 FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = '$(mysql_escape_literal "$db_name")' LIMIT 1" 2>>"$LOG_FILE" || true)" ]; then
|
|
rm -f "$RESTORE_ROLLBACK_FILE"
|
|
RESTORE_ROLLBACK_FILE=""
|
|
return 0
|
|
fi
|
|
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Creating rollback dump for ${db_name}" >> "$LOG_FILE"
|
|
if mysqldump_exec \
|
|
--single-transaction \
|
|
--quick \
|
|
--skip-lock-tables \
|
|
--routines \
|
|
--triggers \
|
|
--events \
|
|
--default-character-set=utf8mb4 \
|
|
"$db_name" > "$RESTORE_ROLLBACK_FILE" 2>>"$LOG_FILE"; then
|
|
RESTORE_ROLLBACK_CREATED=1
|
|
return 0
|
|
fi
|
|
|
|
rm -f "$RESTORE_ROLLBACK_FILE"
|
|
RESTORE_ROLLBACK_FILE=""
|
|
return 1
|
|
}
|
|
|
|
restore_alt_database_from_rollback() {
|
|
local db_name="$1"
|
|
if [ "$RESTORE_ROLLBACK_CREATED" -ne 1 ] || [ -z "${RESTORE_ROLLBACK_FILE:-}" ] || [ ! -r "$RESTORE_ROLLBACK_FILE" ]; then
|
|
return 0
|
|
fi
|
|
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Restoring rollback dump for ${db_name}" >> "$LOG_FILE"
|
|
mysql_exec mysql -e "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$db_name");" >> "$LOG_FILE" 2>&1 || true
|
|
mysql_exec mysql -e "CREATE DATABASE $(mysql_quote_identifier "$db_name") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" >> "$LOG_FILE" 2>&1 || true
|
|
mysql_exec "$db_name" < "$RESTORE_ROLLBACK_FILE" >> "$LOG_FILE" 2>&1 || {
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: rollback restore failed for ${db_name}" >> "$LOG_FILE"
|
|
return 1
|
|
}
|
|
}
|
|
|
|
quote_sh_value() {
|
|
local value="$1"
|
|
value="${value//\'/\'\\\'\'}"
|
|
printf "'%s'" "$value"
|
|
}
|
|
|
|
set_job_var() {
|
|
local key="$1"
|
|
local value="$2"
|
|
local tmp
|
|
local owner_group=""
|
|
if [ -f "$JOB_FILE" ]; then
|
|
owner_group="$(stat -c '%u:%g' "$JOB_FILE" 2>/dev/null || stat -f '%u:%g' "$JOB_FILE" 2>/dev/null || true)"
|
|
fi
|
|
tmp="$(mktemp)"
|
|
grep -v "^${key}=" "$JOB_FILE" > "$tmp" 2>/dev/null || true
|
|
printf "%s=%s\n" "$key" "$(quote_sh_value "$value")" >> "$tmp"
|
|
mv "$tmp" "$JOB_FILE"
|
|
chmod 600 "$JOB_FILE" 2>/dev/null || true
|
|
if [ "$(id -u)" -eq 0 ] && [ -n "$owner_group" ]; then
|
|
chown "$owner_group" "$JOB_FILE" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
on_exit() {
|
|
local code="$1"
|
|
if [ "$code" -ne 0 ] && [ -z "${ERROR_MSG:-}" ] && [ -n "${LOG_FILE:-}" ]; then
|
|
ERROR_MSG="Zadanie przywracania zakończyło się błędem (kod ${code}) bez zarejestrowanego szczegółu."
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${ERROR_MSG}" >> "$LOG_FILE"
|
|
fi
|
|
notify_user "$code"
|
|
}
|
|
trap 'log_unexpected_error "$LINENO" "$BASH_COMMAND" "$?"' ERR
|
|
trap cleanup_restore_rollback INT TERM
|
|
trap 'code=$?; cleanup_restore_rollback; on_exit "$code"' EXIT
|
|
|
|
[ -f "$COMMON_FILE" ] || {
|
|
echo "Brak helpera MySQL: $COMMON_FILE" >&2
|
|
exit 1
|
|
}
|
|
# shellcheck disable=SC1090
|
|
source "$COMMON_FILE"
|
|
|
|
# shellcheck disable=SC1090
|
|
source "$JOB_FILE"
|
|
|
|
JOB_ID="${JOB_ID:-$(basename "$JOB_FILE" .env)}"
|
|
DA_USER="${DA_USER:-}"
|
|
DB_NAME="${DB_NAME:-}"
|
|
RESTORE_MODE="${RESTORE_MODE:-overwrite}"
|
|
SOURCE_DB_NAME="${SOURCE_DB_NAME:-}"
|
|
SOURCE_FILE="${SOURCE_FILE:-}"
|
|
|
|
LOG_FILE="${LOG_DIR}/${JOB_ID}.log"
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Start restore job ${JOB_ID}" > "$LOG_FILE"
|
|
|
|
if [ -z "$DA_USER" ] || [ -z "$DB_NAME" ] || [ -z "$SOURCE_FILE" ]; then
|
|
fail "Brak DA_USER, DB_NAME lub SOURCE_FILE w pliku zadania."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$DB_NAME" != "${DA_USER}_"* ]]; then
|
|
fail "Baza ${DB_NAME} nie należy do użytkownika ${DA_USER}."
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -r "$SOURCE_FILE" ]; then
|
|
fail "Nie można odczytać pliku źródłowego: ${SOURCE_FILE}"
|
|
exit 1
|
|
fi
|
|
|
|
load_plugin_settings
|
|
mysql_load_alt_credentials
|
|
MYSQL_BIN="$(mysql_alt_binary mysql)" || {
|
|
fail "Brak binarium mysql."
|
|
exit 1
|
|
}
|
|
|
|
if [ -z "$(mysql_exec mysql -Nse "SELECT 1 FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = '${DB_NAME}'" 2>>"$LOG_FILE" || true)" ]; then
|
|
fail "Docelowa baza danych nie istnieje: ${DB_NAME}"
|
|
exit 1
|
|
fi
|
|
|
|
if ! backup_alt_database_for_rollback "$DB_NAME"; then
|
|
fail "Nie udało się utworzyć kopii rollback przed przywracaniem bazy ${DB_NAME}."
|
|
exit 1
|
|
fi
|
|
|
|
if ! preclean_database "$DB_NAME"; then
|
|
restore_alt_database_from_rollback "$DB_NAME" || true
|
|
fail "Nie udało się przygotować bazy ${DB_NAME} do importu; przywrócono kopię rollback, jeśli była dostępna."
|
|
exit 1
|
|
fi
|
|
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Restore source file: ${SOURCE_FILE}" >> "$LOG_FILE"
|
|
if [ -n "$SOURCE_DB_NAME" ]; then
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Restore source DB label: ${SOURCE_DB_NAME}" >> "$LOG_FILE"
|
|
fi
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Restore target DB: ${DB_NAME}" >> "$LOG_FILE"
|
|
|
|
set +e
|
|
if [[ "$SOURCE_FILE" == *.gz ]]; then
|
|
gunzip -c "$SOURCE_FILE" | sanitize_mysql_dump | mysql_exec "$DB_NAME" >> "$LOG_FILE" 2>&1
|
|
RC=${PIPESTATUS[2]}
|
|
else
|
|
sanitize_mysql_dump < "$SOURCE_FILE" | mysql_exec "$DB_NAME" >> "$LOG_FILE" 2>&1
|
|
RC=${PIPESTATUS[1]}
|
|
fi
|
|
set -e
|
|
|
|
if [ "$RC" -ne 0 ]; then
|
|
restore_alt_database_from_rollback "$DB_NAME" || true
|
|
fail "Import pliku ${SOURCE_FILE} do bazy ${DB_NAME} zakończył się błędem."
|
|
exit 1
|
|
fi
|
|
|
|
set_job_var "END_TS" "$(date +%s)"
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Restore completed successfully." >> "$LOG_FILE"
|
|
RESTORE_OK=1
|
|
exit 0
|