Files
alt-mysql/scripts/setup/mysql_common.sh
T

541 lines
18 KiB
Bash
Executable File

#!/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"
}
RESTORE_ROLLBACK_FILE=""
RESTORE_ROLLBACK_CREATED=0
cleanup_restore_rollback() {
if [ -n "${RESTORE_ROLLBACK_FILE:-}" ] && [ -f "$RESTORE_ROLLBACK_FILE" ]; then
rm -f "$RESTORE_ROLLBACK_FILE"
fi
RESTORE_ROLLBACK_FILE=""
RESTORE_ROLLBACK_CREATED=0
}
backup_alt_database_for_rollback() {
local db="$1"
cleanup_restore_rollback
RESTORE_ROLLBACK_FILE="$(mktemp)"
if [ -z "$(mysql_exec mysql -Nse "SELECT 1 FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='$(mysql_escape_literal "$db")' LIMIT 1" 2>>"${LOG_FILE:-/dev/null}" || true)" ]; then
cleanup_restore_rollback
return 0
fi
if mysqldump_exec \
--single-transaction \
--quick \
--skip-lock-tables \
--routines \
--triggers \
--events \
--default-character-set=utf8mb4 \
"$db" > "$RESTORE_ROLLBACK_FILE" 2>>"${LOG_FILE:-/dev/null}"; then
RESTORE_ROLLBACK_CREATED=1
return 0
fi
cleanup_restore_rollback
return 1
}
restore_alt_database_from_rollback() {
local db="$1"
if [ "$RESTORE_ROLLBACK_CREATED" -ne 1 ] || [ -z "${RESTORE_ROLLBACK_FILE:-}" ] || [ ! -r "$RESTORE_ROLLBACK_FILE" ]; then
return 0
fi
mysql_exec mysql -e "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$db");" >> "${LOG_FILE:-/dev/null}" 2>&1 || true
mysql_exec mysql -e "CREATE DATABASE $(mysql_quote_identifier "$db") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" >> "${LOG_FILE:-/dev/null}" 2>&1 || true
mysql_exec "$db" < "$RESTORE_ROLLBACK_FILE" >> "${LOG_FILE:-/dev/null}" 2>&1 || {
return 1
}
}