Files
alt-mysql/scripts/setup/mysql_host_sync.sh
T
2026-07-04 15:48:19 +02:00

377 lines
9.6 KiB
Bash
Executable File

#!/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 "$@"