feat: parse DirectAdmin native backup/<dbname>.conf format
This commit is contained in:
+122
@@ -0,0 +1,122 @@
|
|||||||
|
#!/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"
|
||||||
|
SETTINGS_FILE="${DA_ALT_MYSQL_SETTINGS_FILE:-$PLUGIN_DIR/plugin-settings.conf}"
|
||||||
|
LOG_FILE="${DA_ALT_MYSQL_RESTORE_LOG:-$PLUGIN_DIR/data/logs/da-restore.log}"
|
||||||
|
|
||||||
|
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 native backup restore: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
url_decode() {
|
||||||
|
local encoded="${1//+/ }"
|
||||||
|
printf '%b' "${encoded//%/\\x}"
|
||||||
|
}
|
||||||
|
|
||||||
|
native_conf_field() {
|
||||||
|
local qs="$1" field="$2" pair
|
||||||
|
local -a pairs
|
||||||
|
IFS='&' read -ra pairs <<< "$qs"
|
||||||
|
for pair in "${pairs[@]}"; do
|
||||||
|
if [ "${pair%%=*}" = "$field" ]; then
|
||||||
|
printf '%s' "${pair#*=}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
NATIVE_PRIV_FLAG_MAP=(
|
||||||
|
"select_priv:SELECT" "insert_priv:INSERT" "update_priv:UPDATE" "delete_priv:DELETE"
|
||||||
|
"create_priv:CREATE" "drop_priv:DROP" "index_priv:INDEX" "alter_priv:ALTER"
|
||||||
|
"create_tmp_table_priv:CREATE TEMPORARY TABLES" "lock_tables_priv:LOCK TABLES"
|
||||||
|
"create_view_priv:CREATE VIEW" "show_view_priv:SHOW VIEW"
|
||||||
|
"create_routine_priv:CREATE ROUTINE" "alter_routine_priv:ALTER ROUTINE"
|
||||||
|
"execute_priv:EXECUTE" "event_priv:EVENT" "trigger_priv:TRIGGER"
|
||||||
|
"references_priv:REFERENCES"
|
||||||
|
)
|
||||||
|
|
||||||
|
native_conf_privilege_list() {
|
||||||
|
local qs="$1"
|
||||||
|
local entry flag name value
|
||||||
|
local -a privs=()
|
||||||
|
for entry in "${NATIVE_PRIV_FLAG_MAP[@]}"; do
|
||||||
|
flag="${entry%%:*}"
|
||||||
|
name="${entry#*:}"
|
||||||
|
value="$(native_conf_field "$qs" "$flag")"
|
||||||
|
[ "$value" = "Y" ] && privs+=("$name")
|
||||||
|
done
|
||||||
|
if [ "${#privs[@]}" -eq 0 ]; then
|
||||||
|
printf 'ALL'
|
||||||
|
else
|
||||||
|
local IFS=,
|
||||||
|
printf '%s' "${privs[*]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_native_db_conf() {
|
||||||
|
local conf_file="$1"
|
||||||
|
local line key value
|
||||||
|
|
||||||
|
NATIVE_DB_CHARSET=""
|
||||||
|
NATIVE_DB_COLLATION=""
|
||||||
|
NATIVE_DB_USER_NAMES=()
|
||||||
|
NATIVE_DB_USER_HOST=()
|
||||||
|
NATIVE_DB_USER_HASH=()
|
||||||
|
NATIVE_DB_USER_PLUGIN=()
|
||||||
|
NATIVE_DB_USER_PRIVS=()
|
||||||
|
|
||||||
|
[ -r "$conf_file" ] || { log "cannot read conf file: $conf_file"; return 1; }
|
||||||
|
|
||||||
|
while IFS= read -r line || [ -n "$line" ]; do
|
||||||
|
[ -n "$line" ] || continue
|
||||||
|
[[ "$line" == *=* ]] || continue
|
||||||
|
key="${line%%=*}"
|
||||||
|
value="${line#*=}"
|
||||||
|
|
||||||
|
case "$key" in
|
||||||
|
db_collation)
|
||||||
|
NATIVE_DB_CHARSET="$(native_conf_field "$value" DEFAULT_CHARACTER_SET_NAME)"
|
||||||
|
NATIVE_DB_COLLATION="$(native_conf_field "$value" DEFAULT_COLLATION_NAME)"
|
||||||
|
;;
|
||||||
|
accesshosts) ;;
|
||||||
|
*)
|
||||||
|
NATIVE_DB_USER_NAMES+=("$key")
|
||||||
|
NATIVE_DB_USER_HOST+=("$(native_conf_field "$value" accesshosts)")
|
||||||
|
NATIVE_DB_USER_HASH+=("$(url_decode "$(native_conf_field "$value" passwd)")")
|
||||||
|
NATIVE_DB_USER_PLUGIN+=("$(native_conf_field "$value" plugin)")
|
||||||
|
NATIVE_DB_USER_PRIVS+=("$(native_conf_privilege_list "$value")")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < "$conf_file"
|
||||||
|
|
||||||
|
if [ -z "$NATIVE_DB_CHARSET" ]; then
|
||||||
|
log "missing db_collation charset in $conf_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ "${#NATIVE_DB_USER_NAMES[@]}" -eq 0 ]; then
|
||||||
|
log "no MySQL users found in $conf_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
[ -r "$COMMON_FILE" ] || die "missing helper: $COMMON_FILE"
|
||||||
|
# shellcheck source=../setup/mysql_common.sh
|
||||||
|
source "$COMMON_FILE"
|
||||||
|
mysql_load_plugin_settings_file "$SETTINGS_FILE"
|
||||||
|
# main() is added in Task 3.
|
||||||
|
fi
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
SCRIPT="$PLUGIN_DIR/scripts/da-integration/alt_mysql_native_backup_restore.sh"
|
||||||
|
TMP_DIR="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "FAIL: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Source only the parsing functions, without triggering main() (the script
|
||||||
|
# guards its own main() call behind a BASH_SOURCE check, see Task 3 Step 1).
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "$SCRIPT"
|
||||||
|
|
||||||
|
# --- Real example content, copied verbatim from example_backup/psy/backup/psy_it301.conf ---
|
||||||
|
CONF_FILE="$TMP_DIR/psy_it301.conf"
|
||||||
|
cat > "$CONF_FILE" <<'EOF'
|
||||||
|
accesshosts=0=localhost
|
||||||
|
db_collation=DEFAULT_CHARACTER_SET_NAME=utf8mb3&DEFAULT_COLLATION_NAME=utf8mb3_general_ci&SCHEMA_NAME=psy_it301
|
||||||
|
psy_it301=accesshosts=localhost&alter_priv=Y&alter_routine_priv=Y&create_priv=Y&create_routine_priv=Y&create_tmp_table_priv=Y&create_view_priv=Y&delete_priv=Y&drop_priv=Y&event_priv=Y&execute_priv=Y&index_priv=Y&insert_priv=Y&lock_tables_priv=Y&passwd=%2A8E2A6497279CF1B7F115E213B9904BD32703F4EF&plugin=mysql_native_password&references_priv=Y&select_priv=Y&show_view_priv=Y&trigger_priv=Y&update_priv=Y
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# url_decode
|
||||||
|
[ "$(url_decode '%2A8E2A6497279CF1B7F115E213B9904BD32703F4EF')" = "*8E2A6497279CF1B7F115E213B9904BD32703F4EF" ] \
|
||||||
|
|| fail "url_decode did not decode %2A correctly"
|
||||||
|
|
||||||
|
# native_conf_field
|
||||||
|
QS="accesshosts=localhost&select_priv=Y&passwd=%2Aabc&plugin=mysql_native_password"
|
||||||
|
[ "$(native_conf_field "$QS" passwd)" = "%2Aabc" ] || fail "native_conf_field did not extract passwd"
|
||||||
|
[ "$(native_conf_field "$QS" plugin)" = "mysql_native_password" ] || fail "native_conf_field did not extract plugin"
|
||||||
|
[ "$(native_conf_field "$QS" missing_field)" = "" ] || fail "native_conf_field should return empty for a missing field"
|
||||||
|
|
||||||
|
# native_conf_privilege_list
|
||||||
|
PRIVS="$(native_conf_privilege_list "$QS")"
|
||||||
|
[ "$PRIVS" = "SELECT" ] || fail "expected only SELECT for a single select_priv=Y field, got: $PRIVS"
|
||||||
|
|
||||||
|
NO_PRIVS="$(native_conf_privilege_list "accesshosts=localhost&plugin=mysql_native_password")"
|
||||||
|
[ "$NO_PRIVS" = "ALL" ] || fail "expected ALL when no *_priv=Y flags are present, got: $NO_PRIVS"
|
||||||
|
|
||||||
|
# parse_native_db_conf against the real example file
|
||||||
|
parse_native_db_conf "$CONF_FILE" || fail "parse_native_db_conf should succeed on a valid conf file"
|
||||||
|
|
||||||
|
[ "$NATIVE_DB_CHARSET" = "utf8mb3" ] || fail "expected charset utf8mb3, got: $NATIVE_DB_CHARSET"
|
||||||
|
[ "$NATIVE_DB_COLLATION" = "utf8mb3_general_ci" ] || fail "expected collation utf8mb3_general_ci, got: $NATIVE_DB_COLLATION"
|
||||||
|
[ "${#NATIVE_DB_USER_NAMES[@]}" -eq 1 ] || fail "expected exactly 1 user, got: ${#NATIVE_DB_USER_NAMES[@]}"
|
||||||
|
[ "${NATIVE_DB_USER_NAMES[0]}" = "psy_it301" ] || fail "expected username psy_it301, got: ${NATIVE_DB_USER_NAMES[0]}"
|
||||||
|
[ "${NATIVE_DB_USER_HOST[0]}" = "localhost" ] || fail "expected host localhost, got: ${NATIVE_DB_USER_HOST[0]}"
|
||||||
|
[ "${NATIVE_DB_USER_HASH[0]}" = "*8E2A6497279CF1B7F115E213B9904BD32703F4EF" ] || fail "password hash not decoded correctly, got: ${NATIVE_DB_USER_HASH[0]}"
|
||||||
|
[ "${NATIVE_DB_USER_PLUGIN[0]}" = "mysql_native_password" ] || fail "expected mysql_native_password plugin, got: ${NATIVE_DB_USER_PLUGIN[0]}"
|
||||||
|
echo "${NATIVE_DB_USER_PRIVS[0]}" | grep -q "SELECT" || fail "expected SELECT in privilege list, got: ${NATIVE_DB_USER_PRIVS[0]}"
|
||||||
|
|
||||||
|
# --- A conf file with two users on the same database ---
|
||||||
|
MULTI_CONF="$TMP_DIR/multi.conf"
|
||||||
|
cat > "$MULTI_CONF" <<'EOF'
|
||||||
|
accesshosts=0=localhost
|
||||||
|
db_collation=DEFAULT_CHARACTER_SET_NAME=utf8mb4&DEFAULT_COLLATION_NAME=utf8mb4_general_ci&SCHEMA_NAME=demo_multi
|
||||||
|
demo_owner=accesshosts=localhost&select_priv=Y&insert_priv=Y&passwd=%2Aaaa&plugin=mysql_native_password
|
||||||
|
demo_reader=accesshosts=localhost&select_priv=Y&passwd=%2Abbb&plugin=mysql_native_password
|
||||||
|
EOF
|
||||||
|
|
||||||
|
parse_native_db_conf "$MULTI_CONF" || fail "parse_native_db_conf should succeed on a multi-user conf file"
|
||||||
|
[ "${#NATIVE_DB_USER_NAMES[@]}" -eq 2 ] || fail "expected 2 users in a multi-user conf, got: ${#NATIVE_DB_USER_NAMES[@]}"
|
||||||
|
|
||||||
|
# --- A malformed conf file (no db_collation) must fail closed, not guess ---
|
||||||
|
BAD_CONF="$TMP_DIR/bad.conf"
|
||||||
|
cat > "$BAD_CONF" <<'EOF'
|
||||||
|
accesshosts=0=localhost
|
||||||
|
demo_user=select_priv=Y&passwd=%2Aaaa&plugin=mysql_native_password
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if parse_native_db_conf "$BAD_CONF" 2>/dev/null; then
|
||||||
|
fail "parse_native_db_conf must fail when db_collation is missing, not guess a default"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "alt_mysql_native_backup_restore_parse_test: OK"
|
||||||
Reference in New Issue
Block a user