Import alt-mysql plugin

This commit is contained in:
Marek Miklewicz
2026-07-04 15:48:19 +02:00
commit d71fcf9ace
101 changed files with 18635 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=./mysql_common.sh
source "$SCRIPT_DIR/mysql_common.sh"
fail() {
echo "alt-mysql health: $*" >&2
exit 1
}
mysql_load_alt_runtime
mysql_load_alt_credentials
if command -v systemctl >/dev/null 2>&1; then
systemctl is-active --quiet "$MYSQL_ALT_SERVICE_NAME" || fail "service is not active: $MYSQL_ALT_SERVICE_NAME"
fi
[ -r "$MYSQL_ALT_MY_CNF" ] || fail "client defaults file is not readable: $MYSQL_ALT_MY_CNF"
[ -r "$MYSQL_ALT_MYSQL_CONF" ] || fail "credentials file is not readable: $MYSQL_ALT_MYSQL_CONF"
[ -n "$MYSQL_ALT_PORT" ] || fail "alt port was not discovered"
[ -n "$MYSQL_ALT_SOCKET" ] || fail "alt socket was not discovered"
"$(mysql_alt_binary mysql)" --defaults-extra-file="$MYSQL_ALT_MY_CNF" -e "SELECT 1" >/dev/null \
|| fail "cannot connect through $MYSQL_ALT_MY_CNF"
mysql_ensure_metadata_schema
mysql_exec mysql -e "SELECT COUNT(*) FROM da_plugin_database_owners" >/dev/null \
|| fail "metadata schema is not usable"
echo "alt-mysql health: OK"
+126
View File
@@ -0,0 +1,126 @@
#!/bin/bash
set -euo pipefail
PLUGIN_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
CUSTOMBUILD_ROOT="${CUSTOMBUILD_ROOT:-/usr/local/directadmin/custombuild}"
ACTION="${1:-install}"
SETTINGS_FILE="${MYSQL_PLUGIN_SETTINGS_FILE:-$PLUGIN_DIR/plugin-settings.conf}"
MARKER="managed by DirectAdmin MySQL plugin CustomBuild hook"
log() {
printf 'mysql custombuild-hooks: %s\n' "$*"
}
die() {
printf 'mysql custombuild-hooks: %s\n' "$*" >&2
exit 1
}
read_setting() {
local key="$1"
local default="$2"
local line value
[ -r "$SETTINGS_FILE" ] || {
printf '%s\n' "$default"
return 0
}
line="$(grep -E "^${key}=" "$SETTINGS_FILE" | tail -n 1 || true)"
[ -n "$line" ] || {
printf '%s\n' "$default"
return 0
}
value="${line#*=}"
value="${value%%#*}"
value="${value%\"}"
value="${value#\"}"
value="${value%\'}"
value="${value#\'}"
value="$(printf '%s' "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
printf '%s\n' "${value:-$default}"
}
custombuild_hooks_enabled() {
case "$(read_setting CUSTOMBUILD_HOOKS_ENABLED true)" in
true|yes|on|1) return 0 ;;
*) return 1 ;;
esac
}
write_hook() {
local target="$1"
local app="$2"
local repair_script="$3"
local health_script="$4"
local repair_name health_name tmp
[ -f "$repair_script" ] || die "missing repair script: $repair_script"
[ -f "$health_script" ] || die "missing health check script: $health_script"
repair_name="$(basename "$repair_script")"
health_name="$(basename "$health_script")"
mkdir -p "$(dirname "$target")"
tmp="$(mktemp)"
cat > "$tmp" <<EOF
#!/bin/bash
# $MARKER
set -euo pipefail
PLUGIN_DIR='$PLUGIN_DIR'
LOG_DIR="\$PLUGIN_DIR/data/logs"
LOG_FILE="\$LOG_DIR/custombuild-${app}-repair.log"
mkdir -p "\$LOG_DIR" 2>/dev/null || true
{
printf '%s\\n' "=== \$(date '+%Y-%m-%d %H:%M:%S') ${app} CustomBuild post hook ==="
"\$PLUGIN_DIR/scripts/setup/${repair_name}"
if ! "\$PLUGIN_DIR/scripts/setup/${health_name}"; then
printf '%s\\n' "${app}: warning: health check failed after repair"
fi
} >> "\$LOG_FILE" 2>&1
EOF
install -m 0755 "$tmp" "$target"
rm -f "$tmp"
log "installed CustomBuild post hook: $target"
}
install_all() {
custombuild_hooks_enabled || {
log "disabled by CUSTOMBUILD_HOOKS_ENABLED"
return 0
}
write_hook \
"$CUSTOMBUILD_ROOT/custom/hooks/roundcube/post/90-alt-mysql-repair.sh" \
"roundcube" \
"$PLUGIN_DIR/scripts/setup/roundcube_repair_config.sh" \
"$PLUGIN_DIR/scripts/setup/roundcube_health_check.sh"
write_hook \
"$CUSTOMBUILD_ROOT/custom/hooks/phpmyadmin/post/90-alt-mysql-repair.sh" \
"phpmyadmin" \
"$PLUGIN_DIR/scripts/setup/phpmyadmin_repair.sh" \
"$PLUGIN_DIR/scripts/setup/phpmyadmin_health_check.sh"
}
uninstall_all() {
local hook
for hook in \
"$CUSTOMBUILD_ROOT/custom/hooks/roundcube/post/90-alt-mysql-repair.sh" \
"$CUSTOMBUILD_ROOT/custom/hooks/phpmyadmin/post/90-alt-mysql-repair.sh"
do
if [ -f "$hook" ] && grep -Fq "$MARKER" "$hook"; then
rm -f "$hook"
log "removed CustomBuild post hook: $hook"
fi
done
}
case "$ACTION" in
install) install_all ;;
uninstall) uninstall_all ;;
*) die "unknown action: $ACTION" ;;
esac
+124
View File
@@ -0,0 +1,124 @@
#!/bin/bash
set -Eeuo pipefail
PLUGIN_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
HOOK_ROOT="${DA_CUSTOM_HOOK_DIR:-/usr/local/directadmin/scripts/custom}"
ACTION="${1:-install}"
MARKER="managed by DirectAdmin MySQL plugin hook dispatcher"
log() {
printf 'mysql da-integration: %s\n' "$*"
}
die() {
printf 'mysql da-integration: %s\n' "$*" >&2
exit 1
}
write_dispatcher() {
local target="$1"
local tmp
tmp="$(mktemp)"
cat > "$tmp" <<'EOF'
#!/bin/bash
# managed by DirectAdmin MySQL plugin hook dispatcher
set -u
hook_name="$(basename "$0")"
hook_dir="${0}.d"
status=0
if [ ! -d "$hook_dir" ]; then
exit 0
fi
for hook in "$hook_dir"/*.sh; do
[ -e "$hook" ] || continue
[ -x "$hook" ] || continue
DA_HOOK_NAME="$hook_name" "$hook" "$@"
status=$?
if [ "$status" -ne 0 ]; then
exit "$status"
fi
done
exit 0
EOF
install -m 0755 "$tmp" "$target"
rm -f "$tmp"
}
install_hook() {
local hook_name="$1"
local source_script="$2"
local link_name="${3:-10-alt-mysql.sh}"
local target="$HOOK_ROOT/$hook_name"
local hook_dir="$HOOK_ROOT/${hook_name}.d"
local preserved=""
[ -f "$source_script" ] || die "missing hook source: $source_script"
chmod 0755 "$source_script" 2>/dev/null || true
mkdir -p "$hook_dir"
if [ -e "$target" ] && ! grep -Fq "$MARKER" "$target" 2>/dev/null; then
if [ -d "$target" ]; then
die "cannot preserve hook because target is a directory: $target"
fi
preserved="$hook_dir/00-existing.sh"
if [ -e "$preserved" ]; then
preserved="$hook_dir/00-existing.$(date +%Y%m%d%H%M%S).sh"
fi
mv "$target" "$preserved"
chmod 0755 "$preserved" 2>/dev/null || true
log "preserved existing hook $hook_name as $preserved"
fi
write_dispatcher "$target"
ln -sfn "$source_script" "$hook_dir/$link_name"
chmod 0755 "$target" "$source_script" 2>/dev/null || true
log "installed hook $hook_name -> $source_script"
}
remove_hook_link() {
local hook_name="$1"
local link_name="${2:-10-alt-mysql.sh}"
local target="$HOOK_ROOT/$hook_name"
local hook_dir="$HOOK_ROOT/${hook_name}.d"
rm -f "$hook_dir/$link_name"
if [ -d "$hook_dir" ] && ! find "$hook_dir" -mindepth 1 -maxdepth 1 -print -quit | grep -q .; then
rmdir "$hook_dir" 2>/dev/null || true
if [ -f "$target" ] && grep -Fq "$MARKER" "$target" 2>/dev/null; then
rm -f "$target"
fi
fi
}
install_all() {
mkdir -p "$HOOK_ROOT"
install_hook "user_backup_compress_pre.sh" "$PLUGIN_DIR/scripts/da-integration/alt_mysql_user_backup_compress_pre.sh"
install_hook "user_restore_post_pre_cleanup.sh" "$PLUGIN_DIR/scripts/da-integration/alt_mysql_user_restore_post_pre_cleanup.sh"
install_hook "database_create_pre.sh" "$PLUGIN_DIR/scripts/da-integration/block_native_database_action.sh" "20-alt-mysql-block-native-db.sh"
install_hook "database_delete_pre.sh" "$PLUGIN_DIR/scripts/da-integration/block_native_database_action.sh" "20-alt-mysql-block-native-db.sh"
install_hook "database_user_password_change_pre.sh" "$PLUGIN_DIR/scripts/da-integration/block_native_database_action.sh" "20-alt-mysql-block-native-db.sh"
install_hook "database_user_create_post.sh" "$PLUGIN_DIR/scripts/da-integration/block_native_database_action.sh" "20-alt-mysql-observe-native-db.sh"
install_hook "database_destroy_user_post.sh" "$PLUGIN_DIR/scripts/da-integration/block_native_database_action.sh" "20-alt-mysql-observe-native-db.sh"
}
uninstall_all() {
remove_hook_link "user_backup_compress_pre.sh"
remove_hook_link "user_restore_post_pre_cleanup.sh"
remove_hook_link "database_create_pre.sh" "20-alt-mysql-block-native-db.sh"
remove_hook_link "database_delete_pre.sh" "20-alt-mysql-block-native-db.sh"
remove_hook_link "database_user_password_change_pre.sh" "20-alt-mysql-block-native-db.sh"
remove_hook_link "database_user_create_post.sh" "20-alt-mysql-observe-native-db.sh"
remove_hook_link "database_destroy_user_post.sh" "20-alt-mysql-observe-native-db.sh"
}
case "$ACTION" in
install) install_all ;;
uninstall) uninstall_all ;;
*) die "unknown action: $ACTION" ;;
esac
+52
View File
@@ -0,0 +1,52 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=./mysql_common.sh
source "$SCRIPT_DIR/mysql_common.sh"
usage() {
cat <<'EOF'
Użycie:
dbrestore.sh <plik.sql|plik.sql.gz> [nazwa_bazy]
Opis:
Przywraca pojedynczą bazę MySQL z pliku dumpa. Gdy nazwa bazy nie zostanie
podana, skrypt wyprowadza ją z nazwy pliku.
EOF
}
[ "${1:-}" = "--help" ] && {
usage
exit 0
}
[ $# -ge 1 ] || {
usage
exit 1
}
FILE="$1"
[ -f "$FILE" ] || {
echo "Plik nie istnieje: $FILE" >&2
exit 1
}
case "$FILE" in
*.sql|*.sql.gz|*.gz) ;;
*)
echo "Dozwolone są pliki .sql, .sql.gz lub .gz." >&2
exit 1
;;
esac
TARGET_DB="${2:-$(basename "$FILE" | sed -E 's/\.sql(\.gz)?$//; s/\.gz$//')}"
TARGET_DB="$(mysql_safe_identifier "$TARGET_DB")"
mysql_load_alt_credentials
mysql_exec mysql -e "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$TARGET_DB");"
mysql_exec mysql -e "CREATE DATABASE $(mysql_quote_identifier "$TARGET_DB") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql_restore_stream "$TARGET_DB" "$FILE"
echo "Przywracanie bazy ${TARGET_DB} zakończone."
+99
View File
@@ -0,0 +1,99 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=./mysql_common.sh
source "$SCRIPT_DIR/mysql_common.sh"
BASE_DIR="${BASE_DIR:-/home/admin/mysql_backup}"
DATE_FORMAT="${DATE_FORMAT:-%d-%m-%Y}"
ADD_TIME="${ADD_TIME:-0}"
RETENTION="${RETENTION:-7}"
ENABLE_COMPRESSION="${ENABLE_COMPRESSION:-1}"
LOG_FILE="${LOG_FILE:-/var/log/mysql_backup.log}"
timestamp() {
date '+%Y-%m-%d %H:%M:%S'
}
log() {
printf '[%s] %s\n' "$(timestamp)" "$*" | tee -a "$LOG_FILE"
}
dump_globals_file() {
local output_file="$1"
local tmp_file
tmp_file="$(mktemp)"
{
echo "-- Global users and grants for MySQL"
echo "-- Generated at $(timestamp)"
} > "$tmp_file"
while IFS=$'\t' read -r db_user db_host; do
[ -n "$db_user" ] || continue
[ -n "$db_host" ] || continue
printf "DROP USER IF EXISTS '%s'@'%s';\n" "$db_user" "$db_host" >> "$tmp_file"
mysql_exec mysql -Nse "SHOW CREATE USER '${db_user}'@'${db_host}'" >> "$tmp_file" || true
echo ";" >> "$tmp_file"
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 '${db_user}'@'${db_host}'" || true)
echo >> "$tmp_file"
done < <(
mysql_exec mysql -Nse "
SELECT user, host
FROM mysql.user
WHERE user <> ''
AND user NOT IN ('mysql.sys', 'mysql.session', 'mysql.infoschema', 'mariadb.sys')
ORDER BY user, host
" || true
)
if [ "$ENABLE_COMPRESSION" = "1" ]; then
gzip -9 < "$tmp_file" > "$output_file"
else
mv "$tmp_file" "$output_file"
return 0
fi
rm -f "$tmp_file"
}
mysql_load_alt_credentials
mkdir -p "$BASE_DIR"
if [ "$ADD_TIME" = "1" ]; then
DATE_STR="$(date +"${DATE_FORMAT}_%H-%M")"
else
DATE_STR="$(date +"${DATE_FORMAT}")"
fi
BACKUP_DIR="${BASE_DIR}/${DATE_STR}"
mkdir -p "$BACKUP_DIR"
log "Start backupu MySQL do ${BACKUP_DIR}"
GLOBALS_FILE="${BACKUP_DIR}/_globals.sql"
if [ "$ENABLE_COMPRESSION" = "1" ]; then
GLOBALS_FILE="${GLOBALS_FILE}.gz"
fi
dump_globals_file "$GLOBALS_FILE"
log "Zapisano ${GLOBALS_FILE##*/}"
while IFS= read -r db_name; do
[ -n "$db_name" ] || continue
target_file="${BACKUP_DIR}/${db_name}.sql"
if [ "$ENABLE_COMPRESSION" = "1" ]; then
mysqldump_exec --single-transaction --skip-lock-tables --routines --triggers --events --databases "$db_name" | gzip -9 > "${target_file}.gz"
log "Backup ${db_name} -> ${target_file##*/}.gz"
else
mysqldump_exec --single-transaction --skip-lock-tables --routines --triggers --events --databases "$db_name" > "$target_file"
log "Backup ${db_name} -> ${target_file##*/}"
fi
done < <(mysql_non_system_databases)
if [ "$RETENTION" -gt 0 ]; then
find "$BASE_DIR" -mindepth 1 -maxdepth 1 -type d -mtime +"$RETENTION" -exec rm -rf {} +
fi
log "Backup MySQL zakończony."
+489
View File
@@ -0,0 +1,489 @@
#!/bin/bash
set -euo pipefail
MYSQL_PLUGIN_CREDENTIALS_FILE="${MYSQL_PLUGIN_CREDENTIALS_FILE:-/usr/local/directadmin/conf/alt-mysql.conf}"
MYSQL_PLUGIN_METADATA_FILE="${MYSQL_PLUGIN_METADATA_FILE:-/usr/local/directadmin/conf/alt-mariadb.env}"
MYSQL_PLUGIN_ALT_MY_CNF="${MYSQL_PLUGIN_ALT_MY_CNF:-/usr/local/directadmin/conf/alt-my.cnf}"
MYSQL_PLUGIN_ALT_INSTANCE_CNF="${MYSQL_PLUGIN_ALT_INSTANCE_CNF:-/etc/alt-mariadb.cnf}"
trim_value() {
local value="${1:-}"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
printf '%s' "$value"
}
strip_inline_comment() {
local value
value="$(trim_value "${1:-}")"
value="${value%%#*}"
printf '%s' "$(trim_value "$value")"
}
unquote_value() {
local value
value="$(trim_value "${1:-}")"
if [ "${#value}" -ge 2 ] && [ "${value:0:1}" = "'" ] && [ "${value: -1}" = "'" ]; then
value="${value:1:${#value}-2}"
elif [ "${#value}" -ge 2 ] && [ "${value:0:1}" = '"' ] && [ "${value: -1}" = '"' ]; then
value="${value:1:${#value}-2}"
fi
printf '%s' "$value"
}
mysql_require_root() {
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
echo "Ten skrypt musi być uruchomiony jako root." >&2
exit 1
fi
}
mysql_detect_binary() {
local bin_name="$1"
local candidate=""
local alt_name=""
if [ -n "${MYSQL_PLUGIN_CLIENT_BIN_DIR:-}" ]; then
for alt_name in "$bin_name" "$(mysql_binary_alias "$bin_name")"; do
[ -n "$alt_name" ] || continue
candidate="${MYSQL_PLUGIN_CLIENT_BIN_DIR%/}/${alt_name}"
if [ -x "$candidate" ]; then
printf '%s\n' "$candidate"
return 0
fi
done
fi
for alt_name in "$bin_name" "$(mysql_binary_alias "$bin_name")"; do
[ -n "$alt_name" ] || continue
candidate="$(command -v "$alt_name" 2>/dev/null || true)"
if [ -n "$candidate" ]; then
printf '%s\n' "$candidate"
return 0
fi
done
for alt_name in "$bin_name" "$(mysql_binary_alias "$bin_name")"; do
[ -n "$alt_name" ] || continue
while IFS= read -r candidate; do
if [ -x "$candidate" ]; then
printf '%s\n' "$candidate"
return 0
fi
done < <(find /usr/local -maxdepth 3 -type f -path '/usr/local/mariadb-*/bin/'"$alt_name" 2>/dev/null | sort -V -r)
for candidate in /usr/bin/"$alt_name" /usr/local/bin/"$alt_name"; do
if [ -x "$candidate" ]; then
printf '%s\n' "$candidate"
return 0
fi
done
done
return 1
}
mysql_binary_alias() {
case "$1" in
mysql) printf 'mariadb\n' ;;
mysqldump) printf 'mariadb-dump\n' ;;
mysqladmin) printf 'mariadb-admin\n' ;;
mysql_upgrade) printf 'mariadb-upgrade\n' ;;
*) printf '\n' ;;
esac
}
mysql_load_plugin_settings_file() {
local file="${1:-}"
local raw_line key value
[ -n "$file" ] || return 0
[ -r "$file" ] || return 0
while IFS= read -r raw_line || [ -n "$raw_line" ]; do
raw_line="$(trim_value "$raw_line")"
[ -n "$raw_line" ] || continue
case "$raw_line" in
\#*) continue ;;
esac
[[ "$raw_line" == *=* ]] || continue
key="$(trim_value "${raw_line%%=*}")"
value="$(unquote_value "$(strip_inline_comment "${raw_line#*=}")")"
case "$key" in
MYSQL_PLUGIN_CREDENTIALS_FILE) MYSQL_PLUGIN_CREDENTIALS_FILE="$value" ;;
MYSQL_PLUGIN_METADATA_FILE) MYSQL_PLUGIN_METADATA_FILE="$value" ;;
MYSQL_PLUGIN_ALT_MY_CNF) MYSQL_PLUGIN_ALT_MY_CNF="$value" ;;
MYSQL_PLUGIN_ALT_MARIADB_CNF) MYSQL_PLUGIN_ALT_INSTANCE_CNF="$value" ;;
MYSQL_PLUGIN_CLIENT_BIN_DIR) MYSQL_PLUGIN_CLIENT_BIN_DIR="$value" ;;
esac
done < "$file"
}
mysql_read_key_value() {
local file="$1"
local key="$2"
[ -r "$file" ] || return 1
awk -F= -v key="$key" '
function trim(s) {
sub(/^[[:space:]]+/, "", s)
sub(/[[:space:]]+$/, "", s)
return s
}
$0 ~ /^[[:space:]]*#/ || $0 !~ /=/ { next }
{
k = trim($1)
if (k != key) { next }
sub(/^[^=]*=/, "")
v = trim($0)
if (v ~ /^'\''.*'\''$/ || v ~ /^".*"$/) {
v = substr(v, 2, length(v) - 2)
}
print v
exit
}
' "$file"
}
mysql_read_cnf_value() {
local file="$1"
local section="$2"
local key="$3"
[ -r "$file" ] || return 1
awk -F= -v section="$section" -v key="$key" '
function trim(s) {
sub(/^[[:space:]]+/, "", s)
sub(/[[:space:]]+$/, "", s)
return s
}
/^[[:space:]]*#/ || /^[[:space:]]*;/ { next }
/^\[[^]]+\]/ {
current=$0
gsub(/^\[[[:space:]]*/, "", current)
gsub(/[[:space:]]*\]$/, "", current)
current=trim(current)
next
}
current == section && $0 ~ /=/ {
k=trim($1)
if (k != key) { next }
sub(/^[^=]*=/, "")
v=trim($0)
if (v ~ /^'\''.*'\''$/ || v ~ /^".*"$/) {
v=substr(v, 2, length(v)-2)
}
print v
exit
}
' "$file"
}
mysql_load_alt_runtime() {
MYSQL_ALT_METADATA_FILE="${MYSQL_ALT_METADATA_FILE:-$MYSQL_PLUGIN_METADATA_FILE}"
MYSQL_ALT_MYSQL_CONF="${MYSQL_ALT_MYSQL_CONF:-$MYSQL_PLUGIN_CREDENTIALS_FILE}"
MYSQL_ALT_MY_CNF="${MYSQL_ALT_MY_CNF:-$MYSQL_PLUGIN_ALT_MY_CNF}"
MYSQL_ALT_INSTANCE_CNF="${MYSQL_ALT_INSTANCE_CNF:-$MYSQL_PLUGIN_ALT_INSTANCE_CNF}"
MYSQL_ALT_SERVICE_NAME="${MYSQL_ALT_SERVICE_NAME:-alt-mariadb}"
MYSQL_ALT_BASEDIR="${MYSQL_ALT_BASEDIR:-}"
MYSQL_ALT_DATADIR="${MYSQL_ALT_DATADIR:-}"
MYSQL_ALT_PORT="${MYSQL_ALT_PORT:-}"
MYSQL_ALT_SOCKET="${MYSQL_ALT_SOCKET:-}"
if [ -r "$MYSQL_ALT_METADATA_FILE" ]; then
MYSQL_ALT_SERVICE_NAME="$(mysql_read_key_value "$MYSQL_ALT_METADATA_FILE" SERVICE_NAME || printf '%s' "$MYSQL_ALT_SERVICE_NAME")"
MYSQL_ALT_BASEDIR="$(mysql_read_key_value "$MYSQL_ALT_METADATA_FILE" MARIADB_DIR || printf '%s' "$MYSQL_ALT_BASEDIR")"
MYSQL_ALT_DATADIR="$(mysql_read_key_value "$MYSQL_ALT_METADATA_FILE" DATADIR || printf '%s' "$MYSQL_ALT_DATADIR")"
MYSQL_ALT_PORT="$(mysql_read_key_value "$MYSQL_ALT_METADATA_FILE" PORT || printf '%s' "$MYSQL_ALT_PORT")"
MYSQL_ALT_SOCKET="$(mysql_read_key_value "$MYSQL_ALT_METADATA_FILE" SOCKET || printf '%s' "$MYSQL_ALT_SOCKET")"
MYSQL_ALT_MYSQL_CONF="$(mysql_read_key_value "$MYSQL_ALT_METADATA_FILE" ALT_MYSQL_CONF || printf '%s' "$MYSQL_ALT_MYSQL_CONF")"
MYSQL_ALT_MY_CNF="$(mysql_read_key_value "$MYSQL_ALT_METADATA_FILE" ALT_MY_CNF || printf '%s' "$MYSQL_ALT_MY_CNF")"
MYSQL_ALT_INSTANCE_CNF="$(mysql_read_key_value "$MYSQL_ALT_METADATA_FILE" INSTANCE_CNF || printf '%s' "$MYSQL_ALT_INSTANCE_CNF")"
fi
if [ -r "$MYSQL_ALT_MYSQL_CONF" ]; then
[ -n "$MYSQL_ALT_PORT" ] || MYSQL_ALT_PORT="$(mysql_read_key_value "$MYSQL_ALT_MYSQL_CONF" port || true)"
[ -n "$MYSQL_ALT_SOCKET" ] || MYSQL_ALT_SOCKET="$(mysql_read_key_value "$MYSQL_ALT_MYSQL_CONF" socket || true)"
fi
if [ -r "$MYSQL_ALT_INSTANCE_CNF" ]; then
[ -n "$MYSQL_ALT_BASEDIR" ] || MYSQL_ALT_BASEDIR="$(mysql_read_cnf_value "$MYSQL_ALT_INSTANCE_CNF" mariadb basedir || true)"
[ -n "$MYSQL_ALT_DATADIR" ] || MYSQL_ALT_DATADIR="$(mysql_read_cnf_value "$MYSQL_ALT_INSTANCE_CNF" mariadb datadir || true)"
[ -n "$MYSQL_ALT_PORT" ] || MYSQL_ALT_PORT="$(mysql_read_cnf_value "$MYSQL_ALT_INSTANCE_CNF" mariadb port || true)"
[ -n "$MYSQL_ALT_SOCKET" ] || MYSQL_ALT_SOCKET="$(mysql_read_cnf_value "$MYSQL_ALT_INSTANCE_CNF" mariadb socket || true)"
fi
if [ -n "$MYSQL_ALT_BASEDIR" ] && [ -d "$MYSQL_ALT_BASEDIR/bin" ]; then
MYSQL_PLUGIN_CLIENT_BIN_DIR="${MYSQL_PLUGIN_CLIENT_BIN_DIR:-$MYSQL_ALT_BASEDIR/bin}"
fi
[ -n "$MYSQL_ALT_PORT" ] || MYSQL_ALT_PORT="3306"
[ -n "$MYSQL_ALT_MYSQL_CONF" ] || MYSQL_ALT_MYSQL_CONF="/usr/local/directadmin/conf/alt-mysql.conf"
[ -n "$MYSQL_ALT_MY_CNF" ] || MYSQL_ALT_MY_CNF="/usr/local/directadmin/conf/alt-my.cnf"
[ -n "$MYSQL_ALT_INSTANCE_CNF" ] || MYSQL_ALT_INSTANCE_CNF="/etc/alt-mariadb.cnf"
[ -n "$MYSQL_ALT_SERVICE_NAME" ] || MYSQL_ALT_SERVICE_NAME="alt-mariadb"
}
mysql_alt_service_name() {
mysql_load_alt_runtime
printf '%s\n' "$MYSQL_ALT_SERVICE_NAME"
}
mysql_alt_basedir() {
mysql_load_alt_runtime
printf '%s\n' "$MYSQL_ALT_BASEDIR"
}
mysql_alt_datadir() {
mysql_load_alt_runtime
printf '%s\n' "$MYSQL_ALT_DATADIR"
}
mysql_alt_port() {
mysql_load_alt_runtime
printf '%s\n' "$MYSQL_ALT_PORT"
}
mysql_alt_socket() {
mysql_load_alt_runtime
printf '%s\n' "$MYSQL_ALT_SOCKET"
}
mysql_alt_client_defaults() {
mysql_load_alt_runtime
printf '%s\n' "$MYSQL_ALT_MY_CNF"
}
mysql_alt_binary() {
local bin_name="$1"
mysql_load_alt_runtime
mysql_detect_binary "$bin_name"
}
mysql_native_binary() {
local bin_name="$1"
local alt_name candidate
for alt_name in "$bin_name" "$(mysql_binary_alias "$bin_name")"; do
[ -n "$alt_name" ] || continue
for candidate in /usr/bin/"$alt_name" /usr/local/bin/"$alt_name"; do
if [ -x "$candidate" ]; then
printf '%s\n' "$candidate"
return 0
fi
done
candidate="$(command -v "$alt_name" 2>/dev/null || true)"
if [ -n "$candidate" ]; then
printf '%s\n' "$candidate"
return 0
fi
done
return 1
}
mysql_load_credentials() {
local file="${1:-$MYSQL_PLUGIN_CREDENTIALS_FILE}"
[ -r "$file" ] || {
echo "Brak pliku poświadczeń MySQL: $file" >&2
exit 1
}
MYSQL_HOST=""
MYSQL_PORT=""
MYSQL_USER=""
MYSQL_PASSWORD=""
MYSQL_DATABASE=""
MYSQL_SOCKET=""
while IFS= read -r raw_line || [ -n "$raw_line" ]; do
raw_line="$(trim_value "$raw_line")"
[ -n "$raw_line" ] || continue
case "$raw_line" in
\#*) continue ;;
esac
if [[ "$raw_line" == *=* ]]; then
local key="${raw_line%%=*}"
local value="${raw_line#*=}"
key="$(printf '%s' "$key" | tr '[:upper:]' '[:lower:]')"
key="$(trim_value "$key")"
value="$(unquote_value "$(strip_inline_comment "$value")")"
case "$key" in
host|mysql_host) MYSQL_HOST="$value" ;;
port|mysql_port) MYSQL_PORT="$value" ;;
user|username|mysql_user) MYSQL_USER="$value" ;;
password|passwd|pass|mysql_password) MYSQL_PASSWORD="$value" ;;
database|dbname|mysql_database) MYSQL_DATABASE="$value" ;;
socket|mysql_socket) MYSQL_SOCKET="$value" ;;
esac
continue
fi
local host_part port_part db_part user_part pass_part rest
host_part="${raw_line%%:*}"
rest="${raw_line#*:}"
port_part="${rest%%:*}"
rest="${rest#*:}"
db_part="${rest%%:*}"
rest="${rest#*:}"
user_part="${rest%%:*}"
pass_part="${rest#*:}"
MYSQL_HOST="$host_part"
MYSQL_PORT="$port_part"
MYSQL_DATABASE="$db_part"
MYSQL_USER="$user_part"
MYSQL_PASSWORD="$pass_part"
break
done < "$file"
MYSQL_HOST="$(trim_value "${MYSQL_HOST:-}")"
MYSQL_PORT="$(trim_value "${MYSQL_PORT:-}")"
MYSQL_USER="$(trim_value "${MYSQL_USER:-}")"
MYSQL_PASSWORD="${MYSQL_PASSWORD:-}"
MYSQL_DATABASE="$(trim_value "${MYSQL_DATABASE:-}")"
MYSQL_SOCKET="$(trim_value "${MYSQL_SOCKET:-}")"
[ -n "$MYSQL_HOST" ] || MYSQL_HOST="localhost"
[ -n "$MYSQL_PORT" ] || MYSQL_PORT="3306"
[ -n "$MYSQL_USER" ] || MYSQL_USER="root"
[ -n "$MYSQL_DATABASE" ] || MYSQL_DATABASE="mysql"
}
mysql_load_alt_credentials() {
mysql_load_alt_runtime
mysql_load_credentials "$MYSQL_ALT_MYSQL_CONF"
}
mysql_base_args() {
local args=("--user=$MYSQL_USER")
if [ "$MYSQL_HOST" = "localhost" ]; then
if [ -n "$MYSQL_SOCKET" ]; then
args+=("--socket=$MYSQL_SOCKET")
else
args+=("--host=localhost")
fi
else
args+=("--protocol=TCP" "--host=$MYSQL_HOST" "--port=$MYSQL_PORT")
fi
printf '%s\n' "${args[@]}"
}
mysql_exec() {
local database="$1"
shift
local -a args=()
while IFS= read -r arg; do
args+=("$arg")
done < <(mysql_base_args)
MYSQL_PWD="$MYSQL_PASSWORD" "$(mysql_detect_binary mysql)" "${args[@]}" --database="$database" "$@"
}
mysql_exec_sql() {
local database="$1"
local sql="$2"
mysql_exec "$database" -e "$sql"
}
mysqldump_exec() {
local -a args=()
while IFS= read -r arg; do
args+=("$arg")
done < <(mysql_base_args)
MYSQL_PWD="$MYSQL_PASSWORD" "$(mysql_detect_binary mysqldump)" "${args[@]}" "$@"
}
mysql_alt_version() {
mysql_load_alt_credentials
mysql_exec mysql -Nse "SELECT VERSION()" 2>/dev/null || true
}
mysql_ensure_metadata_schema() {
mysql_exec mysql -e "
CREATE TABLE IF NOT EXISTS da_plugin_access_hosts (
da_user VARCHAR(255) NOT NULL,
role_name VARCHAR(255) NOT NULL,
host_pattern VARCHAR(255) NOT NULL,
note VARCHAR(180) NOT NULL DEFAULT '',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (role_name, host_pattern),
KEY access_hosts_da_user_idx (da_user)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS da_plugin_grant_profiles (
db_name VARCHAR(255) NOT NULL,
role_name VARCHAR(255) NOT NULL,
privileges TEXT NOT NULL,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (db_name, role_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS da_plugin_database_owners (
db_name VARCHAR(255) NOT NULL,
da_user VARCHAR(255) NOT NULL,
role_name VARCHAR(255) NOT NULL,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (db_name),
KEY database_owners_da_user_idx (da_user),
KEY database_owners_role_idx (role_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS da_plugin_sso_accounts (
role_name VARCHAR(255) NOT NULL,
host_pattern VARCHAR(255) NOT NULL,
expires_at INT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (role_name, host_pattern),
KEY sso_accounts_expires_idx (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS da_plugin_system_databases (
service_name VARCHAR(64) NOT NULL,
db_name VARCHAR(255) NOT NULL,
db_user VARCHAR(255) NOT NULL,
endpoint_mode VARCHAR(32) NOT NULL DEFAULT 'tcp',
config_path VARCHAR(512) NOT NULL DEFAULT '',
enabled TINYINT(1) NOT NULL DEFAULT 1,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (service_name),
KEY system_databases_db_idx (db_name),
KEY system_databases_user_idx (db_user)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
"
}
mysql_service_name() {
local service
for service in mysqld mariadb mysql; do
if systemctl list-unit-files "${service}.service" >/dev/null 2>&1; then
printf '%s\n' "$service"
return 0
fi
done
printf '%s\n' "mysqld"
}
mysql_safe_identifier() {
local value="${1:-}"
if [[ ! "$value" =~ ^[A-Za-z0-9_]+$ ]]; then
echo "Nieprawidłowy identyfikator MySQL: $value" >&2
exit 1
fi
printf '%s' "$value"
}
mysql_quote_identifier() {
local value
value="$(mysql_safe_identifier "$1")"
printf '`%s`' "$value"
}
mysql_escape_literal() {
printf "%s" "${1:-}" | sed "s/'/''/g"
}
mysql_non_system_databases() {
mysql_exec mysql -Nse "SHOW DATABASES" | grep -Ev '^(information_schema|performance_schema|mysql|sys)$' || true
}
mysql_restore_stream() {
local database="$1"
local file="$2"
if [[ "$file" == *.gz ]]; then
gunzip -c "$file" | mysql_exec "$database"
return ${PIPESTATUS[1]}
fi
mysql_exec "$database" < "$file"
}
+376
View File
@@ -0,0 +1,376 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
PLUGIN_SETTINGS_FILE="${MYSQL_HOST_SYNC_PLUGIN_SETTINGS:-$PLUGIN_DIR/plugin-settings.conf}"
# shellcheck source=./mysql_common.sh
source "$SCRIPT_DIR/mysql_common.sh"
load_plugin_settings() {
local file="$1"
local raw_line key value
[ -r "$file" ] || return 0
while IFS= read -r raw_line || [ -n "$raw_line" ]; do
raw_line="$(trim_value "$raw_line")"
[ -n "$raw_line" ] || continue
case "$raw_line" in
\#*) continue ;;
esac
[[ "$raw_line" == *=* ]] || continue
key="$(trim_value "${raw_line%%=*}")"
value="$(unquote_value "$(strip_inline_comment "${raw_line#*=}")")"
case "$key" in
MYSQL_PLUGIN_CREDENTIALS_FILE) MYSQL_PLUGIN_CREDENTIALS_FILE="$value" ;;
MYSQL_PLUGIN_METADATA_FILE) MYSQL_PLUGIN_METADATA_FILE="$value" ;;
MYSQL_PLUGIN_ALT_MY_CNF) MYSQL_PLUGIN_ALT_MY_CNF="$value" ;;
MYSQL_PLUGIN_CLIENT_BIN_DIR) MYSQL_PLUGIN_CLIENT_BIN_DIR="$value" ;;
MYSQL_PLUGIN_REMOTE_BIND_ADDRESS) MYSQL_PLUGIN_REMOTE_BIND_ADDRESS="$value" ;;
MYSQL_PLUGIN_CSF_ALLOW_FILE) MYSQL_HOST_SYNC_CSF_ALLOW="$value" ;;
MYSQL_PLUGIN_CSF_CONF_FILE) MYSQL_HOST_SYNC_CSF_CONF="$value" ;;
MYSQL_PLUGIN_CSF_MANAGED_ALLOW_FILE) MYSQL_HOST_SYNC_MANAGED_ALLOW="$value" ;;
MYSQL_PLUGIN_ALT_MARIADB_CNF) MYSQL_HOST_SYNC_INSTANCE_CNF="$value" ;;
MYSQL_PLUGIN_ALT_MARIADB_SERVICE) MYSQL_HOST_SYNC_SERVICE="$value" ;;
esac
done < "$file"
}
require_root_or_sudo() {
if [ "${MYSQL_HOST_SYNC_ASSUME_ROOT:-0}" = "1" ]; then
return 0
fi
if [ "${EUID:-$(id -u)}" -eq 0 ]; then
return 0
fi
if command -v sudo >/dev/null 2>&1; then
exec sudo -n "$0" "$@"
fi
echo "mysql: synchronizacja hostów wymaga uprawnień root." >&2
exit 1
}
is_ipv4() {
local ip="${1:-}"
local IFS=.
local -a octets
[[ "$ip" =~ ^[0-9]+(\.[0-9]+){3}$ ]] || return 1
read -r -a octets <<< "$ip"
[ "${#octets[@]}" -eq 4 ] || return 1
local octet
for octet in "${octets[@]}"; do
[[ "$octet" =~ ^[0-9]+$ ]] || return 1
[ "$octet" -ge 0 ] && [ "$octet" -le 255 ] || return 1
done
}
is_local_host() {
case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in
""|localhost|127.0.0.1|::1) return 0 ;;
*) return 1 ;;
esac
}
collect_remote_hosts() {
local sql output host
sql="SELECT DISTINCT host_pattern FROM da_plugin_access_hosts WHERE host_pattern <> '' ORDER BY host_pattern"
if ! output="$(mysql_exec mysql --batch --skip-column-names --raw -e "$sql")"; then
echo "mysql: nie można odczytać listy hostów zdalnych z da_plugin_access_hosts." >&2
return 1
fi
while IFS= read -r host || [ -n "$host" ]; do
host="$(trim_value "$host")"
[ -n "$host" ] || continue
is_local_host "$host" && continue
is_ipv4 "$host" || continue
printf '%s\n' "$host"
done <<< "$output" | sort -u
}
write_managed_allow_file() {
local target="$1"
local port="$2"
local tmp
shift 2
local -a hosts=("$@")
install -d -m 0755 "$(dirname "$target")"
tmp="$(mktemp)"
{
echo "# Managed by DirectAdmin MySQL plugin. Do not edit manually."
echo "# Remote MariaDB access for port $port."
echo "# Source: da_plugin_access_hosts in the plugin metadata schema."
local host
for host in "${hosts[@]}"; do
echo "tcp|in|d=$port|s=$host"
done
} > "$tmp"
if [ ! -f "$target" ] || ! cmp -s "$tmp" "$target"; then
if [ -f "$target" ]; then
cat "$tmp" > "$target"
chmod 0644 "$target"
else
install -m 0644 "$tmp" "$target"
fi
changed_csf=1
fi
rm -f "$tmp"
}
ensure_csf_include() {
local csf_allow="$1"
local managed_allow="$2"
local include_line="Include $managed_allow"
[ -f "$csf_allow" ] || {
echo "mysql: brak pliku CSF allow: $csf_allow" >&2
return 1
}
if grep -Fxq "$include_line" "$csf_allow"; then
return 0
fi
printf '\n%s\n' "$include_line" >> "$csf_allow"
changed_csf=1
}
remove_port_from_csv() {
local csv="$1"
local port="$2"
local old_ifs="$IFS"
local item out="" start end left_start left_end right_start right_end
IFS=','
for item in $csv; do
item="$(trim_value "$item")"
[ -n "$item" ] || continue
if [ "$item" = "$port" ]; then
continue
fi
if [[ "$item" =~ ^([0-9]+):([0-9]+)$ ]]; then
start="${BASH_REMATCH[1]}"
end="${BASH_REMATCH[2]}"
if [ "$start" -le "$port" ] && [ "$port" -le "$end" ]; then
left_start="$start"
left_end=$((port - 1))
right_start=$((port + 1))
right_end="$end"
if [ "$left_start" -le "$left_end" ]; then
if [ -z "$out" ]; then
out="$left_start"
else
out="$out,$left_start"
fi
if [ "$left_start" -ne "$left_end" ]; then
out="$out:$left_end"
fi
fi
if [ "$right_start" -le "$right_end" ]; then
if [ -z "$out" ]; then
out="$right_start"
else
out="$out,$right_start"
fi
if [ "$right_start" -ne "$right_end" ]; then
out="$out:$right_end"
fi
fi
continue
fi
fi
if [ -z "$out" ]; then
out="$item"
else
out="$out,$item"
fi
done
IFS="$old_ifs"
printf '%s' "$out"
}
remove_port_from_global_csf_lists() {
local csf_conf="$1"
local port="$2"
local tmp changed=0 line new_list new_line
[ -f "$csf_conf" ] || {
echo "mysql: brak pliku CSF config: $csf_conf" >&2
return 1
}
tmp="$(mktemp)"
while IFS= read -r line || [ -n "$line" ]; do
if [[ "$line" =~ ^([[:space:]]*)(TCP6?_IN)([[:space:]]*=[[:space:]]*)\"([^\"]*)\"(.*)$ ]]; then
new_list="$(remove_port_from_csv "${BASH_REMATCH[4]}" "$port")"
new_line="${BASH_REMATCH[1]}${BASH_REMATCH[2]}${BASH_REMATCH[3]}\"$new_list\"${BASH_REMATCH[5]}"
if [ "$new_line" != "$line" ]; then
changed=1
fi
printf '%s\n' "$new_line" >> "$tmp"
else
printf '%s\n' "$line" >> "$tmp"
fi
done < "$csf_conf"
if [ "$changed" -eq 1 ]; then
cat "$tmp" > "$csf_conf"
chmod 0600 "$csf_conf"
changed_csf=1
fi
rm -f "$tmp"
}
ensure_remote_bind_address() {
local cnf="$1"
local desired="$2"
local tmp changed=0 inserted=0 in_mariadb=0 line new_line
[ -f "$cnf" ] || {
echo "mysql: brak pliku konfiguracji MariaDB: $cnf" >&2
return 1
}
is_ipv4 "$desired" || {
echo "mysql: nieprawidłowy bind-address dla zdalnego MySQL: $desired" >&2
return 1
}
tmp="$(mktemp)"
while IFS= read -r line || [ -n "$line" ]; do
if [[ "$line" =~ ^[[:space:]]*\[mariadb\][[:space:]]*$ ]]; then
in_mariadb=1
printf '%s\n' "$line" >> "$tmp"
continue
fi
if [ "$in_mariadb" -eq 1 ] && [[ "$line" =~ ^[[:space:]]*\[.*\][[:space:]]*$ ]]; then
if [ "$inserted" -eq 0 ]; then
printf 'bind-address=%s\n' "$desired" >> "$tmp"
inserted=1
changed=1
fi
in_mariadb=0
fi
if [ "$in_mariadb" -eq 1 ] && [[ "$line" =~ ^[[:space:]]*bind-address[[:space:]]*= ]]; then
new_line="bind-address=$desired"
if [ "$line" != "$new_line" ]; then
changed=1
fi
printf '%s\n' "$new_line" >> "$tmp"
inserted=1
continue
fi
printf '%s\n' "$line" >> "$tmp"
done < "$cnf"
if [ "$inserted" -eq 0 ]; then
printf '\n[mariadb]\nbind-address=%s\n' "$desired" >> "$tmp"
changed=1
fi
if [ "$changed" -eq 1 ]; then
cat "$tmp" > "$cnf"
chmod 0640 "$cnf"
changed_bind=1
fi
rm -f "$tmp"
}
reload_csf() {
if [ "${MYSQL_HOST_SYNC_SKIP_CSF_RELOAD:-0}" = "1" ]; then
return 0
fi
if command -v csf >/dev/null 2>&1; then
csf -r
return 0
fi
echo "mysql: nie znaleziono polecenia csf, nie można przeładować firewalla." >&2
return 1
}
restart_mariadb_service() {
local service="$1"
if [ "${MYSQL_HOST_SYNC_SKIP_SERVICE_RESTART:-0}" = "1" ]; then
return 0
fi
if command -v systemctl >/dev/null 2>&1; then
systemctl restart "$service"
return 0
fi
if command -v service >/dev/null 2>&1; then
service "$service" restart
return 0
fi
echo "mysql: nie znaleziono systemctl/service, nie można zrestartować $service." >&2
return 1
}
main() {
require_root_or_sudo "$@"
load_plugin_settings "$PLUGIN_SETTINGS_FILE"
mysql_load_alt_credentials
mysql_ensure_metadata_schema
local csf_dir="${MYSQL_HOST_SYNC_CSF_DIR:-/etc/csf}"
local csf_allow="${MYSQL_HOST_SYNC_CSF_ALLOW:-$csf_dir/csf.allow}"
local csf_conf="${MYSQL_HOST_SYNC_CSF_CONF:-$csf_dir/csf.conf}"
local managed_allow="${MYSQL_HOST_SYNC_MANAGED_ALLOW:-$csf_dir/alt-mysql-plugin.allow}"
local instance_cnf="${MYSQL_HOST_SYNC_INSTANCE_CNF:-$MYSQL_ALT_INSTANCE_CNF}"
local bind_address="${MYSQL_REMOTE_BIND_ADDRESS:-${MYSQL_PLUGIN_REMOTE_BIND_ADDRESS:-0.0.0.0}}"
local service="${MYSQL_HOST_SYNC_SERVICE:-$MYSQL_ALT_SERVICE_NAME}"
local changed_csf=0 changed_bind=0 host_output
local -a hosts=()
host_output="$(collect_remote_hosts)"
while IFS= read -r host || [ -n "$host" ]; do
[ -n "$host" ] || continue
hosts+=("$host")
done <<< "$host_output"
write_managed_allow_file "$managed_allow" "$MYSQL_PORT" "${hosts[@]}"
ensure_csf_include "$csf_allow" "$managed_allow"
remove_port_from_global_csf_lists "$csf_conf" "$MYSQL_PORT"
ensure_remote_bind_address "$instance_cnf" "$bind_address"
if [ "$changed_bind" -eq 1 ]; then
restart_mariadb_service "$service"
fi
if [ "$changed_csf" -eq 1 ]; then
reload_csf
fi
cat <<EOF
mysql: zsynchronizowano zdalne hosty MariaDB.
Port: $MYSQL_PORT
Bind address: $bind_address
Plik CSF include: $managed_allow
Liczba hostów: ${#hosts[@]}
EOF
}
main "$@"
+365
View File
@@ -0,0 +1,365 @@
#!/bin/bash
set -euo pipefail
# Upgrade alternatywnej instancji MariaDB instalowanej z oficjalnego tarballa.
TARGET_MARIADB_VERSION="${TARGET_MARIADB_VERSION:-11.8}"
MARIADB_DIR="${MARIADB_DIR:-/usr/local/mariadb-$TARGET_MARIADB_VERSION}"
DATADIR="${DATADIR:-/var/lib/mariadb_data}"
SERVICE_NAME="${SERVICE_NAME:-alt-mariadb}"
INSTANCE_CNF="${INSTANCE_CNF:-/etc/alt-mariadb.cnf}"
ALT_MY_CNF="${ALT_MY_CNF:-/usr/local/directadmin/conf/alt-my.cnf}"
ALT_MYSQL_CONF="${ALT_MYSQL_CONF:-/usr/local/directadmin/conf/alt-mysql.conf}"
ALT_METADATA_ENV="${ALT_METADATA_ENV:-/usr/local/directadmin/conf/alt-mariadb.env}"
DOWNLOAD_DIR="${DOWNLOAD_DIR:-/usr/local/src}"
MARIADB_TARBALL_URL="${MARIADB_TARBALL_URL:-}"
FORCE_UPGRADE="${FORCE_UPGRADE:-no}"
PORT="${PORT:-}"
SOCKET="${SOCKET:-}"
CURRENT_MARIADB_DIR=""
RESOLVED_MARIADB_RELEASE=""
DOWNLOAD_URL=""
TARBALL_PATH=""
TMP_DIR=""
TARGET_BACKUP_DIR=""
INSTANCE_CNF_BACKUP=""
UPGRADE_EXTRACTED=0
UPGRADE_COMMITTED=0
SERVICE_WAS_ACTIVE=0
log() {
printf '[INFO] %s\n' "$*"
}
warn() {
printf '[UWAGA] %s\n' "$*" >&2
}
die() {
printf '[BŁĄD] %s\n' "$*" >&2
exit 1
}
cleanup() {
if [ -n "${TMP_DIR:-}" ] && [ -d "$TMP_DIR" ]; then
rm -rf "$TMP_DIR"
fi
}
trap cleanup EXIT
rollback_upgrade() {
local code="$1"
if [ "$code" -eq 0 ] || [ "$UPGRADE_COMMITTED" -eq 1 ]; then
cleanup
return 0
fi
warn "Upgrade nie zakończył się poprawnie, uruchamiam rollback."
if [ -n "$INSTANCE_CNF_BACKUP" ] && [ -r "$INSTANCE_CNF_BACKUP" ]; then
cp -a "$INSTANCE_CNF_BACKUP" "$INSTANCE_CNF" || true
warn "RESTORED_INSTANCE_CNF: $INSTANCE_CNF"
fi
if [ "$UPGRADE_EXTRACTED" -eq 1 ] && [ -d "$MARIADB_DIR" ]; then
if [ -n "$TARGET_BACKUP_DIR" ] && [ -d "$TARGET_BACKUP_DIR" ]; then
rm -rf "$MARIADB_DIR" || true
elif [ "$MARIADB_DIR" != "$CURRENT_MARIADB_DIR" ]; then
rm -rf "$MARIADB_DIR" || true
fi
fi
if [ -n "$TARGET_BACKUP_DIR" ] && [ -d "$TARGET_BACKUP_DIR" ] && [ ! -e "$MARIADB_DIR" ]; then
mv "$TARGET_BACKUP_DIR" "$MARIADB_DIR" || true
warn "RESTORED_PREVIOUS_DIR: $MARIADB_DIR"
fi
if [ "$SERVICE_WAS_ACTIVE" -eq 1 ]; then
if command -v systemctl >/dev/null 2>&1; then
systemctl daemon-reload || true
systemctl restart "$SERVICE_NAME" || true
fi
fi
cleanup
}
trap 'code=$?; rollback_upgrade "$code"' EXIT
require_root() {
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
die "Ten skrypt musi być uruchomiony jako root."
fi
}
validate_version() {
case "$TARGET_MARIADB_VERSION" in
10.11|11.4|11.8)
log "Wybrano docelową wersję MariaDB: $TARGET_MARIADB_VERSION."
;;
*)
die "Nieobsługiwana wersja MariaDB: $TARGET_MARIADB_VERSION. Dozwolone wartości: 10.11, 11.4, 11.8."
;;
esac
}
detect_architecture() {
case "$(uname -m)" in
x86_64|amd64) printf 'x86_64\n' ;;
*) die "Nieobsługiwana architektura: $(uname -m). Skrypt obsługuje automatycznie tylko x86_64/amd64." ;;
esac
}
detect_current_mariadb_dir() {
if [ -r "$INSTANCE_CNF" ]; then
CURRENT_MARIADB_DIR="$(awk -F= '
/^[[:space:]]*basedir[[:space:]]*=/ {
value=$2
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
print value
exit
}
' "$INSTANCE_CNF")"
DATADIR="$(awk -F= '
/^[[:space:]]*datadir[[:space:]]*=/ {
value=$2
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
print value
exit
}
' "$INSTANCE_CNF")"
PORT="$(awk -F= '
/^[[:space:]]*port[[:space:]]*=/ {
value=$2
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
print value
exit
}
' "$INSTANCE_CNF")"
SOCKET="$(awk -F= '
/^[[:space:]]*socket[[:space:]]*=/ {
value=$2
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
print value
exit
}
' "$INSTANCE_CNF")"
fi
if [ -z "$CURRENT_MARIADB_DIR" ] && systemctl cat "$SERVICE_NAME" >/dev/null 2>&1; then
CURRENT_MARIADB_DIR="$(systemctl cat "$SERVICE_NAME" \
| awk '/ExecStart=/ {
sub(/^.*ExecStart=/, "")
sub(/\/bin\/mariadbd.*$/, "")
print
exit
}')"
fi
[ -n "$CURRENT_MARIADB_DIR" ] || die "Nie udało się wykryć bieżącego katalogu MariaDB."
[ -x "$CURRENT_MARIADB_DIR/bin/mariadbd" ] || die "Bieżący katalog MariaDB nie zawiera bin/mariadbd: $CURRENT_MARIADB_DIR."
[ -n "$DATADIR" ] || die "Nie udało się wykryć katalogu danych z $INSTANCE_CNF."
log "Wykryto bieżący katalog MariaDB: $CURRENT_MARIADB_DIR."
}
resolve_latest_release_from_archive() {
local series="$1"
local listing=""
local release=""
listing="$(curl -fsSL "https://archive.mariadb.org/" || true)"
release="$(printf '%s\n' "$listing" \
| grep -Eo "mariadb-${series//./\\.}\.[0-9]+/" \
| sed 's#^mariadb-##; s#/$##' \
| sort -V \
| tail -n 1)"
[ -n "$release" ] || return 1
printf '%s\n' "$release"
}
resolve_tarball_from_directory() {
local release="$1"
local arch="$2"
local dir_url="https://archive.mariadb.org/mariadb-${release}/bintar-linux-systemd-${arch}/"
local listing=""
local file=""
listing="$(curl -fsSL "$dir_url" || true)"
file="$(printf '%s\n' "$listing" \
| grep -Eo "mariadb-${release}-linux-systemd-${arch}\.tar\.(gz|xz)" \
| head -n 1)"
[ -n "$file" ] || return 1
printf '%s%s\n' "$dir_url" "$file"
}
resolve_download_url() {
local arch=""
arch="$(detect_architecture)"
if [ -n "$MARIADB_TARBALL_URL" ]; then
DOWNLOAD_URL="$MARIADB_TARBALL_URL"
log "Używam ręcznie ustawionego adresu tarballa MariaDB."
return 0
fi
RESOLVED_MARIADB_RELEASE="$(resolve_latest_release_from_archive "$TARGET_MARIADB_VERSION" || true)"
[ -n "$RESOLVED_MARIADB_RELEASE" ] || die "Nie udało się wykryć najnowszego wydania dla gałęzi $TARGET_MARIADB_VERSION."
DOWNLOAD_URL="$(resolve_tarball_from_directory "$RESOLVED_MARIADB_RELEASE" "$arch" || true)"
[ -n "$DOWNLOAD_URL" ] || die "Nie udało się automatycznie ustalić URL tarballa. Ustaw MARIADB_TARBALL_URL."
log "Wykryto tarball MariaDB $RESOLVED_MARIADB_RELEASE."
}
download_tarball() {
local filename=""
install -d -m 0755 "$DOWNLOAD_DIR"
filename="$(basename "${DOWNLOAD_URL%%\?*}")"
TARBALL_PATH="$DOWNLOAD_DIR/$filename"
log "Pobieram tarball MariaDB do $TARBALL_PATH."
curl -fL --retry 3 --connect-timeout 20 -o "$TARBALL_PATH" "$DOWNLOAD_URL"
[ -s "$TARBALL_PATH" ] || die "Pobrany tarball jest pusty albo nie istnieje: $TARBALL_PATH."
}
extract_tarball() {
local extracted_dir=""
local backup_dir=""
if [ -d "$MARIADB_DIR" ] && [ -n "$(find "$MARIADB_DIR" -mindepth 1 -maxdepth 1 -print -quit)" ]; then
if [ "$FORCE_UPGRADE" != "yes" ]; then
die "Katalog docelowy $MARIADB_DIR już istnieje. Ustaw FORCE_UPGRADE=yes, aby zastąpić go po kopii."
fi
TARGET_BACKUP_DIR="${MARIADB_DIR}.pre-upgrade.$(date +%Y%m%d%H%M%S)"
log "Tworzę kopię istniejącego katalogu docelowego: $TARGET_BACKUP_DIR."
mv "$MARIADB_DIR" "$TARGET_BACKUP_DIR"
fi
TMP_DIR="$(mktemp -d)"
tar -xf "$TARBALL_PATH" -C "$TMP_DIR"
extracted_dir="$(find "$TMP_DIR" -mindepth 1 -maxdepth 1 -type d -print -quit)"
[ -n "$extracted_dir" ] || die "Tarball nie zawiera oczekiwanego katalogu MariaDB."
mv "$extracted_dir" "$MARIADB_DIR"
chown -R root:root "$MARIADB_DIR"
UPGRADE_EXTRACTED=1
log "Nowe binaria MariaDB są w $MARIADB_DIR."
}
rewrite_instance_basedir() {
local backup=""
[ -r "$INSTANCE_CNF" ] || die "Brak konfiguracji instancji: $INSTANCE_CNF."
backup="${INSTANCE_CNF}.pre-upgrade.$(date +%Y%m%d%H%M%S)"
cp -a "$INSTANCE_CNF" "$backup"
INSTANCE_CNF_BACKUP="$backup"
sed -i "s#^[[:space:]]*basedir[[:space:]]*=.*#basedir=$MARIADB_DIR#" "$INSTANCE_CNF"
sed -i "s#^[[:space:]]*plugin-dir[[:space:]]*=.*#plugin-dir=$MARIADB_DIR/lib/plugin#" "$INSTANCE_CNF"
log "Zaktualizowano basedir i plugin-dir w $INSTANCE_CNF. Kopia: $backup."
}
restart_service() {
log "Restartuję usługę $SERVICE_NAME."
systemctl daemon-reload
systemctl restart "$SERVICE_NAME"
sleep 5
systemctl is-active --quiet "$SERVICE_NAME" || {
systemctl status "$SERVICE_NAME" --no-pager || true
die "Usługa $SERVICE_NAME nie uruchomiła się po upgrade."
}
}
run_mariadb_upgrade() {
local upgrade_bin=""
if [ -x "$MARIADB_DIR/bin/mariadb-upgrade" ]; then
upgrade_bin="$MARIADB_DIR/bin/mariadb-upgrade"
elif [ -x "$MARIADB_DIR/bin/mysql_upgrade" ]; then
upgrade_bin="$MARIADB_DIR/bin/mysql_upgrade"
else
warn "Nie znaleziono mariadb-upgrade ani mysql_upgrade, pomijam etap aktualizacji tabel systemowych."
return 0
fi
[ -r "$ALT_MY_CNF" ] || die "Brak pliku klienta alternatywnej instancji: $ALT_MY_CNF."
log "Uruchamiam aktualizację tabel systemowych MariaDB."
"$upgrade_bin" --defaults-extra-file="$ALT_MY_CNF"
}
credential_group() {
if getent group diradmin >/dev/null 2>&1; then
printf 'diradmin\n'
else
printf 'root\n'
fi
}
sh_single_quote() {
local value="$1"
value="${value//\'/\'\\\'\'}"
printf "'%s'" "$value"
}
write_alt_metadata_env() {
local group_name metadata_mode tmp_file
group_name="$(credential_group)"
metadata_mode="0600"
if [ "$group_name" = "diradmin" ]; then
metadata_mode="0640"
fi
tmp_file="$(mktemp)"
cat > "$tmp_file" <<EOF
# Metadata alternatywnej instancji MariaDB zarządzanej przez mysql_upgrade.sh.
# Ten plik jest kontraktem dla pluginu DirectAdmin i nie jest natywną konfiguracją MySQL DirectAdmina.
SERVICE_NAME=$(sh_single_quote "$SERVICE_NAME")
MARIADB_VERSION=$(sh_single_quote "$TARGET_MARIADB_VERSION")
MARIADB_RELEASE=$(sh_single_quote "${RESOLVED_MARIADB_RELEASE:-$TARGET_MARIADB_VERSION}")
MARIADB_DIR=$(sh_single_quote "$MARIADB_DIR")
DATADIR=$(sh_single_quote "$DATADIR")
PORT=$(sh_single_quote "$PORT")
SOCKET=$(sh_single_quote "$SOCKET")
ALT_MYSQL_CONF=$(sh_single_quote "$ALT_MYSQL_CONF")
ALT_MY_CNF=$(sh_single_quote "$ALT_MY_CNF")
INSTANCE_CNF=$(sh_single_quote "$INSTANCE_CNF")
EOF
install -m "$metadata_mode" -o root -g "$group_name" "$tmp_file" "$ALT_METADATA_ENV"
rm -f "$tmp_file"
log "Zaktualizowano metadane alternatywnej instancji: $ALT_METADATA_ENV."
}
print_summary() {
cat <<EOF
Upgrade alternatywnej instancji MariaDB zakończony.
Poprzedni katalog programu: $CURRENT_MARIADB_DIR
Nowy katalog programu: $MARIADB_DIR
Katalog danych: $DATADIR
Konfiguracja instancji: $INSTANCE_CNF
Usługa systemd: $SERVICE_NAME
Polecenie testowe:
$MARIADB_DIR/bin/mariadb --defaults-extra-file=$ALT_MY_CNF -e "SELECT VERSION();"
EOF
}
main() {
require_root
validate_version
detect_current_mariadb_dir
resolve_download_url
download_tarball
if systemctl is-active --quiet "$SERVICE_NAME"; then
SERVICE_WAS_ACTIVE=1
fi
systemctl stop "$SERVICE_NAME"
extract_tarball
rewrite_instance_basedir
restart_service
run_mariadb_upgrade
write_alt_metadata_env
UPGRADE_COMMITTED=1
print_summary
}
main "$@"
+42
View File
@@ -0,0 +1,42 @@
#!/bin/bash
set -euo pipefail
PHPMYADMIN_DIR="${PHPMYADMIN_PRIVATE_DIR:-${PHPMYADMIN_DIR:-/var/www/html/alt-mysql-phpmyadmin}}"
CONFIG_INC="${PHPMYADMIN_DIR}/config.inc.php"
SIGNON_SCRIPT="${PHPMYADMIN_DIR}/da_signon.php"
SIGNON_URL_PAGE="${PHPMYADMIN_DIR}/da_login.php"
RUNTIME_CONFIG="${PHPMYADMIN_DIR}/runtime/config.json"
fail() {
printf 'phpMyAdmin alt-mysql health: %s\n' "$*" >&2
exit 1
}
[ -d "$PHPMYADMIN_DIR" ] || fail "phpMyAdmin directory not found: $PHPMYADMIN_DIR"
[ -r "$PHPMYADMIN_DIR/index.php" ] || fail "phpMyAdmin index.php is not readable: $PHPMYADMIN_DIR/index.php"
[ -r "$CONFIG_INC" ] || fail "config.inc.php is not readable: $CONFIG_INC"
[ -r "$SIGNON_SCRIPT" ] || fail "signon script is not readable: $SIGNON_SCRIPT"
[ -r "$SIGNON_URL_PAGE" ] || fail "SignonURL page is not readable: $SIGNON_URL_PAGE"
grep -Fq "DA_MYSQL_PMA_DEFAULT_CONF" "$CONFIG_INC" || fail "config.inc.php does not configure alt-mysql endpoint"
grep -Fq "auth_type'] = 'signon" "$CONFIG_INC" || fail "config.inc.php does not configure signon auth"
grep -Fq "SignonURL'] = da_mysql_phpmyadmin_signon_url" "$CONFIG_INC" || fail "config.inc.php does not configure dynamic SignonURL"
grep -Fq "da_mysql_login_consume_ticket" "$SIGNON_URL_PAGE" || fail "da_login.php does not consume SSO tickets"
grep -Fq "DA_MYSQL_DEFAULT_CONF = '/usr/local/directadmin/conf/alt-mysql.conf'" "$SIGNON_SCRIPT" \
|| fail "signon script does not default to alt-mysql.conf"
if [ -r "$RUNTIME_CONFIG" ] && command -v php >/dev/null 2>&1; then
php -r '
$path = $argv[1];
$data = json_decode(file_get_contents($path), true);
if (!is_array($data)) {
fwrite(STDERR, "runtime config is not valid JSON\n");
exit(2);
}
if (($data["credentials_file"] ?? "") === "/usr/local/directadmin/conf/mysql.conf") {
fwrite(STDERR, "runtime config points to native DirectAdmin mysql.conf\n");
exit(3);
}
' "$RUNTIME_CONFIG" || fail "runtime config validation failed"
fi
echo "phpMyAdmin alt-mysql health: OK"
+486
View File
@@ -0,0 +1,486 @@
#!/bin/bash
set -euo pipefail
PHPMYADMIN_PRIVATE_DIR="${PHPMYADMIN_PRIVATE_DIR:-${PHPMYADMIN_DIR:-/var/www/html/alt-mysql-phpmyadmin}}"
PHPMYADMIN_VERSION="${PHPMYADMIN_VERSION:-5.2.3}"
PHPMYADMIN_TARBALL_URL="${PHPMYADMIN_TARBALL_URL:-https://files.phpmyadmin.net/phpMyAdmin/5.2.3/phpMyAdmin-5.2.3-all-languages.tar.gz}"
PHPMYADMIN_DOWNLOAD_DIR="${PHPMYADMIN_DOWNLOAD_DIR:-/usr/local/src}"
PHPMYADMIN_SOURCE_DIR="${PHPMYADMIN_SOURCE_DIR:-}"
RUNTIME_DIR="${PHPMYADMIN_PRIVATE_DIR}/runtime"
TICKETS_DIR="${RUNTIME_DIR}/tickets"
CONFIG_INC="${PHPMYADMIN_PRIVATE_DIR}/config.inc.php"
SIGNON_SCRIPT="${PHPMYADMIN_PRIVATE_DIR}/da_signon.php"
SIGNON_URL_PAGE="${PHPMYADMIN_PRIVATE_DIR}/da_login.php"
BLOWFISH_SECRET_FILE="${RUNTIME_DIR}/blowfish_secret"
if [ "${PHPMYADMIN_INSTALL_ASSUME_ROOT:-0}" != "1" ] && [ "${EUID:-$(id -u)}" -ne 0 ]; then
echo "Skrypt musi być uruchomiony jako root." >&2
exit 1
fi
log() {
printf 'phpMyAdmin alt-mysql: %s\n' "$*"
}
die() {
printf 'phpMyAdmin alt-mysql: %s\n' "$*" >&2
exit 1
}
install_private_copy_from_source() {
local source_dir="$1"
[ -d "$source_dir" ] || die "Katalog źródłowy phpMyAdmin nie istnieje: $source_dir"
[ -f "$source_dir/index.php" ] || die "Katalog źródłowy nie wygląda jak phpMyAdmin: $source_dir"
mkdir -p "$PHPMYADMIN_PRIVATE_DIR"
cp -a "$source_dir"/. "$PHPMYADMIN_PRIVATE_DIR"/
}
download_private_copy() {
local tarball extracted tmp_dir
command -v curl >/dev/null 2>&1 || die "Brak curl; ustaw PHPMYADMIN_SOURCE_DIR albo doinstaluj curl."
command -v tar >/dev/null 2>&1 || die "Brak tar."
mkdir -p "$PHPMYADMIN_DOWNLOAD_DIR"
tarball="$PHPMYADMIN_DOWNLOAD_DIR/phpMyAdmin-${PHPMYADMIN_VERSION}-all-languages.tar.gz"
if [ ! -s "$tarball" ]; then
log "Pobieram prywatny phpMyAdmin ${PHPMYADMIN_VERSION}."
curl -fL --retry 3 --connect-timeout 20 -o "$tarball" "$PHPMYADMIN_TARBALL_URL"
fi
tmp_dir="$(mktemp -d)"
tar -xzf "$tarball" -C "$tmp_dir"
extracted="$(find "$tmp_dir" -mindepth 1 -maxdepth 1 -type d -name 'phpMyAdmin-*' -print -quit)"
[ -n "$extracted" ] || {
rm -rf "$tmp_dir"
die "Tarball phpMyAdmin nie zawiera oczekiwanego katalogu."
}
install_private_copy_from_source "$extracted"
rm -rf "$tmp_dir"
}
ensure_private_copy() {
if [ -f "$PHPMYADMIN_PRIVATE_DIR/index.php" ]; then
return 0
fi
rm -rf "$PHPMYADMIN_PRIVATE_DIR"
if [ -n "$PHPMYADMIN_SOURCE_DIR" ]; then
install_private_copy_from_source "$PHPMYADMIN_SOURCE_DIR"
else
download_private_copy
fi
}
generate_secret() {
if [ -r "$BLOWFISH_SECRET_FILE" ]; then
local existing
existing="$(tr -d '\r\n' < "$BLOWFISH_SECRET_FILE")"
if [ -n "$existing" ]; then
printf '%s\n' "$existing"
return 0
fi
fi
if command -v openssl >/dev/null 2>&1; then
openssl rand -hex 32
return 0
fi
tr -dc 'a-f0-9' </dev/urandom | head -c 64
printf '\n'
}
write_private_config() {
local secret
mkdir -p "$RUNTIME_DIR" "$TICKETS_DIR"
secret="$(generate_secret)"
printf '%s\n' "$secret" > "$BLOWFISH_SECRET_FILE"
chmod 600 "$BLOWFISH_SECRET_FILE" 2>/dev/null || true
cat > "$RUNTIME_DIR/.htaccess" <<'EOF'
Require all denied
Deny from all
EOF
cat > "$TICKETS_DIR/.htaccess" <<'EOF'
Require all denied
Deny from all
EOF
printf '%s\n' '<!doctype html><title>Forbidden</title>' > "$RUNTIME_DIR/index.html"
printf '%s\n' '<!doctype html><title>Forbidden</title>' > "$TICKETS_DIR/index.html"
cat > "$CONFIG_INC" <<'PHP'
<?php
declare(strict_types=1);
const DA_MYSQL_PMA_DEFAULT_CONF = '/usr/local/directadmin/conf/alt-mysql.conf';
const DA_MYSQL_PMA_RUNTIME_CONFIG = __DIR__ . '/runtime/config.json';
const DA_MYSQL_PMA_BLOWFISH_SECRET_FILE = __DIR__ . '/runtime/blowfish_secret';
function da_mysql_phpmyadmin_runtime_config(): array
{
if (!is_readable(DA_MYSQL_PMA_RUNTIME_CONFIG)) {
return [];
}
$raw = file_get_contents(DA_MYSQL_PMA_RUNTIME_CONFIG);
if (!is_string($raw) || $raw === '') {
return [];
}
$decoded = json_decode($raw, true);
return is_array($decoded) ? $decoded : [];
}
function da_mysql_phpmyadmin_parse_conf(string $path): array
{
$data = [
'host' => '127.0.0.1',
'port' => '33033',
'socket' => '',
];
if (!is_readable($path)) {
return $data;
}
$lines = file($path, FILE_IGNORE_NEW_LINES);
if ($lines === false) {
return $data;
}
foreach ($lines as $line) {
$line = trim($line);
if ($line === '' || $line[0] === '#' || strpos($line, '=') === false) {
continue;
}
[$key, $value] = explode('=', $line, 2);
$key = strtolower(trim($key));
$value = trim(trim($value), "\"'");
if ($key === 'host' && $value !== '') {
$data['host'] = $value;
} elseif (($key === 'port' || $key === 'mysql_port') && ctype_digit($value)) {
$data['port'] = $value;
} elseif (($key === 'socket' || $key === 'mysql_socket') && $value !== '') {
$data['socket'] = $value;
}
}
if ($data['host'] === '' || $data['host'] === 'localhost') {
$data['host'] = '127.0.0.1';
}
return $data;
}
function da_mysql_phpmyadmin_blowfish_secret(): string
{
if (is_readable(DA_MYSQL_PMA_BLOWFISH_SECRET_FILE)) {
$secret = trim((string)file_get_contents(DA_MYSQL_PMA_BLOWFISH_SECRET_FILE));
if ($secret !== '') {
return $secret;
}
}
return 'alt-mysql-phpmyadmin-secret-change-me';
}
function da_mysql_phpmyadmin_url_path(array $runtimeConfig): string
{
$path = trim((string)($runtimeConfig['phpmyadmin_url_path'] ?? '/alt-mysql-phpmyadmin'));
if ($path === '') {
return '/alt-mysql-phpmyadmin';
}
if ($path[0] !== '/') {
$path = '/' . $path;
}
$path = rtrim($path, '/');
return $path !== '' ? $path : '/alt-mysql-phpmyadmin';
}
function da_mysql_phpmyadmin_public_base_url(array $runtimeConfig): string
{
$configured = trim((string)($runtimeConfig['phpmyadmin_public_base_url'] ?? ''));
if (preg_match('#^https?://#i', $configured) === 1) {
return rtrim($configured, '/');
}
$scheme = 'http';
$https = strtolower((string)($_SERVER['HTTPS'] ?? ''));
if ($https !== '' && $https !== 'off' && $https !== '0') {
$scheme = 'https';
} elseif (strtolower((string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https') {
$scheme = 'https';
} elseif (strtolower((string)($_SERVER['REQUEST_SCHEME'] ?? '')) === 'https') {
$scheme = 'https';
}
$host = trim((string)($_SERVER['HTTP_HOST'] ?? ''));
if ($host === '') {
$host = trim((string)($_SERVER['SERVER_NAME'] ?? 'localhost'));
}
if ($host === '') {
$host = 'localhost';
}
return $scheme . '://' . $host;
}
function da_mysql_phpmyadmin_signon_url(array $runtimeConfig): string
{
return da_mysql_phpmyadmin_public_base_url($runtimeConfig)
. da_mysql_phpmyadmin_url_path($runtimeConfig)
. '/da_login.php';
}
$runtimeConfig = da_mysql_phpmyadmin_runtime_config();
$credentialsFile = (string)($runtimeConfig['credentials_file'] ?? DA_MYSQL_PMA_DEFAULT_CONF);
if ($credentialsFile === '') {
$credentialsFile = DA_MYSQL_PMA_DEFAULT_CONF;
}
$daMysqlServer = da_mysql_phpmyadmin_parse_conf($credentialsFile);
$cfg = [];
$cfg['blowfish_secret'] = da_mysql_phpmyadmin_blowfish_secret();
$cfg['TempDir'] = __DIR__ . '/runtime/tmp';
$cfg['LoginCookieValidity'] = 900;
$i = 0;
$i++;
$cfg['Servers'][$i]['host'] = $daMysqlServer['host'];
$cfg['Servers'][$i]['port'] = $daMysqlServer['port'];
$cfg['Servers'][$i]['socket'] = $daMysqlServer['host'] === 'localhost' ? $daMysqlServer['socket'] : '';
$cfg['Servers'][$i]['auth_type'] = 'signon';
$cfg['Servers'][$i]['SignonScript'] = __DIR__ . '/da_signon.php';
$cfg['Servers'][$i]['SignonSession'] = 'DA_MYSQL_PHPMYADMIN';
$cfg['Servers'][$i]['SignonURL'] = da_mysql_phpmyadmin_signon_url($runtimeConfig);
$cfg['Servers'][$i]['LogoutURL'] = da_mysql_phpmyadmin_signon_url($runtimeConfig);
$cfg['Servers'][$i]['AllowNoPassword'] = false;
$cfg['Servers'][$i]['verbose'] = 'DirectAdmin alt-mysql';
$cfg['Servers'][$i]['only_db'] = '';
$cfg['ServerDefault'] = 1;
$cfg['AllowArbitraryServer'] = false;
PHP
cat > "$SIGNON_URL_PAGE" <<'PHP'
<?php
declare(strict_types=1);
const DA_MYSQL_SESSION = 'DA_MYSQL_PHPMYADMIN';
const DA_MYSQL_RUNTIME_CONFIG = __DIR__ . '/runtime/config.json';
function da_mysql_login_runtime_config(): array
{
if (!is_readable(DA_MYSQL_RUNTIME_CONFIG)) {
return [];
}
$raw = file_get_contents(DA_MYSQL_RUNTIME_CONFIG);
if (!is_string($raw) || $raw === '') {
return [];
}
$decoded = json_decode($raw, true);
return is_array($decoded) ? $decoded : [];
}
function da_mysql_login_url_path(array $runtimeConfig): string
{
$path = trim((string)($runtimeConfig['phpmyadmin_url_path'] ?? '/alt-mysql-phpmyadmin'));
if ($path === '') {
return '/alt-mysql-phpmyadmin';
}
if ($path[0] !== '/') {
$path = '/' . $path;
}
$path = rtrim($path, '/');
return $path !== '' ? $path : '/alt-mysql-phpmyadmin';
}
function da_mysql_login_ticket_dir(array $runtimeConfig): string
{
$dir = trim((string)($runtimeConfig['ticket_dir'] ?? ''));
if ($dir !== '') {
return rtrim($dir, '/');
}
return __DIR__ . '/runtime/tickets';
}
function da_mysql_login_error(string $message): void
{
http_response_code(400);
header('Content-Type: text/html; charset=UTF-8');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
$safe = htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
echo '<!doctype html><html lang="en"><head><meta charset="utf-8"><title>alt-mysql phpMyAdmin</title></head><body>';
echo '<h1>alt-mysql phpMyAdmin</h1><p>' . $safe . '</p>';
echo '</body></html>';
exit;
}
function da_mysql_login_consume_ticket(array $runtimeConfig): array
{
$token = strtolower((string)($_GET['ticket'] ?? ''));
if (preg_match('/\A[a-f0-9]{64}\z/', $token) !== 1) {
da_mysql_login_error('Missing or invalid phpMyAdmin login ticket.');
}
$path = da_mysql_login_ticket_dir($runtimeConfig) . '/' . $token . '.json';
if (!is_readable($path)) {
da_mysql_login_error('The phpMyAdmin login ticket is not available or has already been used.');
}
$raw = file_get_contents($path);
@unlink($path);
$ticket = is_string($raw) ? json_decode($raw, true) : null;
if (!is_array($ticket)) {
da_mysql_login_error('The phpMyAdmin login ticket is invalid.');
}
if ((int)($ticket['expires_at'] ?? 0) < time()) {
da_mysql_login_error('The phpMyAdmin login ticket has expired.');
}
$auth = $ticket['auth'] ?? [];
if (!is_array($auth) || (string)($auth['user'] ?? '') === '' || (string)($auth['password'] ?? '') === '') {
da_mysql_login_error('The phpMyAdmin login ticket does not contain credentials.');
}
return $ticket;
}
function da_mysql_login_redirect_target(array $ticket): string
{
$route = (string)($ticket['route'] ?? '/database/structure');
if (preg_match('#^/[A-Za-z0-9_./-]+$#', $route) !== 1 || strpos($route, '//') !== false) {
$route = '/';
}
$query = ['route' => $route];
$db = (string)($ticket['db'] ?? ($ticket['auth']['db'] ?? ''));
if ($db !== '') {
$query['db'] = $db;
}
return 'index.php?' . http_build_query($query);
}
$runtimeConfig = da_mysql_login_runtime_config();
$ticket = da_mysql_login_consume_ticket($runtimeConfig);
$secure = false;
$https = strtolower((string)($_SERVER['HTTPS'] ?? ''));
if ($https !== '' && $https !== 'off' && $https !== '0') {
$secure = true;
} elseif (strtolower((string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https') {
$secure = true;
}
session_name(DA_MYSQL_SESSION);
session_set_cookie_params([
'lifetime' => 0,
'path' => da_mysql_login_url_path($runtimeConfig),
'secure' => $secure,
'httponly' => true,
'samesite' => 'Lax',
]);
session_start();
$_SESSION['DA_MYSQL_PHPMYADMIN_AUTH'] = $ticket['auth'];
session_write_close();
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
header('Location: ' . da_mysql_login_redirect_target($ticket), true, 302);
exit;
PHP
cat > "$SIGNON_SCRIPT" <<'PHP'
<?php
declare(strict_types=1);
const DA_MYSQL_SESSION = 'DA_MYSQL_PHPMYADMIN';
const DA_MYSQL_DEFAULT_CONF = '/usr/local/directadmin/conf/alt-mysql.conf';
const DA_MYSQL_RUNTIME_CONFIG = __DIR__ . '/runtime/config.json';
function da_mysql_runtime_config(): array
{
if (!is_readable(DA_MYSQL_RUNTIME_CONFIG)) {
return [];
}
$raw = file_get_contents(DA_MYSQL_RUNTIME_CONFIG);
if (!is_string($raw) || $raw === '') {
return [];
}
$decoded = json_decode($raw, true);
return is_array($decoded) ? $decoded : [];
}
function da_mysql_signon_session_auth(): array
{
$payload = $_SESSION['DA_MYSQL_PHPMYADMIN_AUTH'] ?? [];
return is_array($payload) ? $payload : [];
}
function da_mysql_signon_read_auth(): array
{
$previousActive = session_status() === PHP_SESSION_ACTIVE;
$previousName = session_name();
$previousId = $previousActive ? session_id() : '';
if ($previousActive && $previousName === DA_MYSQL_SESSION) {
return da_mysql_signon_session_auth();
}
if ($previousActive) {
session_write_close();
}
session_name(DA_MYSQL_SESSION);
session_id('');
$ssoSessionId = (string)($_COOKIE[DA_MYSQL_SESSION] ?? '');
if ($ssoSessionId !== '' && preg_match('/\A[A-Za-z0-9,-]+\z/', $ssoSessionId) === 1) {
session_id($ssoSessionId);
}
session_start();
$payload = da_mysql_signon_session_auth();
session_write_close();
if ($previousActive) {
session_name($previousName);
if ($previousId !== '') {
session_id($previousId);
}
session_start();
} elseif ($previousName !== '') {
session_name($previousName);
}
return $payload;
}
function get_login_credentials($user = ''): array
{
$payload = da_mysql_signon_read_auth();
$username = (string)($payload['user'] ?? '');
$password = (string)($payload['password'] ?? '');
return [$username, $password];
}
PHP
mkdir -p "$RUNTIME_DIR/tmp"
chown -R diradmin:diradmin "$RUNTIME_DIR" "$CONFIG_INC" "$SIGNON_SCRIPT" "$SIGNON_URL_PAGE" 2>/dev/null || true
chmod 755 "$RUNTIME_DIR" "$TICKETS_DIR" "$RUNTIME_DIR/tmp" 2>/dev/null || true
chmod 644 "$CONFIG_INC" "$SIGNON_SCRIPT" "$SIGNON_URL_PAGE" "$RUNTIME_DIR/.htaccess" "$TICKETS_DIR/.htaccess" "$RUNTIME_DIR/index.html" "$TICKETS_DIR/index.html" 2>/dev/null || true
}
ensure_private_copy
write_private_config
log "Prywatna kopia phpMyAdmin dla alt-mysql jest gotowa w ${PHPMYADMIN_PRIVATE_DIR}."
@@ -0,0 +1,5 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
exec "$SCRIPT_DIR/phpmyadmin_install.sh" "$@"
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
exec "$SCRIPT_DIR/phpmyadmin_install.sh"
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
set -euo pipefail
CB_DIR="/usr/local/directadmin/custombuild"
[ "${EUID:-$(id -u)}" -eq 0 ] || {
echo "Skrypt musi być uruchomiony jako root." >&2
exit 1
}
[ -x "${CB_DIR}/build" ] || {
echo "Nie znaleziono DirectAdmin CustomBuild w ${CB_DIR}." >&2
exit 1
}
php_slots=()
for slot in 1 2 3 4 5 6 7 8 9; do
version="$(awk -F= -v key="php${slot}_release" '$1 == key {print $2}' "${CB_DIR}/options.conf" 2>/dev/null | tr -d '[:space:]')"
case "${version,,}" in
""|no|off) continue ;;
esac
php_slots+=("$version")
done
[ ${#php_slots[@]} -gt 0 ] || {
echo "Nie wykryto aktywnych wersji PHP w CustomBuild." >&2
exit 1
}
for version in "${php_slots[@]}"; do
echo "Rebuild PHP ${version}"
"${CB_DIR}/build" php "$version"
done
"${CB_DIR}/build" rewrite_confs || true
echo "Rekompilacja PHP zakończona. Rozszerzenia mysqli i pdo_mysql są dostarczane przez standardowy build DirectAdmin."
+68
View File
@@ -0,0 +1,68 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=./mysql_common.sh
source "$SCRIPT_DIR/mysql_common.sh"
usage() {
cat <<'EOF'
Użycie:
restore_all.sh <katalog_backupu> [--restore-users]
Opis:
Przywraca wszystkie bazy MySQL z katalogu backupu. Jeśli obecny jest plik
`_globals.sql` lub `_globals.sql.gz`, można go odtworzyć flagą
`--restore-users`.
EOF
}
BACKUP_DIR=""
RESTORE_USERS="0"
for arg in "$@"; do
case "$arg" in
--help|-h)
usage
exit 0
;;
--restore-users)
RESTORE_USERS="1"
;;
*)
if [ -n "$BACKUP_DIR" ]; then
echo "Podano więcej niż jeden katalog backupu." >&2
exit 1
fi
BACKUP_DIR="$arg"
;;
esac
done
[ -n "$BACKUP_DIR" ] || {
usage
exit 1
}
[ -d "$BACKUP_DIR" ] || {
echo "Katalog nie istnieje: $BACKUP_DIR" >&2
exit 1
}
mysql_load_alt_credentials
if [ "$RESTORE_USERS" = "1" ]; then
for globals_file in "$BACKUP_DIR/_globals.sql.gz" "$BACKUP_DIR/_globals.sql"; do
[ -f "$globals_file" ] || continue
mysql_restore_stream mysql "$globals_file"
break
done
fi
find "$BACKUP_DIR" -maxdepth 1 -type f \( -name '*.sql' -o -name '*.sql.gz' \) ! -name '_globals.*' | sort | while IFS= read -r file; do
[ -n "$file" ] || continue
db_name="$(basename "$file" | sed -E 's/\.sql(\.gz)?$//')"
db_name="$(mysql_safe_identifier "$db_name")"
mysql_exec mysql -e "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$db_name");"
mysql_exec mysql -e "CREATE DATABASE $(mysql_quote_identifier "$db_name") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql_restore_stream "$db_name" "$file"
echo "Przywrócono ${db_name}"
done
+186
View File
@@ -0,0 +1,186 @@
#!/bin/bash
set -euo pipefail
ROUNDCUBE_PLUGIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
ROUNDCUBE_SETTINGS_FILE="${ROUNDCUBE_SETTINGS_FILE:-$ROUNDCUBE_PLUGIN_DIR/plugin-settings.conf}"
ROUNDCUBE_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./mysql_common.sh
source "$ROUNDCUBE_SCRIPT_DIR/mysql_common.sh"
mysql_load_plugin_settings_file "$ROUNDCUBE_SETTINGS_FILE"
roundcube_read_setting() {
local key="$1"
local default="$2"
local line value
[ -r "$ROUNDCUBE_SETTINGS_FILE" ] || {
printf '%s\n' "$default"
return 0
}
line="$(grep -E "^${key}=" "$ROUNDCUBE_SETTINGS_FILE" | tail -n 1 || true)"
[ -n "$line" ] || {
printf '%s\n' "$default"
return 0
}
value="${line#*=}"
value="${value%%#*}"
value="${value%\"}"
value="${value#\"}"
value="${value%\'}"
value="${value#\'}"
value="$(printf '%s' "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
printf '%s\n' "${value:-$default}"
}
roundcube_enabled() {
case "$(roundcube_read_setting ROUNDCUBE_ENABLED true)" in
true|yes|on|1) return 0 ;;
*) return 1 ;;
esac
}
roundcube_config_file() {
roundcube_read_setting ROUNDCUBE_CONFIG_FILE /var/www/html/roundcube/config/config.inc.php
}
roundcube_custom_config_file() {
roundcube_read_setting ROUNDCUBE_CUSTOM_CONFIG_FILE /usr/local/directadmin/custombuild/custom/roundcube/config.inc.php
}
roundcube_write_custombuild_config() {
case "$(roundcube_read_setting ROUNDCUBE_WRITE_CUSTOMBUILD_CONFIG true)" in
true|yes|on|1) return 0 ;;
*) return 1 ;;
esac
}
roundcube_db_name() {
roundcube_read_setting ROUNDCUBE_DB_NAME roundcube
}
roundcube_db_user() {
roundcube_read_setting ROUNDCUBE_DB_USER roundcube
}
roundcube_password_file() {
roundcube_read_setting ROUNDCUBE_DB_PASSWORD_FILE /usr/local/directadmin/conf/alt-roundcube.conf
}
roundcube_native_my_cnf() {
roundcube_read_setting ROUNDCUBE_NATIVE_MY_CNF /usr/local/directadmin/conf/my.cnf
}
roundcube_endpoint_mode() {
roundcube_read_setting ROUNDCUBE_ALT_DSN_MODE tcp
}
roundcube_generate_secret() {
if command -v openssl >/dev/null 2>&1; then
openssl rand -base64 36 | tr -d '\n'
return 0
fi
tr -dc 'A-Za-z0-9_@%+=' </dev/urandom | head -c 48
}
roundcube_load_or_create_password() {
local file user pass group_name tmp
file="$(roundcube_password_file)"
user="$(roundcube_db_user)"
if [ -r "$file" ]; then
pass="$(awk -F= '$1 == "password" || $1 == "passwd" { sub(/^[^=]*=/, ""); print; exit }' "$file")"
if [ -n "$pass" ]; then
printf '%s\n' "$pass"
return 0
fi
fi
pass="$(roundcube_generate_secret)"
group_name=""
if command -v getent >/dev/null 2>&1 && getent group diradmin >/dev/null 2>&1; then
group_name="diradmin"
elif command -v getent >/dev/null 2>&1 && getent group root >/dev/null 2>&1; then
group_name="root"
fi
tmp="$(mktemp)"
{
printf 'user=%s\n' "$user"
printf 'password=%s\n' "$pass"
} > "$tmp"
if [ "${EUID:-$(id -u)}" -eq 0 ] && [ -n "$group_name" ]; then
install -m 0640 -o root -g "$group_name" "$tmp" "$file"
else
install -m 0600 "$tmp" "$file"
fi
rm -f "$tmp"
printf '%s\n' "$pass"
}
roundcube_load_password() {
local file pass
file="$(roundcube_password_file)"
[ -r "$file" ] || return 1
pass="$(awk -F= '$1 == "password" || $1 == "passwd" { sub(/^[^=]*=/, ""); print; exit }' "$file")"
[ -n "$pass" ] || return 1
printf '%s\n' "$pass"
}
roundcube_urlencode() {
php -r 'echo rawurlencode($argv[1]);' "$1"
}
roundcube_alt_dsn() {
local db user pass host port mode encoded_user encoded_pass
mysql_load_alt_runtime
db="$(roundcube_db_name)"
user="$(roundcube_db_user)"
pass="$(roundcube_load_or_create_password)"
mode="$(roundcube_endpoint_mode)"
encoded_user="$(roundcube_urlencode "$user")"
encoded_pass="$(roundcube_urlencode "$pass")"
case "$mode" in
socket)
printf 'mysql://%s:%s@unix(%s)/%s\n' "$encoded_user" "$encoded_pass" "$MYSQL_ALT_SOCKET" "$db"
;;
*)
host="127.0.0.1"
port="$MYSQL_ALT_PORT"
printf 'mysql://%s:%s@%s:%s/%s\n' "$encoded_user" "$encoded_pass" "$host" "$port" "$db"
;;
esac
}
roundcube_sql_quote() {
local value="$1"
value="${value//\\/\\\\}"
value="${value//\'/\'\'}"
printf "'%s'" "$value"
}
roundcube_create_alt_database_and_user() {
local db user pass q_user q_pass
mysql_load_alt_credentials
mysql_ensure_metadata_schema
db="$(roundcube_db_name)"
user="$(roundcube_db_user)"
pass="$(roundcube_load_or_create_password)"
q_user="$(roundcube_sql_quote "$user")"
q_pass="$(roundcube_sql_quote "$pass")"
mysql_safe_identifier "$db" >/dev/null
mysql_safe_identifier "$user" >/dev/null
mysql_exec mysql -e "CREATE DATABASE IF NOT EXISTS $(mysql_quote_identifier "$db") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql_exec mysql -e "CREATE USER IF NOT EXISTS ${q_user}@'localhost' IDENTIFIED BY ${q_pass};"
mysql_exec mysql -e "ALTER USER ${q_user}@'localhost' IDENTIFIED BY ${q_pass};"
mysql_exec mysql -e "CREATE USER IF NOT EXISTS ${q_user}@'127.0.0.1' IDENTIFIED BY ${q_pass};"
mysql_exec mysql -e "ALTER USER ${q_user}@'127.0.0.1' IDENTIFIED BY ${q_pass};"
mysql_exec mysql -e "GRANT ALL PRIVILEGES ON $(mysql_quote_identifier "$db").* TO ${q_user}@'localhost';"
mysql_exec mysql -e "GRANT ALL PRIVILEGES ON $(mysql_quote_identifier "$db").* TO ${q_user}@'127.0.0.1';"
mysql_exec mysql -e "
INSERT INTO da_plugin_system_databases (service_name, db_name, db_user, endpoint_mode, config_path, enabled)
VALUES ('roundcube', '$(mysql_escape_literal "$db")', '$(mysql_escape_literal "$user")', '$(mysql_escape_literal "$(roundcube_endpoint_mode)")', '$(mysql_escape_literal "$(roundcube_config_file)")', 1)
ON DUPLICATE KEY UPDATE db_name=VALUES(db_name), db_user=VALUES(db_user), endpoint_mode=VALUES(endpoint_mode), config_path=VALUES(config_path), enabled=VALUES(enabled), updated_at=CURRENT_TIMESTAMP;
"
}
+38
View File
@@ -0,0 +1,38 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=./roundcube_common.sh
source "$SCRIPT_DIR/roundcube_common.sh"
fail() {
echo "roundcube alt-mysql health: $*" >&2
exit 1
}
roundcube_enabled || {
echo "roundcube alt-mysql health: disabled"
exit 0
}
CONFIG_FILE="$(roundcube_config_file)"
DB_NAME="$(roundcube_db_name)"
DB_USER="$(roundcube_db_user)"
PASS="$(roundcube_load_password)" || fail "Roundcube password file is missing or invalid: $(roundcube_password_file)"
mysql_load_alt_credentials
mysql_load_alt_runtime
[ -r "$CONFIG_FILE" ] || fail "config file is not readable: $CONFIG_FILE"
grep -Fq "BEGIN DIRECTADMIN MYSQL PLUGIN ROUNDCUBE" "$CONFIG_FILE" || fail "managed Roundcube DB block is missing"
grep -Fq "$DB_NAME" "$CONFIG_FILE" || fail "Roundcube config does not reference expected database"
if grep -Fq "/var/lib/mysql/mysql.sock" "$CONFIG_FILE"; then
fail "Roundcube config references native DirectAdmin socket"
fi
if grep -Fq "/usr/local/directadmin/conf/mysql.conf" "$CONFIG_FILE"; then
fail "Roundcube config references native DirectAdmin mysql.conf"
fi
MYSQL_PWD="$PASS" "$(mysql_alt_binary mysql)" --protocol=TCP --host=127.0.0.1 --port="$MYSQL_ALT_PORT" --user="$DB_USER" --database="$DB_NAME" -e "SELECT 1" >/dev/null \
|| fail "cannot connect to Roundcube database on alt-mariadb as $DB_USER"
echo "roundcube alt-mysql health: OK"
@@ -0,0 +1,65 @@
#!/bin/bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=./roundcube_common.sh
source "$SCRIPT_DIR/roundcube_common.sh"
FORCE="${FORCE:-0}"
SKIP_NATIVE_DUMP="${SKIP_NATIVE_DUMP:-0}"
LOG_FILE="${LOG_FILE:-$ROUNDCUBE_PLUGIN_DIR/data/logs/roundcube-migrate.log}"
mkdir -p "$(dirname "$LOG_FILE")"
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG_FILE"
}
die() {
log "ERROR: $*"
exit 1
}
roundcube_enabled || die "Roundcube integration is disabled by ROUNDCUBE_ENABLED."
DB_NAME="$(roundcube_db_name)"
NATIVE_MY_CNF="$(roundcube_native_my_cnf)"
TMP_DUMP="$(mktemp)"
trap 'rm -f "$TMP_DUMP"' EXIT
roundcube_create_alt_database_and_user
if [ "$SKIP_NATIVE_DUMP" != "1" ]; then
[ -r "$NATIVE_MY_CNF" ] || die "Native DirectAdmin MySQL config not readable: $NATIVE_MY_CNF"
NATIVE_MYSQL_BIN="$(mysql_native_binary mysql)" || die "native mysql/mariadb client not found"
NATIVE_MYSQLDUMP_BIN="$(mysql_native_binary mysqldump)" || die "native mysqldump/mariadb-dump client not found"
if ! "$NATIVE_MYSQL_BIN" --defaults-extra-file="$NATIVE_MY_CNF" --batch --skip-column-names information_schema \
-e "SELECT 1 FROM SCHEMATA WHERE SCHEMA_NAME='$(mysql_escape_literal "$DB_NAME")' LIMIT 1" | grep -q 1; then
if [ "$FORCE" != "1" ]; then
die "Native Roundcube database $DB_NAME not found. Use SKIP_NATIVE_DUMP=1 if this is a new Roundcube install."
fi
log "Native Roundcube database $DB_NAME not found; continuing because FORCE=1."
else
log "Dumping native Roundcube database: $DB_NAME"
"$NATIVE_MYSQLDUMP_BIN" --defaults-extra-file="$NATIVE_MY_CNF" \
--single-transaction \
--quick \
--skip-lock-tables \
--routines \
--triggers \
--events \
--default-character-set=utf8mb4 \
"$DB_NAME" > "$TMP_DUMP" 2>>"$LOG_FILE" || die "native Roundcube dump failed"
log "Importing Roundcube database into alt-mariadb: $DB_NAME"
mysql_exec mysql -e "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$DB_NAME");"
mysql_exec mysql -e "CREATE DATABASE $(mysql_quote_identifier "$DB_NAME") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql_exec "$DB_NAME" < "$TMP_DUMP" >> "$LOG_FILE" 2>&1 || die "Roundcube import into alt-mariadb failed"
roundcube_create_alt_database_and_user
fi
fi
"$SCRIPT_DIR/roundcube_repair_config.sh"
"$SCRIPT_DIR/roundcube_health_check.sh"
log "Roundcube migration to alt-mariadb completed."
+79
View File
@@ -0,0 +1,79 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=./roundcube_common.sh
source "$SCRIPT_DIR/roundcube_common.sh"
rewrite_roundcube_config() {
local source_file="$1"
local target_file="$2"
local dsn="$3"
local tmp parent
[ -f "$source_file" ] || {
echo "roundcube: source config file not found: $source_file" >&2
return 1
}
if [ -e "$target_file" ]; then
[ -w "$target_file" ] || {
echo "roundcube: config file is not writable: $target_file" >&2
return 1
}
else
parent="$(dirname "$target_file")"
mkdir -p "$parent"
fi
tmp="$(mktemp)"
awk '
/BEGIN DIRECTADMIN MYSQL PLUGIN ROUNDCUBE/ { skip=1; next }
/END DIRECTADMIN MYSQL PLUGIN ROUNDCUBE/ { skip=0; next }
skip != 1 { print }
' "$source_file" > "$tmp"
cat >> "$tmp" <<PHP
// BEGIN DIRECTADMIN MYSQL PLUGIN ROUNDCUBE
\$config['db_dsnw'] = '$dsn';
// END DIRECTADMIN MYSQL PLUGIN ROUNDCUBE
PHP
if [ -e "$target_file" ]; then
cat "$tmp" > "$target_file"
else
install -m 0644 "$tmp" "$target_file"
fi
rm -f "$tmp"
}
roundcube_enabled || {
echo "roundcube: integration disabled by ROUNDCUBE_ENABLED."
exit 0
}
CONFIG_FILE="$(roundcube_config_file)"
[ -f "$CONFIG_FILE" ] || {
echo "roundcube: config file not found: $CONFIG_FILE" >&2
exit 1
}
[ -w "$CONFIG_FILE" ] || {
echo "roundcube: config file is not writable: $CONFIG_FILE" >&2
exit 1
}
DSN="$(roundcube_alt_dsn)"
rewrite_roundcube_config "$CONFIG_FILE" "$CONFIG_FILE" "$DSN"
echo "roundcube: config repaired: $CONFIG_FILE"
if roundcube_write_custombuild_config; then
CUSTOM_CONFIG_FILE="$(roundcube_custom_config_file)"
if [ -f "$CUSTOM_CONFIG_FILE" ]; then
rewrite_roundcube_config "$CUSTOM_CONFIG_FILE" "$CUSTOM_CONFIG_FILE" "$DSN"
else
rewrite_roundcube_config "$CONFIG_FILE" "$CUSTOM_CONFIG_FILE" "$DSN"
fi
echo "roundcube: CustomBuild config repaired: $CUSTOM_CONFIG_FILE"
fi