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

344 lines
12 KiB
Bash
Executable File

#!/bin/bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PLUGIN_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
COMMON_FILE="$SCRIPT_DIR/mysql_common.sh"
NATIVE_MY_CNF="${NATIVE_MY_CNF:-/etc/my.cnf}"
NATIVE_MY_CNF_D="${NATIVE_MY_CNF_D:-/etc/my.cnf.d}"
NATIVE_MYSQL_CONF="${NATIVE_MYSQL_CONF:-/usr/local/directadmin/conf/mysql.conf}"
CUSTOMBUILD_OPTIONS_CONF="${CUSTOMBUILD_OPTIONS_CONF:-/usr/local/directadmin/custombuild/options.conf}"
NEW_NATIVE_PORT="${NEW_NATIVE_PORT:-3307}"
DA_CLI_BIN="${DA_CLI_BIN:-da}"
OS_RELEASE_FILE="${OS_RELEASE_FILE:-/etc/os-release}"
LOG_FILE="${NATIVE_TAKEOVER_LOG:-$PLUGIN_DIR/data/logs/native-port-takeover.log}"
mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$LOG_FILE"
printf '[INFO] %s\n' "$*"
}
die() {
log "ERROR: $*"
printf '[BŁĄD] %s\n' "$*" >&2
exit 1
}
# --- state populated by the detection functions, consumed by later steps ---
NATIVE_CURRENT_PORT=""
NATIVE_CURRENT_SOCKET=""
NATIVE_CURRENT_HOST=""
NATIVE_CURRENT_USER=""
NATIVE_CURRENT_PASS=""
NATIVE_CURRENT_MYSQL_INST=""
require_root() {
[ "$(id -u)" -eq 0 ] || die "Ten skrypt musi zostać uruchomiony jako root."
}
check_os_guard() {
[ -r "$OS_RELEASE_FILE" ] || die "Nie można odczytać $OS_RELEASE_FILE."
local os_id="" os_version=""
os_id="$(awk -F= '$1=="ID"{gsub(/"/,"",$2); print $2}' "$OS_RELEASE_FILE")"
os_version="$(awk -F= '$1=="VERSION_ID"{gsub(/"/,"",$2); print $2}' "$OS_RELEASE_FILE")"
[ "$os_id" = "almalinux" ] || die "Ten skrypt obsługuje wyłącznie AlmaLinux 8/9 (wykryto: ${os_id:-nieznany})."
case "$os_version" in
8*|9*) ;;
*) die "Ten skrypt obsługuje wyłącznie AlmaLinux 8/9 (wykryto wersję: ${os_version:-nieznana})." ;;
esac
log "Wykryto obsługiwany system: AlmaLinux $os_version."
}
read_native_mysql_conf() {
[ -r "$NATIVE_MYSQL_CONF" ] || die "Nie znaleziono pliku $NATIVE_MYSQL_CONF."
NATIVE_CURRENT_USER="$(mysql_read_key_value "$NATIVE_MYSQL_CONF" user || true)"
NATIVE_CURRENT_PASS="$(mysql_read_key_value "$NATIVE_MYSQL_CONF" passwd || true)"
NATIVE_CURRENT_PORT="$(mysql_read_key_value "$NATIVE_MYSQL_CONF" port || true)"
NATIVE_CURRENT_SOCKET="$(mysql_read_key_value "$NATIVE_MYSQL_CONF" socket || true)"
NATIVE_CURRENT_HOST="$(mysql_read_key_value "$NATIVE_MYSQL_CONF" host || true)"
[ -n "$NATIVE_CURRENT_USER" ] || die "Nie udało się odczytać pola user z $NATIVE_MYSQL_CONF."
[ -n "$NATIVE_CURRENT_PORT" ] || die "Nie udało się odczytać pola port z $NATIVE_MYSQL_CONF."
}
read_current_mysql_inst() {
[ -r "$CUSTOMBUILD_OPTIONS_CONF" ] || die "Nie znaleziono pliku $CUSTOMBUILD_OPTIONS_CONF."
NATIVE_CURRENT_MYSQL_INST="$(mysql_read_key_value "$CUSTOMBUILD_OPTIONS_CONF" mysql_inst || true)"
[ -n "$NATIVE_CURRENT_MYSQL_INST" ] || die "Nie udało się odczytać mysql_inst z $CUSTOMBUILD_OPTIONS_CONF."
log "Bieżąca wartość mysql_inst: $NATIVE_CURRENT_MYSQL_INST."
}
already_migrated() {
[ "$NATIVE_CURRENT_PORT" != "3306" ]
}
check_native_reachable() {
local bin
bin="$(mysql_detect_binary mysql)" || die "Nie znaleziono klienta mysql/mariadb do sprawdzenia natywnej instancji."
MYSQL_PWD="$NATIVE_CURRENT_PASS" "$bin" \
--user="$NATIVE_CURRENT_USER" \
--protocol=TCP --host=127.0.0.1 --port="$NATIVE_CURRENT_PORT" \
-e "SELECT 1" >/dev/null 2>>"$LOG_FILE" \
|| die "Natywna instancja MariaDB nie odpowiada na porcie $NATIVE_CURRENT_PORT z bieżącymi poświadczeniami."
log "Natywna instancja MariaDB odpowiada na porcie $NATIVE_CURRENT_PORT."
}
port_is_listening() {
local port="$1"
if command -v ss >/dev/null 2>&1; then
ss -ltn "( sport = :$port )" 2>/dev/null | awk 'NR > 1 { found=1 } END { exit !found }'
return $?
fi
if command -v lsof >/dev/null 2>&1; then
lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1
return $?
fi
log "Nie znaleziono ss ani lsof - zakładam, że port $port jest wolny."
return 1
}
preflight_new_port_free() {
if port_is_listening "$NEW_NATIVE_PORT"; then
die "Port $NEW_NATIVE_PORT jest już zajęty. Zmień NEW_NATIVE_PORT i spróbuj ponownie."
fi
log "Port docelowy $NEW_NATIVE_PORT jest wolny."
}
DID_SET_MYSQL_INST_NO=0
CONFIG_BACKUPS=()
disable_custombuild_mysql_management() {
if [ "$NATIVE_CURRENT_MYSQL_INST" = "no" ]; then
log "mysql_inst jest już ustawione na 'no' - pomijam."
return 0
fi
"$DA_CLI_BIN" build set mysql_inst no >>"$LOG_FILE" 2>&1 \
|| die "Nie udało się ustawić mysql_inst=no przez '$DA_CLI_BIN build set mysql_inst no'."
DID_SET_MYSQL_INST_NO=1
log "Ustawiono mysql_inst=no przez CustomBuild."
}
restore_custombuild_mysql_management() {
[ "$DID_SET_MYSQL_INST_NO" -eq 1 ] || return 0
"$DA_CLI_BIN" build set mysql_inst "$NATIVE_CURRENT_MYSQL_INST" >>"$LOG_FILE" 2>&1 \
|| log "OSTRZEŻENIE: nie udało się przywrócić mysql_inst=$NATIVE_CURRENT_MYSQL_INST."
log "Przywrócono mysql_inst=$NATIVE_CURRENT_MYSQL_INST."
}
backup_config_file() {
local file="$1"
local backup
backup="${file}.bak.$(date +%Y%m%d%H%M%S)"
cp -a "$file" "$backup"
CONFIG_BACKUPS+=("$file:$backup")
log "Utworzono kopię zapasową $file jako $backup."
}
rewrite_port_in_file() {
local file="$1" new_port="$2"
local tmp changed=0
[ -r "$file" ] || return 1
tmp="$(mktemp)"
awk -v new_port="$new_port" '
BEGIN { in_section = 0; changed = 0 }
/^[[:space:]]*\[[^]]+\][[:space:]]*$/ {
section = tolower($0)
gsub(/^[[:space:]]*\[[[:space:]]*/, "", section)
gsub(/[[:space:]]*\][[:space:]]*$/, "", section)
in_section = (section == "mysqld" || section == "mariadb" || section == "server")
print
next
}
in_section && /^[[:space:]]*port[[:space:]]*=/ {
print "port=" new_port
changed = 1
next
}
{ print }
END { if (changed) exit 0; else exit 1 }
' "$file" > "$tmp"
local awk_status=$?
if [ "$awk_status" -eq 0 ]; then
cp "$tmp" "$file"
changed=1
fi
rm -f "$tmp"
[ "$changed" -eq 1 ]
}
find_and_rewrite_native_port_configs() {
local any_changed=0
local f
for f in "$NATIVE_MY_CNF" "$NATIVE_MY_CNF_D"/*.cnf; do
[ -f "$f" ] || continue
if grep -Eq '^[[:space:]]*port[[:space:]]*=[[:space:]]*3306[[:space:]]*$' "$f"; then
backup_config_file "$f"
if rewrite_port_in_file "$f" "$NEW_NATIVE_PORT"; then
any_changed=1
log "Zaktualizowano port w $f."
fi
fi
done
if [ "$any_changed" -eq 0 ]; then
backup_config_file "$NATIVE_MY_CNF"
if grep -Eq '^[[:space:]]*\[mysqld\][[:space:]]*$' "$NATIVE_MY_CNF"; then
awk -v new_port="$NEW_NATIVE_PORT" '
{ print }
/^[[:space:]]*\[mysqld\][[:space:]]*$/ && !done { print "port=" new_port; done=1 }
' "$NATIVE_MY_CNF" > "${NATIVE_MY_CNF}.tmp" && mv "${NATIVE_MY_CNF}.tmp" "$NATIVE_MY_CNF"
else
printf '\n[mysqld]\nport=%s\n' "$NEW_NATIVE_PORT" >> "$NATIVE_MY_CNF"
fi
log "Brak istniejącej dyrektywy port= w sekcji serwera - dodano port=$NEW_NATIVE_PORT do $NATIVE_MY_CNF."
fi
}
update_native_mysql_conf_port() {
local target_port="$1"
local tmp
tmp="$(mktemp)"
awk -v new_port="$target_port" '
BEGIN { done = 0 }
/^[[:space:]]*port[[:space:]]*=/ { print "port=" new_port; done=1; next }
{ print }
END { if (!done) print "port=" new_port }
' "$NATIVE_MYSQL_CONF" > "$tmp"
cp "$tmp" "$NATIVE_MYSQL_CONF"
rm -f "$tmp"
}
apply_config_mutations() {
backup_config_file "$NATIVE_MYSQL_CONF"
find_and_rewrite_native_port_configs
update_native_mysql_conf_port "$NEW_NATIVE_PORT"
log "Zaktualizowano $NATIVE_MYSQL_CONF na port $NEW_NATIVE_PORT."
}
rollback_config_changes() {
local pair file backup
for pair in "${CONFIG_BACKUPS[@]}"; do
file="${pair%%:*}"
backup="${pair#*:}"
if [ -r "$backup" ]; then
cp -a "$backup" "$file"
log "Przywrócono $file z kopii zapasowej $backup."
fi
done
}
NATIVE_SERVICE_NAME=""
DID_RESTART_NATIVE=0
DID_RESTART_DA=0
# Guards rollback_service_state so it only acts while wired via the EXIT trap
# below. NOTE: this is intentionally an EXIT trap, not an ERR trap - see the
# comment above the trap installation in main() for why.
ROLLBACK_ARMED=0
detect_native_service_name() {
NATIVE_SERVICE_NAME="$(mysql_service_name)"
log "Wykryta usługa natywnej MariaDB: $NATIVE_SERVICE_NAME."
}
restart_native_service() {
systemctl restart "$NATIVE_SERVICE_NAME" || die "Nie udało się zrestartować usługi $NATIVE_SERVICE_NAME."
DID_RESTART_NATIVE=1
}
verify_native_listening_new_port() {
local i
for i in $(seq 1 30); do
if port_is_listening "$NEW_NATIVE_PORT" && ! port_is_listening 3306; then
log "Natywna instancja nasłuchuje na porcie $NEW_NATIVE_PORT, port 3306 wolny."
return 0
fi
sleep 1
done
die "Natywna instancja nie nasłuchuje na porcie $NEW_NATIVE_PORT po restarcie (lub port 3306 wciąż zajęty)."
}
restart_directadmin_service() {
systemctl restart directadmin || die "Nie udało się zrestartować usługi directadmin."
DID_RESTART_DA=1
}
verify_da_connectivity_new_port() {
local bin
bin="$(mysql_detect_binary mysql)" || die "Nie znaleziono klienta mysql/mariadb do weryfikacji łączności DA."
MYSQL_PWD="$NATIVE_CURRENT_PASS" "$bin" \
--user="$NATIVE_CURRENT_USER" \
--protocol=TCP --host=127.0.0.1 --port="$NEW_NATIVE_PORT" \
-e "SELECT 1" >/dev/null 2>>"$LOG_FILE" \
|| die "DirectAdmin nie może połączyć się z natywną instancją na nowym porcie $NEW_NATIVE_PORT."
log "Potwierdzono łączność DirectAdmina z natywną instancją na porcie $NEW_NATIVE_PORT."
}
rollback_service_state() {
[ "$ROLLBACK_ARMED" -eq 1 ] || return 0
rollback_config_changes
restore_custombuild_mysql_management
if [ -n "$NATIVE_SERVICE_NAME" ]; then
systemctl restart "$NATIVE_SERVICE_NAME" \
|| log "OSTRZEŻENIE: nie udało się zrestartować $NATIVE_SERVICE_NAME podczas wycofywania zmian."
fi
if [ "$DID_RESTART_DA" -eq 1 ]; then
systemctl restart directadmin \
|| log "OSTRZEŻENIE: nie udało się zrestartować directadmin podczas wycofywania zmian."
fi
log "Wycofano zmiany przejęcia portu natywnej instancji MariaDB."
}
main() {
require_root
check_os_guard
read_native_mysql_conf
read_current_mysql_inst
if already_migrated; then
log "Natywna instancja już działa na porcie $NATIVE_CURRENT_PORT (nie 3306) - pomijam przejęcie."
exit 0
fi
check_native_reachable
preflight_new_port_free
detect_native_service_name
# NOTE: deliberately an EXIT trap, not an ERR trap as originally planned.
# die() (used by every failure path below) calls `exit` directly, and bash's
# ERR trap does not fire for a function that terminates via an explicit
# `exit` call - it only fires when a command's non-zero status propagates
# through normal control flow. An EXIT trap fires on *any* shell exit
# (explicit exit, errexit-triggered exit, or falling off the end of the
# script), which is what "any failure from this point on triggers
# rollback" actually requires here. ROLLBACK_ARMED guards it so it is a
# no-op before this point and after a successful run.
ROLLBACK_ARMED=1
trap 'rollback_service_state' EXIT
disable_custombuild_mysql_management
apply_config_mutations
restart_native_service
verify_native_listening_new_port
restart_directadmin_service
verify_da_connectivity_new_port
ROLLBACK_ARMED=0
trap - EXIT
log "Przejęcie portu natywnej instancji MariaDB zakończone powodzeniem: $NATIVE_CURRENT_PORT -> $NEW_NATIVE_PORT."
}
[ -r "$COMMON_FILE" ] || die "missing helper: $COMMON_FILE"
# shellcheck source=./mysql_common.sh
source "$COMMON_FILE"
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi