#!/bin/bash set -Eeuo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" COMMON_FILE="$PLUGIN_DIR/scripts/setup/mysql_common.sh" LOG_FILE="${DA_ALT_MYSQL_BACKUP_LOG:-$PLUGIN_DIR/data/logs/da-backup.log}" DRY_RUN=0 for arg in "$@"; do case "$arg" in --dry-run) DRY_RUN=1 ;; esac done mkdir -p "$(dirname "$LOG_FILE")" log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$LOG_FILE" } die() { log "ERROR: $*" printf 'alt-mysql backup hook: %s\n' "$*" >&2 exit 1 } [ -r "$COMMON_FILE" ] || die "missing helper: $COMMON_FILE" # shellcheck source=../setup/mysql_common.sh source "$COMMON_FILE" mysql_load_plugin_settings_file "$PLUGIN_DIR/plugin-settings.conf" json_escape() { printf '%s' "${1:-}" | sed 's/\\/\\\\/g; s/"/\\"/g' } json_string() { printf '"%s"' "$(json_escape "${1:-}")" } sha256_file() { local file="$1" if command -v sha256sum >/dev/null 2>&1; then sha256sum "$file" | awk '{print $1}' return 0 fi if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$file" | awk '{print $1}' return 0 fi die "missing sha256sum/shasum" } detect_da_user() { local value="${DA_USER:-${username:-${user:-${USERNAME:-}}}}" if [ -z "$value" ] && [ "${1:-}" != "--dry-run" ]; then value="${1:-}" fi if [[ ! "$value" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]*$ ]]; then die "cannot detect safe DirectAdmin username" fi printf '%s\n' "$value" } candidate_dir_if_valid() { local candidate="${1:-}" [ -n "$candidate" ] || return 1 [ -d "$candidate" ] || return 1 printf '%s\n' "$(cd "$candidate" && pwd -P)" } candidate_backup_root_if_valid() { local candidate="${1:-}" local dir="" if ! dir="$(candidate_dir_if_valid "$candidate")"; then return 1 fi if [ "$DRY_RUN" -eq 1 ] || [ -d "$dir/backup" ]; then printf '%s\n' "$dir" return 0 fi return 1 } detect_backup_root() { local current candidate dir file_candidate current="$(pwd -P)" for candidate in \ "${DA_ALT_MYSQL_BACKUP_ROOT:-}" \ "${backup_path:-}" \ "${backupdir:-}" \ "${backup_dir:-}" \ "${tmpdir:-}" \ "$current"; do if dir="$(candidate_backup_root_if_valid "$candidate")"; then printf '%s\n' "$dir" return 0 fi done for file_candidate in "${file:-}" "${filename:-}" "${backup_file:-}"; do [ -n "$file_candidate" ] || continue candidate="$(dirname "$file_candidate")" if dir="$(candidate_backup_root_if_valid "$candidate")"; then printf '%s\n' "$dir" return 0 fi done die "cannot detect DirectAdmin backup assembly directory" } list_user_databases() { local da_user="$1" local escaped_user escaped_user="$(mysql_escape_literal "$da_user")" mysql_exec mysql -Nse " SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME LIKE '${escaped_user}\\_%' ESCAPE '\\' ORDER BY SCHEMA_NAME " | grep -Ev '^(information_schema|performance_schema|mysql|sys)$' || true } list_user_roles() { local da_user="$1" local escaped_user escaped_user="$(mysql_escape_literal "$da_user")" mysql_exec mysql -Nse " SELECT DISTINCT User FROM mysql.user WHERE User LIKE '${escaped_user}\\_%' ESCAPE '\\' AND User <> '' ORDER BY User " || true } dump_users_file() { local da_user="$1" local output_file="$2" local tmp_file user host q_user q_host create_sql tmp_file="$(mktemp)" { printf '%s\n' "-- alt-mysql user definitions for $da_user" printf '%s\n' "SET sql_log_bin=0;" } > "$tmp_file" while IFS=$'\t' read -r user host; do [ -n "${user:-}" ] || continue [ -n "${host:-}" ] || continue q_user="$(mysql_escape_literal "$user")" q_host="$(mysql_escape_literal "$host")" create_sql="$(mysql_exec mysql -Nse "SHOW CREATE USER '${q_user}'@'${q_host}'" | awk -F'\t' '{print $NF}' || true)" [ -n "$create_sql" ] || continue printf "DROP USER IF EXISTS '%s'@'%s';\n" "$q_user" "$q_host" >> "$tmp_file" printf '%s;\n' "$create_sql" >> "$tmp_file" done < <( mysql_exec mysql -Nse " SELECT User, Host FROM mysql.user WHERE User LIKE '$(mysql_escape_literal "$da_user")\\_%' ESCAPE '\\' AND User <> '' ORDER BY User, Host " || true ) printf '%s\n' "SET sql_log_bin=1;" >> "$tmp_file" gzip -9 < "$tmp_file" > "$output_file" rm -f "$tmp_file" } dump_grants_file() { local da_user="$1" local output_file="$2" local tmp_file user host q_user q_host grant_line tmp_file="$(mktemp)" printf '%s\n' "-- alt-mysql grants for $da_user" > "$tmp_file" while IFS=$'\t' read -r user host; do [ -n "${user:-}" ] || continue [ -n "${host:-}" ] || continue q_user="$(mysql_escape_literal "$user")" q_host="$(mysql_escape_literal "$host")" while IFS= read -r grant_line; do [ -n "$grant_line" ] || continue printf '%s;\n' "$grant_line" >> "$tmp_file" done < <(mysql_exec mysql -Nse "SHOW GRANTS FOR '${q_user}'@'${q_host}'" || true) printf '\n' >> "$tmp_file" done < <( mysql_exec mysql -Nse " SELECT User, Host FROM mysql.user WHERE User LIKE '$(mysql_escape_literal "$da_user")\\_%' ESCAPE '\\' AND User <> '' ORDER BY User, Host " || true ) gzip -9 < "$tmp_file" > "$output_file" rm -f "$tmp_file" } dump_metadata_table() { local table="$1" local where="$2" local output_file="$3" mysqldump_exec \ --single-transaction \ --skip-lock-tables \ --no-create-info \ --compact \ --where="$where" \ mysql "$table" | gzip -9 > "$output_file" } write_manifest() { local da_user="$1" local payload_dir="$2" shift 2 local db_json="$1" local manifest="$payload_dir/manifest.json" local version version="$(mysql_alt_version)" mysql_load_alt_runtime cat > "$manifest" < "$db_file" rel_file="databases/${db}.sql.gz" sha="$(sha256_file "$db_file")" entry=" { \"name\": $(json_string "$db"), \"file\": $(json_string "$rel_file"), \"sha256\": $(json_string "$sha") }" if [ -n "$db_entries" ]; then db_entries+=$',\n' fi db_entries+="$entry" log "added database dump: $db" done < <(list_user_databases "$da_user") dump_users_file "$da_user" "$grants_dir/users.sql.gz" dump_grants_file "$da_user" "$grants_dir/grants.sql.gz" prefix="$(mysql_escape_literal "$da_user")" where="db_name LIKE '${prefix}\\_%'" dump_metadata_table da_plugin_database_owners "$where" "$metadata_dir/da_plugin_database_owners.sql.gz" dump_metadata_table da_plugin_grant_profiles "$where" "$metadata_dir/da_plugin_grant_profiles.sql.gz" where="da_user = '${prefix}' OR role_name LIKE '${prefix}\\_%'" dump_metadata_table da_plugin_access_hosts "$where" "$metadata_dir/da_plugin_access_hosts.sql.gz" write_manifest "$da_user" "$payload_dir" "$db_entries" chmod -R go-rwx "$payload_dir" 2>/dev/null || true log "backup payload completed: $payload_dir" } main "$@"