#!/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 <