ba64342702
phpMyAdmin login was broken because phpmyadmin_install.sh never wired the private phpMyAdmin copy into the web server: no Apache Alias was registered via DirectAdmin CustomBuild, so the SSO redirect target was unreachable. Add configure_apache_alias()/apply_apache_alias() (Alias + Directory blocks, ./build rewrite_confs, httpd reload), detect the real Apache group instead of hardcoding diradmin:diradmin, stop silently swallowing chown/chmod failures, and verify an optional SHA256 for the downloaded phpMyAdmin tarball. Extend phpmyadmin_health_check.sh to detect a missing/incorrect Apache alias and to probe HTTP reachability. Security hardening: scope temporary phpMyAdmin SSO MySQL roles to 127.0.0.1 instead of '%', tighten SSO ticket file permissions to 0640 (they contain a plaintext MySQL password), and log MySQL connection failures for diagnosis instead of swallowing them silently. Bump version to 1.2.12 and rebuild the release archive.
313 lines
8.4 KiB
Bash
Executable File
313 lines
8.4 KiB
Bash
Executable File
#!/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 "$@"
|