From f15da3b7aab12a5c6968d318592d309a5dde8516 Mon Sep 17 00:00:00 2001 From: Marek Miklewicz Date: Sun, 5 Jul 2026 21:25:28 +0200 Subject: [PATCH] feat: add detection functions for native MariaDB port takeover --- scripts/setup/native_mariadb_port_takeover.sh | 116 ++++++++++++++++++ ...ative_mariadb_port_takeover_detect_test.sh | 115 +++++++++++++++++ 2 files changed, 231 insertions(+) create mode 100755 scripts/setup/native_mariadb_port_takeover.sh create mode 100644 tests/native_mariadb_port_takeover_detect_test.sh diff --git a/scripts/setup/native_mariadb_port_takeover.sh b/scripts/setup/native_mariadb_port_takeover.sh new file mode 100755 index 0000000..5496d00 --- /dev/null +++ b/scripts/setup/native_mariadb_port_takeover.sh @@ -0,0 +1,116 @@ +#!/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." +} + +[ -r "$COMMON_FILE" ] || die "missing helper: $COMMON_FILE" +# shellcheck source=./mysql_common.sh +source "$COMMON_FILE" + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + : # main() is added in Task 4. +fi diff --git a/tests/native_mariadb_port_takeover_detect_test.sh b/tests/native_mariadb_port_takeover_detect_test.sh new file mode 100644 index 0000000..2c7f19b --- /dev/null +++ b/tests/native_mariadb_port_takeover_detect_test.sh @@ -0,0 +1,115 @@ +#!/bin/bash +set -euo pipefail + +PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)" +SCRIPT="$PLUGIN_DIR/scripts/setup/native_mariadb_port_takeover.sh" +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +fail() { + echo "FAIL: $*" >&2 + exit 1 +} + +# Source only the detection functions, without triggering main() (main() is +# added in Task 4, guarded behind a BASH_SOURCE check). +NATIVE_TAKEOVER_LOG="$TMP_DIR/takeover.log" \ + source "$SCRIPT" + +# --- check_os_guard: AlmaLinux 9 must pass --- +cat > "$TMP_DIR/os-release-alma9" <<'EOF' +ID="almalinux" +VERSION_ID="9.4" +PRETTY_NAME="AlmaLinux 9.4" +EOF +OS_RELEASE_FILE="$TMP_DIR/os-release-alma9" check_os_guard \ + || fail "AlmaLinux 9 should pass the OS guard" + +# --- check_os_guard: AlmaLinux 8 must pass --- +cat > "$TMP_DIR/os-release-alma8" <<'EOF' +ID="almalinux" +VERSION_ID="8.10" +EOF +OS_RELEASE_FILE="$TMP_DIR/os-release-alma8" check_os_guard \ + || fail "AlmaLinux 8 should pass the OS guard" + +# --- check_os_guard: CloudLinux must be refused --- +cat > "$TMP_DIR/os-release-cloudlinux" <<'EOF' +ID="cloudlinux" +VERSION_ID="8.9" +ID_LIKE="rhel fedora centos" +EOF +if ( OS_RELEASE_FILE="$TMP_DIR/os-release-cloudlinux" check_os_guard 2>/dev/null ); then + fail "CloudLinux must be refused by the OS guard" +fi + +# --- check_os_guard: AlmaLinux 7 (out of supported range) must be refused --- +cat > "$TMP_DIR/os-release-alma7" <<'EOF' +ID="almalinux" +VERSION_ID="7.9" +EOF +if ( OS_RELEASE_FILE="$TMP_DIR/os-release-alma7" check_os_guard 2>/dev/null ); then + fail "AlmaLinux 7 must be refused by the OS guard (only 8/9 supported)" +fi + +# --- read_native_mysql_conf + already_migrated --- +cat > "$TMP_DIR/mysql.conf.3306" <<'EOF' +user=da_admin +passwd=secret +port=3306 +socket=/var/lib/mysql/mysql.sock +host= +EOF +NATIVE_MYSQL_CONF="$TMP_DIR/mysql.conf.3306" read_native_mysql_conf \ + || fail "read_native_mysql_conf should succeed on a valid mysql.conf" +[ "$NATIVE_CURRENT_USER" = "da_admin" ] || fail "expected user da_admin, got: $NATIVE_CURRENT_USER" +[ "$NATIVE_CURRENT_PORT" = "3306" ] || fail "expected port 3306, got: $NATIVE_CURRENT_PORT" +already_migrated && fail "already_migrated should be false when native port is still 3306" + +cat > "$TMP_DIR/mysql.conf.3307" <<'EOF' +user=da_admin +passwd=secret +port=3307 +socket=/var/lib/mysql/mysql.sock +host= +EOF +NATIVE_MYSQL_CONF="$TMP_DIR/mysql.conf.3307" read_native_mysql_conf \ + || fail "read_native_mysql_conf should succeed on an already-migrated mysql.conf" +already_migrated || fail "already_migrated should be true when native port is 3307" + +# --- read_current_mysql_inst --- +cat > "$TMP_DIR/options.conf" <<'EOF' +mysql_inst=mariadb +mariadb=10.6 +EOF +CUSTOMBUILD_OPTIONS_CONF="$TMP_DIR/options.conf" read_current_mysql_inst \ + || fail "read_current_mysql_inst should succeed" +[ "$NATIVE_CURRENT_MYSQL_INST" = "mariadb" ] || fail "expected mysql_inst=mariadb, got: $NATIVE_CURRENT_MYSQL_INST" + +# --- preflight_new_port_free using a faked `ss` on PATH --- +mkdir -p "$TMP_DIR/bin" +cat > "$TMP_DIR/bin/ss" <<'STUB' +#!/bin/bash +# Pretends port 3307 is free and port 9999 is occupied. +for arg in "$@"; do + case "$arg" in + *:3307*) exit 0 ;; + *:9999*) + echo "header line" + echo "occupied line" + exit 0 + ;; + esac +done +exit 0 +STUB +chmod 755 "$TMP_DIR/bin/ss" + +PATH="$TMP_DIR/bin:$PATH" NEW_NATIVE_PORT="3307" preflight_new_port_free \ + || fail "preflight_new_port_free should pass when the target port is free" + +if ( PATH="$TMP_DIR/bin:$PATH" NEW_NATIVE_PORT="9999" preflight_new_port_free 2>/dev/null ); then + fail "preflight_new_port_free should refuse when the target port is already occupied" +fi + +echo "native_mariadb_port_takeover_detect_test: OK"