Initial import of db-manager
This commit is contained in:
Executable
+437
@@ -0,0 +1,437 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
ERROR_MSG=""
|
||||
BACKUP_OK=0
|
||||
DA_USER=""
|
||||
LOG_FILE=""
|
||||
TMP_DB_USER="tmprestore"
|
||||
TMP_DB_PASS=""
|
||||
TMP_DB_ACTIVE=0
|
||||
SECURE_BACKUP="true"
|
||||
OWN_BACKUP_USER_RESTORE="true"
|
||||
|
||||
JOB_FILE="${1:-}"
|
||||
if [ -z "$JOB_FILE" ] || [ ! -f "$JOB_FILE" ]; then
|
||||
echo "db-manager: missing job file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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
|
||||
fi
|
||||
local subject message
|
||||
if [ "$code" -eq 0 ] && [ "$BACKUP_OK" -eq 1 ]; then
|
||||
subject="Backup bazy danych zakonczony pomyslnie"
|
||||
message="Backup bazy ${DB_NAME:-} zostal wykonany."
|
||||
else
|
||||
subject="Blad wykonywania backupu bazy danych"
|
||||
if [ -n "$ERROR_MSG" ]; then
|
||||
message="Blad backupu: ${ERROR_MSG}"
|
||||
else
|
||||
message="Blad backupu bazy ${DB_NAME:-}. Log: ${LOG_FILE:-brak}."
|
||||
fi
|
||||
if [ -n "${LOG_FILE:-}" ] && [ -f "$LOG_FILE" ]; then
|
||||
local log_content
|
||||
log_content="$(cat "$LOG_FILE")"
|
||||
message="${message}\n\nLog:\n${log_content}"
|
||||
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"
|
||||
}
|
||||
|
||||
db_lock_path_for_db() {
|
||||
local db_name="$1"
|
||||
local safe
|
||||
safe="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_.-' '_')"
|
||||
printf '%s/data/lock/%s.lock' "$PLUGIN_DIR" "$safe"
|
||||
}
|
||||
|
||||
release_db_lock() {
|
||||
local db_name lock_path
|
||||
db_name="${DB_NAME:-}"
|
||||
if [ -z "$db_name" ] || [ -z "${PLUGIN_DIR:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
lock_path="$(db_lock_path_for_db "$db_name")"
|
||||
if [ -f "$lock_path" ]; then
|
||||
rm -f "$lock_path" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
on_exit() {
|
||||
local code="$1"
|
||||
cleanup_tmp_db_user
|
||||
notify_user "$code"
|
||||
}
|
||||
trap 'on_exit $?' EXIT
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$JOB_FILE"
|
||||
|
||||
DA_USER="${DA_USER:-}"
|
||||
DB_NAME="${DB_NAME:-}"
|
||||
DATE_DIR="${DATE_DIR:-}"
|
||||
|
||||
if [ -z "$DA_USER" ] || [ -z "$DB_NAME" ]; then
|
||||
echo "Missing DA_USER or DB_NAME in job file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
JOB_BASE="$(basename "$JOB_FILE")"
|
||||
JOB_ID="${JOB_BASE%%.*}"
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CONFIG_FILE="${PLUGIN_DIR}/plugin-settings.conf"
|
||||
LOG_DIR="${PLUGIN_DIR}/data/logs"
|
||||
LOG_FILE="${LOG_DIR}/${JOB_ID}.log"
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
fail() {
|
||||
local msg="$1"
|
||||
ERROR_MSG="$msg"
|
||||
echo "$msg" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
fail "Missing ${CONFIG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
USER_SQL_BACKUP_DIR="${USER_SQL_BACKUP_DIR:-/home/da_user/mysql_backups/user_backups}"
|
||||
SECURE_BACKUP="${SECURE_BACKUP:-true}"
|
||||
OWN_BACKUP_USER_RESTORE="${OWN_BACKUP_USER_RESTORE:-true}"
|
||||
SECURE_BACKUP="$(echo "$SECURE_BACKUP" | tr '[:upper:]' '[:lower:]')"
|
||||
OWN_BACKUP_USER_RESTORE="$(echo "$OWN_BACKUP_USER_RESTORE" | tr '[:upper:]' '[:lower:]')"
|
||||
if [ "$SECURE_BACKUP" = "1" ] || [ "$SECURE_BACKUP" = "yes" ]; then
|
||||
SECURE_BACKUP="true"
|
||||
fi
|
||||
if [ "$OWN_BACKUP_USER_RESTORE" = "1" ] || [ "$OWN_BACKUP_USER_RESTORE" = "yes" ]; then
|
||||
OWN_BACKUP_USER_RESTORE="true"
|
||||
fi
|
||||
|
||||
apply_da_user() {
|
||||
local value="$1"
|
||||
value="${value//DA_USER/$DA_USER}"
|
||||
value="${value//da_user/$DA_USER}"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
enforce_root_backup_tree_ownership() {
|
||||
local root_dir="$1"
|
||||
if [ "$(id -u)" -ne 0 ] || [ -z "$root_dir" ] || [ -L "$root_dir" ]; then
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "$root_dir" 2>/dev/null || true
|
||||
find "$root_dir" \( -type d -o -type f \) -exec chown root:root {} + 2>/dev/null || true
|
||||
}
|
||||
|
||||
USER_SQL_BACKUP_DIR="$(apply_da_user "$USER_SQL_BACKUP_DIR")"
|
||||
TARGET_ROOT="$USER_SQL_BACKUP_DIR"
|
||||
MYSQL_BACKUPS_ROOT="$(apply_da_user "/home/da_user/mysql_backups")"
|
||||
enforce_root_backup_tree_ownership "$MYSQL_BACKUPS_ROOT"
|
||||
|
||||
if [[ "$DB_NAME" != "${DA_USER}_"* && "$DB_NAME" != "$DA_USER" ]]; then
|
||||
fail "DB_NAME does not belong to user"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
build_tmp_db_user() {
|
||||
local db_name="$1"
|
||||
local cleaned name
|
||||
cleaned="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_' '_')"
|
||||
name="dbtmp__${cleaned}"
|
||||
if [ "${#name}" -gt 32 ]; then
|
||||
name="${name:0:32}"
|
||||
fi
|
||||
if [ -z "$name" ]; then
|
||||
name="tmprestore"
|
||||
fi
|
||||
printf '%s' "$name"
|
||||
}
|
||||
|
||||
TMP_DB_USER="$(build_tmp_db_user "$DB_NAME")"
|
||||
|
||||
if [ -z "$DATE_DIR" ]; then
|
||||
DATE_DIR="$(date +%d-%m-%Y_%H_%M)"
|
||||
if [ -d "${TARGET_ROOT%/}/${DATE_DIR}" ]; then
|
||||
DATE_DIR="$(date +%d-%m-%Y_%H_%M_%S)"
|
||||
fi
|
||||
fi
|
||||
if [[ "$DATE_DIR" == *".."* || "$DATE_DIR" == */* ]]; then
|
||||
fail "Invalid DATE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TARGET_DIR="${TARGET_ROOT%/}/${DATE_DIR}"
|
||||
mkdir -p "$TARGET_DIR"
|
||||
chmod 700 "$TARGET_DIR" || true
|
||||
MARKER_FILE="${TARGET_DIR}/.db-manager-owned"
|
||||
{
|
||||
echo "OWNER=db-manager"
|
||||
echo "DA_USER=${DA_USER}"
|
||||
echo "DATE_DIR=${DATE_DIR}"
|
||||
echo "CREATED_TS=$(date +%s)"
|
||||
} > "$MARKER_FILE"
|
||||
chmod 600 "$MARKER_FILE" 2>/dev/null || true
|
||||
if [ "$(id -u)" -eq 0 ] && [ "$OWN_BACKUP_USER_RESTORE" = "true" ]; then
|
||||
chown root:root "$TARGET_DIR" "$MARKER_FILE" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
TARGET_FILE="${TARGET_DIR}/${DB_NAME}.sql.gz"
|
||||
if [ -e "$TARGET_FILE" ]; then
|
||||
IDX=1
|
||||
while [ -e "${TARGET_DIR}/${DB_NAME}_${IDX}.sql.gz" ]; do
|
||||
IDX=$((IDX+1))
|
||||
done
|
||||
TARGET_FILE="${TARGET_DIR}/${DB_NAME}_${IDX}.sql.gz"
|
||||
fi
|
||||
|
||||
MYSQL_CONF="/usr/local/directadmin/conf/mysql.conf"
|
||||
if [ ! -f "$MYSQL_CONF" ]; then
|
||||
fail "Missing ${MYSQL_CONF}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MYSQL_USER=""
|
||||
MYSQL_PASS=""
|
||||
MYSQL_HOST=""
|
||||
MYSQL_PORT=""
|
||||
MYSQL_SOCKET=""
|
||||
while IFS='=' read -r key val; do
|
||||
key="${key%%[[:space:]]*}"
|
||||
val="${val##[[:space:]]}"
|
||||
case "$key" in
|
||||
user) MYSQL_USER="$val" ;;
|
||||
passwd) MYSQL_PASS="$val" ;;
|
||||
host) MYSQL_HOST="$val" ;;
|
||||
port) MYSQL_PORT="$val" ;;
|
||||
socket) MYSQL_SOCKET="$val" ;;
|
||||
esac
|
||||
done < <(grep -v '^[[:space:]]*#' "$MYSQL_CONF" | grep -v '^[[:space:]]*$')
|
||||
|
||||
MYSQL_OPTS=()
|
||||
if [ -n "$MYSQL_USER" ]; then MYSQL_OPTS+=("-u${MYSQL_USER}"); fi
|
||||
if [ -n "$MYSQL_PASS" ]; then MYSQL_OPTS+=("-p${MYSQL_PASS}"); fi
|
||||
if [ -n "$MYSQL_HOST" ]; then MYSQL_OPTS+=("-h${MYSQL_HOST}"); fi
|
||||
if [ -n "$MYSQL_PORT" ] && [ "$MYSQL_PORT" != "0" ]; then MYSQL_OPTS+=("-P${MYSQL_PORT}"); fi
|
||||
if [ -n "$MYSQL_SOCKET" ]; then MYSQL_OPTS+=("--socket=${MYSQL_SOCKET}"); fi
|
||||
|
||||
PATH="/usr/local/mysql/bin:/usr/local/mariadb/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}"
|
||||
export PATH
|
||||
|
||||
resolve_bin() {
|
||||
local name path
|
||||
for name in "$@"; do
|
||||
if path="$(command -v "$name" 2>/dev/null)"; then
|
||||
printf '%s' "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
sql_escape() {
|
||||
local v="$1"
|
||||
v="${v//\\/\\\\}"
|
||||
v="${v//\'/\\\'}"
|
||||
printf '%s' "$v"
|
||||
}
|
||||
|
||||
random_password() {
|
||||
rand_chars() {
|
||||
local charset="$1"
|
||||
local need="$2"
|
||||
local out="" chunk
|
||||
while [ "${#out}" -lt "$need" ]; do
|
||||
chunk="$(LC_ALL=C tr -dc "$charset" </dev/urandom 2>/dev/null | head -c $((need - ${#out})) || true)"
|
||||
if [ -z "$chunk" ]; then
|
||||
break
|
||||
fi
|
||||
out+="$chunk"
|
||||
done
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
local lower='abcdefghijklmnopqrstuvwxyz'
|
||||
local upper='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
local digits='0123456789'
|
||||
local special='-'
|
||||
local all="${lower}${upper}${digits}${special}"
|
||||
local seed extra mixed out tmp
|
||||
local -a chars=()
|
||||
local i j
|
||||
|
||||
seed="$(rand_chars "$lower" 1)$(rand_chars "$upper" 1)$(rand_chars "$digits" 1)$(rand_chars "$special" 1)"
|
||||
extra="$(rand_chars "$all" 20)"
|
||||
mixed="${seed}${extra}"
|
||||
|
||||
if [ "${#mixed}" -lt 24 ]; then
|
||||
mixed="aA1-"
|
||||
while [ "${#mixed}" -lt 24 ]; do
|
||||
mixed="${mixed}aA1-"
|
||||
done
|
||||
mixed="${mixed:0:24}"
|
||||
fi
|
||||
|
||||
for ((i=0; i<${#mixed}; i++)); do
|
||||
chars+=("${mixed:i:1}")
|
||||
done
|
||||
for ((i=${#chars[@]}-1; i>0; i--)); do
|
||||
j=$((RANDOM % (i + 1)))
|
||||
tmp="${chars[i]}"
|
||||
chars[i]="${chars[j]}"
|
||||
chars[j]="$tmp"
|
||||
done
|
||||
|
||||
out=""
|
||||
for tmp in "${chars[@]}"; do
|
||||
out+="$tmp"
|
||||
done
|
||||
printf '%s' "${out:0:24}"
|
||||
}
|
||||
|
||||
cleanup_tmp_db_user() {
|
||||
if [ "${TMP_DB_ACTIVE:-0}" -ne 1 ]; then
|
||||
return
|
||||
fi
|
||||
if [ -z "${MYSQL_BIN:-}" ]; then
|
||||
return
|
||||
fi
|
||||
local user_esc
|
||||
user_esc="$(sql_escape "$TMP_DB_USER")"
|
||||
local sql="
|
||||
DROP USER IF EXISTS '${user_esc}'@'localhost';
|
||||
DROP USER IF EXISTS '${user_esc}'@'127.0.0.1';
|
||||
FLUSH PRIVILEGES;"
|
||||
set +e
|
||||
"${MYSQL_BIN}" "${MYSQL_OPTS[@]}" -e "$sql" >>"$LOG_FILE" 2>&1
|
||||
set -e
|
||||
TMP_DB_ACTIVE=0
|
||||
}
|
||||
|
||||
create_tmp_db_user_for_backup() {
|
||||
TMP_DB_PASS="$(random_password)"
|
||||
local user_esc pass_esc db_esc sql
|
||||
user_esc="$(sql_escape "$TMP_DB_USER")"
|
||||
pass_esc="$(sql_escape "$TMP_DB_PASS")"
|
||||
db_esc="${DB_NAME//\`/\`\`}"
|
||||
sql="
|
||||
DROP USER IF EXISTS '${user_esc}'@'localhost';
|
||||
DROP USER IF EXISTS '${user_esc}'@'127.0.0.1';
|
||||
CREATE USER '${user_esc}'@'localhost' IDENTIFIED BY '${pass_esc}';
|
||||
CREATE USER '${user_esc}'@'127.0.0.1' IDENTIFIED BY '${pass_esc}';
|
||||
GRANT SELECT, SHOW VIEW, EVENT, TRIGGER, LOCK TABLES ON \`${db_esc}\`.* TO '${user_esc}'@'localhost';
|
||||
GRANT SELECT, SHOW VIEW, EVENT, TRIGGER, LOCK TABLES ON \`${db_esc}\`.* TO '${user_esc}'@'127.0.0.1';
|
||||
FLUSH PRIVILEGES;"
|
||||
set +e
|
||||
"${MYSQL_BIN}" "${MYSQL_OPTS[@]}" -e "$sql" >>"$LOG_FILE" 2>&1
|
||||
local rc=$?
|
||||
set -e
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
TMP_DB_ACTIVE=1
|
||||
return 0
|
||||
}
|
||||
|
||||
if ! MYSQLDUMP_BIN="$(resolve_bin mysqldump mariadb-dump)"; then
|
||||
fail "mysqldump client not found (checked: mysqldump, mariadb-dump; PATH=${PATH})"
|
||||
exit 1
|
||||
fi
|
||||
if ! MYSQL_BIN="$(resolve_bin mysql mariadb)"; then
|
||||
fail "mysql client not found (checked: mysql, mariadb; PATH=${PATH})"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MYSQL_ADMIN_CMD=("$MYSQL_BIN" "${MYSQL_OPTS[@]}" -N -B)
|
||||
MYSQL_TABLES_CMD=("${MYSQL_ADMIN_CMD[@]}")
|
||||
MYSQLDUMP_CONN_OPTS=("${MYSQL_OPTS[@]}")
|
||||
if [ "$SECURE_BACKUP" = "true" ]; then
|
||||
if ! create_tmp_db_user_for_backup; then
|
||||
fail "Nie udalo sie utworzyc tymczasowego uzytkownika bazy dla backupu."
|
||||
exit 1
|
||||
fi
|
||||
MYSQLDUMP_CONN_OPTS=("-u${TMP_DB_USER}" "-p${TMP_DB_PASS}")
|
||||
if [ -n "$MYSQL_SOCKET" ]; then
|
||||
MYSQLDUMP_CONN_OPTS+=("--socket=${MYSQL_SOCKET}" "-hlocalhost")
|
||||
else
|
||||
MYSQLDUMP_CONN_OPTS+=("-h127.0.0.1")
|
||||
if [ -n "$MYSQL_PORT" ] && [ "$MYSQL_PORT" != "0" ]; then
|
||||
MYSQLDUMP_CONN_OPTS+=("-P${MYSQL_PORT}")
|
||||
fi
|
||||
fi
|
||||
MYSQL_TABLES_CMD=("$MYSQL_BIN" "${MYSQLDUMP_CONN_OPTS[@]}" -N -B)
|
||||
fi
|
||||
MYSQLDUMP_CMD=("$MYSQLDUMP_BIN" "${MYSQLDUMP_CONN_OPTS[@]}" --single-transaction --routines --events --triggers --verbose --databases "$DB_NAME")
|
||||
|
||||
echo "Rozpoczynam backup bazy: ${DB_NAME}" >> "$LOG_FILE"
|
||||
echo "Tryb backupu: verbose" >> "$LOG_FILE"
|
||||
if [ "$SECURE_BACKUP" = "true" ]; then
|
||||
echo "Tryb bezpieczny backupu: ON (tymczasowy uzytkownik bazy)" >> "$LOG_FILE"
|
||||
else
|
||||
echo "Tryb bezpieczny backupu: OFF" >> "$LOG_FILE"
|
||||
fi
|
||||
DB_NAME_BT="${DB_NAME//\`/\`\`}"
|
||||
set +e
|
||||
TABLES_RAW="$("${MYSQL_TABLES_CMD[@]}" -e "SHOW TABLES FROM \`${DB_NAME_BT}\`" 2>>"$LOG_FILE")"
|
||||
TABLES_CODE=$?
|
||||
set -e
|
||||
if [ "$TABLES_CODE" -eq 0 ]; then
|
||||
if [ -n "$TABLES_RAW" ]; then
|
||||
echo "Tabele do backupu:" >> "$LOG_FILE"
|
||||
while IFS= read -r tbl; do
|
||||
[ -z "$tbl" ] && continue
|
||||
echo " - ${tbl}" >> "$LOG_FILE"
|
||||
done <<< "$TABLES_RAW"
|
||||
else
|
||||
echo "Baza nie zawiera tabel." >> "$LOG_FILE"
|
||||
fi
|
||||
else
|
||||
echo "Nie udalo sie pobrac listy tabel dla ${DB_NAME}." >> "$LOG_FILE"
|
||||
fi
|
||||
|
||||
set +e
|
||||
${MYSQLDUMP_CMD[@]} 2>>"$LOG_FILE" | gzip -c > "$TARGET_FILE"
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
if [ "$EXIT_CODE" -ne 0 ]; then
|
||||
rm -f "$TARGET_FILE"
|
||||
fail "Backup bazy ${DB_NAME} zakonczony bledem."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(id -u)" -eq 0 ] && [ "$OWN_BACKUP_USER_RESTORE" = "true" ]; then
|
||||
chmod 700 "$TARGET_DIR" 2>/dev/null || true
|
||||
chmod 600 "$TARGET_FILE" "$MARKER_FILE" 2>/dev/null || true
|
||||
chown root:root "$TARGET_DIR" "$TARGET_FILE" "$MARKER_FILE" 2>/dev/null || true
|
||||
else
|
||||
chown "${DA_USER}:${DA_USER}" "$TARGET_FILE" 2>/dev/null || true
|
||||
fi
|
||||
enforce_root_backup_tree_ownership "$MYSQL_BACKUPS_ROOT"
|
||||
echo "Backup zapisany: ${TARGET_FILE}" >> "$LOG_FILE"
|
||||
BACKUP_OK=1
|
||||
exit 0
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CPIF="/usr/local/directadmin/data/admin/custom_package_items.conf"
|
||||
CPIL='db_manager=type=checkbox&string=Włącz zarządzanie backupami baz danych&desc=Włącz zarządzanie backupami baz danych&default=no'
|
||||
|
||||
plugin_always_enabled() {
|
||||
local settings_file="$PLUGIN_DIR/plugin-settings.conf"
|
||||
local line key value
|
||||
|
||||
[ -r "$settings_file" ] || return 1
|
||||
while IFS= read -r line; do
|
||||
line="${line%%#*}"
|
||||
key="${line%%=*}"
|
||||
value="${line#*=}"
|
||||
key="$(printf '%s' "$key" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
|
||||
if [ "$key" != "always_enable" ]; then
|
||||
continue
|
||||
fi
|
||||
value="$(printf '%s' "$value" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]\"'"'"'')"
|
||||
case "$value" in
|
||||
1|true|yes|y|on) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
done < "$settings_file"
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
bootstrap_custom_package_item() {
|
||||
local install_cpif=0
|
||||
local count
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "db-manager: warning: not running as root, cannot update $CPIF" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -f "$CPIF" ]; then
|
||||
sed -i '/^db-manager=/d' "$CPIF" || true
|
||||
sed -i '/^db_manager=/d' "$CPIF" || true
|
||||
count="$(grep -m1 -c '^db_manager=' "$CPIF" || true)"
|
||||
if [ "${count:-0}" -eq 0 ] && ! plugin_always_enabled; then
|
||||
install_cpif=1
|
||||
fi
|
||||
else
|
||||
if ! plugin_always_enabled; then
|
||||
install_cpif=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$install_cpif" -eq 1 ]; then
|
||||
printf '%s\n' "$CPIL" >> "$CPIF"
|
||||
fi
|
||||
|
||||
if [ -f "$CPIF" ]; then
|
||||
chown diradmin:diradmin "$CPIF" 2>/dev/null || true
|
||||
chmod 640 "$CPIF" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "${DB_MANAGER_INSTALL_LIB_ONLY:-0}" = "1" ]; then
|
||||
return 0 2>/dev/null || exit 0
|
||||
fi
|
||||
|
||||
# Ensure expected permissions and ownership so DirectAdmin can update plugin.conf.
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
chown -R diradmin:diradmin "$PLUGIN_DIR" || true
|
||||
else
|
||||
echo "db-manager: warning: not running as root, cannot chown to diradmin" >&2
|
||||
fi
|
||||
|
||||
chmod 755 "$PLUGIN_DIR" "$PLUGIN_DIR/hooks" "$PLUGIN_DIR/user" "$PLUGIN_DIR/scripts" "$PLUGIN_DIR/images" || true
|
||||
chmod 755 "$PLUGIN_DIR/user/index.html" "$PLUGIN_DIR/scripts/install.sh" "$PLUGIN_DIR/scripts/uninstall.sh" "$PLUGIN_DIR/scripts/restore.sh" "$PLUGIN_DIR/scripts/backup.sh" "$PLUGIN_DIR/scripts/worker.sh" || true
|
||||
chmod 644 "$PLUGIN_DIR/plugin.conf" "$PLUGIN_DIR/hooks/user_txt.html" "$PLUGIN_DIR/plugin-settings.conf" "$PLUGIN_DIR/user/enhanced.css" "$PLUGIN_DIR/user/evolution.css" "$PLUGIN_DIR/images/user_icon.svg" || true
|
||||
|
||||
DATA_DIR="$PLUGIN_DIR/data"
|
||||
mkdir -p "$DATA_DIR/jobs/pending" "$DATA_DIR/jobs/processing" "$DATA_DIR/jobs/done" "$DATA_DIR/logs" "$DATA_DIR/pids" "$DATA_DIR/cancel" "$DATA_DIR/csrf" "$DATA_DIR/lock"
|
||||
chmod 700 "$DATA_DIR" "$DATA_DIR/jobs" "$DATA_DIR/jobs/pending" "$DATA_DIR/jobs/processing" "$DATA_DIR/jobs/done" "$DATA_DIR/logs" "$DATA_DIR/pids" "$DATA_DIR/cancel" "$DATA_DIR/csrf" "$DATA_DIR/lock" || true
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
chown -R diradmin:diradmin "$DATA_DIR" || true
|
||||
fi
|
||||
|
||||
CRON_FILE="/etc/cron.d/db-manager"
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
echo "* * * * * root /usr/local/directadmin/plugins/db-manager/scripts/worker.sh >/dev/null 2>&1" > "$CRON_FILE"
|
||||
chmod 644 "$CRON_FILE" || true
|
||||
else
|
||||
echo "db-manager: warning: not running as root, cannot install cron job" >&2
|
||||
fi
|
||||
|
||||
bootstrap_custom_package_item
|
||||
|
||||
echo "db-manager: install complete"
|
||||
Executable
+723
@@ -0,0 +1,723 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
ERROR_MSG=""
|
||||
SUCCESS_MSG=""
|
||||
RESTORE_OK=0
|
||||
DA_USER=""
|
||||
PRE_BACKUP_PATH=""
|
||||
LOG_FILE=""
|
||||
TMP_DB_USER="tmprestore"
|
||||
TMP_DB_PASS=""
|
||||
TMP_DB_ACTIVE=0
|
||||
SECURE_RESTORE="true"
|
||||
SECURE_BACKUP="true"
|
||||
OWN_BACKUP_USER_RESTORE="true"
|
||||
|
||||
JOB_FILE="${1:-}"
|
||||
if [ -z "$JOB_FILE" ] || [ ! -f "$JOB_FILE" ]; then
|
||||
echo "db-manager: missing job file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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
|
||||
fi
|
||||
local subject message
|
||||
if [ "$code" -eq 0 ] && [ "$RESTORE_OK" -eq 1 ]; then
|
||||
subject="Przywracanie bazy danych zakonczone pomyslnie"
|
||||
if [ -n "$PRE_BACKUP_PATH" ]; then
|
||||
message="Przywracanie bazy ${DB_NAME:-} z dumpa ${DATE_DIR:-} zakonczone. Backup przed przywroceniem: ${PRE_BACKUP_PATH}."
|
||||
else
|
||||
message="Przywracanie bazy ${DB_NAME:-} z dumpa ${DATE_DIR:-} zakonczone."
|
||||
fi
|
||||
else
|
||||
subject="Blad przywracania bazy danych"
|
||||
if [ -n "$ERROR_MSG" ]; then
|
||||
message="Blad przywracania: ${ERROR_MSG}"
|
||||
else
|
||||
message="Blad przywracania bazy ${DB_NAME:-}. Log: ${LOG_FILE:-brak}."
|
||||
fi
|
||||
if [ -n "${LOG_FILE:-}" ] && [ -f "$LOG_FILE" ]; then
|
||||
local log_content
|
||||
log_content="$(cat "$LOG_FILE")"
|
||||
message="${message}\n\nLog:\n${log_content}"
|
||||
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"
|
||||
}
|
||||
|
||||
db_lock_path_for_db() {
|
||||
local db_name="$1"
|
||||
local safe
|
||||
safe="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_.-' '_')"
|
||||
printf '%s/data/lock/%s.lock' "$PLUGIN_DIR" "$safe"
|
||||
}
|
||||
|
||||
release_db_lock() {
|
||||
local db_name lock_path
|
||||
db_name="${DB_NAME:-}"
|
||||
if [ -z "$db_name" ] || [ -z "${PLUGIN_DIR:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
lock_path="$(db_lock_path_for_db "$db_name")"
|
||||
if [ -f "$lock_path" ]; then
|
||||
rm -f "$lock_path" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
on_exit() {
|
||||
local code="$1"
|
||||
cleanup_tmp_db_user
|
||||
notify_user "$code"
|
||||
}
|
||||
trap 'on_exit $?' EXIT
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$JOB_FILE"
|
||||
|
||||
DA_USER="${DA_USER:-}"
|
||||
DB_NAME="${DB_NAME:-}"
|
||||
SOURCE_DB_NAME="${SOURCE_DB_NAME:-}"
|
||||
DATE_DIR="${DATE_DIR:-}"
|
||||
DUMP_FILE="${DUMP_FILE:-}"
|
||||
BACKUP_SOURCE="${BACKUP_SOURCE:-hosting}"
|
||||
if [ -z "$SOURCE_DB_NAME" ]; then
|
||||
SOURCE_DB_NAME="$DB_NAME"
|
||||
fi
|
||||
|
||||
if [ -z "$DA_USER" ] || [ -z "$DB_NAME" ] || [ -z "$DATE_DIR" ]; then
|
||||
echo "Missing DA_USER, DB_NAME or DATE_DIR in job file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
JOB_BASE="$(basename "$JOB_FILE")"
|
||||
JOB_ID="${JOB_BASE%%.*}"
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CONFIG_FILE="${PLUGIN_DIR}/plugin-settings.conf"
|
||||
LOG_DIR="${PLUGIN_DIR}/data/logs"
|
||||
LOG_FILE="${LOG_DIR}/${JOB_ID}.log"
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
fail() {
|
||||
local msg="$1"
|
||||
ERROR_MSG="$msg"
|
||||
echo "$msg" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
fail "Missing ${CONFIG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
HITME_SQL_BACKUP_DIR="${HITME_SQL_BACKUP_DIR:-/home/admin/mysql_backups}"
|
||||
USER_SQL_BACKUP_DIR="${USER_SQL_BACKUP_DIR:-/home/da_user/mysql_backups/user_backups}"
|
||||
PRE_RESTORE_BACKUP_DIR="${PRE_RESTORE_BACKUP_DIR:-/home/da_user/mysql_backups/pre_restore_backups}"
|
||||
PRE_RESTORE_USER_BACKUP_DIR="${PRE_RESTORE_USER_BACKUP_DIR:-/home/da_user/mysql_backups/pre_restore_user_backups}"
|
||||
ENABLE_PRE_RESTORE_BACKUP="${ENABLE_PRE_RESTORE_BACKUP:-true}"
|
||||
ENABLE_USER_PRE_RESTORE_BACKUP="${ENABLE_USER_PRE_RESTORE_BACKUP:-true}"
|
||||
SECURE_RESTORE="${SECURE_RESTORE:-true}"
|
||||
SECURE_BACKUP="${SECURE_BACKUP:-true}"
|
||||
OWN_BACKUP_USER_RESTORE="${OWN_BACKUP_USER_RESTORE:-true}"
|
||||
ENABLE_PRE_RESTORE_BACKUP="$(echo "$ENABLE_PRE_RESTORE_BACKUP" | tr '[:upper:]' '[:lower:]')"
|
||||
ENABLE_USER_PRE_RESTORE_BACKUP="$(echo "$ENABLE_USER_PRE_RESTORE_BACKUP" | tr '[:upper:]' '[:lower:]')"
|
||||
SECURE_RESTORE="$(echo "$SECURE_RESTORE" | tr '[:upper:]' '[:lower:]')"
|
||||
SECURE_BACKUP="$(echo "$SECURE_BACKUP" | tr '[:upper:]' '[:lower:]')"
|
||||
OWN_BACKUP_USER_RESTORE="$(echo "$OWN_BACKUP_USER_RESTORE" | tr '[:upper:]' '[:lower:]')"
|
||||
if [ "$ENABLE_PRE_RESTORE_BACKUP" = "1" ] || [ "$ENABLE_PRE_RESTORE_BACKUP" = "yes" ]; then
|
||||
ENABLE_PRE_RESTORE_BACKUP="true"
|
||||
fi
|
||||
if [ "$ENABLE_USER_PRE_RESTORE_BACKUP" = "1" ] || [ "$ENABLE_USER_PRE_RESTORE_BACKUP" = "yes" ]; then
|
||||
ENABLE_USER_PRE_RESTORE_BACKUP="true"
|
||||
fi
|
||||
if [ "$SECURE_RESTORE" = "1" ] || [ "$SECURE_RESTORE" = "yes" ]; then
|
||||
SECURE_RESTORE="true"
|
||||
fi
|
||||
if [ "$SECURE_BACKUP" = "1" ] || [ "$SECURE_BACKUP" = "yes" ]; then
|
||||
SECURE_BACKUP="true"
|
||||
fi
|
||||
if [ "$OWN_BACKUP_USER_RESTORE" = "1" ] || [ "$OWN_BACKUP_USER_RESTORE" = "yes" ]; then
|
||||
OWN_BACKUP_USER_RESTORE="true"
|
||||
fi
|
||||
|
||||
apply_da_user() {
|
||||
local value="$1"
|
||||
value="${value//DA_USER/$DA_USER}"
|
||||
value="${value//da_user/$DA_USER}"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
enforce_root_backup_tree_ownership() {
|
||||
local root_dir="$1"
|
||||
if [ "$(id -u)" -ne 0 ] || [ -z "$root_dir" ] || [ -L "$root_dir" ]; then
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "$root_dir" 2>/dev/null || true
|
||||
find "$root_dir" \( -type d -o -type f \) -exec chown root:root {} + 2>/dev/null || true
|
||||
}
|
||||
|
||||
quote_sh_value() {
|
||||
local value="$1"
|
||||
value="${value//\'/\'\\\'\'}"
|
||||
printf "'%s'" "$value"
|
||||
}
|
||||
|
||||
set_job_var() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
local tmp
|
||||
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
|
||||
}
|
||||
|
||||
USER_SQL_BACKUP_DIR="$(apply_da_user "$USER_SQL_BACKUP_DIR")"
|
||||
PRE_RESTORE_BACKUP_DIR="$(apply_da_user "$PRE_RESTORE_BACKUP_DIR")"
|
||||
PRE_RESTORE_USER_BACKUP_DIR="$(apply_da_user "$PRE_RESTORE_USER_BACKUP_DIR")"
|
||||
MYSQL_BACKUPS_ROOT="$(apply_da_user "/home/da_user/mysql_backups")"
|
||||
enforce_root_backup_tree_ownership "$MYSQL_BACKUPS_ROOT"
|
||||
|
||||
PRE_RESTORE_TARGET_DIR="$PRE_RESTORE_BACKUP_DIR"
|
||||
if [ "$BACKUP_SOURCE" = "user" ]; then
|
||||
PRE_RESTORE_TARGET_DIR="$PRE_RESTORE_USER_BACKUP_DIR"
|
||||
fi
|
||||
|
||||
PRE_RESTORE_ENABLED="$ENABLE_PRE_RESTORE_BACKUP"
|
||||
if [ "$BACKUP_SOURCE" = "user" ]; then
|
||||
PRE_RESTORE_ENABLED="$ENABLE_USER_PRE_RESTORE_BACKUP"
|
||||
fi
|
||||
|
||||
if [[ "$DATE_DIR" == *".."* || "$DATE_DIR" == */* ]]; then
|
||||
fail "Invalid DATE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DB_NAME" == *".."* || "$DB_NAME" == */* ]]; then
|
||||
fail "Invalid DB_NAME"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DB_NAME" != "${DA_USER}_"* && "$DB_NAME" != "$DA_USER" ]]; then
|
||||
fail "DB_NAME does not belong to user"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$SOURCE_DB_NAME" != "${DA_USER}_"* && "$SOURCE_DB_NAME" != "$DA_USER" ]]; then
|
||||
fail "SOURCE_DB_NAME does not belong to user"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
build_tmp_db_user() {
|
||||
local db_name="$1"
|
||||
local cleaned name
|
||||
cleaned="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_' '_')"
|
||||
name="dbtmp__${cleaned}"
|
||||
if [ "${#name}" -gt 32 ]; then
|
||||
name="${name:0:32}"
|
||||
fi
|
||||
if [ -z "$name" ]; then
|
||||
name="tmprestore"
|
||||
fi
|
||||
printf '%s' "$name"
|
||||
}
|
||||
|
||||
TMP_DB_USER="$(build_tmp_db_user "$DB_NAME")"
|
||||
|
||||
resolve_dump_path() {
|
||||
local source_name="${SOURCE_DB_NAME:-$DB_NAME}"
|
||||
local base_path="${HITME_SQL_BACKUP_DIR%/}/${DATE_DIR}/${source_name}"
|
||||
local candidate=""
|
||||
|
||||
if [ -n "${DUMP_FILE:-}" ]; then
|
||||
candidate="$DUMP_FILE"
|
||||
if [ -f "$candidate" ]; then
|
||||
printf '%s' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
for candidate in "${base_path}.sql.gz" "${base_path}.gz" "${base_path}.sql"; do
|
||||
if [ -f "$candidate" ]; then
|
||||
printf '%s' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
path_in_dir() {
|
||||
local path="$1"
|
||||
local dir="$2"
|
||||
local real_path real_dir
|
||||
real_path="$(realpath "$path" 2>/dev/null || true)"
|
||||
real_dir="$(realpath "$dir" 2>/dev/null || true)"
|
||||
if [ -z "$real_path" ] || [ -z "$real_dir" ]; then
|
||||
return 1
|
||||
fi
|
||||
if [ "$real_path" = "$real_dir" ]; then
|
||||
return 0
|
||||
fi
|
||||
case "$real_path" in
|
||||
"$real_dir"/*) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
path_in_any_dir() {
|
||||
local path="$1"
|
||||
shift
|
||||
local dir
|
||||
for dir in "$@"; do
|
||||
if [ -n "$dir" ] && path_in_dir "$path" "$dir"; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
is_trusted_user_backup_dir() {
|
||||
local slot_dir="$1"
|
||||
local marker owner user uid
|
||||
marker="${slot_dir%/}/.db-manager-owned"
|
||||
if [ -f "$marker" ]; then
|
||||
owner="$(grep -E '^OWNER=' "$marker" 2>/dev/null | tail -n1 | cut -d= -f2- || true)"
|
||||
user="$(grep -E '^DA_USER=' "$marker" 2>/dev/null | tail -n1 | cut -d= -f2- || true)"
|
||||
if [ "$owner" = "db-manager" ] && [ "$user" = "$DA_USER" ]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
uid="$(stat -c '%u' "$slot_dir" 2>/dev/null || stat -f '%u' "$slot_dir" 2>/dev/null || echo '')"
|
||||
if [ "$uid" = "0" ]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
if ! DUMP_PATH="$(resolve_dump_path)"; then
|
||||
fail "Dump file not found for database ${SOURCE_DB_NAME} in ${HITME_SQL_BACKUP_DIR%/}/${DATE_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ALLOWED_DUMP_DIRS=()
|
||||
if [ "$BACKUP_SOURCE" = "user" ]; then
|
||||
ALLOWED_DUMP_DIRS+=("$USER_SQL_BACKUP_DIR" "$PRE_RESTORE_USER_BACKUP_DIR")
|
||||
else
|
||||
ALLOWED_DUMP_DIRS+=("$HITME_SQL_BACKUP_DIR" "$PRE_RESTORE_BACKUP_DIR")
|
||||
fi
|
||||
if ! path_in_any_dir "$DUMP_PATH" "${ALLOWED_DUMP_DIRS[@]}"; then
|
||||
fail "Dump file path is outside allowed directories."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$BACKUP_SOURCE" = "user" ] && [ "$OWN_BACKUP_USER_RESTORE" = "true" ]; then
|
||||
if ! is_trusted_user_backup_dir "$(dirname "$DUMP_PATH")"; then
|
||||
fail "Nie mozna przywrocic tej kopii zapasowej, poniewaz nie zostala utworzona przez plugin DB-Manager."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
MYSQL_CONF="/usr/local/directadmin/conf/mysql.conf"
|
||||
if [ ! -f "$MYSQL_CONF" ]; then
|
||||
fail "Missing ${MYSQL_CONF}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MYSQL_USER=""
|
||||
MYSQL_PASS=""
|
||||
MYSQL_HOST=""
|
||||
MYSQL_PORT=""
|
||||
MYSQL_SOCKET=""
|
||||
while IFS='=' read -r key val; do
|
||||
key="${key%%[[:space:]]*}"
|
||||
val="${val##[[:space:]]}"
|
||||
case "$key" in
|
||||
user) MYSQL_USER="$val" ;;
|
||||
passwd) MYSQL_PASS="$val" ;;
|
||||
host) MYSQL_HOST="$val" ;;
|
||||
port) MYSQL_PORT="$val" ;;
|
||||
socket) MYSQL_SOCKET="$val" ;;
|
||||
esac
|
||||
done < <(grep -v '^[[:space:]]*#' "$MYSQL_CONF" | grep -v '^[[:space:]]*$')
|
||||
|
||||
MYSQL_OPTS=()
|
||||
if [ -n "$MYSQL_USER" ]; then
|
||||
MYSQL_OPTS+=("-u${MYSQL_USER}")
|
||||
fi
|
||||
if [ -n "$MYSQL_PASS" ]; then
|
||||
MYSQL_OPTS+=("-p${MYSQL_PASS}")
|
||||
fi
|
||||
if [ -n "$MYSQL_HOST" ]; then
|
||||
MYSQL_OPTS+=("-h${MYSQL_HOST}")
|
||||
fi
|
||||
if [ -n "$MYSQL_PORT" ] && [ "$MYSQL_PORT" != "0" ]; then
|
||||
MYSQL_OPTS+=("-P${MYSQL_PORT}")
|
||||
fi
|
||||
if [ -n "$MYSQL_SOCKET" ]; then
|
||||
MYSQL_OPTS+=("--socket=${MYSQL_SOCKET}")
|
||||
fi
|
||||
|
||||
# Cron from /etc/cron.d may run with a minimal PATH.
|
||||
PATH="/usr/local/mysql/bin:/usr/local/mariadb/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}"
|
||||
export PATH
|
||||
|
||||
resolve_bin() {
|
||||
local name path
|
||||
for name in "$@"; do
|
||||
if path="$(command -v "$name" 2>/dev/null)"; then
|
||||
printf '%s' "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
sql_escape() {
|
||||
local v="$1"
|
||||
v="${v//\\/\\\\}"
|
||||
v="${v//\'/\\\'}"
|
||||
printf '%s' "$v"
|
||||
}
|
||||
|
||||
random_password() {
|
||||
rand_chars() {
|
||||
local charset="$1"
|
||||
local need="$2"
|
||||
local out="" chunk
|
||||
while [ "${#out}" -lt "$need" ]; do
|
||||
chunk="$(LC_ALL=C tr -dc "$charset" </dev/urandom 2>/dev/null | head -c $((need - ${#out})) || true)"
|
||||
if [ -z "$chunk" ]; then
|
||||
break
|
||||
fi
|
||||
out+="$chunk"
|
||||
done
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
local lower='abcdefghijklmnopqrstuvwxyz'
|
||||
local upper='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
local digits='0123456789'
|
||||
local special='-'
|
||||
local all="${lower}${upper}${digits}${special}"
|
||||
local seed extra mixed out tmp
|
||||
local -a chars=()
|
||||
local i j
|
||||
|
||||
seed="$(rand_chars "$lower" 1)$(rand_chars "$upper" 1)$(rand_chars "$digits" 1)$(rand_chars "$special" 1)"
|
||||
extra="$(rand_chars "$all" 20)"
|
||||
mixed="${seed}${extra}"
|
||||
|
||||
if [ "${#mixed}" -lt 24 ]; then
|
||||
mixed="aA1-"
|
||||
while [ "${#mixed}" -lt 24 ]; do
|
||||
mixed="${mixed}aA1-"
|
||||
done
|
||||
mixed="${mixed:0:24}"
|
||||
fi
|
||||
|
||||
for ((i=0; i<${#mixed}; i++)); do
|
||||
chars+=("${mixed:i:1}")
|
||||
done
|
||||
for ((i=${#chars[@]}-1; i>0; i--)); do
|
||||
j=$((RANDOM % (i + 1)))
|
||||
tmp="${chars[i]}"
|
||||
chars[i]="${chars[j]}"
|
||||
chars[j]="$tmp"
|
||||
done
|
||||
|
||||
out=""
|
||||
for tmp in "${chars[@]}"; do
|
||||
out+="$tmp"
|
||||
done
|
||||
printf '%s' "${out:0:24}"
|
||||
}
|
||||
|
||||
cleanup_tmp_db_user() {
|
||||
if [ "${TMP_DB_ACTIVE:-0}" -ne 1 ]; then
|
||||
return
|
||||
fi
|
||||
if [ -z "${MYSQL_BIN:-}" ]; then
|
||||
return
|
||||
fi
|
||||
local user_esc sql
|
||||
user_esc="$(sql_escape "$TMP_DB_USER")"
|
||||
sql="
|
||||
DROP USER IF EXISTS '${user_esc}'@'localhost';
|
||||
DROP USER IF EXISTS '${user_esc}'@'127.0.0.1';
|
||||
FLUSH PRIVILEGES;"
|
||||
set +e
|
||||
"${MYSQL_BIN}" "${MYSQL_OPTS[@]}" -e "$sql" >>"$LOG_FILE" 2>&1
|
||||
set -e
|
||||
TMP_DB_ACTIVE=0
|
||||
}
|
||||
|
||||
create_tmp_db_user_for_restore() {
|
||||
TMP_DB_PASS="$(random_password)"
|
||||
local user_esc pass_esc db_esc sql
|
||||
user_esc="$(sql_escape "$TMP_DB_USER")"
|
||||
pass_esc="$(sql_escape "$TMP_DB_PASS")"
|
||||
db_esc="${DB_NAME//\`/\`\`}"
|
||||
sql="
|
||||
DROP USER IF EXISTS '${user_esc}'@'localhost';
|
||||
DROP USER IF EXISTS '${user_esc}'@'127.0.0.1';
|
||||
CREATE USER '${user_esc}'@'localhost' IDENTIFIED BY '${pass_esc}';
|
||||
CREATE USER '${user_esc}'@'127.0.0.1' IDENTIFIED BY '${pass_esc}';
|
||||
GRANT ALL PRIVILEGES ON \`${db_esc}\`.* TO '${user_esc}'@'localhost';
|
||||
GRANT ALL PRIVILEGES ON \`${db_esc}\`.* TO '${user_esc}'@'127.0.0.1';
|
||||
FLUSH PRIVILEGES;"
|
||||
set +e
|
||||
"${MYSQL_BIN}" "${MYSQL_OPTS[@]}" -e "$sql" >>"$LOG_FILE" 2>&1
|
||||
local rc=$?
|
||||
set -e
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
TMP_DB_ACTIVE=1
|
||||
return 0
|
||||
}
|
||||
|
||||
sanitize_sql_stream_for_target_db() {
|
||||
local src_db="$1"
|
||||
local dst_db="$2"
|
||||
local marker_file="${3:-}"
|
||||
awk -v src_db="$src_db" -v dst_db="$dst_db" -v marker_file="$marker_file" '
|
||||
function escape_ere(str, out, i, ch) {
|
||||
out = ""
|
||||
for (i = 1; i <= length(str); i++) {
|
||||
ch = substr(str, i, 1)
|
||||
if (ch ~ /[][(){}.^$*+?|\\]/) {
|
||||
out = out "\\" ch
|
||||
} else {
|
||||
out = out ch
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
BEGIN {
|
||||
src_bt = "`" src_db "`"
|
||||
dst_bt = "`" dst_db "`"
|
||||
src_bt_ere = escape_ere(src_bt)
|
||||
fq_pattern = src_bt_ere "[[:space:]]*\\.[[:space:]]*`"
|
||||
changed = 0
|
||||
needs_db_replace = (src_db != dst_db)
|
||||
}
|
||||
{
|
||||
line = $0
|
||||
lower = tolower(line)
|
||||
if (lower ~ /^[[:space:]]*create[[:space:]]+database[[:space:]]+/) {
|
||||
changed = 1
|
||||
next
|
||||
}
|
||||
if (lower ~ /^[[:space:]]*\/\*![0-9]+[[:space:]]+create[[:space:]]+database[[:space:]]+/) {
|
||||
changed = 1
|
||||
next
|
||||
}
|
||||
if (lower ~ /^[[:space:]]*drop[[:space:]]+database([[:space:]]+if[[:space:]]+exists)?[[:space:]]+/) {
|
||||
changed = 1
|
||||
next
|
||||
}
|
||||
if (lower ~ /^[[:space:]]*\/\*![0-9]+[[:space:]]+drop[[:space:]]+database([[:space:]]+if[[:space:]]+exists)?[[:space:]]+/) {
|
||||
changed = 1
|
||||
next
|
||||
}
|
||||
if (lower ~ /^[[:space:]]*use[[:space:]]+/ || lower ~ /^[[:space:]]*\/\*![0-9]+[[:space:]]+use[[:space:]]+/) {
|
||||
changed = 1
|
||||
print "USE " dst_bt ";"
|
||||
next
|
||||
}
|
||||
if (needs_db_replace) {
|
||||
if (gsub(fq_pattern, dst_bt ".`", line) > 0) {
|
||||
changed = 1
|
||||
}
|
||||
}
|
||||
print line
|
||||
}
|
||||
END {
|
||||
if (marker_file != "") {
|
||||
if (changed) {
|
||||
print "1" > marker_file
|
||||
} else {
|
||||
print "0" > marker_file
|
||||
}
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
if ! MYSQL_BIN="$(resolve_bin mysql mariadb)"; then
|
||||
fail "mysql client not found (checked: mysql, mariadb; PATH=${PATH})"
|
||||
exit 1
|
||||
fi
|
||||
MYSQL_ADMIN_CMD=("$MYSQL_BIN" "${MYSQL_OPTS[@]}")
|
||||
MYSQL_RESTORE_CMD=("${MYSQL_ADMIN_CMD[@]}")
|
||||
|
||||
MYSQLDUMP_BIN=""
|
||||
MYSQL_PREBACKUP_DUMP_CMD=()
|
||||
if [ "$PRE_RESTORE_ENABLED" = "true" ]; then
|
||||
if ! MYSQLDUMP_BIN="$(resolve_bin mysqldump mariadb-dump)"; then
|
||||
fail "mysqldump client not found (checked: mysqldump, mariadb-dump; PATH=${PATH})"
|
||||
exit 1
|
||||
fi
|
||||
MYSQL_PREBACKUP_DUMP_CMD=("$MYSQLDUMP_BIN" "${MYSQL_OPTS[@]}" --single-transaction --routines --events --triggers)
|
||||
fi
|
||||
|
||||
NEED_TMP_USER=0
|
||||
if [ "$SECURE_RESTORE" = "true" ]; then
|
||||
NEED_TMP_USER=1
|
||||
fi
|
||||
if [ "$PRE_RESTORE_ENABLED" = "true" ] && [ "$SECURE_BACKUP" = "true" ]; then
|
||||
NEED_TMP_USER=1
|
||||
fi
|
||||
if [ "$NEED_TMP_USER" -eq 1 ]; then
|
||||
if ! create_tmp_db_user_for_restore; then
|
||||
fail "Nie udalo sie utworzyc tymczasowego uzytkownika bazy."
|
||||
exit 1
|
||||
fi
|
||||
TMP_CONN_OPTS=("-u${TMP_DB_USER}" "-p${TMP_DB_PASS}")
|
||||
if [ -n "$MYSQL_SOCKET" ]; then
|
||||
TMP_CONN_OPTS+=("--socket=${MYSQL_SOCKET}" "-hlocalhost")
|
||||
else
|
||||
TMP_CONN_OPTS+=("-h127.0.0.1")
|
||||
if [ -n "$MYSQL_PORT" ] && [ "$MYSQL_PORT" != "0" ]; then
|
||||
TMP_CONN_OPTS+=("-P${MYSQL_PORT}")
|
||||
fi
|
||||
fi
|
||||
if [ "$SECURE_RESTORE" = "true" ]; then
|
||||
MYSQL_RESTORE_CMD=("$MYSQL_BIN" "${TMP_CONN_OPTS[@]}")
|
||||
fi
|
||||
if [ "$PRE_RESTORE_ENABLED" = "true" ] && [ "$SECURE_BACKUP" = "true" ]; then
|
||||
MYSQL_PREBACKUP_DUMP_CMD=("$MYSQLDUMP_BIN" "${TMP_CONN_OPTS[@]}" --single-transaction --routines --events --triggers)
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$SECURE_RESTORE" = "true" ]; then
|
||||
echo "Tryb bezpieczny przywracania: ON (tymczasowy uzytkownik bazy)" >> "$LOG_FILE"
|
||||
else
|
||||
echo "Tryb bezpieczny przywracania: OFF" >> "$LOG_FILE"
|
||||
fi
|
||||
if [ "$PRE_RESTORE_ENABLED" = "true" ]; then
|
||||
if [ "$SECURE_BACKUP" = "true" ]; then
|
||||
echo "Tryb bezpieczny backupu przed przywroceniem: ON (tymczasowy uzytkownik bazy)" >> "$LOG_FILE"
|
||||
else
|
||||
echo "Tryb bezpieczny backupu przed przywroceniem: OFF" >> "$LOG_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$PRE_RESTORE_ENABLED" = "true" ]; then
|
||||
DB_NAME_SQL="${DB_NAME//\'/\\\'}"
|
||||
set +e
|
||||
DB_EXISTS="$(${MYSQL_ADMIN_CMD[@]} -N -B -e "SHOW DATABASES LIKE '${DB_NAME_SQL}'" 2>>"$LOG_FILE")"
|
||||
EXISTS_CODE=$?
|
||||
set -e
|
||||
if [ "$EXISTS_CODE" -ne 0 ]; then
|
||||
fail "Nie udalo sie sprawdzic istnienia bazy ${DB_NAME}."
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$DB_EXISTS" ]; then
|
||||
mkdir -p "$PRE_RESTORE_TARGET_DIR"
|
||||
chmod 700 "$PRE_RESTORE_TARGET_DIR" || true
|
||||
BACKUP_DATE="$(date +%d-%m-%Y)"
|
||||
BASE_NAME="${DB_NAME}_przed_przywroceniem_${BACKUP_DATE}"
|
||||
TARGET_PATH="${PRE_RESTORE_TARGET_DIR%/}/${BASE_NAME}.sql.gz"
|
||||
IDX=1
|
||||
while [ -e "$TARGET_PATH" ]; do
|
||||
TARGET_PATH="${PRE_RESTORE_TARGET_DIR%/}/${BASE_NAME}_${IDX}.sql.gz"
|
||||
IDX=$((IDX+1))
|
||||
done
|
||||
|
||||
set +e
|
||||
${MYSQL_PREBACKUP_DUMP_CMD[@]} --databases "$DB_NAME" 2>>"$LOG_FILE" | gzip -c > "$TARGET_PATH"
|
||||
DUMP_CODE=$?
|
||||
set -e
|
||||
|
||||
if [ "$DUMP_CODE" -ne 0 ]; then
|
||||
rm -f "$TARGET_PATH"
|
||||
fail "Nie udalo sie wykonac backupu bazy ${DB_NAME}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
chown root:root "$PRE_RESTORE_TARGET_DIR" "$TARGET_PATH" 2>/dev/null || true
|
||||
chmod 700 "$PRE_RESTORE_TARGET_DIR" 2>/dev/null || true
|
||||
chmod 600 "$TARGET_PATH" 2>/dev/null || true
|
||||
else
|
||||
chown "${DA_USER}:${DA_USER}" "$TARGET_PATH" 2>/dev/null || true
|
||||
fi
|
||||
enforce_root_backup_tree_ownership "$MYSQL_BACKUPS_ROOT"
|
||||
PRE_BACKUP_PATH="$TARGET_PATH"
|
||||
set_job_var "PRE_BACKUP_PATH" "$PRE_BACKUP_PATH"
|
||||
echo "Wykonano backup przed przywroceniem: ${PRE_BACKUP_PATH}" >> "$LOG_FILE"
|
||||
else
|
||||
echo "Baza ${DB_NAME} nie istnieje - pomijam backup przed przywroceniem." >> "$LOG_FILE"
|
||||
fi
|
||||
else
|
||||
echo "Backup przed przywroceniem jest wylaczony dla tego typu przywracania." >> "$LOG_FILE"
|
||||
fi
|
||||
|
||||
DB_NAME_BT="${DB_NAME//\`/\`\`}"
|
||||
${MYSQL_ADMIN_CMD[@]} -e "CREATE DATABASE IF NOT EXISTS \`${DB_NAME_BT}\`" >> "$LOG_FILE" 2>&1
|
||||
|
||||
set +e
|
||||
SANITIZE_MARKER_FILE="$(mktemp)"
|
||||
printf "0\n" > "$SANITIZE_MARKER_FILE"
|
||||
if [ "$SOURCE_DB_NAME" != "$DB_NAME" ]; then
|
||||
echo "Sanityzacja dumpa przed importem: ${SOURCE_DB_NAME} -> ${DB_NAME}" >> "$LOG_FILE"
|
||||
fi
|
||||
if [[ "$DUMP_PATH" == *.gz ]]; then
|
||||
gunzip -c "$DUMP_PATH" \
|
||||
| sanitize_sql_stream_for_target_db "$SOURCE_DB_NAME" "$DB_NAME" "$SANITIZE_MARKER_FILE" \
|
||||
| ${MYSQL_RESTORE_CMD[@]} --database="$DB_NAME" --verbose >> "$LOG_FILE" 2>&1
|
||||
RESTORE_CODE=$?
|
||||
else
|
||||
cat "$DUMP_PATH" \
|
||||
| sanitize_sql_stream_for_target_db "$SOURCE_DB_NAME" "$DB_NAME" "$SANITIZE_MARKER_FILE" \
|
||||
| ${MYSQL_RESTORE_CMD[@]} --database="$DB_NAME" --verbose >> "$LOG_FILE" 2>&1
|
||||
RESTORE_CODE=$?
|
||||
fi
|
||||
set -e
|
||||
|
||||
SANITIZATION_REQUIRED="Nie"
|
||||
if [ -r "$SANITIZE_MARKER_FILE" ]; then
|
||||
if grep -q '^1$' "$SANITIZE_MARKER_FILE"; then
|
||||
SANITIZATION_REQUIRED="Tak"
|
||||
fi
|
||||
fi
|
||||
rm -f "$SANITIZE_MARKER_FILE"
|
||||
set_job_var "SANITIZATION_REQUIRED" "$SANITIZATION_REQUIRED"
|
||||
echo "Koniecznosc Sanityzacji: ${SANITIZATION_REQUIRED}" >> "$LOG_FILE"
|
||||
|
||||
if [ "$RESTORE_CODE" -ne 0 ]; then
|
||||
fail "Przywracanie bazy ${DB_NAME} zakonczone bledem."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RESTORE_OK=1
|
||||
SUCCESS_MSG="Przywracanie bazy ${DB_NAME} zakonczone pomyslnie."
|
||||
echo "$SUCCESS_MSG" >> "$LOG_FILE"
|
||||
|
||||
exit 0
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
CRON_FILE="/etc/cron.d/db-manager"
|
||||
CPIF="/usr/local/directadmin/data/admin/custom_package_items.conf"
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
rm -f "$CRON_FILE"
|
||||
if [ -f "$CPIF" ]; then
|
||||
sed -i '/^db-manager=/d' "$CPIF" || true
|
||||
sed -i '/^db_manager=/d' "$CPIF" || true
|
||||
chown diradmin:diradmin "$CPIF" 2>/dev/null || true
|
||||
chmod 640 "$CPIF" 2>/dev/null || true
|
||||
fi
|
||||
else
|
||||
echo "db-manager: warning: not running as root, cannot remove cron job or update custom package items" >&2
|
||||
fi
|
||||
|
||||
echo "db-manager: uninstall complete"
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA_DIR="$PLUGIN_DIR/data"
|
||||
JOB_DIR="$DATA_DIR/jobs/pending"
|
||||
PROCESSING_DIR="$DATA_DIR/jobs/processing"
|
||||
DONE_DIR="$DATA_DIR/jobs/done"
|
||||
WORKER_LOCK_DIR="$DATA_DIR/worker.lock"
|
||||
DB_LOCK_DIR="$DATA_DIR/lock"
|
||||
PID_DIR="$DATA_DIR/pids"
|
||||
CANCEL_DIR="$DATA_DIR/cancel"
|
||||
|
||||
mkdir -p "$JOB_DIR" "$PROCESSING_DIR" "$DONE_DIR" "$PID_DIR" "$CANCEL_DIR" "$DB_LOCK_DIR"
|
||||
|
||||
if ! mkdir "$WORKER_LOCK_DIR" 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
rmdir "$WORKER_LOCK_DIR" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
db_lock_path_for_db() {
|
||||
local db_name="$1"
|
||||
local safe
|
||||
safe="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_.-' '_')"
|
||||
printf '%s/%s.lock' "$DB_LOCK_DIR" "$safe"
|
||||
}
|
||||
|
||||
release_db_lock_later() {
|
||||
local lock_path="$1"
|
||||
if [ -z "$lock_path" ]; then
|
||||
return 0
|
||||
fi
|
||||
(
|
||||
sleep 10
|
||||
rm -f "$lock_path" 2>/dev/null || true
|
||||
) >/dev/null 2>&1 &
|
||||
}
|
||||
|
||||
while true; do
|
||||
JOB_FILE=$(ls -1 "$JOB_DIR"/*.env 2>/dev/null | head -n 1)
|
||||
if [ -z "${JOB_FILE:-}" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
BASE_NAME="$(basename "$JOB_FILE")"
|
||||
RUN_FILE="$PROCESSING_DIR/$BASE_NAME"
|
||||
mv "$JOB_FILE" "$RUN_FILE"
|
||||
JOB_ID="${BASE_NAME%.env}"
|
||||
PID_FILE="$PID_DIR/${JOB_ID}.pid"
|
||||
|
||||
JOB_TYPE="restore"
|
||||
DB_NAME=""
|
||||
DB_LOCK_FILE=""
|
||||
# shellcheck disable=SC1090
|
||||
source "$RUN_FILE" || true
|
||||
JOB_TYPE="${JOB_TYPE:-restore}"
|
||||
DB_NAME="${DB_NAME:-}"
|
||||
DB_LOCK_FILE="${DB_LOCK_FILE:-}"
|
||||
|
||||
LOCK_PATH=""
|
||||
if [ -n "$DB_LOCK_FILE" ]; then
|
||||
case "$DB_LOCK_FILE" in
|
||||
"$DB_LOCK_DIR"/*.lock) LOCK_PATH="$DB_LOCK_FILE" ;;
|
||||
*) LOCK_PATH="" ;;
|
||||
esac
|
||||
fi
|
||||
if [ -z "$LOCK_PATH" ] && [ -n "$DB_NAME" ]; then
|
||||
LOCK_PATH="$(db_lock_path_for_db "$DB_NAME")"
|
||||
fi
|
||||
|
||||
RUNNER="$PLUGIN_DIR/scripts/restore.sh"
|
||||
if [ "$JOB_TYPE" = "backup" ]; then
|
||||
RUNNER="$PLUGIN_DIR/scripts/backup.sh"
|
||||
fi
|
||||
|
||||
set +e
|
||||
"$RUNNER" "$RUN_FILE" &
|
||||
RUN_PID=$!
|
||||
echo "PID=${RUN_PID}" >> "$RUN_FILE"
|
||||
echo "${RUN_PID}" > "$PID_FILE"
|
||||
wait "$RUN_PID"
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
rm -f "$PID_FILE"
|
||||
|
||||
CANCEL_FLAG="${CANCEL_DIR}/${JOB_ID}.flag"
|
||||
if [ -f "$CANCEL_FLAG" ]; then
|
||||
rm -f "$CANCEL_FLAG"
|
||||
mv "$RUN_FILE" "$DONE_DIR/${JOB_ID}.cancel"
|
||||
elif [ "$EXIT_CODE" -eq 0 ]; then
|
||||
mv "$RUN_FILE" "$DONE_DIR/${JOB_ID}.ok"
|
||||
else
|
||||
mv "$RUN_FILE" "$DONE_DIR/${JOB_ID}.fail"
|
||||
fi
|
||||
|
||||
release_db_lock_later "$LOCK_PATH"
|
||||
done
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user