Import alt-mysql plugin
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
#!/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" <<JSON
|
||||
{
|
||||
"format": "da-alt-mysql-v1",
|
||||
"created_at": "$(date -u '+%Y-%m-%dT%H:%M:%SZ')",
|
||||
"da_user": $(json_string "$da_user"),
|
||||
"mariadb_version": $(json_string "$version"),
|
||||
"source": "alt-mariadb",
|
||||
"source_instance": {
|
||||
"service": $(json_string "$MYSQL_ALT_SERVICE_NAME"),
|
||||
"basedir": $(json_string "$MYSQL_ALT_BASEDIR"),
|
||||
"datadir": $(json_string "$MYSQL_ALT_DATADIR"),
|
||||
"port": $(json_string "$MYSQL_ALT_PORT"),
|
||||
"socket": $(json_string "$MYSQL_ALT_SOCKET")
|
||||
},
|
||||
"databases": [
|
||||
$db_json
|
||||
]
|
||||
}
|
||||
JSON
|
||||
}
|
||||
|
||||
main() {
|
||||
local da_user backup_root payload_dir db_dir grants_dir metadata_dir
|
||||
local db db_file rel_file sha db_entries entry prefix where
|
||||
da_user="$(detect_da_user "${1:-}")"
|
||||
backup_root="$(detect_backup_root)"
|
||||
payload_dir="$backup_root/backup/alt_mysql"
|
||||
db_dir="$payload_dir/databases"
|
||||
grants_dir="$payload_dir/grants"
|
||||
metadata_dir="$payload_dir/metadata"
|
||||
|
||||
log "backup hook env: user=$da_user pwd=$(pwd -P) file=${file:-} filename=${filename:-} root=$backup_root"
|
||||
|
||||
mysql_load_alt_credentials
|
||||
mysql_ensure_metadata_schema
|
||||
|
||||
if [ "$DRY_RUN" -eq 1 ]; then
|
||||
printf 'Would write alt-mysql payload for %s into %s\n' "$da_user" "$payload_dir"
|
||||
list_user_databases "$da_user"
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -rf "$payload_dir"
|
||||
mkdir -p "$db_dir" "$grants_dir" "$metadata_dir"
|
||||
|
||||
db_entries=""
|
||||
while IFS= read -r db; do
|
||||
[ -n "$db" ] || continue
|
||||
db_file="$db_dir/${db}.sql.gz"
|
||||
mysqldump_exec \
|
||||
--single-transaction \
|
||||
--quick \
|
||||
--skip-lock-tables \
|
||||
--routines \
|
||||
--triggers \
|
||||
--events \
|
||||
--default-character-set=utf8mb4 \
|
||||
"$db" | gzip -9 > "$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 "$@"
|
||||
Reference in New Issue
Block a user