Import alt-mysql plugin
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
ERROR_MSG=""
|
||||
BACKUP_OK=0
|
||||
DA_USER=""
|
||||
DB_NAME=""
|
||||
JOB_ID=""
|
||||
LOG_FILE=""
|
||||
|
||||
JOB_FILE="${1:-}"
|
||||
if [ -z "$JOB_FILE" ] || [ ! -f "$JOB_FILE" ]; then
|
||||
echo "mysql: missing job file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CONFIG_FILE="${PLUGIN_DIR}/plugin-settings.conf"
|
||||
LOG_DIR="${PLUGIN_DIR}/data/logs"
|
||||
COMMON_FILE="${PLUGIN_DIR}/scripts/setup/mysql_common.sh"
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
urlencode() {
|
||||
local s="$1"
|
||||
local out=""
|
||||
local i c
|
||||
for ((i=0; i<${#s}; i++)); do
|
||||
c="${s:i:1}"
|
||||
case "$c" in
|
||||
[a-zA-Z0-9.~_-]) out+="$c" ;;
|
||||
' ') out+='%20' ;;
|
||||
*) printf -v c '%%%02X' "'$c"; out+="$c" ;;
|
||||
esac
|
||||
done
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
notify_user() {
|
||||
local code="$1"
|
||||
if [ -z "${DA_USER:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local subject message
|
||||
if [ "$code" -eq 0 ] && [ "$BACKUP_OK" -eq 1 ]; then
|
||||
subject="Backup bazy MySQL zakończony pomyślnie"
|
||||
message="Backup bazy ${DB_NAME} został wykonany. Plik: ${TARGET_FILE:-nieznany}."
|
||||
else
|
||||
subject="Błąd wykonywania backupu bazy MySQL"
|
||||
if [ -n "$ERROR_MSG" ]; then
|
||||
message="Backup bazy ${DB_NAME} nie powiódł się: ${ERROR_MSG}"
|
||||
else
|
||||
message="Backup bazy ${DB_NAME} nie powiódł się. Sprawdź log zadania."
|
||||
fi
|
||||
fi
|
||||
|
||||
local task_queue="/usr/local/directadmin/data/task.queue"
|
||||
local users="select1%3D$(urlencode "$DA_USER")"
|
||||
local line="action=notify&value=users&subject=$(urlencode "$subject")&message=$(urlencode "$message")&users=${users}"
|
||||
printf "%s\n" "$line" >> "$task_queue"
|
||||
}
|
||||
|
||||
fail() {
|
||||
local msg="$1"
|
||||
ERROR_MSG="$msg"
|
||||
if [ -n "${LOG_FILE:-}" ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${msg}" >> "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
log_unexpected_error() {
|
||||
local line_no="$1"
|
||||
local command="$2"
|
||||
local code="$3"
|
||||
if [ -z "${LOG_FILE:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ -n "${ERROR_MSG:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
ERROR_MSG="Nieoczekiwany błąd wykonywania backupu (kod ${code}, linia ${line_no})."
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${ERROR_MSG} CMD: ${command}" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
load_plugin_settings() {
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
fail "Brak pliku konfiguracyjnego pluginu: $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
}
|
||||
|
||||
quote_sh_value() {
|
||||
local value="$1"
|
||||
value="${value//\'/\'\\\'\'}"
|
||||
printf "'%s'" "$value"
|
||||
}
|
||||
|
||||
set_job_var() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
local tmp
|
||||
local owner_group=""
|
||||
if [ -f "$JOB_FILE" ]; then
|
||||
owner_group="$(stat -c '%u:%g' "$JOB_FILE" 2>/dev/null || stat -f '%u:%g' "$JOB_FILE" 2>/dev/null || true)"
|
||||
fi
|
||||
tmp="$(mktemp)"
|
||||
grep -v "^${key}=" "$JOB_FILE" > "$tmp" 2>/dev/null || true
|
||||
printf "%s=%s\n" "$key" "$(quote_sh_value "$value")" >> "$tmp"
|
||||
mv "$tmp" "$JOB_FILE"
|
||||
chmod 600 "$JOB_FILE" 2>/dev/null || true
|
||||
if [ "$(id -u)" -eq 0 ] && [ -n "$owner_group" ]; then
|
||||
chown "$owner_group" "$JOB_FILE" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
on_exit() {
|
||||
local code="$1"
|
||||
if [ "$code" -ne 0 ] && [ -z "${ERROR_MSG:-}" ] && [ -n "${LOG_FILE:-}" ]; then
|
||||
ERROR_MSG="Zadanie backupu zakończyło się błędem (kod ${code}) bez zarejestrowanego szczegółu."
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${ERROR_MSG}" >> "$LOG_FILE"
|
||||
fi
|
||||
notify_user "$code"
|
||||
}
|
||||
trap 'log_unexpected_error "$LINENO" "$BASH_COMMAND" "$?"' ERR
|
||||
trap 'on_exit $?' EXIT
|
||||
|
||||
[ -f "$COMMON_FILE" ] || {
|
||||
echo "Brak helpera MySQL: $COMMON_FILE" >&2
|
||||
exit 1
|
||||
}
|
||||
# shellcheck disable=SC1090
|
||||
source "$COMMON_FILE"
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$JOB_FILE"
|
||||
|
||||
JOB_ID="${JOB_ID:-$(basename "$JOB_FILE" .env)}"
|
||||
DA_USER="${DA_USER:-}"
|
||||
DB_NAME="${DB_NAME:-}"
|
||||
DATE_DIR="${DATE_DIR:-$(date +%Y-%m-%d)}"
|
||||
COMPRESS_BACKUP="${COMPRESS_BACKUP:-1}"
|
||||
USER_BASE_BACKUP_DIR="${USER_BASE_BACKUP_DIR:-/home/${DA_USER}/mysql_backup}"
|
||||
JOB_COMPRESS_BACKUP="${COMPRESS_BACKUP}"
|
||||
JOB_USER_BASE_BACKUP_DIR="${USER_BASE_BACKUP_DIR}"
|
||||
|
||||
LOG_FILE="${LOG_DIR}/${JOB_ID}.log"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Start backup job ${JOB_ID}" > "$LOG_FILE"
|
||||
|
||||
if [ -z "$DA_USER" ] || [ -z "$DB_NAME" ]; then
|
||||
fail "Brak DA_USER lub DB_NAME w pliku zadania."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DB_NAME" != "${DA_USER}_"* ]]; then
|
||||
fail "Baza ${DB_NAME} nie należy do użytkownika ${DA_USER}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
load_plugin_settings
|
||||
COMPRESS_BACKUP="${JOB_COMPRESS_BACKUP:-$COMPRESS_BACKUP}"
|
||||
USER_BASE_BACKUP_DIR="${JOB_USER_BASE_BACKUP_DIR:-$USER_BASE_BACKUP_DIR}"
|
||||
mysql_load_alt_credentials
|
||||
MYSQL_BIN="$(mysql_alt_binary mysql)" || {
|
||||
fail "Brak binarium mysql."
|
||||
exit 1
|
||||
}
|
||||
MYSQLDUMP_BIN="$(mysql_alt_binary mysqldump)" || {
|
||||
fail "Brak binarium mysqldump."
|
||||
exit 1
|
||||
}
|
||||
mapfile -t MYSQL_ARGS < <(mysql_base_args)
|
||||
|
||||
if [[ "$COMPRESS_BACKUP" =~ ^(true|yes|on)$ ]]; then
|
||||
COMPRESS_BACKUP="1"
|
||||
fi
|
||||
if [[ "$COMPRESS_BACKUP" =~ ^(false|no|off)$ ]]; then
|
||||
COMPRESS_BACKUP="0"
|
||||
fi
|
||||
|
||||
TARGET_DIR="${USER_BASE_BACKUP_DIR%/}/${DATE_DIR}"
|
||||
mkdir -p "$TARGET_DIR"
|
||||
chmod 700 "$TARGET_DIR" 2>/dev/null || true
|
||||
|
||||
if [ "$COMPRESS_BACKUP" = "1" ]; then
|
||||
TARGET_FILE="${TARGET_DIR}/${DB_NAME}.sql.gz"
|
||||
else
|
||||
TARGET_FILE="${TARGET_DIR}/${DB_NAME}.sql"
|
||||
fi
|
||||
TMP_FILE="${TARGET_FILE}.tmp.$$"
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup source DB: ${DB_NAME}" >> "$LOG_FILE"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup target file: ${TARGET_FILE}" >> "$LOG_FILE"
|
||||
|
||||
set +e
|
||||
if [ "$COMPRESS_BACKUP" = "1" ]; then
|
||||
MYSQL_PWD="$MYSQL_PASSWORD" "$MYSQLDUMP_BIN" \
|
||||
"${MYSQL_ARGS[@]}" \
|
||||
--single-transaction \
|
||||
--quick \
|
||||
--skip-lock-tables \
|
||||
--routines \
|
||||
--triggers \
|
||||
--events \
|
||||
--default-character-set=utf8mb4 \
|
||||
"$DB_NAME" 2>>"$LOG_FILE" | gzip -c > "$TMP_FILE"
|
||||
RC=${PIPESTATUS[0]}
|
||||
else
|
||||
MYSQL_PWD="$MYSQL_PASSWORD" "$MYSQLDUMP_BIN" \
|
||||
"${MYSQL_ARGS[@]}" \
|
||||
--single-transaction \
|
||||
--quick \
|
||||
--skip-lock-tables \
|
||||
--routines \
|
||||
--triggers \
|
||||
--events \
|
||||
--default-character-set=utf8mb4 \
|
||||
"$DB_NAME" > "$TMP_FILE" 2>>"$LOG_FILE"
|
||||
RC=$?
|
||||
fi
|
||||
set -e
|
||||
|
||||
if [ "$RC" -ne 0 ]; then
|
||||
rm -f "$TMP_FILE"
|
||||
fail "mysqldump zakończył się błędem dla bazy ${DB_NAME}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mv "$TMP_FILE" "$TARGET_FILE"
|
||||
chmod 600 "$TARGET_FILE" 2>/dev/null || true
|
||||
set_job_var "TARGET_FILE" "$TARGET_FILE"
|
||||
set_job_var "END_TS" "$(date +%s)"
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup completed successfully." >> "$LOG_FILE"
|
||||
BACKUP_OK=1
|
||||
exit 0
|
||||
@@ -0,0 +1,293 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
COMMON_FILE="$PLUGIN_DIR/scripts/setup/mysql_common.sh"
|
||||
SETTINGS_FILE="$PLUGIN_DIR/plugin-settings.conf"
|
||||
LOG_FILE="${DA_ALT_MYSQL_RESTORE_LOG:-$PLUGIN_DIR/data/logs/da-restore.log}"
|
||||
DA_USER=""
|
||||
PAYLOAD_DIR=""
|
||||
OVERWRITE_POLICY=""
|
||||
RESTORE_ROLLBACK_FILE=""
|
||||
RESTORE_ROLLBACK_CREATED=0
|
||||
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
log() {
|
||||
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
die() {
|
||||
log "ERROR: $*"
|
||||
printf 'alt-mysql restore payload: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: alt_mysql_restore_payload.sh --user DA_USER --payload /path/to/backup/alt_mysql [--overwrite allow|deny]
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--user) DA_USER="${2:-}"; shift 2 ;;
|
||||
--payload) PAYLOAD_DIR="${2:-}"; shift 2 ;;
|
||||
--overwrite) OVERWRITE_POLICY="${2:-}"; shift 2 ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$DA_USER" ] || usage
|
||||
[ -n "$PAYLOAD_DIR" ] || usage
|
||||
[[ "$DA_USER" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]*$ ]] || die "unsafe DirectAdmin username: $DA_USER"
|
||||
[ -d "$PAYLOAD_DIR" ] || die "payload directory does not exist: $PAYLOAD_DIR"
|
||||
[ -r "$COMMON_FILE" ] || die "missing helper: $COMMON_FILE"
|
||||
# shellcheck source=../setup/mysql_common.sh
|
||||
source "$COMMON_FILE"
|
||||
mysql_load_plugin_settings_file "$SETTINGS_FILE"
|
||||
|
||||
read_plugin_setting() {
|
||||
local key="$1"
|
||||
local default="$2"
|
||||
local line value
|
||||
[ -r "$SETTINGS_FILE" ] || {
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
}
|
||||
line="$(grep -E "^${key}=" "$SETTINGS_FILE" | tail -n 1 || true)"
|
||||
[ -n "$line" ] || {
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
}
|
||||
value="${line#*=}"
|
||||
value="${value%%#*}"
|
||||
value="${value%\"}"
|
||||
value="${value#\"}"
|
||||
value="${value%\'}"
|
||||
value="${value#\'}"
|
||||
value="$(printf '%s' "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||||
printf '%s\n' "${value:-$default}"
|
||||
}
|
||||
|
||||
sha256_file() {
|
||||
local file="$1"
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
sha256sum "$file" | awk '{print $1}'
|
||||
return 0
|
||||
fi
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$file" | awk '{print $1}'
|
||||
return 0
|
||||
fi
|
||||
die "missing sha256sum/shasum"
|
||||
}
|
||||
|
||||
require_php_cli() {
|
||||
command -v php >/dev/null 2>&1 || die "php CLI is required to parse manifest.json"
|
||||
}
|
||||
|
||||
manifest_database_rows() {
|
||||
local manifest="$1"
|
||||
php -r '
|
||||
$manifest = $argv[1];
|
||||
$data = json_decode(file_get_contents($manifest), true);
|
||||
if (!is_array($data) || ($data["format"] ?? "") !== "da-alt-mysql-v1") {
|
||||
fwrite(STDERR, "invalid manifest\n");
|
||||
exit(3);
|
||||
}
|
||||
foreach (($data["databases"] ?? []) as $db) {
|
||||
$name = (string)($db["name"] ?? "");
|
||||
$file = (string)($db["file"] ?? "");
|
||||
$sha = (string)($db["sha256"] ?? "");
|
||||
if ($name === "" || $file === "" || $sha === "") {
|
||||
fwrite(STDERR, "invalid database entry\n");
|
||||
exit(4);
|
||||
}
|
||||
echo $name, "\t", $file, "\t", $sha, "\n";
|
||||
}
|
||||
' "$manifest"
|
||||
}
|
||||
|
||||
safe_database_name() {
|
||||
local db="$1"
|
||||
[[ "$db" =~ ^[A-Za-z0-9_]+$ ]] || die "unsafe database name in payload: $db"
|
||||
printf '%s\n' "$db"
|
||||
}
|
||||
|
||||
database_allowed_for_user() {
|
||||
local db="$1"
|
||||
[[ "$db" == "${DA_USER}_"* ]]
|
||||
}
|
||||
|
||||
import_gzip_sql() {
|
||||
local database="$1"
|
||||
local file="$2"
|
||||
if [[ "$file" == *.gz ]]; then
|
||||
gunzip -c "$file" | mysql_exec "$database"
|
||||
return "${PIPESTATUS[1]}"
|
||||
fi
|
||||
mysql_exec "$database" < "$file"
|
||||
}
|
||||
|
||||
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" || true)" ]; then
|
||||
cleanup_restore_rollback
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "creating rollback dump: $db"
|
||||
if mysqldump_exec \
|
||||
--single-transaction \
|
||||
--quick \
|
||||
--skip-lock-tables \
|
||||
--routines \
|
||||
--triggers \
|
||||
--events \
|
||||
--default-character-set=utf8mb4 \
|
||||
"$db" > "$RESTORE_ROLLBACK_FILE" 2>>"$LOG_FILE"; 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
|
||||
|
||||
log "restoring rollback dump: $db"
|
||||
mysql_exec mysql -e "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$db");" >> "$LOG_FILE" 2>&1 || true
|
||||
mysql_exec mysql -e "CREATE DATABASE $(mysql_quote_identifier "$db") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" >> "$LOG_FILE" 2>&1 || true
|
||||
mysql_exec "$db" < "$RESTORE_ROLLBACK_FILE" >> "$LOG_FILE" 2>&1 || {
|
||||
log "ERROR: rollback restore failed for $db"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
trap cleanup_restore_rollback INT TERM EXIT
|
||||
|
||||
metadata_cleanup_for_user() {
|
||||
local escaped_user
|
||||
escaped_user="$(mysql_escape_literal "$DA_USER")"
|
||||
mysql_exec mysql -e "DELETE FROM da_plugin_access_hosts WHERE da_user='${escaped_user}' OR role_name LIKE '${escaped_user}\\_%';"
|
||||
mysql_exec mysql -e "DELETE FROM da_plugin_grant_profiles WHERE db_name LIKE '${escaped_user}\\_%' OR role_name LIKE '${escaped_user}\\_%';"
|
||||
mysql_exec mysql -e "DELETE FROM da_plugin_database_owners WHERE db_name LIKE '${escaped_user}\\_%' OR role_name LIKE '${escaped_user}\\_%';"
|
||||
}
|
||||
|
||||
verify_database_exists() {
|
||||
local db="$1"
|
||||
local escaped_db
|
||||
escaped_db="$(mysql_escape_literal "$db")"
|
||||
[ -n "$(mysql_exec mysql -Nse "SELECT 1 FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='${escaped_db}' LIMIT 1")" ] \
|
||||
|| die "database was not restored: $db"
|
||||
}
|
||||
|
||||
sync_remote_hosts_if_enabled() {
|
||||
local allow sync_script
|
||||
allow="$(read_plugin_setting allow_remote_hosts 0)"
|
||||
[ "$allow" = "1" ] || return 0
|
||||
sync_script="$(read_plugin_setting MYSQL_PLUGIN_SUDO_SYNC_HOSTS /usr/local/directadmin/plugins/alt-mysql/scripts/setup/mysql_host_sync.sh)"
|
||||
if [ -x "$sync_script" ]; then
|
||||
"$sync_script" >> "$LOG_FILE" 2>&1 || log "WARNING: host sync failed after restore"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
local manifest users_file grants_file metadata_file db file rel_file expected_sha actual_sha overwrite
|
||||
manifest="$PAYLOAD_DIR/manifest.json"
|
||||
users_file="$PAYLOAD_DIR/grants/users.sql.gz"
|
||||
grants_file="$PAYLOAD_DIR/grants/grants.sql.gz"
|
||||
[ -r "$manifest" ] || die "missing manifest: $manifest"
|
||||
[ -r "$users_file" ] || die "missing users payload: $users_file"
|
||||
[ -r "$grants_file" ] || die "missing grants payload: $grants_file"
|
||||
|
||||
require_php_cli
|
||||
mysql_load_alt_credentials
|
||||
mysql_ensure_metadata_schema
|
||||
|
||||
overwrite="${OVERWRITE_POLICY:-$(read_plugin_setting DA_ALT_MYSQL_RESTORE_OVERWRITE allow)}"
|
||||
case "$overwrite" in
|
||||
allow|deny) ;;
|
||||
prompt) overwrite="deny" ;;
|
||||
*) overwrite="deny" ;;
|
||||
esac
|
||||
|
||||
log "restore payload start: user=$DA_USER payload=$PAYLOAD_DIR overwrite=$overwrite"
|
||||
manifest_database_rows "$manifest" >/dev/null
|
||||
|
||||
while IFS=$'\t' read -r db rel_file expected_sha; do
|
||||
db="$(safe_database_name "$db")"
|
||||
database_allowed_for_user "$db" || die "database $db does not match target user prefix $DA_USER"
|
||||
file="$PAYLOAD_DIR/$rel_file"
|
||||
[ -r "$file" ] || die "missing database dump: $file"
|
||||
actual_sha="$(sha256_file "$file")"
|
||||
[ "$actual_sha" = "$expected_sha" ] || die "checksum mismatch for $db"
|
||||
if [ "$overwrite" = "deny" ] && [ -n "$(mysql_exec mysql -Nse "SELECT 1 FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='$(mysql_escape_literal "$db")' LIMIT 1")" ]; then
|
||||
die "target database exists and overwrite is denied: $db"
|
||||
fi
|
||||
done < <(manifest_database_rows "$manifest")
|
||||
|
||||
log "restoring user definitions"
|
||||
import_gzip_sql mysql "$users_file" >> "$LOG_FILE" 2>&1 || die "cannot restore MySQL users from payload"
|
||||
|
||||
metadata_cleanup_for_user
|
||||
|
||||
while IFS=$'\t' read -r db rel_file expected_sha; do
|
||||
db="$(safe_database_name "$db")"
|
||||
file="$PAYLOAD_DIR/$rel_file"
|
||||
log "restoring database: $db"
|
||||
backup_alt_database_for_rollback "$db" || die "cannot create rollback dump before restoring database: $db"
|
||||
if ! mysql_exec mysql -e "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$db");" >> "$LOG_FILE" 2>&1; then
|
||||
restore_alt_database_from_rollback "$db" || true
|
||||
die "cannot drop database before restore: $db"
|
||||
fi
|
||||
if ! mysql_exec mysql -e "CREATE DATABASE $(mysql_quote_identifier "$db") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" >> "$LOG_FILE" 2>&1; then
|
||||
restore_alt_database_from_rollback "$db" || true
|
||||
die "cannot create database before restore: $db"
|
||||
fi
|
||||
if ! import_gzip_sql "$db" "$file" >> "$LOG_FILE" 2>&1; then
|
||||
restore_alt_database_from_rollback "$db" || true
|
||||
die "cannot import database dump: $db"
|
||||
fi
|
||||
cleanup_restore_rollback
|
||||
verify_database_exists "$db"
|
||||
done < <(manifest_database_rows "$manifest")
|
||||
|
||||
for metadata_file in \
|
||||
"$PAYLOAD_DIR/metadata/da_plugin_database_owners.sql.gz" \
|
||||
"$PAYLOAD_DIR/metadata/da_plugin_grant_profiles.sql.gz" \
|
||||
"$PAYLOAD_DIR/metadata/da_plugin_access_hosts.sql.gz"; do
|
||||
if [ -r "$metadata_file" ]; then
|
||||
log "restoring metadata: ${metadata_file##*/}"
|
||||
import_gzip_sql mysql "$metadata_file" >> "$LOG_FILE" 2>&1 || die "cannot restore metadata: $metadata_file"
|
||||
fi
|
||||
done
|
||||
|
||||
log "restoring grants"
|
||||
import_gzip_sql mysql "$grants_file" >> "$LOG_FILE" 2>&1 || die "cannot restore grants from payload"
|
||||
mysql_exec mysql -e "FLUSH PRIVILEGES;" >> "$LOG_FILE" 2>&1 || true
|
||||
sync_remote_hosts_if_enabled
|
||||
|
||||
log "restore payload completed: user=$DA_USER"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,312 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
COMMON_FILE="$PLUGIN_DIR/scripts/setup/mysql_common.sh"
|
||||
LOG_FILE="${DA_ALT_MYSQL_BACKUP_LOG:-$PLUGIN_DIR/data/logs/da-backup.log}"
|
||||
DRY_RUN=0
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
log() {
|
||||
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
die() {
|
||||
log "ERROR: $*"
|
||||
printf 'alt-mysql backup hook: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ -r "$COMMON_FILE" ] || die "missing helper: $COMMON_FILE"
|
||||
# shellcheck source=../setup/mysql_common.sh
|
||||
source "$COMMON_FILE"
|
||||
mysql_load_plugin_settings_file "$PLUGIN_DIR/plugin-settings.conf"
|
||||
|
||||
json_escape() {
|
||||
printf '%s' "${1:-}" | sed 's/\\/\\\\/g; s/"/\\"/g'
|
||||
}
|
||||
|
||||
json_string() {
|
||||
printf '"%s"' "$(json_escape "${1:-}")"
|
||||
}
|
||||
|
||||
sha256_file() {
|
||||
local file="$1"
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
sha256sum "$file" | awk '{print $1}'
|
||||
return 0
|
||||
fi
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$file" | awk '{print $1}'
|
||||
return 0
|
||||
fi
|
||||
die "missing sha256sum/shasum"
|
||||
}
|
||||
|
||||
detect_da_user() {
|
||||
local value="${DA_USER:-${username:-${user:-${USERNAME:-}}}}"
|
||||
if [ -z "$value" ] && [ "${1:-}" != "--dry-run" ]; then
|
||||
value="${1:-}"
|
||||
fi
|
||||
if [[ ! "$value" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]*$ ]]; then
|
||||
die "cannot detect safe DirectAdmin username"
|
||||
fi
|
||||
printf '%s\n' "$value"
|
||||
}
|
||||
|
||||
candidate_dir_if_valid() {
|
||||
local candidate="${1:-}"
|
||||
[ -n "$candidate" ] || return 1
|
||||
[ -d "$candidate" ] || return 1
|
||||
printf '%s\n' "$(cd "$candidate" && pwd -P)"
|
||||
}
|
||||
|
||||
candidate_backup_root_if_valid() {
|
||||
local candidate="${1:-}"
|
||||
local dir=""
|
||||
if ! dir="$(candidate_dir_if_valid "$candidate")"; then
|
||||
return 1
|
||||
fi
|
||||
if [ "$DRY_RUN" -eq 1 ] || [ -d "$dir/backup" ]; then
|
||||
printf '%s\n' "$dir"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
detect_backup_root() {
|
||||
local current candidate dir file_candidate
|
||||
current="$(pwd -P)"
|
||||
|
||||
for candidate in \
|
||||
"${DA_ALT_MYSQL_BACKUP_ROOT:-}" \
|
||||
"${backup_path:-}" \
|
||||
"${backupdir:-}" \
|
||||
"${backup_dir:-}" \
|
||||
"${tmpdir:-}" \
|
||||
"$current"; do
|
||||
if dir="$(candidate_backup_root_if_valid "$candidate")"; then
|
||||
printf '%s\n' "$dir"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
for file_candidate in "${file:-}" "${filename:-}" "${backup_file:-}"; do
|
||||
[ -n "$file_candidate" ] || continue
|
||||
candidate="$(dirname "$file_candidate")"
|
||||
if dir="$(candidate_backup_root_if_valid "$candidate")"; then
|
||||
printf '%s\n' "$dir"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
die "cannot detect DirectAdmin backup assembly directory"
|
||||
}
|
||||
|
||||
list_user_databases() {
|
||||
local da_user="$1"
|
||||
local escaped_user
|
||||
escaped_user="$(mysql_escape_literal "$da_user")"
|
||||
mysql_exec mysql -Nse "
|
||||
SELECT SCHEMA_NAME
|
||||
FROM information_schema.SCHEMATA
|
||||
WHERE SCHEMA_NAME LIKE '${escaped_user}\\_%' ESCAPE '\\'
|
||||
ORDER BY SCHEMA_NAME
|
||||
" | grep -Ev '^(information_schema|performance_schema|mysql|sys)$' || true
|
||||
}
|
||||
|
||||
list_user_roles() {
|
||||
local da_user="$1"
|
||||
local escaped_user
|
||||
escaped_user="$(mysql_escape_literal "$da_user")"
|
||||
mysql_exec mysql -Nse "
|
||||
SELECT DISTINCT User
|
||||
FROM mysql.user
|
||||
WHERE User LIKE '${escaped_user}\\_%' ESCAPE '\\'
|
||||
AND User <> ''
|
||||
ORDER BY User
|
||||
" || true
|
||||
}
|
||||
|
||||
dump_users_file() {
|
||||
local da_user="$1"
|
||||
local output_file="$2"
|
||||
local tmp_file user host q_user q_host create_sql
|
||||
|
||||
tmp_file="$(mktemp)"
|
||||
{
|
||||
printf '%s\n' "-- alt-mysql user definitions for $da_user"
|
||||
printf '%s\n' "SET sql_log_bin=0;"
|
||||
} > "$tmp_file"
|
||||
|
||||
while IFS=$'\t' read -r user host; do
|
||||
[ -n "${user:-}" ] || continue
|
||||
[ -n "${host:-}" ] || continue
|
||||
q_user="$(mysql_escape_literal "$user")"
|
||||
q_host="$(mysql_escape_literal "$host")"
|
||||
create_sql="$(mysql_exec mysql -Nse "SHOW CREATE USER '${q_user}'@'${q_host}'" | awk -F'\t' '{print $NF}' || true)"
|
||||
[ -n "$create_sql" ] || continue
|
||||
printf "DROP USER IF EXISTS '%s'@'%s';\n" "$q_user" "$q_host" >> "$tmp_file"
|
||||
printf '%s;\n' "$create_sql" >> "$tmp_file"
|
||||
done < <(
|
||||
mysql_exec mysql -Nse "
|
||||
SELECT User, Host
|
||||
FROM mysql.user
|
||||
WHERE User LIKE '$(mysql_escape_literal "$da_user")\\_%' ESCAPE '\\'
|
||||
AND User <> ''
|
||||
ORDER BY User, Host
|
||||
" || true
|
||||
)
|
||||
|
||||
printf '%s\n' "SET sql_log_bin=1;" >> "$tmp_file"
|
||||
gzip -9 < "$tmp_file" > "$output_file"
|
||||
rm -f "$tmp_file"
|
||||
}
|
||||
|
||||
dump_grants_file() {
|
||||
local da_user="$1"
|
||||
local output_file="$2"
|
||||
local tmp_file user host q_user q_host grant_line
|
||||
|
||||
tmp_file="$(mktemp)"
|
||||
printf '%s\n' "-- alt-mysql grants for $da_user" > "$tmp_file"
|
||||
|
||||
while IFS=$'\t' read -r user host; do
|
||||
[ -n "${user:-}" ] || continue
|
||||
[ -n "${host:-}" ] || continue
|
||||
q_user="$(mysql_escape_literal "$user")"
|
||||
q_host="$(mysql_escape_literal "$host")"
|
||||
while IFS= read -r grant_line; do
|
||||
[ -n "$grant_line" ] || continue
|
||||
printf '%s;\n' "$grant_line" >> "$tmp_file"
|
||||
done < <(mysql_exec mysql -Nse "SHOW GRANTS FOR '${q_user}'@'${q_host}'" || true)
|
||||
printf '\n' >> "$tmp_file"
|
||||
done < <(
|
||||
mysql_exec mysql -Nse "
|
||||
SELECT User, Host
|
||||
FROM mysql.user
|
||||
WHERE User LIKE '$(mysql_escape_literal "$da_user")\\_%' ESCAPE '\\'
|
||||
AND User <> ''
|
||||
ORDER BY User, Host
|
||||
" || true
|
||||
)
|
||||
|
||||
gzip -9 < "$tmp_file" > "$output_file"
|
||||
rm -f "$tmp_file"
|
||||
}
|
||||
|
||||
dump_metadata_table() {
|
||||
local table="$1"
|
||||
local where="$2"
|
||||
local output_file="$3"
|
||||
mysqldump_exec \
|
||||
--single-transaction \
|
||||
--skip-lock-tables \
|
||||
--no-create-info \
|
||||
--compact \
|
||||
--where="$where" \
|
||||
mysql "$table" | gzip -9 > "$output_file"
|
||||
}
|
||||
|
||||
write_manifest() {
|
||||
local da_user="$1"
|
||||
local payload_dir="$2"
|
||||
shift 2
|
||||
local db_json="$1"
|
||||
local manifest="$payload_dir/manifest.json"
|
||||
local version
|
||||
version="$(mysql_alt_version)"
|
||||
mysql_load_alt_runtime
|
||||
|
||||
cat > "$manifest" <<JSON
|
||||
{
|
||||
"format": "da-alt-mysql-v1",
|
||||
"created_at": "$(date -u '+%Y-%m-%dT%H:%M:%SZ')",
|
||||
"da_user": $(json_string "$da_user"),
|
||||
"mariadb_version": $(json_string "$version"),
|
||||
"source": "alt-mariadb",
|
||||
"source_instance": {
|
||||
"service": $(json_string "$MYSQL_ALT_SERVICE_NAME"),
|
||||
"basedir": $(json_string "$MYSQL_ALT_BASEDIR"),
|
||||
"datadir": $(json_string "$MYSQL_ALT_DATADIR"),
|
||||
"port": $(json_string "$MYSQL_ALT_PORT"),
|
||||
"socket": $(json_string "$MYSQL_ALT_SOCKET")
|
||||
},
|
||||
"databases": [
|
||||
$db_json
|
||||
]
|
||||
}
|
||||
JSON
|
||||
}
|
||||
|
||||
main() {
|
||||
local da_user backup_root payload_dir db_dir grants_dir metadata_dir
|
||||
local db db_file rel_file sha db_entries entry prefix where
|
||||
da_user="$(detect_da_user "${1:-}")"
|
||||
backup_root="$(detect_backup_root)"
|
||||
payload_dir="$backup_root/backup/alt_mysql"
|
||||
db_dir="$payload_dir/databases"
|
||||
grants_dir="$payload_dir/grants"
|
||||
metadata_dir="$payload_dir/metadata"
|
||||
|
||||
log "backup hook env: user=$da_user pwd=$(pwd -P) file=${file:-} filename=${filename:-} root=$backup_root"
|
||||
|
||||
mysql_load_alt_credentials
|
||||
mysql_ensure_metadata_schema
|
||||
|
||||
if [ "$DRY_RUN" -eq 1 ]; then
|
||||
printf 'Would write alt-mysql payload for %s into %s\n' "$da_user" "$payload_dir"
|
||||
list_user_databases "$da_user"
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -rf "$payload_dir"
|
||||
mkdir -p "$db_dir" "$grants_dir" "$metadata_dir"
|
||||
|
||||
db_entries=""
|
||||
while IFS= read -r db; do
|
||||
[ -n "$db" ] || continue
|
||||
db_file="$db_dir/${db}.sql.gz"
|
||||
mysqldump_exec \
|
||||
--single-transaction \
|
||||
--quick \
|
||||
--skip-lock-tables \
|
||||
--routines \
|
||||
--triggers \
|
||||
--events \
|
||||
--default-character-set=utf8mb4 \
|
||||
"$db" | gzip -9 > "$db_file"
|
||||
rel_file="databases/${db}.sql.gz"
|
||||
sha="$(sha256_file "$db_file")"
|
||||
entry=" { \"name\": $(json_string "$db"), \"file\": $(json_string "$rel_file"), \"sha256\": $(json_string "$sha") }"
|
||||
if [ -n "$db_entries" ]; then
|
||||
db_entries+=$',\n'
|
||||
fi
|
||||
db_entries+="$entry"
|
||||
log "added database dump: $db"
|
||||
done < <(list_user_databases "$da_user")
|
||||
|
||||
dump_users_file "$da_user" "$grants_dir/users.sql.gz"
|
||||
dump_grants_file "$da_user" "$grants_dir/grants.sql.gz"
|
||||
|
||||
prefix="$(mysql_escape_literal "$da_user")"
|
||||
where="db_name LIKE '${prefix}\\_%'"
|
||||
dump_metadata_table da_plugin_database_owners "$where" "$metadata_dir/da_plugin_database_owners.sql.gz"
|
||||
dump_metadata_table da_plugin_grant_profiles "$where" "$metadata_dir/da_plugin_grant_profiles.sql.gz"
|
||||
where="da_user = '${prefix}' OR role_name LIKE '${prefix}\\_%'"
|
||||
dump_metadata_table da_plugin_access_hosts "$where" "$metadata_dir/da_plugin_access_hosts.sql.gz"
|
||||
|
||||
write_manifest "$da_user" "$payload_dir" "$db_entries"
|
||||
chmod -R go-rwx "$payload_dir" 2>/dev/null || true
|
||||
log "backup payload completed: $payload_dir"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,165 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
SETTINGS_FILE="$PLUGIN_DIR/plugin-settings.conf"
|
||||
LOG_FILE="${DA_ALT_MYSQL_RESTORE_LOG:-$PLUGIN_DIR/data/logs/da-restore.log}"
|
||||
RESTORE_PAYLOAD_SCRIPT="$SCRIPT_DIR/alt_mysql_restore_payload.sh"
|
||||
NATIVE_MIGRATE_SCRIPT="$SCRIPT_DIR/da_restore_native_staging_to_alt_mysql.sh"
|
||||
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
log() {
|
||||
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
die() {
|
||||
log "ERROR: $*"
|
||||
printf 'alt-mysql restore hook: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
read_plugin_setting() {
|
||||
local key="$1"
|
||||
local default="$2"
|
||||
local line value
|
||||
[ -r "$SETTINGS_FILE" ] || {
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
}
|
||||
line="$(grep -E "^${key}=" "$SETTINGS_FILE" | tail -n 1 || true)"
|
||||
[ -n "$line" ] || {
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
}
|
||||
value="${line#*=}"
|
||||
value="${value%%#*}"
|
||||
value="${value%\"}"
|
||||
value="${value#\"}"
|
||||
value="${value%\'}"
|
||||
value="${value#\'}"
|
||||
value="$(printf '%s' "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||||
printf '%s\n' "${value:-$default}"
|
||||
}
|
||||
|
||||
detect_da_user() {
|
||||
local value="${DA_USER:-${username:-${user:-${USERNAME:-}}}}"
|
||||
if [ -z "$value" ]; then
|
||||
value="${1:-}"
|
||||
fi
|
||||
if [[ ! "$value" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]*$ ]]; then
|
||||
die "cannot detect safe DirectAdmin username"
|
||||
fi
|
||||
printf '%s\n' "$value"
|
||||
}
|
||||
|
||||
candidate_dir_if_valid() {
|
||||
local candidate="${1:-}"
|
||||
[ -n "$candidate" ] || return 1
|
||||
[ -d "$candidate" ] || return 1
|
||||
printf '%s\n' "$(cd "$candidate" && pwd -P)"
|
||||
}
|
||||
|
||||
find_payload_in_dir() {
|
||||
local root="$1"
|
||||
local payload=""
|
||||
[ -d "$root" ] || return 1
|
||||
for payload in \
|
||||
"$root/backup/alt_mysql" \
|
||||
"$root/alt_mysql" \
|
||||
"$root/mysql/backup/alt_mysql"; do
|
||||
if [ -r "$payload/manifest.json" ]; then
|
||||
printf '%s\n' "$payload"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
payload="$(find "$root" -maxdepth 5 -type f -path '*/backup/alt_mysql/manifest.json' -print -quit 2>/dev/null || true)"
|
||||
if [ -n "$payload" ]; then
|
||||
dirname "$payload"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
extract_payload_from_archive() {
|
||||
local archive="$1"
|
||||
local tmp_dir payload_member
|
||||
[ -r "$archive" ] || return 1
|
||||
command -v tar >/dev/null 2>&1 || return 1
|
||||
|
||||
payload_member="$(tar -tf "$archive" 2>/dev/null | grep -E '(^|/)backup/alt_mysql/manifest\.json$' | head -n 1 || true)"
|
||||
[ -n "$payload_member" ] || return 1
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
tar -xf "$archive" -C "$tmp_dir" --wildcards '*/backup/alt_mysql/*' 'backup/alt_mysql/*' 2>/dev/null \
|
||||
|| tar -xf "$archive" -C "$tmp_dir" 2>/dev/null
|
||||
|
||||
find_payload_in_dir "$tmp_dir"
|
||||
}
|
||||
|
||||
detect_payload_dir() {
|
||||
local current candidate dir archive payload
|
||||
current="$(pwd -P)"
|
||||
for candidate in \
|
||||
"${DA_ALT_MYSQL_RESTORE_ROOT:-}" \
|
||||
"${restore_path:-}" \
|
||||
"${restore_dir:-}" \
|
||||
"${tmpdir:-}" \
|
||||
"${backup_path:-}" \
|
||||
"$current"; do
|
||||
if dir="$(candidate_dir_if_valid "$candidate")"; then
|
||||
if payload="$(find_payload_in_dir "$dir")"; then
|
||||
printf '%s\n' "$payload"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
for archive in "${filename:-}" "${file:-}" "${backup_file:-}"; do
|
||||
[ -n "$archive" ] || continue
|
||||
if payload="$(extract_payload_from_archive "$archive")"; then
|
||||
printf '%s\n' "$payload"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
main() {
|
||||
local da_user enabled overwrite payload native_mode
|
||||
da_user="$(detect_da_user "${1:-}")"
|
||||
enabled="$(read_plugin_setting DA_ALT_MYSQL_RESTORE_ENABLED true)"
|
||||
native_mode="$(read_plugin_setting DA_ALT_MYSQL_NATIVE_STAGING_MIGRATE true)"
|
||||
|
||||
log "restore hook env: user=$da_user pwd=$(pwd -P) file=${file:-} filename=${filename:-}"
|
||||
|
||||
case "$enabled" in
|
||||
true|yes|on|1) ;;
|
||||
*) log "restore hook disabled by DA_ALT_MYSQL_RESTORE_ENABLED=$enabled"; return 0 ;;
|
||||
esac
|
||||
|
||||
overwrite="$(read_plugin_setting DA_ALT_MYSQL_RESTORE_OVERWRITE allow)"
|
||||
|
||||
if payload="$(detect_payload_dir)"; then
|
||||
[ -x "$RESTORE_PAYLOAD_SCRIPT" ] || die "restore payload script is not executable: $RESTORE_PAYLOAD_SCRIPT"
|
||||
log "plugin payload detected: $payload"
|
||||
"$RESTORE_PAYLOAD_SCRIPT" --user "$da_user" --payload "$payload" --overwrite "$overwrite"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "plugin payload not found; considering native staging migration"
|
||||
case "$native_mode" in
|
||||
true|yes|on|1)
|
||||
[ -x "$NATIVE_MIGRATE_SCRIPT" ] || die "native staging migration script is not executable: $NATIVE_MIGRATE_SCRIPT"
|
||||
"$NATIVE_MIGRATE_SCRIPT" --user "$da_user"
|
||||
;;
|
||||
*)
|
||||
log "native staging migration disabled; account restore continues without alt-mysql DB migration"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
SETTINGS_FILE="$PLUGIN_DIR/plugin-settings.conf"
|
||||
LOG_FILE="${DA_ALT_MYSQL_DB_HOOK_LOG:-$PLUGIN_DIR/data/logs/da-db-hooks.log}"
|
||||
HOOK_NAME="${DA_HOOK_NAME:-$(basename "$0")}"
|
||||
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
read_plugin_setting() {
|
||||
local key="$1"
|
||||
local default="$2"
|
||||
local line value
|
||||
[ -r "$SETTINGS_FILE" ] || {
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
}
|
||||
line="$(grep -E "^${key}=" "$SETTINGS_FILE" | tail -n 1 || true)"
|
||||
[ -n "$line" ] || {
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
}
|
||||
value="${line#*=}"
|
||||
value="${value%%#*}"
|
||||
value="${value%\"}"
|
||||
value="${value#\"}"
|
||||
value="${value%\'}"
|
||||
value="${value#\'}"
|
||||
value="$(printf '%s' "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||||
printf '%s\n' "${value:-$default}"
|
||||
}
|
||||
|
||||
log() {
|
||||
printf '[%s] hook=%s username=%s database=%s user=%s %s\n' \
|
||||
"$(date '+%Y-%m-%d %H:%M:%S')" \
|
||||
"$HOOK_NAME" \
|
||||
"${username:-}" \
|
||||
"${database:-${name:-}}" \
|
||||
"${user:-}" \
|
||||
"$*" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
enabled="$(read_plugin_setting DA_ALT_MYSQL_BLOCK_NATIVE_DB_HOOKS true)"
|
||||
case "$enabled" in
|
||||
true|yes|on|1) ;;
|
||||
*) log "native DB hook blocker disabled"; exit 0 ;;
|
||||
esac
|
||||
|
||||
if [ "${DA_ALT_MYSQL_ALLOW_NATIVE_DB_ACTION:-0}" = "1" ]; then
|
||||
log "native DB action explicitly allowed by environment"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
case "$HOOK_NAME" in
|
||||
*_post.sh|*_post)
|
||||
log "native DirectAdmin database action observed after completion"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
log "blocked native DirectAdmin database action"
|
||||
cat >&2 <<'EOF'
|
||||
Databases on this server are managed by the MySQL plugin and the alt-mariadb instance.
|
||||
Use the MySQL plugin instead of DirectAdmin native database actions.
|
||||
EOF
|
||||
exit 1
|
||||
@@ -0,0 +1,448 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
COMMON_FILE="$PLUGIN_DIR/scripts/setup/mysql_common.sh"
|
||||
SETTINGS_FILE="$PLUGIN_DIR/plugin-settings.conf"
|
||||
LOG_FILE="${DA_ALT_MYSQL_RESTORE_LOG:-$PLUGIN_DIR/data/logs/da-restore.log}"
|
||||
DA_USER=""
|
||||
NATIVE_MY_CNF="${NATIVE_MY_CNF:-/usr/local/directadmin/conf/my.cnf}"
|
||||
OVERWRITE_POLICY=""
|
||||
CLEANUP_POLICY=""
|
||||
RESTORE_ROLLBACK_FILE=""
|
||||
RESTORE_ROLLBACK_CREATED=0
|
||||
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
log() {
|
||||
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
die() {
|
||||
log "ERROR: $*"
|
||||
printf 'native staging to alt-mysql: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: da_restore_native_staging_to_alt_mysql.sh --user DA_USER [--native-my-cnf /path] [--overwrite allow|deny] [--cleanup after_success|keep]
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--user) DA_USER="${2:-}"; shift 2 ;;
|
||||
--native-my-cnf) NATIVE_MY_CNF="${2:-}"; shift 2 ;;
|
||||
--overwrite) OVERWRITE_POLICY="${2:-}"; shift 2 ;;
|
||||
--cleanup) CLEANUP_POLICY="${2:-}"; shift 2 ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$DA_USER" ] || usage
|
||||
[[ "$DA_USER" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]*$ ]] || die "unsafe DirectAdmin username: $DA_USER"
|
||||
[ -r "$COMMON_FILE" ] || die "missing helper: $COMMON_FILE"
|
||||
# shellcheck source=../setup/mysql_common.sh
|
||||
source "$COMMON_FILE"
|
||||
mysql_load_plugin_settings_file "$SETTINGS_FILE"
|
||||
|
||||
read_plugin_setting() {
|
||||
local key="$1"
|
||||
local default="$2"
|
||||
local line value
|
||||
[ -r "$SETTINGS_FILE" ] || {
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
}
|
||||
line="$(grep -E "^${key}=" "$SETTINGS_FILE" | tail -n 1 || true)"
|
||||
[ -n "$line" ] || {
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
}
|
||||
value="${line#*=}"
|
||||
value="${value%%#*}"
|
||||
value="${value%\"}"
|
||||
value="${value#\"}"
|
||||
value="${value%\'}"
|
||||
value="${value#\'}"
|
||||
value="$(printf '%s' "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||||
printf '%s\n' "${value:-$default}"
|
||||
}
|
||||
|
||||
native_mysql() {
|
||||
"$NATIVE_MYSQL_BIN" --defaults-extra-file="$NATIVE_MY_CNF" "$@"
|
||||
}
|
||||
|
||||
native_query() {
|
||||
local database="$1"
|
||||
local sql="$2"
|
||||
native_mysql --batch --skip-column-names "$database" -e "$sql"
|
||||
}
|
||||
|
||||
safe_identifier() {
|
||||
local value="$1"
|
||||
[[ "$value" =~ ^[A-Za-z0-9_]+$ ]] || die "unsafe MySQL identifier: $value"
|
||||
printf '%s\n' "$value"
|
||||
}
|
||||
|
||||
list_native_databases() {
|
||||
native_query information_schema "
|
||||
SELECT SCHEMA_NAME
|
||||
FROM SCHEMATA
|
||||
WHERE SCHEMA_NAME LIKE '$(mysql_escape_literal "$DA_USER")\\_%' ESCAPE '\\'
|
||||
ORDER BY SCHEMA_NAME
|
||||
" | grep -Ev '^(information_schema|performance_schema|mysql|sys)$' || true
|
||||
}
|
||||
|
||||
list_native_user_hosts() {
|
||||
native_query mysql "
|
||||
SELECT User, Host
|
||||
FROM user
|
||||
WHERE User LIKE '$(mysql_escape_literal "$DA_USER")\\_%' ESCAPE '\\'
|
||||
AND User <> ''
|
||||
ORDER BY User, Host
|
||||
" || true
|
||||
}
|
||||
|
||||
list_native_roles_for_db() {
|
||||
local db="$1"
|
||||
native_query mysql "
|
||||
SELECT DISTINCT User
|
||||
FROM db
|
||||
WHERE Db = '$(mysql_escape_literal "$db")'
|
||||
AND User LIKE '$(mysql_escape_literal "$DA_USER")\\_%' ESCAPE '\\'
|
||||
AND User <> ''
|
||||
ORDER BY User
|
||||
" || true
|
||||
}
|
||||
|
||||
native_show_create_user() {
|
||||
local user="$1"
|
||||
local host="$2"
|
||||
native_query mysql "SHOW CREATE USER '$(mysql_escape_literal "$user")'@'$(mysql_escape_literal "$host")'" | awk -F'\t' '{print $NF}'
|
||||
}
|
||||
|
||||
native_show_grants() {
|
||||
local user="$1"
|
||||
local host="$2"
|
||||
native_query mysql "SHOW GRANTS FOR '$(mysql_escape_literal "$user")'@'$(mysql_escape_literal "$host")'" || true
|
||||
}
|
||||
|
||||
apply_user_definition_to_alt() {
|
||||
local user="$1"
|
||||
local host="$2"
|
||||
local create_sql
|
||||
create_sql="$(native_show_create_user "$user" "$host" || true)"
|
||||
[ -n "$create_sql" ] || die "cannot recover password/authentication definition for ${user}@${host}"
|
||||
|
||||
{
|
||||
printf "DROP USER IF EXISTS '%s'@'%s';\n" "$(mysql_escape_literal "$user")" "$(mysql_escape_literal "$host")"
|
||||
printf '%s;\n' "$create_sql"
|
||||
} | mysql_exec mysql >> "$LOG_FILE" 2>&1 || die "cannot recreate user with preserved password: ${user}@${host}"
|
||||
}
|
||||
|
||||
apply_grants_to_alt() {
|
||||
local user="$1"
|
||||
local host="$2"
|
||||
local grant_line
|
||||
while IFS= read -r grant_line; do
|
||||
[ -n "$grant_line" ] || continue
|
||||
printf '%s;\n' "$grant_line" | mysql_exec mysql >> "$LOG_FILE" 2>&1 \
|
||||
|| die "cannot apply grant for ${user}@${host}"
|
||||
done < <(native_show_grants "$user" "$host")
|
||||
}
|
||||
|
||||
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" || true)" ]; then
|
||||
cleanup_restore_rollback
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "creating rollback dump: $db"
|
||||
if mysqldump_exec \
|
||||
--single-transaction \
|
||||
--quick \
|
||||
--skip-lock-tables \
|
||||
--routines \
|
||||
--triggers \
|
||||
--events \
|
||||
--default-character-set=utf8mb4 \
|
||||
"$db" > "$RESTORE_ROLLBACK_FILE" 2>>"$LOG_FILE"; 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
|
||||
|
||||
log "restoring rollback dump: $db"
|
||||
mysql_exec mysql -e "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$db");" >> "$LOG_FILE" 2>&1 || true
|
||||
mysql_exec mysql -e "CREATE DATABASE $(mysql_quote_identifier "$db") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" >> "$LOG_FILE" 2>&1 || true
|
||||
mysql_exec "$db" < "$RESTORE_ROLLBACK_FILE" >> "$LOG_FILE" 2>&1 || {
|
||||
log "ERROR: rollback restore failed for $db"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
trap cleanup_restore_rollback INT TERM EXIT
|
||||
|
||||
db_privilege_profile() {
|
||||
local db="$1"
|
||||
local role="$2"
|
||||
local row privs=()
|
||||
row="$(native_query mysql "
|
||||
SELECT Select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv, Index_priv, Alter_priv,
|
||||
Create_tmp_table_priv, Lock_tables_priv, Create_view_priv, Show_view_priv, Create_routine_priv,
|
||||
Alter_routine_priv, Execute_priv, Event_priv, Trigger_priv
|
||||
FROM db
|
||||
WHERE Db='$(mysql_escape_literal "$db")'
|
||||
AND User='$(mysql_escape_literal "$role")'
|
||||
LIMIT 1
|
||||
" | head -n 1 || true)"
|
||||
|
||||
if [ -z "$row" ]; then
|
||||
printf 'ALL\n'
|
||||
return 0
|
||||
fi
|
||||
|
||||
IFS=$'\t' read -r select_p insert_p update_p delete_p create_p drop_p index_p alter_p tmp_p lock_p create_view_p show_view_p create_routine_p alter_routine_p execute_p event_p trigger_p <<< "$row"
|
||||
[ "${select_p:-N}" = "Y" ] && privs+=("SELECT")
|
||||
[ "${insert_p:-N}" = "Y" ] && privs+=("INSERT")
|
||||
[ "${update_p:-N}" = "Y" ] && privs+=("UPDATE")
|
||||
[ "${delete_p:-N}" = "Y" ] && privs+=("DELETE")
|
||||
[ "${create_p:-N}" = "Y" ] && privs+=("CREATE")
|
||||
[ "${drop_p:-N}" = "Y" ] && privs+=("DROP")
|
||||
[ "${index_p:-N}" = "Y" ] && privs+=("INDEX")
|
||||
[ "${alter_p:-N}" = "Y" ] && privs+=("ALTER")
|
||||
[ "${tmp_p:-N}" = "Y" ] && privs+=("CREATE TEMPORARY TABLES")
|
||||
[ "${lock_p:-N}" = "Y" ] && privs+=("LOCK TABLES")
|
||||
[ "${create_view_p:-N}" = "Y" ] && privs+=("CREATE VIEW")
|
||||
[ "${show_view_p:-N}" = "Y" ] && privs+=("SHOW VIEW")
|
||||
[ "${create_routine_p:-N}" = "Y" ] && privs+=("CREATE ROUTINE")
|
||||
[ "${alter_routine_p:-N}" = "Y" ] && privs+=("ALTER ROUTINE")
|
||||
[ "${execute_p:-N}" = "Y" ] && privs+=("EXECUTE")
|
||||
[ "${event_p:-N}" = "Y" ] && privs+=("EVENT")
|
||||
[ "${trigger_p:-N}" = "Y" ] && privs+=("TRIGGER")
|
||||
|
||||
if [ "${#privs[@]}" -eq 0 ]; then
|
||||
printf 'ALL\n'
|
||||
else
|
||||
local IFS=,
|
||||
printf '%s\n' "${privs[*]}"
|
||||
fi
|
||||
}
|
||||
|
||||
metadata_cleanup_for_user() {
|
||||
local escaped_user
|
||||
escaped_user="$(mysql_escape_literal "$DA_USER")"
|
||||
mysql_exec mysql -e "DELETE FROM da_plugin_access_hosts WHERE da_user='${escaped_user}' OR role_name LIKE '${escaped_user}\\_%';"
|
||||
mysql_exec mysql -e "DELETE FROM da_plugin_grant_profiles WHERE db_name LIKE '${escaped_user}\\_%' OR role_name LIKE '${escaped_user}\\_%';"
|
||||
mysql_exec mysql -e "DELETE FROM da_plugin_database_owners WHERE db_name LIKE '${escaped_user}\\_%' OR role_name LIKE '${escaped_user}\\_%';"
|
||||
}
|
||||
|
||||
rebuild_metadata_for_db() {
|
||||
local db="$1"
|
||||
local owner="" role profile host
|
||||
while IFS= read -r role; do
|
||||
[ -n "$role" ] || continue
|
||||
if [ -z "$owner" ]; then
|
||||
owner="$role"
|
||||
fi
|
||||
profile="$(db_privilege_profile "$db" "$role")"
|
||||
mysql_exec mysql -e "
|
||||
INSERT INTO da_plugin_grant_profiles (db_name, role_name, privileges)
|
||||
VALUES ('$(mysql_escape_literal "$db")', '$(mysql_escape_literal "$role")', '$(mysql_escape_literal "$profile")')
|
||||
ON DUPLICATE KEY UPDATE privileges=VALUES(privileges), updated_at=CURRENT_TIMESTAMP;
|
||||
"
|
||||
while IFS=$'\t' read -r _user host; do
|
||||
[ -n "${host:-}" ] || continue
|
||||
mysql_exec mysql -e "
|
||||
INSERT INTO da_plugin_access_hosts (da_user, role_name, host_pattern, note)
|
||||
VALUES ('$(mysql_escape_literal "$DA_USER")', '$(mysql_escape_literal "$role")', '$(mysql_escape_literal "$host")', 'migrated from native DirectAdmin restore')
|
||||
ON DUPLICATE KEY UPDATE da_user=VALUES(da_user), note=VALUES(note), updated_at=CURRENT_TIMESTAMP;
|
||||
"
|
||||
done < <(list_native_user_hosts | awk -F'\t' -v role="$role" '$1 == role { print $0 }')
|
||||
done < <(list_native_roles_for_db "$db")
|
||||
|
||||
if [ -n "$owner" ]; then
|
||||
mysql_exec mysql -e "
|
||||
INSERT INTO da_plugin_database_owners (db_name, da_user, role_name)
|
||||
VALUES ('$(mysql_escape_literal "$db")', '$(mysql_escape_literal "$DA_USER")', '$(mysql_escape_literal "$owner")')
|
||||
ON DUPLICATE KEY UPDATE da_user=VALUES(da_user), role_name=VALUES(role_name), updated_at=CURRENT_TIMESTAMP;
|
||||
"
|
||||
fi
|
||||
}
|
||||
|
||||
verify_alt_database() {
|
||||
local db="$1"
|
||||
[ -n "$(mysql_exec mysql -Nse "SELECT 1 FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='$(mysql_escape_literal "$db")' LIMIT 1")" ] \
|
||||
|| die "alt database missing after import: $db"
|
||||
}
|
||||
|
||||
drop_native_staging() {
|
||||
local db user host remaining
|
||||
log "cleanup native staging for $DA_USER"
|
||||
while IFS= read -r db; do
|
||||
[ -n "$db" ] || continue
|
||||
native_query mysql "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$db")" >> "$LOG_FILE" 2>&1 || die "cannot drop native staged database: $db"
|
||||
done < <(list_native_databases)
|
||||
|
||||
while IFS=$'\t' read -r user host; do
|
||||
[ -n "${user:-}" ] || continue
|
||||
[ -n "${host:-}" ] || continue
|
||||
remaining="$(native_query mysql "
|
||||
SELECT COUNT(*)
|
||||
FROM db
|
||||
WHERE User='$(mysql_escape_literal "$user")'
|
||||
AND Db NOT LIKE '$(mysql_escape_literal "$DA_USER")\\_%' ESCAPE '\\'
|
||||
" | tail -n 1)"
|
||||
if [ "${remaining:-0}" = "0" ]; then
|
||||
native_query mysql "DROP USER IF EXISTS '$(mysql_escape_literal "$user")'@'$(mysql_escape_literal "$host")'" >> "$LOG_FILE" 2>&1 || true
|
||||
else
|
||||
log "keeping native user ${user}@${host}; it still has non-staging grants"
|
||||
fi
|
||||
done < <(list_native_user_hosts)
|
||||
}
|
||||
|
||||
sync_remote_hosts_if_enabled() {
|
||||
local allow sync_script
|
||||
allow="$(read_plugin_setting allow_remote_hosts 0)"
|
||||
[ "$allow" = "1" ] || return 0
|
||||
sync_script="$(read_plugin_setting MYSQL_PLUGIN_SUDO_SYNC_HOSTS /usr/local/directadmin/plugins/alt-mysql/scripts/setup/mysql_host_sync.sh)"
|
||||
if [ -x "$sync_script" ]; then
|
||||
"$sync_script" >> "$LOG_FILE" 2>&1 || log "WARNING: host sync failed after native staging migration"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
local db tmp_dump user host overwrite cleanup native_count
|
||||
[ -r "$NATIVE_MY_CNF" ] || die "native DirectAdmin MySQL client config is not readable: $NATIVE_MY_CNF"
|
||||
NATIVE_MYSQL_BIN="$(mysql_native_binary mysql)" || die "native mysql/mariadb client not found"
|
||||
NATIVE_MYSQLDUMP_BIN="$(mysql_native_binary mysqldump)" || die "native mysqldump/mariadb-dump client not found"
|
||||
|
||||
mysql_load_alt_credentials
|
||||
mysql_ensure_metadata_schema
|
||||
native_mysql mysql -e "SELECT 1" >/dev/null 2>&1 || die "native DirectAdmin MySQL staging is not reachable"
|
||||
mysql_exec mysql -e "SELECT 1" >/dev/null 2>&1 || die "alt-mariadb is not reachable"
|
||||
|
||||
overwrite="${OVERWRITE_POLICY:-$(read_plugin_setting DA_ALT_MYSQL_RESTORE_OVERWRITE allow)}"
|
||||
cleanup="${CLEANUP_POLICY:-$(read_plugin_setting DA_ALT_MYSQL_NATIVE_CLEANUP after_success)}"
|
||||
case "$overwrite" in allow|deny) ;; prompt) overwrite="deny" ;; *) overwrite="deny" ;; esac
|
||||
case "$cleanup" in after_success|keep) ;; *) cleanup="keep" ;; esac
|
||||
|
||||
native_count="$(list_native_databases | wc -l | tr -d '[:space:]')"
|
||||
if [ "${native_count:-0}" = "0" ]; then
|
||||
log "no native staged databases for $DA_USER"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "native staging migration start: user=$DA_USER db_count=$native_count overwrite=$overwrite cleanup=$cleanup"
|
||||
|
||||
if [ "$overwrite" = "deny" ]; then
|
||||
while IFS= read -r db; do
|
||||
[ -n "$db" ] || continue
|
||||
if [ -n "$(mysql_exec mysql -Nse "SELECT 1 FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='$(mysql_escape_literal "$db")' LIMIT 1")" ]; then
|
||||
die "target database exists and overwrite is denied: $db"
|
||||
fi
|
||||
done < <(list_native_databases)
|
||||
fi
|
||||
|
||||
while IFS=$'\t' read -r user host; do
|
||||
[ -n "${user:-}" ] || continue
|
||||
[ -n "${host:-}" ] || continue
|
||||
log "preserving native user definition: ${user}@${host}"
|
||||
apply_user_definition_to_alt "$user" "$host"
|
||||
done < <(list_native_user_hosts)
|
||||
|
||||
metadata_cleanup_for_user
|
||||
|
||||
while IFS= read -r db; do
|
||||
[ -n "$db" ] || continue
|
||||
db="$(safe_identifier "$db")"
|
||||
if [ "$overwrite" = "deny" ] && [ -n "$(mysql_exec mysql -Nse "SELECT 1 FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='$(mysql_escape_literal "$db")' LIMIT 1")" ]; then
|
||||
die "target database exists and overwrite is denied: $db"
|
||||
fi
|
||||
|
||||
tmp_dump="$(mktemp)"
|
||||
log "dumping native staged database: $db"
|
||||
"$NATIVE_MYSQLDUMP_BIN" --defaults-extra-file="$NATIVE_MY_CNF" \
|
||||
--single-transaction \
|
||||
--quick \
|
||||
--skip-lock-tables \
|
||||
--routines \
|
||||
--triggers \
|
||||
--events \
|
||||
--default-character-set=utf8mb4 \
|
||||
"$db" > "$tmp_dump" 2>>"$LOG_FILE" || {
|
||||
rm -f "$tmp_dump"
|
||||
die "cannot dump native staged database: $db"
|
||||
}
|
||||
|
||||
log "importing database into alt-mariadb: $db"
|
||||
backup_alt_database_for_rollback "$db" || {
|
||||
rm -f "$tmp_dump"
|
||||
die "cannot create rollback dump before importing native staged database: $db"
|
||||
}
|
||||
if ! mysql_exec mysql -e "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$db");" >> "$LOG_FILE" 2>&1; then
|
||||
restore_alt_database_from_rollback "$db" || true
|
||||
rm -f "$tmp_dump"
|
||||
die "cannot drop target database before importing native staged database: $db"
|
||||
fi
|
||||
if ! mysql_exec mysql -e "CREATE DATABASE $(mysql_quote_identifier "$db") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" >> "$LOG_FILE" 2>&1; then
|
||||
restore_alt_database_from_rollback "$db" || true
|
||||
rm -f "$tmp_dump"
|
||||
die "cannot create target database before importing native staged database: $db"
|
||||
fi
|
||||
mysql_exec "$db" < "$tmp_dump" >> "$LOG_FILE" 2>&1 || {
|
||||
restore_alt_database_from_rollback "$db" || true
|
||||
rm -f "$tmp_dump"
|
||||
die "cannot import native staged database into alt-mariadb: $db"
|
||||
}
|
||||
rm -f "$tmp_dump"
|
||||
cleanup_restore_rollback
|
||||
verify_alt_database "$db"
|
||||
rebuild_metadata_for_db "$db"
|
||||
done < <(list_native_databases)
|
||||
|
||||
while IFS=$'\t' read -r user host; do
|
||||
[ -n "${user:-}" ] || continue
|
||||
[ -n "${host:-}" ] || continue
|
||||
log "applying preserved grants: ${user}@${host}"
|
||||
apply_grants_to_alt "$user" "$host"
|
||||
done < <(list_native_user_hosts)
|
||||
|
||||
mysql_exec mysql -e "FLUSH PRIVILEGES;" >> "$LOG_FILE" 2>&1 || true
|
||||
sync_remote_hosts_if_enabled
|
||||
|
||||
if [ "$cleanup" = "after_success" ]; then
|
||||
drop_native_staging
|
||||
else
|
||||
log "native staging cleanup disabled by policy"
|
||||
fi
|
||||
|
||||
log "native staging migration completed: user=$DA_USER"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+249
@@ -0,0 +1,249 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SETTINGS_FILE="${ALT_MYSQL_SETTINGS_FILE:-$PLUGIN_DIR/plugin-settings.conf}"
|
||||
CPIF="${ALT_MYSQL_CUSTOM_PACKAGE_ITEMS_CONF:-/usr/local/directadmin/data/admin/custom_package_items.conf}"
|
||||
SUDOERS_FILE="/etc/sudoers.d/directadmin-alt-mysql-plugin"
|
||||
CRON_FILE="/etc/cron.d/alt-mysql-jobs"
|
||||
IS_ROOT=0
|
||||
if [ "${ALT_MYSQL_TEST_ASSUME_ROOT:-0}" = "1" ] || [ "$(id -u)" -eq 0 ]; then
|
||||
IS_ROOT=1
|
||||
fi
|
||||
|
||||
read_plugin_setting() {
|
||||
local key="$1"
|
||||
local default="$2"
|
||||
local line value
|
||||
|
||||
[ -r "$SETTINGS_FILE" ] || {
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
}
|
||||
|
||||
line="$(grep -E "^${key}=" "$SETTINGS_FILE" | tail -n 1 || true)"
|
||||
if [ -z "$line" ]; then
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
fi
|
||||
|
||||
value="${line#*=}"
|
||||
value="${value%%#*}"
|
||||
value="${value%\"}"
|
||||
value="${value#\"}"
|
||||
value="${value%\'}"
|
||||
value="${value#\'}"
|
||||
value="$(printf '%s' "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||||
printf '%s\n' "${value:-$default}"
|
||||
}
|
||||
|
||||
setting_enabled() {
|
||||
case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
|
||||
1|yes|true|on) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
always_enabled() {
|
||||
setting_enabled "$(read_plugin_setting always_enabled 1)"
|
||||
}
|
||||
|
||||
remove_custom_package_item() {
|
||||
local key="$1"
|
||||
local tmp
|
||||
|
||||
[ -f "$CPIF" ] || return 0
|
||||
tmp="$(mktemp)"
|
||||
grep -Ev "^${key}=" "$CPIF" > "$tmp" || true
|
||||
cat "$tmp" > "$CPIF"
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
set_permissions() {
|
||||
local owner="$1"
|
||||
local mode="$2"
|
||||
local path="$3"
|
||||
if [ -e "$path" ]; then
|
||||
chown "$owner" "$path" 2>/dev/null || true
|
||||
chmod "$mode" "$path" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
set_mode_if_exists() {
|
||||
local mode="$1"
|
||||
local path="$2"
|
||||
if [ -e "$path" ]; then
|
||||
chmod "$mode" "$path" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
bootstrap_custom_package_items() {
|
||||
if [ "$IS_ROOT" -ne 1 ]; then
|
||||
echo "mysql: warning: uruchom jako root, aby zaktualizować $CPIF" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
touch "$CPIF"
|
||||
|
||||
remove_custom_package_item mysql_enabled
|
||||
remove_custom_package_item mysql
|
||||
remove_custom_package_item umysql
|
||||
remove_custom_package_item mysql_max_databases
|
||||
remove_custom_package_item mysql_unlimited
|
||||
remove_custom_package_item umysql_max_databases
|
||||
|
||||
if always_enabled; then
|
||||
set_permissions diradmin:diradmin 640 "$CPIF"
|
||||
return 0
|
||||
fi
|
||||
|
||||
cat >> "$CPIF" <<'EOF'
|
||||
mysql_enabled=type=checkbox&string=Enable mysql&desc=Zezwól na dostęp do baz danych MySQL&default=no
|
||||
mysql=type=text&string=Bazy MySQL&desc=&default=5
|
||||
umysql=type=checkbox&string=Bez ograniczeń&desc=&default=no&custom=autofocus%20onfocus%3D%22%28function%28c%29%7Bif%28c.dataset.pgInit%3D%3D%3D%271%27%29%7Breturn%3B%7Dc.dataset.pgInit%3D%271%27%3Bvar%20l%3Ddocument.querySelector%28%22input%5Bname%3D%27mysql%27%5D%22%29%3Bif%28%21l%29%7Breturn%3B%7Dvar%20r%3Dc.closest%28%27tr%27%29%3Bvar%20td%3Dl.closest%28%27td%27%29%3Bif%28r%26%26td%29%7Bvar%20w%3Ddocument.getElementById%28%27da-pg-unlim-wrap%27%29%3Bif%28%21w%29%7Bw%3Ddocument.createElement%28%27label%27%29%3Bw.id%3D%27da-pg-unlim-wrap%27%3Bw.style.display%3D%27inline-flex%27%3Bw.style.alignItems%3D%27center%27%3Bw.style.gap%3D%278px%27%3Bw.style.marginLeft%3D%2712px%27%3Bw.appendChild%28c%29%3Bw.appendChild%28document.createTextNode%28%27Bez%20ogranicze%C5%84%27%29%29%3Btd.style.display%3D%27flex%27%3Btd.style.alignItems%3D%27center%27%3Btd.style.justifyContent%3D%27space-between%27%3Btd.appendChild%28w%29%3B%7Dr.style.display%3D%27none%27%3B%7Dvar%20s%3Dfunction%28%29%7Bl.disabled%3D%21%21c.checked%3B%7D%3Bc.addEventListener%28%27change%27%2Cs%29%3Bs%28%29%3B%7D%29%28this%29%22%20onchange%3D%22var%20l%3Ddocument.querySelector%28%22input%5Bname%3D%27mysql%27%5D%22%29%3Bif%28l%29%7Bl.disabled%3D%21%21this.checked%3B%7D%22
|
||||
EOF
|
||||
|
||||
set_permissions diradmin:diradmin 640 "$CPIF"
|
||||
}
|
||||
|
||||
bootstrap_sudoers() {
|
||||
if [ "$IS_ROOT" -ne 1 ]; then
|
||||
echo "mysql: warning: uruchom jako root, aby dodać sudoers dla synchronizacji hostów MySQL" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
cat > "$SUDOERS_FILE" <<SUDO
|
||||
# DirectAdmin alt-mysql plugin
|
||||
diradmin ALL=(root) NOPASSWD: /usr/local/directadmin/plugins/alt-mysql/scripts/setup/mysql_host_sync.sh
|
||||
SUDO
|
||||
|
||||
chmod 440 "$SUDOERS_FILE"
|
||||
}
|
||||
|
||||
bootstrap_worker_cron() {
|
||||
if [ "$IS_ROOT" -ne 1 ]; then
|
||||
echo "mysql: warning: uruchom jako root, aby dodać cron workera backup/restore" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
touch "$CRON_FILE"
|
||||
if ! grep -Fq '/usr/local/directadmin/plugins/alt-mysql/scripts/worker.sh' "$CRON_FILE"; then
|
||||
echo '* * * * * root /usr/local/directadmin/plugins/alt-mysql/scripts/worker.sh >/dev/null 2>&1' >> "$CRON_FILE"
|
||||
fi
|
||||
chmod 644 "$CRON_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
bootstrap_phpmyadmin() {
|
||||
local phpmyadmin_setup="$PLUGIN_DIR/scripts/setup/phpmyadmin_private_install.sh"
|
||||
|
||||
if [ "$IS_ROOT" -ne 1 ]; then
|
||||
echo "mysql: warning: uruchom jako root, aby przygotować integrację phpMyAdmin." >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ ! -f "$phpmyadmin_setup" ]; then
|
||||
echo "mysql: error: brak skryptu integracji phpMyAdmin: $phpmyadmin_setup" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
bash "$phpmyadmin_setup"
|
||||
}
|
||||
|
||||
bootstrap_da_integration() {
|
||||
local da_setup="$PLUGIN_DIR/scripts/setup/da_integration_install.sh"
|
||||
|
||||
if [ "$IS_ROOT" -ne 1 ]; then
|
||||
echo "mysql: warning: uruchom jako root, aby zainstalować hooki integracji DirectAdmin backup/restore." >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ ! -f "$da_setup" ]; then
|
||||
echo "mysql: error: brak skryptu integracji DirectAdmin: $da_setup" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
bash "$da_setup" install
|
||||
}
|
||||
|
||||
bootstrap_custombuild_hooks() {
|
||||
local custombuild_setup="$PLUGIN_DIR/scripts/setup/custombuild_hooks_install.sh"
|
||||
|
||||
if [ "$IS_ROOT" -ne 1 ]; then
|
||||
echo "mysql: warning: uruchom jako root, aby zainstalować hooki CustomBuild po aktualizacjach Roundcube/phpMyAdmin." >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ ! -f "$custombuild_setup" ]; then
|
||||
echo "mysql: error: brak skryptu hooków CustomBuild: $custombuild_setup" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
bash "$custombuild_setup" install
|
||||
}
|
||||
|
||||
activate_plugin() {
|
||||
local conf="$PLUGIN_DIR/plugin.conf"
|
||||
if [ -f "$conf" ]; then
|
||||
sed -i 's/^active=.*/active=yes/' "$conf" || true
|
||||
sed -i 's/^installed=.*/installed=yes/' "$conf" || true
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "${ALT_MYSQL_TEST_ONLY_CUSTOM_PACKAGE_ITEMS:-0}" = "1" ]; then
|
||||
bootstrap_custom_package_items
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$PLUGIN_DIR/data"
|
||||
mkdir -p "$PLUGIN_DIR/exec" "$PLUGIN_DIR/exec/lib" "$PLUGIN_DIR/exec/handlers"
|
||||
mkdir -p "$PLUGIN_DIR/data/jobs/pending" "$PLUGIN_DIR/data/jobs/processing" "$PLUGIN_DIR/data/jobs/done"
|
||||
mkdir -p "$PLUGIN_DIR/data/logs" "$PLUGIN_DIR/data/lock" "$PLUGIN_DIR/data/pids" "$PLUGIN_DIR/data/cancel"
|
||||
mkdir -p "$PLUGIN_DIR/data/uploads"
|
||||
|
||||
if [ "$IS_ROOT" -eq 1 ]; then
|
||||
chown -R diradmin:diradmin "$PLUGIN_DIR" 2>/dev/null || true
|
||||
else
|
||||
echo "mysql: warning: uruchom jako root, aby ustawić właściciela plików pluginu (w przeciwnym razie może wystąpić biała strona)." >&2
|
||||
fi
|
||||
|
||||
find "$PLUGIN_DIR" -type d -exec chmod 755 {} + 2>/dev/null || true
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/jobs"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/jobs/pending"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/jobs/processing"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/jobs/done"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/logs"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/lock"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/pids"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/cancel"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/uploads"
|
||||
find "$PLUGIN_DIR" -type f -name '*.html' -exec chmod 755 {} + 2>/dev/null || true
|
||||
find "$PLUGIN_DIR" -type f -name '*.raw' -exec chmod 755 {} + 2>/dev/null || true
|
||||
find "$PLUGIN_DIR" -type f -name '*.css' -exec chmod 644 {} + 2>/dev/null || true
|
||||
find "$PLUGIN_DIR/scripts" -type f -name '*.sh' -exec chmod 755 {} + 2>/dev/null || true
|
||||
find "$PLUGIN_DIR" -type f -name '*.php' -exec chmod 640 {} + 2>/dev/null || true
|
||||
set_mode_if_exists 644 "$PLUGIN_DIR/plugin.conf"
|
||||
set_mode_if_exists 644 "$PLUGIN_DIR/plugin-settings.conf"
|
||||
set_mode_if_exists 644 "$PLUGIN_DIR/php.ini"
|
||||
|
||||
if [ ! -f "$PLUGIN_DIR/data/secret.key" ]; then
|
||||
tr -dc 'a-f0-9' </dev/urandom | head -c 64 > "$PLUGIN_DIR/data/secret.key" || true
|
||||
fi
|
||||
set_mode_if_exists 600 "$PLUGIN_DIR/data/secret.key"
|
||||
|
||||
touch "$PLUGIN_DIR/error.log" 2>/dev/null || true
|
||||
if [ "$IS_ROOT" -eq 1 ]; then
|
||||
chown diradmin:diradmin "$PLUGIN_DIR/error.log" 2>/dev/null || true
|
||||
fi
|
||||
set_mode_if_exists 640 "$PLUGIN_DIR/error.log"
|
||||
|
||||
bootstrap_custom_package_items
|
||||
bootstrap_sudoers
|
||||
bootstrap_worker_cron
|
||||
bootstrap_phpmyadmin
|
||||
bootstrap_da_integration
|
||||
bootstrap_custombuild_hooks
|
||||
activate_plugin
|
||||
|
||||
echo "mysql: instalacja zakończona."
|
||||
echo "Przypomnienie: zapisz pakiety i user.conf w DirectAdmin, aby odświeżyć custom package items."
|
||||
Executable
+872
@@ -0,0 +1,872 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ==============================================================================
|
||||
# Konfiguracja instalacji alternatywnej instancji MariaDB dla serwera DirectAdmin
|
||||
# ==============================================================================
|
||||
|
||||
MARIADB_VERSION="10.11"
|
||||
MARIADB_DIR="/usr/local/mariadb-$MARIADB_VERSION"
|
||||
DATADIR="/var/lib/mariadb_data"
|
||||
PORT="33033"
|
||||
SOCKET="/var/lib/mariadb_data/mariadb.sock"
|
||||
|
||||
MYSQL_USER_SOURCE_FILE="/usr/local/directadmin/conf/mysql.conf"
|
||||
MYSQL_CLIENT_SOURCE_FILE="/usr/local/directadmin/conf/my.cnf"
|
||||
ALT_MYSQL_CONF="/usr/local/directadmin/conf/alt-mysql.conf"
|
||||
ALT_MY_CNF="/usr/local/directadmin/conf/alt-my.cnf"
|
||||
ALT_METADATA_ENV="/usr/local/directadmin/conf/alt-mariadb.env"
|
||||
INSTANCE_CNF="/etc/alt-mariadb.cnf"
|
||||
SERVICE_NAME="alt-mariadb"
|
||||
SERVICE_FILE="/etc/systemd/system/alt-mariadb.service"
|
||||
LOG_DIR="/var/log/alt-mariadb"
|
||||
RUN_DIR="/run/alt-mariadb"
|
||||
SYSTEM_USER="mysql"
|
||||
SYSTEM_GROUP="mysql"
|
||||
BIND_ADDRESS="127.0.0.1"
|
||||
|
||||
FORCE_INSTALL="no"
|
||||
FORCE_REINIT="no"
|
||||
FORCE_SERVICE_REWRITE="yes"
|
||||
|
||||
# Jeżeli automatyczne wykrywanie adresu tarballa zawiedzie, ustaw pełny oficjalny URL ręcznie.
|
||||
MARIADB_TARBALL_URL=""
|
||||
|
||||
# Katalog roboczy pobierania. Skrypt nie używa pakietów RPM/DNF MariaDB ani MySQL.
|
||||
DOWNLOAD_DIR="/usr/local/src"
|
||||
|
||||
ALT_ADMIN_USER=""
|
||||
ALT_ADMIN_PASS=""
|
||||
DA_PORT=""
|
||||
DA_SOCKET=""
|
||||
DA_HOST=""
|
||||
DA_DATADIR=""
|
||||
TARBALL_PATH=""
|
||||
TMP_DIR=""
|
||||
RESOLVED_MARIADB_RELEASE=""
|
||||
DOWNLOAD_URL=""
|
||||
|
||||
log() {
|
||||
printf '[INFO] %s\n' "$*"
|
||||
}
|
||||
|
||||
warn() {
|
||||
printf '[UWAGA] %s\n' "$*" >&2
|
||||
}
|
||||
|
||||
die() {
|
||||
printf '[BŁĄD] %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "${TMP_DIR:-}" && -d "$TMP_DIR" ]]; then
|
||||
rm -rf "$TMP_DIR"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Użycie:
|
||||
$0 <MARIADB_VERSION> <PORT>
|
||||
|
||||
Przykłady:
|
||||
$0 10.11 33033
|
||||
$0 11.4 3020
|
||||
$0 11.8 3021
|
||||
|
||||
Dozwolone gałęzie MariaDB: 10.11, 11.4, 11.8.
|
||||
EOF
|
||||
}
|
||||
|
||||
usage_error() {
|
||||
usage >&2
|
||||
printf '[BŁĄD] %s\n' "$*" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
apply_cli_args() {
|
||||
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ $# -gt 2 ]]; then
|
||||
usage_error "Podano zbyt wiele argumentów. Oczekiwano: MARIADB_VERSION PORT."
|
||||
fi
|
||||
|
||||
if [[ $# -lt 2 ]]; then
|
||||
usage_error "Podaj wersję MariaDB i port TCP, np. 11.4 3020."
|
||||
fi
|
||||
|
||||
if [[ $# -ge 1 && -n "${1:-}" ]]; then
|
||||
MARIADB_VERSION="$1"
|
||||
fi
|
||||
|
||||
if [[ $# -ge 2 && -n "${2:-}" ]]; then
|
||||
PORT="$2"
|
||||
fi
|
||||
|
||||
MARIADB_DIR="/usr/local/mariadb-$MARIADB_VERSION"
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
die "Ten skrypt musi zostać uruchomiony jako root."
|
||||
fi
|
||||
}
|
||||
|
||||
detect_os() {
|
||||
[[ -r /etc/os-release ]] || die "Nie można odczytać /etc/os-release."
|
||||
. /etc/os-release
|
||||
|
||||
local os_id="${ID:-}"
|
||||
local os_like="${ID_LIKE:-}"
|
||||
|
||||
case "$os_id" in
|
||||
almalinux|rhel|rocky|centos|ol)
|
||||
log "Wykryto zgodny system: ${PRETTY_NAME:-$os_id}."
|
||||
;;
|
||||
*)
|
||||
if [[ "$os_like" == *"rhel"* || "$os_like" == *"fedora"* ]]; then
|
||||
warn "System wygląda na zgodny z RHEL: ${PRETTY_NAME:-$os_id}."
|
||||
else
|
||||
die "Ten skrypt jest przeznaczony dla AlmaLinux lub systemów zgodnych z RHEL."
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
read_kv_file_value() {
|
||||
local file="$1"
|
||||
local key="$2"
|
||||
|
||||
awk -F= -v key="$key" '
|
||||
$1 == key {
|
||||
sub(/^[^=]*=/, "")
|
||||
print
|
||||
exit
|
||||
}
|
||||
' "$file"
|
||||
}
|
||||
|
||||
load_da_credentials() {
|
||||
[[ -d /usr/local/directadmin ]] || die "Nie znaleziono katalogu /usr/local/directadmin."
|
||||
[[ -f "$MYSQL_USER_SOURCE_FILE" ]] || die "Nie znaleziono pliku $MYSQL_USER_SOURCE_FILE."
|
||||
[[ -f "$MYSQL_CLIENT_SOURCE_FILE" ]] || die "Nie znaleziono pliku $MYSQL_CLIENT_SOURCE_FILE."
|
||||
|
||||
ALT_ADMIN_USER="$(read_kv_file_value "$MYSQL_USER_SOURCE_FILE" "user")"
|
||||
ALT_ADMIN_PASS="$(read_kv_file_value "$MYSQL_USER_SOURCE_FILE" "passwd")"
|
||||
DA_PORT="$(read_kv_file_value "$MYSQL_USER_SOURCE_FILE" "port")"
|
||||
DA_SOCKET="$(read_kv_file_value "$MYSQL_USER_SOURCE_FILE" "socket")"
|
||||
DA_HOST="$(read_kv_file_value "$MYSQL_USER_SOURCE_FILE" "host")"
|
||||
|
||||
[[ -n "$ALT_ADMIN_USER" ]] || die "Nie udało się odczytać pola user z $MYSQL_USER_SOURCE_FILE."
|
||||
[[ -n "$ALT_ADMIN_PASS" ]] || die "Nie udało się odczytać pola passwd z $MYSQL_USER_SOURCE_FILE."
|
||||
|
||||
DA_DATADIR="$(detect_da_datadir || true)"
|
||||
log "Odczytano dane administratora DirectAdmin bez wyświetlania hasła."
|
||||
}
|
||||
|
||||
detect_da_datadir() {
|
||||
local candidate_files=(
|
||||
"/etc/my.cnf"
|
||||
"/etc/mysql/my.cnf"
|
||||
)
|
||||
local file=""
|
||||
|
||||
for file in "${candidate_files[@]}"; do
|
||||
[[ -r "$file" ]] || continue
|
||||
awk -F= '
|
||||
/^[[:space:]]*datadir[[:space:]]*=/ {
|
||||
value=$2
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
|
||||
gsub(/^"|"$/, "", value)
|
||||
print value
|
||||
exit
|
||||
}
|
||||
' "$file"
|
||||
return 0
|
||||
done
|
||||
|
||||
if [[ -d /var/lib/mysql ]]; then
|
||||
printf '/var/lib/mysql\n'
|
||||
fi
|
||||
}
|
||||
|
||||
validate_version() {
|
||||
case "$MARIADB_VERSION" in
|
||||
10.11|11.4|11.8)
|
||||
log "Wybrano obsługiwaną wersję MariaDB: $MARIADB_VERSION."
|
||||
;;
|
||||
*)
|
||||
die "Nieobsługiwana wersja MariaDB: $MARIADB_VERSION. Dozwolone wartości: 10.11, 11.4, 11.8."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
validate_port() {
|
||||
local numeric_port=""
|
||||
|
||||
[[ "$PORT" =~ ^[0-9]+$ ]] || die "Nieprawidłowy port MariaDB: $PORT. Podaj liczbę od 1 do 65535."
|
||||
numeric_port=$((10#$PORT))
|
||||
(( numeric_port >= 1 && numeric_port <= 65535 )) || die "Nieprawidłowy port MariaDB: $PORT. Podaj liczbę od 1 do 65535."
|
||||
|
||||
log "Wybrano port TCP alternatywnej instancji MariaDB: $PORT."
|
||||
}
|
||||
|
||||
canonical_path() {
|
||||
local path="$1"
|
||||
if command -v realpath >/dev/null 2>&1; then
|
||||
realpath -m "$path"
|
||||
else
|
||||
printf '%s\n' "$path"
|
||||
fi
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
warn "Nie znaleziono narzędzia ss ani lsof, więc nie można pewnie sprawdzić zajętości portu."
|
||||
return 1
|
||||
}
|
||||
|
||||
validate_paths() {
|
||||
local alt_socket_canon da_socket_canon alt_datadir_canon da_datadir_canon
|
||||
|
||||
alt_socket_canon="$(canonical_path "$SOCKET")"
|
||||
da_socket_canon="$(canonical_path "${DA_SOCKET:-}")"
|
||||
alt_datadir_canon="$(canonical_path "$DATADIR")"
|
||||
da_datadir_canon="$(canonical_path "${DA_DATADIR:-}")"
|
||||
|
||||
[[ "$alt_socket_canon" != "$da_socket_canon" ]] || die "Alternatywny socket jest taki sam jak socket DirectAdmin: $SOCKET."
|
||||
[[ "$alt_datadir_canon" != "$(canonical_path /var/lib/mysql)" ]] || die "Alternatywny katalog danych nie może wskazywać na /var/lib/mysql."
|
||||
|
||||
if [[ -n "${DA_DATADIR:-}" ]]; then
|
||||
[[ "$alt_datadir_canon" != "$da_datadir_canon" ]] || die "Alternatywny katalog danych jest taki sam jak wykryty katalog danych DirectAdmin: $DATADIR."
|
||||
fi
|
||||
|
||||
if [[ -n "${DA_PORT:-}" ]]; then
|
||||
[[ "$PORT" != "$DA_PORT" ]] || die "Alternatywny port $PORT jest taki sam jak port MariaDB DirectAdmin."
|
||||
fi
|
||||
|
||||
if port_is_listening "$PORT"; then
|
||||
die "Port $PORT jest już zajęty. Zmień PORT w sekcji konfiguracji."
|
||||
fi
|
||||
|
||||
if [[ -S "$SOCKET" ]] && ! systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
|
||||
die "Socket $SOCKET już istnieje, ale usługa $SERVICE_NAME nie jest aktywna. Przerwano dla bezpieczeństwa."
|
||||
fi
|
||||
|
||||
[[ "$SOCKET" == "$DATADIR"* || "$(dirname "$SOCKET")" != "/var/lib/mysql" ]] || die "Socket nie może znajdować się w katalogu DirectAdmin MariaDB."
|
||||
|
||||
log "Sprawdzono rozdzielenie portu, socketu i katalogu danych od instancji DirectAdmin."
|
||||
}
|
||||
|
||||
install_dependencies() {
|
||||
local deps=(curl tar gzip xz libaio ncurses-compat-libs)
|
||||
local dep=""
|
||||
local safe_deps=()
|
||||
|
||||
command -v dnf >/dev/null 2>&1 || die "Nie znaleziono dnf. Ten skrypt instaluje neutralne zależności wyłącznie przez dnf."
|
||||
|
||||
for dep in "${deps[@]}"; do
|
||||
if [[ "$dep" == *mariadb* || "$dep" == *mysql* || "$dep" == *MariaDB* || "$dep" == *MySQL* ]]; then
|
||||
die "Wewnętrzny błąd bezpieczeństwa: próba instalacji niedozwolonego pakietu $dep."
|
||||
fi
|
||||
safe_deps+=("$dep")
|
||||
done
|
||||
|
||||
log "Instaluję neutralne zależności systemowe bez pakietów MariaDB/MySQL."
|
||||
dnf install -y "${safe_deps[@]}"
|
||||
}
|
||||
|
||||
detect_architecture() {
|
||||
local machine
|
||||
machine="$(uname -m)"
|
||||
|
||||
case "$machine" in
|
||||
x86_64|amd64)
|
||||
printf 'x86_64\n'
|
||||
;;
|
||||
*)
|
||||
die "Nieobsługiwana architektura: $machine. Skrypt obsługuje automatycznie tylko x86_64/amd64."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
resolve_latest_release_from_archive() {
|
||||
local series="$1"
|
||||
local listing=""
|
||||
local release=""
|
||||
|
||||
listing="$(curl -fsSL "https://archive.mariadb.org/" || true)"
|
||||
release="$(printf '%s\n' "$listing" \
|
||||
| grep -Eo "mariadb-${series//./\\.}\.[0-9]+/" \
|
||||
| sed 's#^mariadb-##; s#/$##' \
|
||||
| sort -V \
|
||||
| tail -n 1)"
|
||||
|
||||
[[ -n "$release" ]] || return 1
|
||||
printf '%s\n' "$release"
|
||||
}
|
||||
|
||||
url_exists() {
|
||||
local url="$1"
|
||||
curl -fsIL --retry 2 --connect-timeout 15 "$url" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
resolve_tarball_from_directory() {
|
||||
local release="$1"
|
||||
local arch="$2"
|
||||
local dir_url="https://archive.mariadb.org/mariadb-${release}/bintar-linux-systemd-${arch}/"
|
||||
local listing=""
|
||||
local file=""
|
||||
|
||||
listing="$(curl -fsSL "$dir_url" || true)"
|
||||
file="$(printf '%s\n' "$listing" \
|
||||
| grep -Eo "mariadb-${release}-linux-systemd-${arch}\.tar\.(gz|xz)" \
|
||||
| head -n 1)"
|
||||
|
||||
[[ -n "$file" ]] || return 1
|
||||
printf '%s%s\n' "$dir_url" "$file"
|
||||
}
|
||||
|
||||
resolve_download_url() {
|
||||
local arch=""
|
||||
local candidate=""
|
||||
local candidates=()
|
||||
|
||||
arch="$(detect_architecture)"
|
||||
|
||||
if [[ -n "$MARIADB_TARBALL_URL" ]]; then
|
||||
DOWNLOAD_URL="$MARIADB_TARBALL_URL"
|
||||
log "Używam ręcznie ustawionego adresu tarballa MariaDB."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Wyszukuję oficjalny tarball MariaDB w archiwum MariaDB dla gałęzi $MARIADB_VERSION."
|
||||
warn "Nazwy tarballi mogą zależeć od schematu mirrorów MariaDB; w razie błędu ustaw MARIADB_TARBALL_URL ręcznie."
|
||||
|
||||
RESOLVED_MARIADB_RELEASE="$(resolve_latest_release_from_archive "$MARIADB_VERSION" || true)"
|
||||
[[ -n "$RESOLVED_MARIADB_RELEASE" ]] || die "Nie udało się wykryć najnowszego wydania dla gałęzi $MARIADB_VERSION."
|
||||
|
||||
if candidate="$(resolve_tarball_from_directory "$RESOLVED_MARIADB_RELEASE" "$arch" || true)"; then
|
||||
DOWNLOAD_URL="$candidate"
|
||||
log "Wykryto tarball MariaDB $RESOLVED_MARIADB_RELEASE."
|
||||
return 0
|
||||
fi
|
||||
|
||||
candidates=(
|
||||
"https://archive.mariadb.org/mariadb-${RESOLVED_MARIADB_RELEASE}/bintar-linux-systemd-${arch}/mariadb-${RESOLVED_MARIADB_RELEASE}-linux-systemd-${arch}.tar.gz"
|
||||
"https://archive.mariadb.org/mariadb-${RESOLVED_MARIADB_RELEASE}/bintar-linux-systemd-${arch}/mariadb-${RESOLVED_MARIADB_RELEASE}-linux-systemd-${arch}.tar.xz"
|
||||
"https://downloads.mariadb.org/f/mariadb-${RESOLVED_MARIADB_RELEASE}/bintar-linux-systemd-${arch}/mariadb-${RESOLVED_MARIADB_RELEASE}-linux-systemd-${arch}.tar.gz/from/https%3A//archive.mariadb.org/"
|
||||
)
|
||||
|
||||
for candidate in "${candidates[@]}"; do
|
||||
if url_exists "$candidate"; then
|
||||
DOWNLOAD_URL="$candidate"
|
||||
log "Wykryto działający adres tarballa MariaDB."
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
die "Nie udało się automatycznie ustalić URL tarballa. Ustaw MARIADB_TARBALL_URL w sekcji konfiguracji."
|
||||
}
|
||||
|
||||
download_tarball() {
|
||||
local filename=""
|
||||
|
||||
[[ -n "$DOWNLOAD_URL" ]] || die "Brak adresu pobierania tarballa."
|
||||
install -d -m 0755 "$DOWNLOAD_DIR"
|
||||
|
||||
filename="$(basename "${DOWNLOAD_URL%%\?*}")"
|
||||
[[ "$filename" == *.tar.gz || "$filename" == *.tar.xz || "$filename" == *.tgz ]] || filename="mariadb-${RESOLVED_MARIADB_RELEASE:-$MARIADB_VERSION}-linux-systemd.tar.gz"
|
||||
TARBALL_PATH="$DOWNLOAD_DIR/$filename"
|
||||
|
||||
log "Pobieram oficjalny tarball MariaDB do $TARBALL_PATH."
|
||||
curl -fL --retry 3 --connect-timeout 20 -o "$TARBALL_PATH" "$DOWNLOAD_URL"
|
||||
[[ -s "$TARBALL_PATH" ]] || die "Pobrany tarball jest pusty albo nie istnieje: $TARBALL_PATH."
|
||||
}
|
||||
|
||||
extract_tarball() {
|
||||
local parent_dir=""
|
||||
local extracted_dir=""
|
||||
local backup_dir=""
|
||||
|
||||
[[ -s "$TARBALL_PATH" ]] || die "Nie znaleziono tarballa do rozpakowania."
|
||||
|
||||
if [[ -d "$MARIADB_DIR" ]] && [[ -n "$(find "$MARIADB_DIR" -mindepth 1 -maxdepth 1 -print -quit)" ]]; then
|
||||
if [[ -x "$MARIADB_DIR/bin/mariadbd" && "$FORCE_INSTALL" != "yes" ]]; then
|
||||
log "Katalog $MARIADB_DIR już zawiera binaria MariaDB, używam istniejącej instalacji programu."
|
||||
return 0
|
||||
fi
|
||||
if [[ "$FORCE_INSTALL" != "yes" ]]; then
|
||||
die "Katalog $MARIADB_DIR już istnieje i nie jest pusty. Ustaw FORCE_INSTALL=\"yes\", aby pozwolić na bezpieczne zastąpienie."
|
||||
fi
|
||||
backup_dir="${MARIADB_DIR}.backup.$(date +%Y%m%d%H%M%S)"
|
||||
log "Tworzę kopię istniejącego katalogu programu: $backup_dir."
|
||||
mv "$MARIADB_DIR" "$backup_dir"
|
||||
fi
|
||||
|
||||
parent_dir="$(dirname "$MARIADB_DIR")"
|
||||
install -d -m 0755 "$parent_dir"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
|
||||
log "Rozpakowuję tarball MariaDB do katalogu tymczasowego."
|
||||
tar -xf "$TARBALL_PATH" -C "$TMP_DIR"
|
||||
|
||||
extracted_dir="$(find "$TMP_DIR" -mindepth 1 -maxdepth 1 -type d -print -quit)"
|
||||
[[ -n "$extracted_dir" ]] || die "Tarball nie zawiera oczekiwanego katalogu głównego MariaDB."
|
||||
|
||||
rm -rf "$MARIADB_DIR"
|
||||
mv "$extracted_dir" "$MARIADB_DIR"
|
||||
chown -R root:root "$MARIADB_DIR"
|
||||
log "MariaDB została rozpakowana do $MARIADB_DIR."
|
||||
}
|
||||
|
||||
prepare_user_and_dirs() {
|
||||
local socket_dir=""
|
||||
|
||||
if getent passwd "$SYSTEM_USER" >/dev/null 2>&1 && getent group "$SYSTEM_GROUP" >/dev/null 2>&1; then
|
||||
log "Używam istniejącego użytkownika systemowego $SYSTEM_USER:$SYSTEM_GROUP."
|
||||
elif getent passwd mysql >/dev/null 2>&1 && getent group mysql >/dev/null 2>&1; then
|
||||
SYSTEM_USER="mysql"
|
||||
SYSTEM_GROUP="mysql"
|
||||
log "Używam istniejącego użytkownika systemowego mysql:mysql."
|
||||
else
|
||||
SYSTEM_USER="altmysql"
|
||||
SYSTEM_GROUP="altmysql"
|
||||
if ! getent group "$SYSTEM_GROUP" >/dev/null 2>&1; then
|
||||
groupadd --system "$SYSTEM_GROUP"
|
||||
fi
|
||||
if ! getent passwd "$SYSTEM_USER" >/dev/null 2>&1; then
|
||||
useradd --system --gid "$SYSTEM_GROUP" --home-dir "$DATADIR" --shell /sbin/nologin "$SYSTEM_USER"
|
||||
fi
|
||||
log "Utworzono dedykowanego użytkownika systemowego $SYSTEM_USER:$SYSTEM_GROUP."
|
||||
fi
|
||||
|
||||
socket_dir="$(dirname "$SOCKET")"
|
||||
install -d -m 0750 -o "$SYSTEM_USER" -g "$SYSTEM_GROUP" "$DATADIR"
|
||||
install -d -m 0750 -o "$SYSTEM_USER" -g "$SYSTEM_GROUP" "$LOG_DIR"
|
||||
install -d -m 0750 -o "$SYSTEM_USER" -g "$SYSTEM_GROUP" "$socket_dir"
|
||||
install -d -m 0750 -o "$SYSTEM_USER" -g "$SYSTEM_GROUP" "$RUN_DIR"
|
||||
chown -R "$SYSTEM_USER:$SYSTEM_GROUP" "$DATADIR" "$LOG_DIR" "$socket_dir"
|
||||
}
|
||||
|
||||
backup_existing_file() {
|
||||
local file="$1"
|
||||
local backup=""
|
||||
|
||||
if [[ -e "$file" ]]; then
|
||||
backup="${file}.backup.$(date +%Y%m%d%H%M%S)"
|
||||
cp -a "$file" "$backup"
|
||||
log "Utworzono kopię pliku $file jako $backup."
|
||||
fi
|
||||
}
|
||||
|
||||
write_file_atomic() {
|
||||
local target="$1"
|
||||
local mode="$2"
|
||||
local owner="$3"
|
||||
local group="$4"
|
||||
local tmp_file=""
|
||||
|
||||
tmp_file="$(mktemp)"
|
||||
cat > "$tmp_file"
|
||||
install -m "$mode" -o "$owner" -g "$group" "$tmp_file" "$target"
|
||||
rm -f "$tmp_file"
|
||||
}
|
||||
|
||||
cnf_double_quote() {
|
||||
local value="$1"
|
||||
value="${value//\\/\\\\}"
|
||||
value="${value//\"/\\\"}"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
write_instance_config() {
|
||||
backup_existing_file "$INSTANCE_CNF"
|
||||
|
||||
write_file_atomic "$INSTANCE_CNF" 0640 root "$SYSTEM_GROUP" <<EOF
|
||||
[client]
|
||||
port=$PORT
|
||||
socket=$SOCKET
|
||||
|
||||
[mariadb]
|
||||
user=$SYSTEM_USER
|
||||
basedir=$MARIADB_DIR
|
||||
datadir=$DATADIR
|
||||
port=$PORT
|
||||
socket=$SOCKET
|
||||
pid-file=$RUN_DIR/alt-mariadb.pid
|
||||
bind-address=$BIND_ADDRESS
|
||||
skip-name-resolve
|
||||
log-error=$LOG_DIR/alt-mariadb.log
|
||||
plugin-dir=$MARIADB_DIR/lib/plugin
|
||||
tmpdir=/tmp
|
||||
character-set-server=utf8mb4
|
||||
collation-server=utf8mb4_unicode_ci
|
||||
max_connections=100
|
||||
innodb_buffer_pool_size=512M
|
||||
innodb_log_file_size=256M
|
||||
local-infile=0
|
||||
symbolic-links=0
|
||||
EOF
|
||||
|
||||
log "Zapisano konfigurację alternatywnej instancji w $INSTANCE_CNF z prawem odczytu dla grupy $SYSTEM_GROUP."
|
||||
}
|
||||
|
||||
credential_group() {
|
||||
if getent group diradmin >/dev/null 2>&1; then
|
||||
printf 'diradmin\n'
|
||||
else
|
||||
printf 'root\n'
|
||||
fi
|
||||
}
|
||||
|
||||
write_da_style_credential_files() {
|
||||
local group_name=""
|
||||
local cnf_user=""
|
||||
local cnf_pass=""
|
||||
local cnf_socket=""
|
||||
local credential_mode="0600"
|
||||
|
||||
group_name="$(credential_group)"
|
||||
if [[ "$group_name" == "diradmin" ]]; then
|
||||
credential_mode="0640"
|
||||
fi
|
||||
|
||||
cnf_user="$(cnf_double_quote "$ALT_ADMIN_USER")"
|
||||
cnf_pass="$(cnf_double_quote "$ALT_ADMIN_PASS")"
|
||||
cnf_socket="$(cnf_double_quote "$SOCKET")"
|
||||
|
||||
backup_existing_file "$ALT_MYSQL_CONF"
|
||||
backup_existing_file "$ALT_MY_CNF"
|
||||
|
||||
umask 077
|
||||
write_file_atomic "$ALT_MYSQL_CONF" "$credential_mode" root "$group_name" <<EOF
|
||||
host=
|
||||
passwd=$ALT_ADMIN_PASS
|
||||
port=$PORT
|
||||
socket=$SOCKET
|
||||
user=$ALT_ADMIN_USER
|
||||
EOF
|
||||
|
||||
write_file_atomic "$ALT_MY_CNF" "$credential_mode" root "$group_name" <<EOF
|
||||
[client]
|
||||
user="$cnf_user"
|
||||
password="$cnf_pass"
|
||||
protocol="socket"
|
||||
socket="$cnf_socket"
|
||||
EOF
|
||||
|
||||
log "Zapisano pliki poświadczeń DirectAdmin dla alternatywnej instancji bez wyświetlania hasła."
|
||||
}
|
||||
|
||||
sh_single_quote() {
|
||||
local value="$1"
|
||||
value="${value//\'/\'\\\'\'}"
|
||||
printf "'%s'" "$value"
|
||||
}
|
||||
|
||||
write_alt_metadata_env() {
|
||||
local group_name=""
|
||||
local metadata_mode="0600"
|
||||
|
||||
group_name="$(credential_group)"
|
||||
if [[ "$group_name" == "diradmin" ]]; then
|
||||
metadata_mode="0640"
|
||||
fi
|
||||
|
||||
backup_existing_file "$ALT_METADATA_ENV"
|
||||
|
||||
write_file_atomic "$ALT_METADATA_ENV" "$metadata_mode" root "$group_name" <<EOF
|
||||
# Metadata alternatywnej instancji MariaDB zarządzanej przez install_db.sh.
|
||||
# Ten plik jest kontraktem dla pluginu DirectAdmin i nie jest natywną konfiguracją MySQL DirectAdmina.
|
||||
SERVICE_NAME=$(sh_single_quote "$SERVICE_NAME")
|
||||
MARIADB_VERSION=$(sh_single_quote "$MARIADB_VERSION")
|
||||
MARIADB_RELEASE=$(sh_single_quote "${RESOLVED_MARIADB_RELEASE:-$MARIADB_VERSION}")
|
||||
MARIADB_DIR=$(sh_single_quote "$MARIADB_DIR")
|
||||
DATADIR=$(sh_single_quote "$DATADIR")
|
||||
PORT=$(sh_single_quote "$PORT")
|
||||
SOCKET=$(sh_single_quote "$SOCKET")
|
||||
ALT_MYSQL_CONF=$(sh_single_quote "$ALT_MYSQL_CONF")
|
||||
ALT_MY_CNF=$(sh_single_quote "$ALT_MY_CNF")
|
||||
INSTANCE_CNF=$(sh_single_quote "$INSTANCE_CNF")
|
||||
SERVICE_FILE=$(sh_single_quote "$SERVICE_FILE")
|
||||
LOG_DIR=$(sh_single_quote "$LOG_DIR")
|
||||
RUN_DIR=$(sh_single_quote "$RUN_DIR")
|
||||
SYSTEM_USER=$(sh_single_quote "$SYSTEM_USER")
|
||||
SYSTEM_GROUP=$(sh_single_quote "$SYSTEM_GROUP")
|
||||
BIND_ADDRESS=$(sh_single_quote "$BIND_ADDRESS")
|
||||
EOF
|
||||
|
||||
log "Zapisano metadane alternatywnej instancji w $ALT_METADATA_ENV."
|
||||
}
|
||||
|
||||
datadir_is_initialized() {
|
||||
[[ -d "$DATADIR/mysql" ]] && [[ -f "$DATADIR/mysql/user.frm" || -f "$DATADIR/mysql/global_priv.frm" || -f "$DATADIR/mysql/global_priv.ibd" ]]
|
||||
}
|
||||
|
||||
initialize_datadir() {
|
||||
local install_db=""
|
||||
|
||||
if datadir_is_initialized; then
|
||||
if [[ "$FORCE_REINIT" != "yes" ]]; then
|
||||
log "Katalog danych wygląda na zainicjalizowany, pomijam inicjalizację."
|
||||
return 0
|
||||
fi
|
||||
die "FORCE_REINIT=\"yes\" wymaga ręcznego opróżnienia lub zabezpieczenia katalogu $DATADIR. Skrypt nie usuwa danych automatycznie."
|
||||
fi
|
||||
|
||||
if [[ -n "$(find "$DATADIR" -mindepth 1 -maxdepth 1 -print -quit)" ]] && [[ "$FORCE_REINIT" != "yes" ]]; then
|
||||
die "Katalog $DATADIR zawiera pliki, ale nie wygląda na zainicjalizowany katalog MariaDB. Przerwano dla bezpieczeństwa."
|
||||
fi
|
||||
|
||||
if [[ -x "$MARIADB_DIR/scripts/mariadb-install-db" ]]; then
|
||||
install_db="$MARIADB_DIR/scripts/mariadb-install-db"
|
||||
elif [[ -x "$MARIADB_DIR/bin/mariadb-install-db" ]]; then
|
||||
install_db="$MARIADB_DIR/bin/mariadb-install-db"
|
||||
else
|
||||
die "Nie znaleziono mariadb-install-db w rozpakowanym katalogu MariaDB."
|
||||
fi
|
||||
|
||||
log "Inicjalizuję katalog danych alternatywnej instancji MariaDB."
|
||||
if ! "$install_db" \
|
||||
--defaults-file="$INSTANCE_CNF" \
|
||||
--basedir="$MARIADB_DIR" \
|
||||
--datadir="$DATADIR" \
|
||||
--user="$SYSTEM_USER" \
|
||||
--auth-root-authentication-method=normal; then
|
||||
warn "Inicjalizacja z auth-root-authentication-method=normal nie powiodła się, próbuję wariantu zgodności."
|
||||
"$install_db" \
|
||||
--defaults-file="$INSTANCE_CNF" \
|
||||
--basedir="$MARIADB_DIR" \
|
||||
--datadir="$DATADIR" \
|
||||
--user="$SYSTEM_USER"
|
||||
fi
|
||||
|
||||
chown -R "$SYSTEM_USER:$SYSTEM_GROUP" "$DATADIR"
|
||||
}
|
||||
|
||||
write_systemd_service() {
|
||||
if [[ -e "$SERVICE_FILE" && "$FORCE_SERVICE_REWRITE" != "yes" ]]; then
|
||||
die "Plik usługi $SERVICE_FILE już istnieje. Ustaw FORCE_SERVICE_REWRITE=\"yes\", aby go odtworzyć po wykonaniu kopii."
|
||||
fi
|
||||
|
||||
backup_existing_file "$SERVICE_FILE"
|
||||
|
||||
write_file_atomic "$SERVICE_FILE" 0644 root root <<EOF
|
||||
[Unit]
|
||||
Description=Alternatywna instancja MariaDB
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$SYSTEM_USER
|
||||
Group=$SYSTEM_GROUP
|
||||
RuntimeDirectory=alt-mariadb
|
||||
RuntimeDirectoryMode=0750
|
||||
ExecStart=$MARIADB_DIR/bin/mariadbd --defaults-file=$INSTANCE_CNF
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
LimitNOFILE=65535
|
||||
PrivateTmp=false
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
log "Zapisano i przeładowano usługę systemd $SERVICE_NAME."
|
||||
}
|
||||
|
||||
print_service_diagnostics() {
|
||||
warn "Usługa $SERVICE_NAME nie uruchomiła się poprawnie. Poniżej ostatnie wpisy diagnostyczne."
|
||||
|
||||
if [[ -f "$LOG_DIR/alt-mariadb.log" ]]; then
|
||||
warn "Ostatnie linie z $LOG_DIR/alt-mariadb.log:"
|
||||
tail -n 80 "$LOG_DIR/alt-mariadb.log" >&2 || true
|
||||
else
|
||||
warn "Plik logu $LOG_DIR/alt-mariadb.log jeszcze nie istnieje."
|
||||
fi
|
||||
|
||||
if command -v journalctl >/dev/null 2>&1; then
|
||||
warn "Ostatnie wpisy journalctl dla $SERVICE_NAME:"
|
||||
journalctl -u "$SERVICE_NAME" -n 80 --no-pager >&2 || true
|
||||
fi
|
||||
}
|
||||
|
||||
start_service() {
|
||||
local i=""
|
||||
|
||||
log "Włączam i uruchamiam usługę $SERVICE_NAME."
|
||||
systemctl enable "$SERVICE_NAME"
|
||||
systemctl restart "$SERVICE_NAME"
|
||||
|
||||
for i in $(seq 1 60); do
|
||||
if [[ -S "$SOCKET" ]]; then
|
||||
log "Socket alternatywnej instancji jest dostępny."
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
systemctl status "$SERVICE_NAME" --no-pager || true
|
||||
print_service_diagnostics
|
||||
die "Socket $SOCKET nie pojawił się w ciągu 60 sekund."
|
||||
}
|
||||
|
||||
sql_quote() {
|
||||
local value="$1"
|
||||
value="${value//\\/\\\\}"
|
||||
value="${value//\'/\'\'}"
|
||||
printf "'%s'" "$value"
|
||||
}
|
||||
|
||||
run_sql_as_root_or_admin() {
|
||||
local sql="$1"
|
||||
|
||||
if "$MARIADB_DIR/bin/mariadb" --defaults-extra-file="$ALT_MY_CNF" -e "SELECT 1;" >/dev/null 2>&1; then
|
||||
printf '%s\n' "$sql" | "$MARIADB_DIR/bin/mariadb" --defaults-extra-file="$ALT_MY_CNF"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if "$MARIADB_DIR/bin/mariadb" --protocol=socket --socket="$SOCKET" -uroot -e "SELECT 1;" >/dev/null 2>&1; then
|
||||
printf '%s\n' "$sql" | "$MARIADB_DIR/bin/mariadb" --protocol=socket --socket="$SOCKET" -uroot
|
||||
return 0
|
||||
fi
|
||||
|
||||
die "Nie można zalogować się do alternatywnej instancji jako root bez hasła ani przez plik $ALT_MY_CNF."
|
||||
}
|
||||
|
||||
create_admin_user() {
|
||||
local quoted_user=""
|
||||
local quoted_pass=""
|
||||
local sql=""
|
||||
|
||||
quoted_user="$(sql_quote "$ALT_ADMIN_USER")"
|
||||
quoted_pass="$(sql_quote "$ALT_ADMIN_PASS")"
|
||||
|
||||
sql="
|
||||
CREATE USER IF NOT EXISTS ${quoted_user}@'localhost' IDENTIFIED BY ${quoted_pass};
|
||||
ALTER USER ${quoted_user}@'localhost' IDENTIFIED BY ${quoted_pass};
|
||||
GRANT ALL PRIVILEGES ON *.* TO ${quoted_user}@'localhost' WITH GRANT OPTION;
|
||||
CREATE USER IF NOT EXISTS ${quoted_user}@'127.0.0.1' IDENTIFIED BY ${quoted_pass};
|
||||
ALTER USER ${quoted_user}@'127.0.0.1' IDENTIFIED BY ${quoted_pass};
|
||||
GRANT ALL PRIVILEGES ON *.* TO ${quoted_user}@'127.0.0.1' WITH GRANT OPTION;
|
||||
FLUSH PRIVILEGES;
|
||||
"
|
||||
|
||||
log "Tworzę lub aktualizuję konto administratora alternatywnej instancji bez wyświetlania hasła."
|
||||
run_sql_as_root_or_admin "$sql"
|
||||
}
|
||||
|
||||
run_final_tests() {
|
||||
log "Wykonuję test połączenia przez socket."
|
||||
"$MARIADB_DIR/bin/mariadb" --defaults-extra-file="$ALT_MY_CNF" -e "SELECT VERSION();"
|
||||
|
||||
log "Wykonuję test połączenia TCP do 127.0.0.1 bez wyświetlania hasła w komunikacie."
|
||||
"$MARIADB_DIR/bin/mariadb" \
|
||||
--defaults-extra-file="$ALT_MY_CNF" \
|
||||
--protocol=TCP \
|
||||
--host=127.0.0.1 \
|
||||
--port="$PORT" \
|
||||
-e "SELECT VERSION();"
|
||||
}
|
||||
|
||||
rerun_plugin_install_if_available() {
|
||||
local script_dir=""
|
||||
local plugin_install=""
|
||||
|
||||
if [[ "${SKIP_PLUGIN_INSTALL_REFRESH:-0}" == "1" ]]; then
|
||||
warn "Pominięto odświeżenie install.sh pluginu, bo SKIP_PLUGIN_INSTALL_REFRESH=1."
|
||||
return 0
|
||||
fi
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
plugin_install="$script_dir/install.sh"
|
||||
|
||||
if [[ ! -f "$plugin_install" ]]; then
|
||||
warn "Nie znaleziono install.sh pluginu przy $plugin_install; pomijam odświeżenie pluginu."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Odświeżam instalację pluginu alt-mysql przez $plugin_install."
|
||||
if [[ -x "$plugin_install" ]]; then
|
||||
"$plugin_install"
|
||||
else
|
||||
bash "$plugin_install"
|
||||
fi
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
cat <<EOF
|
||||
|
||||
Instalacja alternatywnej instancji MariaDB zakończona.
|
||||
|
||||
Wersja MariaDB: ${RESOLVED_MARIADB_RELEASE:-$MARIADB_VERSION}
|
||||
Katalog programu: $MARIADB_DIR
|
||||
Katalog danych: $DATADIR
|
||||
Port TCP: $PORT
|
||||
Socket: $SOCKET
|
||||
Konfiguracja instancji: $INSTANCE_CNF
|
||||
Usługa systemd: $SERVICE_NAME
|
||||
Plik usługi: $SERVICE_FILE
|
||||
Logi: $LOG_DIR/alt-mariadb.log
|
||||
Plik DirectAdmin alt-mysql.conf: $ALT_MYSQL_CONF
|
||||
Plik klienta alt-my.cnf: $ALT_MY_CNF
|
||||
Plik metadanych pluginu: $ALT_METADATA_ENV
|
||||
|
||||
Przykładowe polecenia testowe:
|
||||
systemctl status $SERVICE_NAME --no-pager
|
||||
$MARIADB_DIR/bin/mariadb --defaults-extra-file=$ALT_MY_CNF -e "SELECT VERSION();"
|
||||
$MARIADB_DIR/bin/mariadb --defaults-extra-file=$ALT_MY_CNF --protocol=TCP --host=127.0.0.1 --port=$PORT -e "SELECT VERSION();"
|
||||
|
||||
Hasło administratora nie zostało wypisane. Znajduje się wyłącznie w chronionych plikach poświadczeń.
|
||||
EOF
|
||||
}
|
||||
|
||||
main() {
|
||||
apply_cli_args "$@"
|
||||
validate_version
|
||||
validate_port
|
||||
require_root
|
||||
detect_os
|
||||
load_da_credentials
|
||||
validate_paths
|
||||
install_dependencies
|
||||
resolve_download_url
|
||||
download_tarball
|
||||
extract_tarball
|
||||
prepare_user_and_dirs
|
||||
write_instance_config
|
||||
write_da_style_credential_files
|
||||
write_alt_metadata_env
|
||||
initialize_datadir
|
||||
write_systemd_service
|
||||
start_service
|
||||
create_admin_user
|
||||
run_final_tests
|
||||
rerun_plugin_install_if_available
|
||||
print_summary
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,300 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
ERROR_MSG=""
|
||||
RESTORE_OK=0
|
||||
DA_USER=""
|
||||
DB_NAME=""
|
||||
JOB_ID=""
|
||||
LOG_FILE=""
|
||||
RESTORE_MODE=""
|
||||
SOURCE_DB_NAME=""
|
||||
SOURCE_FILE=""
|
||||
RESTORE_ROLLBACK_FILE=""
|
||||
RESTORE_ROLLBACK_CREATED=0
|
||||
|
||||
JOB_FILE="${1:-}"
|
||||
if [ -z "$JOB_FILE" ] || [ ! -f "$JOB_FILE" ]; then
|
||||
echo "mysql: missing job file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CONFIG_FILE="${PLUGIN_DIR}/plugin-settings.conf"
|
||||
LOG_DIR="${PLUGIN_DIR}/data/logs"
|
||||
COMMON_FILE="${PLUGIN_DIR}/scripts/setup/mysql_common.sh"
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
urlencode() {
|
||||
local s="$1"
|
||||
local out=""
|
||||
local i c
|
||||
for ((i=0; i<${#s}; i++)); do
|
||||
c="${s:i:1}"
|
||||
case "$c" in
|
||||
[a-zA-Z0-9.~_-]) out+="$c" ;;
|
||||
' ') out+='%20' ;;
|
||||
*) printf -v c '%%%02X' "'$c"; out+="$c" ;;
|
||||
esac
|
||||
done
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
notify_user() {
|
||||
local code="$1"
|
||||
if [ -z "${DA_USER:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local subject message
|
||||
if [ "$code" -eq 0 ] && [ "$RESTORE_OK" -eq 1 ]; then
|
||||
subject="Przywracanie bazy MySQL zakończone pomyślnie"
|
||||
message="Przywracanie bazy ${DB_NAME} z pliku ${SOURCE_FILE:-nieznany} zakończone."
|
||||
else
|
||||
subject="Błąd przywracania bazy MySQL"
|
||||
if [ -n "$ERROR_MSG" ]; then
|
||||
message="Przywracanie bazy ${DB_NAME} nie powiodło się: ${ERROR_MSG}"
|
||||
else
|
||||
message="Przywracanie bazy ${DB_NAME} nie powiodło się. Sprawdź log zadania."
|
||||
fi
|
||||
fi
|
||||
|
||||
local task_queue="/usr/local/directadmin/data/task.queue"
|
||||
local users="select1%3D$(urlencode "$DA_USER")"
|
||||
local line="action=notify&value=users&subject=$(urlencode "$subject")&message=$(urlencode "$message")&users=${users}"
|
||||
printf "%s\n" "$line" >> "$task_queue"
|
||||
}
|
||||
|
||||
fail() {
|
||||
local msg="$1"
|
||||
ERROR_MSG="$msg"
|
||||
if [ -n "${LOG_FILE:-}" ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${msg}" >> "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
log_unexpected_error() {
|
||||
local line_no="$1"
|
||||
local command="$2"
|
||||
local code="$3"
|
||||
if [ -z "${LOG_FILE:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ -n "${ERROR_MSG:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
ERROR_MSG="Nieoczekiwany błąd wykonywania przywracania (kod ${code}, linia ${line_no})."
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${ERROR_MSG} CMD: ${command}" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
load_plugin_settings() {
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
fail "Brak pliku konfiguracyjnego pluginu: $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
}
|
||||
|
||||
sanitize_mysql_dump() {
|
||||
awk '
|
||||
function lower(s) { return tolower(s) }
|
||||
{
|
||||
line = $0
|
||||
l = lower(line)
|
||||
if (l ~ /^[[:space:]]*create[[:space:]]+database[[:space:]]+/) { next }
|
||||
if (l ~ /^[[:space:]]*drop[[:space:]]+database[[:space:]]+/) { next }
|
||||
if (l ~ /^[[:space:]]*use[[:space:]]+[`"]/ ) { next }
|
||||
if (l ~ /^[[:space:]]*use[[:space:]]+[a-z0-9_]+[[:space:]]*;/) { next }
|
||||
print line
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
preclean_database() {
|
||||
local db_name="$1"
|
||||
local current_charset=""
|
||||
local current_collation=""
|
||||
|
||||
current_charset="$(mysql_exec mysql -Nse "SELECT DEFAULT_CHARACTER_SET_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = '${db_name}'" 2>>"$LOG_FILE" || true)"
|
||||
current_collation="$(mysql_exec mysql -Nse "SELECT DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = '${db_name}'" 2>>"$LOG_FILE" || true)"
|
||||
[ -n "$current_charset" ] || current_charset="utf8mb4"
|
||||
[ -n "$current_collation" ] || current_collation="utf8mb4_unicode_ci"
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Recreating database ${db_name} before restore" >> "$LOG_FILE"
|
||||
mysql_exec mysql -e "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$db_name");" >> "$LOG_FILE" 2>&1
|
||||
mysql_exec mysql -e "CREATE DATABASE $(mysql_quote_identifier "$db_name") CHARACTER SET ${current_charset} COLLATE ${current_collation};" >> "$LOG_FILE" 2>&1
|
||||
}
|
||||
|
||||
cleanup_restore_rollback() {
|
||||
if [ -n "${RESTORE_ROLLBACK_FILE:-}" ] && [ -f "$RESTORE_ROLLBACK_FILE" ]; then
|
||||
rm -f "$RESTORE_ROLLBACK_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
backup_alt_database_for_rollback() {
|
||||
local db_name="$1"
|
||||
RESTORE_ROLLBACK_FILE="$(mktemp)"
|
||||
RESTORE_ROLLBACK_CREATED=0
|
||||
|
||||
if [ -z "$(mysql_exec mysql -Nse "SELECT 1 FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = '$(mysql_escape_literal "$db_name")' LIMIT 1" 2>>"$LOG_FILE" || true)" ]; then
|
||||
rm -f "$RESTORE_ROLLBACK_FILE"
|
||||
RESTORE_ROLLBACK_FILE=""
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Creating rollback dump for ${db_name}" >> "$LOG_FILE"
|
||||
if mysqldump_exec \
|
||||
--single-transaction \
|
||||
--quick \
|
||||
--skip-lock-tables \
|
||||
--routines \
|
||||
--triggers \
|
||||
--events \
|
||||
--default-character-set=utf8mb4 \
|
||||
"$db_name" > "$RESTORE_ROLLBACK_FILE" 2>>"$LOG_FILE"; then
|
||||
RESTORE_ROLLBACK_CREATED=1
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -f "$RESTORE_ROLLBACK_FILE"
|
||||
RESTORE_ROLLBACK_FILE=""
|
||||
return 1
|
||||
}
|
||||
|
||||
restore_alt_database_from_rollback() {
|
||||
local db_name="$1"
|
||||
if [ "$RESTORE_ROLLBACK_CREATED" -ne 1 ] || [ -z "${RESTORE_ROLLBACK_FILE:-}" ] || [ ! -r "$RESTORE_ROLLBACK_FILE" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Restoring rollback dump for ${db_name}" >> "$LOG_FILE"
|
||||
mysql_exec mysql -e "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$db_name");" >> "$LOG_FILE" 2>&1 || true
|
||||
mysql_exec mysql -e "CREATE DATABASE $(mysql_quote_identifier "$db_name") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" >> "$LOG_FILE" 2>&1 || true
|
||||
mysql_exec "$db_name" < "$RESTORE_ROLLBACK_FILE" >> "$LOG_FILE" 2>&1 || {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: rollback restore failed for ${db_name}" >> "$LOG_FILE"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
quote_sh_value() {
|
||||
local value="$1"
|
||||
value="${value//\'/\'\\\'\'}"
|
||||
printf "'%s'" "$value"
|
||||
}
|
||||
|
||||
set_job_var() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
local tmp
|
||||
local owner_group=""
|
||||
if [ -f "$JOB_FILE" ]; then
|
||||
owner_group="$(stat -c '%u:%g' "$JOB_FILE" 2>/dev/null || stat -f '%u:%g' "$JOB_FILE" 2>/dev/null || true)"
|
||||
fi
|
||||
tmp="$(mktemp)"
|
||||
grep -v "^${key}=" "$JOB_FILE" > "$tmp" 2>/dev/null || true
|
||||
printf "%s=%s\n" "$key" "$(quote_sh_value "$value")" >> "$tmp"
|
||||
mv "$tmp" "$JOB_FILE"
|
||||
chmod 600 "$JOB_FILE" 2>/dev/null || true
|
||||
if [ "$(id -u)" -eq 0 ] && [ -n "$owner_group" ]; then
|
||||
chown "$owner_group" "$JOB_FILE" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
on_exit() {
|
||||
local code="$1"
|
||||
if [ "$code" -ne 0 ] && [ -z "${ERROR_MSG:-}" ] && [ -n "${LOG_FILE:-}" ]; then
|
||||
ERROR_MSG="Zadanie przywracania zakończyło się błędem (kod ${code}) bez zarejestrowanego szczegółu."
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${ERROR_MSG}" >> "$LOG_FILE"
|
||||
fi
|
||||
notify_user "$code"
|
||||
}
|
||||
trap 'log_unexpected_error "$LINENO" "$BASH_COMMAND" "$?"' ERR
|
||||
trap cleanup_restore_rollback INT TERM
|
||||
trap 'code=$?; cleanup_restore_rollback; on_exit "$code"' EXIT
|
||||
|
||||
[ -f "$COMMON_FILE" ] || {
|
||||
echo "Brak helpera MySQL: $COMMON_FILE" >&2
|
||||
exit 1
|
||||
}
|
||||
# shellcheck disable=SC1090
|
||||
source "$COMMON_FILE"
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$JOB_FILE"
|
||||
|
||||
JOB_ID="${JOB_ID:-$(basename "$JOB_FILE" .env)}"
|
||||
DA_USER="${DA_USER:-}"
|
||||
DB_NAME="${DB_NAME:-}"
|
||||
RESTORE_MODE="${RESTORE_MODE:-overwrite}"
|
||||
SOURCE_DB_NAME="${SOURCE_DB_NAME:-}"
|
||||
SOURCE_FILE="${SOURCE_FILE:-}"
|
||||
|
||||
LOG_FILE="${LOG_DIR}/${JOB_ID}.log"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Start restore job ${JOB_ID}" > "$LOG_FILE"
|
||||
|
||||
if [ -z "$DA_USER" ] || [ -z "$DB_NAME" ] || [ -z "$SOURCE_FILE" ]; then
|
||||
fail "Brak DA_USER, DB_NAME lub SOURCE_FILE w pliku zadania."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DB_NAME" != "${DA_USER}_"* ]]; then
|
||||
fail "Baza ${DB_NAME} nie należy do użytkownika ${DA_USER}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -r "$SOURCE_FILE" ]; then
|
||||
fail "Nie można odczytać pliku źródłowego: ${SOURCE_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
load_plugin_settings
|
||||
mysql_load_alt_credentials
|
||||
MYSQL_BIN="$(mysql_alt_binary mysql)" || {
|
||||
fail "Brak binarium mysql."
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ -z "$(mysql_exec mysql -Nse "SELECT 1 FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = '${DB_NAME}'" 2>>"$LOG_FILE" || true)" ]; then
|
||||
fail "Docelowa baza danych nie istnieje: ${DB_NAME}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! backup_alt_database_for_rollback "$DB_NAME"; then
|
||||
fail "Nie udało się utworzyć kopii rollback przed przywracaniem bazy ${DB_NAME}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! preclean_database "$DB_NAME"; then
|
||||
restore_alt_database_from_rollback "$DB_NAME" || true
|
||||
fail "Nie udało się przygotować bazy ${DB_NAME} do importu; przywrócono kopię rollback, jeśli była dostępna."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Restore source file: ${SOURCE_FILE}" >> "$LOG_FILE"
|
||||
if [ -n "$SOURCE_DB_NAME" ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Restore source DB label: ${SOURCE_DB_NAME}" >> "$LOG_FILE"
|
||||
fi
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Restore target DB: ${DB_NAME}" >> "$LOG_FILE"
|
||||
|
||||
set +e
|
||||
if [[ "$SOURCE_FILE" == *.gz ]]; then
|
||||
gunzip -c "$SOURCE_FILE" | sanitize_mysql_dump | mysql_exec "$DB_NAME" >> "$LOG_FILE" 2>&1
|
||||
RC=${PIPESTATUS[2]}
|
||||
else
|
||||
sanitize_mysql_dump < "$SOURCE_FILE" | mysql_exec "$DB_NAME" >> "$LOG_FILE" 2>&1
|
||||
RC=${PIPESTATUS[1]}
|
||||
fi
|
||||
set -e
|
||||
|
||||
if [ "$RC" -ne 0 ]; then
|
||||
restore_alt_database_from_rollback "$DB_NAME" || true
|
||||
fail "Import pliku ${SOURCE_FILE} do bazy ${DB_NAME} zakończył się błędem."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set_job_var "END_TS" "$(date +%s)"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Restore completed successfully." >> "$LOG_FILE"
|
||||
RESTORE_OK=1
|
||||
exit 0
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=./mysql_common.sh
|
||||
source "$SCRIPT_DIR/mysql_common.sh"
|
||||
|
||||
fail() {
|
||||
echo "alt-mysql health: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
mysql_load_alt_runtime
|
||||
mysql_load_alt_credentials
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl is-active --quiet "$MYSQL_ALT_SERVICE_NAME" || fail "service is not active: $MYSQL_ALT_SERVICE_NAME"
|
||||
fi
|
||||
|
||||
[ -r "$MYSQL_ALT_MY_CNF" ] || fail "client defaults file is not readable: $MYSQL_ALT_MY_CNF"
|
||||
[ -r "$MYSQL_ALT_MYSQL_CONF" ] || fail "credentials file is not readable: $MYSQL_ALT_MYSQL_CONF"
|
||||
[ -n "$MYSQL_ALT_PORT" ] || fail "alt port was not discovered"
|
||||
[ -n "$MYSQL_ALT_SOCKET" ] || fail "alt socket was not discovered"
|
||||
|
||||
"$(mysql_alt_binary mysql)" --defaults-extra-file="$MYSQL_ALT_MY_CNF" -e "SELECT 1" >/dev/null \
|
||||
|| fail "cannot connect through $MYSQL_ALT_MY_CNF"
|
||||
|
||||
mysql_ensure_metadata_schema
|
||||
mysql_exec mysql -e "SELECT COUNT(*) FROM da_plugin_database_owners" >/dev/null \
|
||||
|| fail "metadata schema is not usable"
|
||||
|
||||
echo "alt-mysql health: OK"
|
||||
@@ -0,0 +1,126 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
CUSTOMBUILD_ROOT="${CUSTOMBUILD_ROOT:-/usr/local/directadmin/custombuild}"
|
||||
ACTION="${1:-install}"
|
||||
SETTINGS_FILE="${MYSQL_PLUGIN_SETTINGS_FILE:-$PLUGIN_DIR/plugin-settings.conf}"
|
||||
MARKER="managed by DirectAdmin MySQL plugin CustomBuild hook"
|
||||
|
||||
log() {
|
||||
printf 'mysql custombuild-hooks: %s\n' "$*"
|
||||
}
|
||||
|
||||
die() {
|
||||
printf 'mysql custombuild-hooks: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
read_setting() {
|
||||
local key="$1"
|
||||
local default="$2"
|
||||
local line value
|
||||
|
||||
[ -r "$SETTINGS_FILE" ] || {
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
}
|
||||
|
||||
line="$(grep -E "^${key}=" "$SETTINGS_FILE" | tail -n 1 || true)"
|
||||
[ -n "$line" ] || {
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
}
|
||||
|
||||
value="${line#*=}"
|
||||
value="${value%%#*}"
|
||||
value="${value%\"}"
|
||||
value="${value#\"}"
|
||||
value="${value%\'}"
|
||||
value="${value#\'}"
|
||||
value="$(printf '%s' "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||||
printf '%s\n' "${value:-$default}"
|
||||
}
|
||||
|
||||
custombuild_hooks_enabled() {
|
||||
case "$(read_setting CUSTOMBUILD_HOOKS_ENABLED true)" in
|
||||
true|yes|on|1) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
write_hook() {
|
||||
local target="$1"
|
||||
local app="$2"
|
||||
local repair_script="$3"
|
||||
local health_script="$4"
|
||||
local repair_name health_name tmp
|
||||
|
||||
[ -f "$repair_script" ] || die "missing repair script: $repair_script"
|
||||
[ -f "$health_script" ] || die "missing health check script: $health_script"
|
||||
repair_name="$(basename "$repair_script")"
|
||||
health_name="$(basename "$health_script")"
|
||||
|
||||
mkdir -p "$(dirname "$target")"
|
||||
tmp="$(mktemp)"
|
||||
cat > "$tmp" <<EOF
|
||||
#!/bin/bash
|
||||
# $MARKER
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR='$PLUGIN_DIR'
|
||||
LOG_DIR="\$PLUGIN_DIR/data/logs"
|
||||
LOG_FILE="\$LOG_DIR/custombuild-${app}-repair.log"
|
||||
|
||||
mkdir -p "\$LOG_DIR" 2>/dev/null || true
|
||||
|
||||
{
|
||||
printf '%s\\n' "=== \$(date '+%Y-%m-%d %H:%M:%S') ${app} CustomBuild post hook ==="
|
||||
"\$PLUGIN_DIR/scripts/setup/${repair_name}"
|
||||
if ! "\$PLUGIN_DIR/scripts/setup/${health_name}"; then
|
||||
printf '%s\\n' "${app}: warning: health check failed after repair"
|
||||
fi
|
||||
} >> "\$LOG_FILE" 2>&1
|
||||
EOF
|
||||
install -m 0755 "$tmp" "$target"
|
||||
rm -f "$tmp"
|
||||
log "installed CustomBuild post hook: $target"
|
||||
}
|
||||
|
||||
install_all() {
|
||||
custombuild_hooks_enabled || {
|
||||
log "disabled by CUSTOMBUILD_HOOKS_ENABLED"
|
||||
return 0
|
||||
}
|
||||
|
||||
write_hook \
|
||||
"$CUSTOMBUILD_ROOT/custom/hooks/roundcube/post/90-alt-mysql-repair.sh" \
|
||||
"roundcube" \
|
||||
"$PLUGIN_DIR/scripts/setup/roundcube_repair_config.sh" \
|
||||
"$PLUGIN_DIR/scripts/setup/roundcube_health_check.sh"
|
||||
|
||||
write_hook \
|
||||
"$CUSTOMBUILD_ROOT/custom/hooks/phpmyadmin/post/90-alt-mysql-repair.sh" \
|
||||
"phpmyadmin" \
|
||||
"$PLUGIN_DIR/scripts/setup/phpmyadmin_repair.sh" \
|
||||
"$PLUGIN_DIR/scripts/setup/phpmyadmin_health_check.sh"
|
||||
}
|
||||
|
||||
uninstall_all() {
|
||||
local hook
|
||||
for hook in \
|
||||
"$CUSTOMBUILD_ROOT/custom/hooks/roundcube/post/90-alt-mysql-repair.sh" \
|
||||
"$CUSTOMBUILD_ROOT/custom/hooks/phpmyadmin/post/90-alt-mysql-repair.sh"
|
||||
do
|
||||
if [ -f "$hook" ] && grep -Fq "$MARKER" "$hook"; then
|
||||
rm -f "$hook"
|
||||
log "removed CustomBuild post hook: $hook"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
case "$ACTION" in
|
||||
install) install_all ;;
|
||||
uninstall) uninstall_all ;;
|
||||
*) die "unknown action: $ACTION" ;;
|
||||
esac
|
||||
@@ -0,0 +1,124 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
HOOK_ROOT="${DA_CUSTOM_HOOK_DIR:-/usr/local/directadmin/scripts/custom}"
|
||||
ACTION="${1:-install}"
|
||||
MARKER="managed by DirectAdmin MySQL plugin hook dispatcher"
|
||||
|
||||
log() {
|
||||
printf 'mysql da-integration: %s\n' "$*"
|
||||
}
|
||||
|
||||
die() {
|
||||
printf 'mysql da-integration: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
write_dispatcher() {
|
||||
local target="$1"
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
cat > "$tmp" <<'EOF'
|
||||
#!/bin/bash
|
||||
# managed by DirectAdmin MySQL plugin hook dispatcher
|
||||
set -u
|
||||
|
||||
hook_name="$(basename "$0")"
|
||||
hook_dir="${0}.d"
|
||||
status=0
|
||||
|
||||
if [ ! -d "$hook_dir" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for hook in "$hook_dir"/*.sh; do
|
||||
[ -e "$hook" ] || continue
|
||||
[ -x "$hook" ] || continue
|
||||
DA_HOOK_NAME="$hook_name" "$hook" "$@"
|
||||
status=$?
|
||||
if [ "$status" -ne 0 ]; then
|
||||
exit "$status"
|
||||
fi
|
||||
done
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
install -m 0755 "$tmp" "$target"
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
install_hook() {
|
||||
local hook_name="$1"
|
||||
local source_script="$2"
|
||||
local link_name="${3:-10-alt-mysql.sh}"
|
||||
local target="$HOOK_ROOT/$hook_name"
|
||||
local hook_dir="$HOOK_ROOT/${hook_name}.d"
|
||||
local preserved=""
|
||||
|
||||
[ -f "$source_script" ] || die "missing hook source: $source_script"
|
||||
chmod 0755 "$source_script" 2>/dev/null || true
|
||||
mkdir -p "$hook_dir"
|
||||
|
||||
if [ -e "$target" ] && ! grep -Fq "$MARKER" "$target" 2>/dev/null; then
|
||||
if [ -d "$target" ]; then
|
||||
die "cannot preserve hook because target is a directory: $target"
|
||||
fi
|
||||
preserved="$hook_dir/00-existing.sh"
|
||||
if [ -e "$preserved" ]; then
|
||||
preserved="$hook_dir/00-existing.$(date +%Y%m%d%H%M%S).sh"
|
||||
fi
|
||||
mv "$target" "$preserved"
|
||||
chmod 0755 "$preserved" 2>/dev/null || true
|
||||
log "preserved existing hook $hook_name as $preserved"
|
||||
fi
|
||||
|
||||
write_dispatcher "$target"
|
||||
ln -sfn "$source_script" "$hook_dir/$link_name"
|
||||
chmod 0755 "$target" "$source_script" 2>/dev/null || true
|
||||
log "installed hook $hook_name -> $source_script"
|
||||
}
|
||||
|
||||
remove_hook_link() {
|
||||
local hook_name="$1"
|
||||
local link_name="${2:-10-alt-mysql.sh}"
|
||||
local target="$HOOK_ROOT/$hook_name"
|
||||
local hook_dir="$HOOK_ROOT/${hook_name}.d"
|
||||
|
||||
rm -f "$hook_dir/$link_name"
|
||||
|
||||
if [ -d "$hook_dir" ] && ! find "$hook_dir" -mindepth 1 -maxdepth 1 -print -quit | grep -q .; then
|
||||
rmdir "$hook_dir" 2>/dev/null || true
|
||||
if [ -f "$target" ] && grep -Fq "$MARKER" "$target" 2>/dev/null; then
|
||||
rm -f "$target"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
install_all() {
|
||||
mkdir -p "$HOOK_ROOT"
|
||||
install_hook "user_backup_compress_pre.sh" "$PLUGIN_DIR/scripts/da-integration/alt_mysql_user_backup_compress_pre.sh"
|
||||
install_hook "user_restore_post_pre_cleanup.sh" "$PLUGIN_DIR/scripts/da-integration/alt_mysql_user_restore_post_pre_cleanup.sh"
|
||||
|
||||
install_hook "database_create_pre.sh" "$PLUGIN_DIR/scripts/da-integration/block_native_database_action.sh" "20-alt-mysql-block-native-db.sh"
|
||||
install_hook "database_delete_pre.sh" "$PLUGIN_DIR/scripts/da-integration/block_native_database_action.sh" "20-alt-mysql-block-native-db.sh"
|
||||
install_hook "database_user_password_change_pre.sh" "$PLUGIN_DIR/scripts/da-integration/block_native_database_action.sh" "20-alt-mysql-block-native-db.sh"
|
||||
install_hook "database_user_create_post.sh" "$PLUGIN_DIR/scripts/da-integration/block_native_database_action.sh" "20-alt-mysql-observe-native-db.sh"
|
||||
install_hook "database_destroy_user_post.sh" "$PLUGIN_DIR/scripts/da-integration/block_native_database_action.sh" "20-alt-mysql-observe-native-db.sh"
|
||||
}
|
||||
|
||||
uninstall_all() {
|
||||
remove_hook_link "user_backup_compress_pre.sh"
|
||||
remove_hook_link "user_restore_post_pre_cleanup.sh"
|
||||
remove_hook_link "database_create_pre.sh" "20-alt-mysql-block-native-db.sh"
|
||||
remove_hook_link "database_delete_pre.sh" "20-alt-mysql-block-native-db.sh"
|
||||
remove_hook_link "database_user_password_change_pre.sh" "20-alt-mysql-block-native-db.sh"
|
||||
remove_hook_link "database_user_create_post.sh" "20-alt-mysql-observe-native-db.sh"
|
||||
remove_hook_link "database_destroy_user_post.sh" "20-alt-mysql-observe-native-db.sh"
|
||||
}
|
||||
|
||||
case "$ACTION" in
|
||||
install) install_all ;;
|
||||
uninstall) uninstall_all ;;
|
||||
*) die "unknown action: $ACTION" ;;
|
||||
esac
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=./mysql_common.sh
|
||||
source "$SCRIPT_DIR/mysql_common.sh"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Użycie:
|
||||
dbrestore.sh <plik.sql|plik.sql.gz> [nazwa_bazy]
|
||||
|
||||
Opis:
|
||||
Przywraca pojedynczą bazę MySQL z pliku dumpa. Gdy nazwa bazy nie zostanie
|
||||
podana, skrypt wyprowadza ją z nazwy pliku.
|
||||
EOF
|
||||
}
|
||||
|
||||
[ "${1:-}" = "--help" ] && {
|
||||
usage
|
||||
exit 0
|
||||
}
|
||||
|
||||
[ $# -ge 1 ] || {
|
||||
usage
|
||||
exit 1
|
||||
}
|
||||
|
||||
FILE="$1"
|
||||
[ -f "$FILE" ] || {
|
||||
echo "Plik nie istnieje: $FILE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
case "$FILE" in
|
||||
*.sql|*.sql.gz|*.gz) ;;
|
||||
*)
|
||||
echo "Dozwolone są pliki .sql, .sql.gz lub .gz." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
TARGET_DB="${2:-$(basename "$FILE" | sed -E 's/\.sql(\.gz)?$//; s/\.gz$//')}"
|
||||
TARGET_DB="$(mysql_safe_identifier "$TARGET_DB")"
|
||||
|
||||
mysql_load_alt_credentials
|
||||
|
||||
mysql_exec mysql -e "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$TARGET_DB");"
|
||||
mysql_exec mysql -e "CREATE DATABASE $(mysql_quote_identifier "$TARGET_DB") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
||||
mysql_restore_stream "$TARGET_DB" "$FILE"
|
||||
|
||||
echo "Przywracanie bazy ${TARGET_DB} zakończone."
|
||||
@@ -0,0 +1,99 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=./mysql_common.sh
|
||||
source "$SCRIPT_DIR/mysql_common.sh"
|
||||
|
||||
BASE_DIR="${BASE_DIR:-/home/admin/mysql_backup}"
|
||||
DATE_FORMAT="${DATE_FORMAT:-%d-%m-%Y}"
|
||||
ADD_TIME="${ADD_TIME:-0}"
|
||||
RETENTION="${RETENTION:-7}"
|
||||
ENABLE_COMPRESSION="${ENABLE_COMPRESSION:-1}"
|
||||
LOG_FILE="${LOG_FILE:-/var/log/mysql_backup.log}"
|
||||
|
||||
timestamp() {
|
||||
date '+%Y-%m-%d %H:%M:%S'
|
||||
}
|
||||
|
||||
log() {
|
||||
printf '[%s] %s\n' "$(timestamp)" "$*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
dump_globals_file() {
|
||||
local output_file="$1"
|
||||
local tmp_file
|
||||
tmp_file="$(mktemp)"
|
||||
{
|
||||
echo "-- Global users and grants for MySQL"
|
||||
echo "-- Generated at $(timestamp)"
|
||||
} > "$tmp_file"
|
||||
|
||||
while IFS=$'\t' read -r db_user db_host; do
|
||||
[ -n "$db_user" ] || continue
|
||||
[ -n "$db_host" ] || continue
|
||||
printf "DROP USER IF EXISTS '%s'@'%s';\n" "$db_user" "$db_host" >> "$tmp_file"
|
||||
mysql_exec mysql -Nse "SHOW CREATE USER '${db_user}'@'${db_host}'" >> "$tmp_file" || true
|
||||
echo ";" >> "$tmp_file"
|
||||
while IFS= read -r grant_line; do
|
||||
[ -n "$grant_line" ] || continue
|
||||
printf "%s;\n" "$grant_line" >> "$tmp_file"
|
||||
done < <(mysql_exec mysql -Nse "SHOW GRANTS FOR '${db_user}'@'${db_host}'" || true)
|
||||
echo >> "$tmp_file"
|
||||
done < <(
|
||||
mysql_exec mysql -Nse "
|
||||
SELECT user, host
|
||||
FROM mysql.user
|
||||
WHERE user <> ''
|
||||
AND user NOT IN ('mysql.sys', 'mysql.session', 'mysql.infoschema', 'mariadb.sys')
|
||||
ORDER BY user, host
|
||||
" || true
|
||||
)
|
||||
|
||||
if [ "$ENABLE_COMPRESSION" = "1" ]; then
|
||||
gzip -9 < "$tmp_file" > "$output_file"
|
||||
else
|
||||
mv "$tmp_file" "$output_file"
|
||||
return 0
|
||||
fi
|
||||
rm -f "$tmp_file"
|
||||
}
|
||||
|
||||
mysql_load_alt_credentials
|
||||
|
||||
mkdir -p "$BASE_DIR"
|
||||
|
||||
if [ "$ADD_TIME" = "1" ]; then
|
||||
DATE_STR="$(date +"${DATE_FORMAT}_%H-%M")"
|
||||
else
|
||||
DATE_STR="$(date +"${DATE_FORMAT}")"
|
||||
fi
|
||||
BACKUP_DIR="${BASE_DIR}/${DATE_STR}"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
log "Start backupu MySQL do ${BACKUP_DIR}"
|
||||
|
||||
GLOBALS_FILE="${BACKUP_DIR}/_globals.sql"
|
||||
if [ "$ENABLE_COMPRESSION" = "1" ]; then
|
||||
GLOBALS_FILE="${GLOBALS_FILE}.gz"
|
||||
fi
|
||||
dump_globals_file "$GLOBALS_FILE"
|
||||
log "Zapisano ${GLOBALS_FILE##*/}"
|
||||
|
||||
while IFS= read -r db_name; do
|
||||
[ -n "$db_name" ] || continue
|
||||
target_file="${BACKUP_DIR}/${db_name}.sql"
|
||||
if [ "$ENABLE_COMPRESSION" = "1" ]; then
|
||||
mysqldump_exec --single-transaction --skip-lock-tables --routines --triggers --events --databases "$db_name" | gzip -9 > "${target_file}.gz"
|
||||
log "Backup ${db_name} -> ${target_file##*/}.gz"
|
||||
else
|
||||
mysqldump_exec --single-transaction --skip-lock-tables --routines --triggers --events --databases "$db_name" > "$target_file"
|
||||
log "Backup ${db_name} -> ${target_file##*/}"
|
||||
fi
|
||||
done < <(mysql_non_system_databases)
|
||||
|
||||
if [ "$RETENTION" -gt 0 ]; then
|
||||
find "$BASE_DIR" -mindepth 1 -maxdepth 1 -type d -mtime +"$RETENTION" -exec rm -rf {} +
|
||||
fi
|
||||
|
||||
log "Backup MySQL zakończony."
|
||||
Executable
+489
@@ -0,0 +1,489 @@
|
||||
#!/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"
|
||||
}
|
||||
Executable
+376
@@ -0,0 +1,376 @@
|
||||
#!/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 "$@"
|
||||
@@ -0,0 +1,365 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Upgrade alternatywnej instancji MariaDB instalowanej z oficjalnego tarballa.
|
||||
|
||||
TARGET_MARIADB_VERSION="${TARGET_MARIADB_VERSION:-11.8}"
|
||||
MARIADB_DIR="${MARIADB_DIR:-/usr/local/mariadb-$TARGET_MARIADB_VERSION}"
|
||||
DATADIR="${DATADIR:-/var/lib/mariadb_data}"
|
||||
SERVICE_NAME="${SERVICE_NAME:-alt-mariadb}"
|
||||
INSTANCE_CNF="${INSTANCE_CNF:-/etc/alt-mariadb.cnf}"
|
||||
ALT_MY_CNF="${ALT_MY_CNF:-/usr/local/directadmin/conf/alt-my.cnf}"
|
||||
ALT_MYSQL_CONF="${ALT_MYSQL_CONF:-/usr/local/directadmin/conf/alt-mysql.conf}"
|
||||
ALT_METADATA_ENV="${ALT_METADATA_ENV:-/usr/local/directadmin/conf/alt-mariadb.env}"
|
||||
DOWNLOAD_DIR="${DOWNLOAD_DIR:-/usr/local/src}"
|
||||
MARIADB_TARBALL_URL="${MARIADB_TARBALL_URL:-}"
|
||||
FORCE_UPGRADE="${FORCE_UPGRADE:-no}"
|
||||
PORT="${PORT:-}"
|
||||
SOCKET="${SOCKET:-}"
|
||||
|
||||
CURRENT_MARIADB_DIR=""
|
||||
RESOLVED_MARIADB_RELEASE=""
|
||||
DOWNLOAD_URL=""
|
||||
TARBALL_PATH=""
|
||||
TMP_DIR=""
|
||||
TARGET_BACKUP_DIR=""
|
||||
INSTANCE_CNF_BACKUP=""
|
||||
UPGRADE_EXTRACTED=0
|
||||
UPGRADE_COMMITTED=0
|
||||
SERVICE_WAS_ACTIVE=0
|
||||
|
||||
log() {
|
||||
printf '[INFO] %s\n' "$*"
|
||||
}
|
||||
|
||||
warn() {
|
||||
printf '[UWAGA] %s\n' "$*" >&2
|
||||
}
|
||||
|
||||
die() {
|
||||
printf '[BŁĄD] %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [ -n "${TMP_DIR:-}" ] && [ -d "$TMP_DIR" ]; then
|
||||
rm -rf "$TMP_DIR"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
rollback_upgrade() {
|
||||
local code="$1"
|
||||
if [ "$code" -eq 0 ] || [ "$UPGRADE_COMMITTED" -eq 1 ]; then
|
||||
cleanup
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "Upgrade nie zakończył się poprawnie, uruchamiam rollback."
|
||||
|
||||
if [ -n "$INSTANCE_CNF_BACKUP" ] && [ -r "$INSTANCE_CNF_BACKUP" ]; then
|
||||
cp -a "$INSTANCE_CNF_BACKUP" "$INSTANCE_CNF" || true
|
||||
warn "RESTORED_INSTANCE_CNF: $INSTANCE_CNF"
|
||||
fi
|
||||
|
||||
if [ "$UPGRADE_EXTRACTED" -eq 1 ] && [ -d "$MARIADB_DIR" ]; then
|
||||
if [ -n "$TARGET_BACKUP_DIR" ] && [ -d "$TARGET_BACKUP_DIR" ]; then
|
||||
rm -rf "$MARIADB_DIR" || true
|
||||
elif [ "$MARIADB_DIR" != "$CURRENT_MARIADB_DIR" ]; then
|
||||
rm -rf "$MARIADB_DIR" || true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$TARGET_BACKUP_DIR" ] && [ -d "$TARGET_BACKUP_DIR" ] && [ ! -e "$MARIADB_DIR" ]; then
|
||||
mv "$TARGET_BACKUP_DIR" "$MARIADB_DIR" || true
|
||||
warn "RESTORED_PREVIOUS_DIR: $MARIADB_DIR"
|
||||
fi
|
||||
|
||||
if [ "$SERVICE_WAS_ACTIVE" -eq 1 ]; then
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl daemon-reload || true
|
||||
systemctl restart "$SERVICE_NAME" || true
|
||||
fi
|
||||
fi
|
||||
|
||||
cleanup
|
||||
}
|
||||
trap 'code=$?; rollback_upgrade "$code"' EXIT
|
||||
|
||||
require_root() {
|
||||
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
|
||||
die "Ten skrypt musi być uruchomiony jako root."
|
||||
fi
|
||||
}
|
||||
|
||||
validate_version() {
|
||||
case "$TARGET_MARIADB_VERSION" in
|
||||
10.11|11.4|11.8)
|
||||
log "Wybrano docelową wersję MariaDB: $TARGET_MARIADB_VERSION."
|
||||
;;
|
||||
*)
|
||||
die "Nieobsługiwana wersja MariaDB: $TARGET_MARIADB_VERSION. Dozwolone wartości: 10.11, 11.4, 11.8."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
detect_architecture() {
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) printf 'x86_64\n' ;;
|
||||
*) die "Nieobsługiwana architektura: $(uname -m). Skrypt obsługuje automatycznie tylko x86_64/amd64." ;;
|
||||
esac
|
||||
}
|
||||
|
||||
detect_current_mariadb_dir() {
|
||||
if [ -r "$INSTANCE_CNF" ]; then
|
||||
CURRENT_MARIADB_DIR="$(awk -F= '
|
||||
/^[[:space:]]*basedir[[:space:]]*=/ {
|
||||
value=$2
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
|
||||
print value
|
||||
exit
|
||||
}
|
||||
' "$INSTANCE_CNF")"
|
||||
DATADIR="$(awk -F= '
|
||||
/^[[:space:]]*datadir[[:space:]]*=/ {
|
||||
value=$2
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
|
||||
print value
|
||||
exit
|
||||
}
|
||||
' "$INSTANCE_CNF")"
|
||||
PORT="$(awk -F= '
|
||||
/^[[:space:]]*port[[:space:]]*=/ {
|
||||
value=$2
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
|
||||
print value
|
||||
exit
|
||||
}
|
||||
' "$INSTANCE_CNF")"
|
||||
SOCKET="$(awk -F= '
|
||||
/^[[:space:]]*socket[[:space:]]*=/ {
|
||||
value=$2
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
|
||||
print value
|
||||
exit
|
||||
}
|
||||
' "$INSTANCE_CNF")"
|
||||
fi
|
||||
|
||||
if [ -z "$CURRENT_MARIADB_DIR" ] && systemctl cat "$SERVICE_NAME" >/dev/null 2>&1; then
|
||||
CURRENT_MARIADB_DIR="$(systemctl cat "$SERVICE_NAME" \
|
||||
| awk '/ExecStart=/ {
|
||||
sub(/^.*ExecStart=/, "")
|
||||
sub(/\/bin\/mariadbd.*$/, "")
|
||||
print
|
||||
exit
|
||||
}')"
|
||||
fi
|
||||
|
||||
[ -n "$CURRENT_MARIADB_DIR" ] || die "Nie udało się wykryć bieżącego katalogu MariaDB."
|
||||
[ -x "$CURRENT_MARIADB_DIR/bin/mariadbd" ] || die "Bieżący katalog MariaDB nie zawiera bin/mariadbd: $CURRENT_MARIADB_DIR."
|
||||
[ -n "$DATADIR" ] || die "Nie udało się wykryć katalogu danych z $INSTANCE_CNF."
|
||||
log "Wykryto bieżący katalog MariaDB: $CURRENT_MARIADB_DIR."
|
||||
}
|
||||
|
||||
resolve_latest_release_from_archive() {
|
||||
local series="$1"
|
||||
local listing=""
|
||||
local release=""
|
||||
|
||||
listing="$(curl -fsSL "https://archive.mariadb.org/" || true)"
|
||||
release="$(printf '%s\n' "$listing" \
|
||||
| grep -Eo "mariadb-${series//./\\.}\.[0-9]+/" \
|
||||
| sed 's#^mariadb-##; s#/$##' \
|
||||
| sort -V \
|
||||
| tail -n 1)"
|
||||
|
||||
[ -n "$release" ] || return 1
|
||||
printf '%s\n' "$release"
|
||||
}
|
||||
|
||||
resolve_tarball_from_directory() {
|
||||
local release="$1"
|
||||
local arch="$2"
|
||||
local dir_url="https://archive.mariadb.org/mariadb-${release}/bintar-linux-systemd-${arch}/"
|
||||
local listing=""
|
||||
local file=""
|
||||
|
||||
listing="$(curl -fsSL "$dir_url" || true)"
|
||||
file="$(printf '%s\n' "$listing" \
|
||||
| grep -Eo "mariadb-${release}-linux-systemd-${arch}\.tar\.(gz|xz)" \
|
||||
| head -n 1)"
|
||||
|
||||
[ -n "$file" ] || return 1
|
||||
printf '%s%s\n' "$dir_url" "$file"
|
||||
}
|
||||
|
||||
resolve_download_url() {
|
||||
local arch=""
|
||||
|
||||
arch="$(detect_architecture)"
|
||||
if [ -n "$MARIADB_TARBALL_URL" ]; then
|
||||
DOWNLOAD_URL="$MARIADB_TARBALL_URL"
|
||||
log "Używam ręcznie ustawionego adresu tarballa MariaDB."
|
||||
return 0
|
||||
fi
|
||||
|
||||
RESOLVED_MARIADB_RELEASE="$(resolve_latest_release_from_archive "$TARGET_MARIADB_VERSION" || true)"
|
||||
[ -n "$RESOLVED_MARIADB_RELEASE" ] || die "Nie udało się wykryć najnowszego wydania dla gałęzi $TARGET_MARIADB_VERSION."
|
||||
DOWNLOAD_URL="$(resolve_tarball_from_directory "$RESOLVED_MARIADB_RELEASE" "$arch" || true)"
|
||||
[ -n "$DOWNLOAD_URL" ] || die "Nie udało się automatycznie ustalić URL tarballa. Ustaw MARIADB_TARBALL_URL."
|
||||
log "Wykryto tarball MariaDB $RESOLVED_MARIADB_RELEASE."
|
||||
}
|
||||
|
||||
download_tarball() {
|
||||
local filename=""
|
||||
|
||||
install -d -m 0755 "$DOWNLOAD_DIR"
|
||||
filename="$(basename "${DOWNLOAD_URL%%\?*}")"
|
||||
TARBALL_PATH="$DOWNLOAD_DIR/$filename"
|
||||
log "Pobieram tarball MariaDB do $TARBALL_PATH."
|
||||
curl -fL --retry 3 --connect-timeout 20 -o "$TARBALL_PATH" "$DOWNLOAD_URL"
|
||||
[ -s "$TARBALL_PATH" ] || die "Pobrany tarball jest pusty albo nie istnieje: $TARBALL_PATH."
|
||||
}
|
||||
|
||||
extract_tarball() {
|
||||
local extracted_dir=""
|
||||
local backup_dir=""
|
||||
|
||||
if [ -d "$MARIADB_DIR" ] && [ -n "$(find "$MARIADB_DIR" -mindepth 1 -maxdepth 1 -print -quit)" ]; then
|
||||
if [ "$FORCE_UPGRADE" != "yes" ]; then
|
||||
die "Katalog docelowy $MARIADB_DIR już istnieje. Ustaw FORCE_UPGRADE=yes, aby zastąpić go po kopii."
|
||||
fi
|
||||
TARGET_BACKUP_DIR="${MARIADB_DIR}.pre-upgrade.$(date +%Y%m%d%H%M%S)"
|
||||
log "Tworzę kopię istniejącego katalogu docelowego: $TARGET_BACKUP_DIR."
|
||||
mv "$MARIADB_DIR" "$TARGET_BACKUP_DIR"
|
||||
fi
|
||||
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
tar -xf "$TARBALL_PATH" -C "$TMP_DIR"
|
||||
extracted_dir="$(find "$TMP_DIR" -mindepth 1 -maxdepth 1 -type d -print -quit)"
|
||||
[ -n "$extracted_dir" ] || die "Tarball nie zawiera oczekiwanego katalogu MariaDB."
|
||||
mv "$extracted_dir" "$MARIADB_DIR"
|
||||
chown -R root:root "$MARIADB_DIR"
|
||||
UPGRADE_EXTRACTED=1
|
||||
log "Nowe binaria MariaDB są w $MARIADB_DIR."
|
||||
}
|
||||
|
||||
rewrite_instance_basedir() {
|
||||
local backup=""
|
||||
|
||||
[ -r "$INSTANCE_CNF" ] || die "Brak konfiguracji instancji: $INSTANCE_CNF."
|
||||
backup="${INSTANCE_CNF}.pre-upgrade.$(date +%Y%m%d%H%M%S)"
|
||||
cp -a "$INSTANCE_CNF" "$backup"
|
||||
INSTANCE_CNF_BACKUP="$backup"
|
||||
sed -i "s#^[[:space:]]*basedir[[:space:]]*=.*#basedir=$MARIADB_DIR#" "$INSTANCE_CNF"
|
||||
sed -i "s#^[[:space:]]*plugin-dir[[:space:]]*=.*#plugin-dir=$MARIADB_DIR/lib/plugin#" "$INSTANCE_CNF"
|
||||
log "Zaktualizowano basedir i plugin-dir w $INSTANCE_CNF. Kopia: $backup."
|
||||
}
|
||||
|
||||
restart_service() {
|
||||
log "Restartuję usługę $SERVICE_NAME."
|
||||
systemctl daemon-reload
|
||||
systemctl restart "$SERVICE_NAME"
|
||||
sleep 5
|
||||
systemctl is-active --quiet "$SERVICE_NAME" || {
|
||||
systemctl status "$SERVICE_NAME" --no-pager || true
|
||||
die "Usługa $SERVICE_NAME nie uruchomiła się po upgrade."
|
||||
}
|
||||
}
|
||||
|
||||
run_mariadb_upgrade() {
|
||||
local upgrade_bin=""
|
||||
|
||||
if [ -x "$MARIADB_DIR/bin/mariadb-upgrade" ]; then
|
||||
upgrade_bin="$MARIADB_DIR/bin/mariadb-upgrade"
|
||||
elif [ -x "$MARIADB_DIR/bin/mysql_upgrade" ]; then
|
||||
upgrade_bin="$MARIADB_DIR/bin/mysql_upgrade"
|
||||
else
|
||||
warn "Nie znaleziono mariadb-upgrade ani mysql_upgrade, pomijam etap aktualizacji tabel systemowych."
|
||||
return 0
|
||||
fi
|
||||
|
||||
[ -r "$ALT_MY_CNF" ] || die "Brak pliku klienta alternatywnej instancji: $ALT_MY_CNF."
|
||||
log "Uruchamiam aktualizację tabel systemowych MariaDB."
|
||||
"$upgrade_bin" --defaults-extra-file="$ALT_MY_CNF"
|
||||
}
|
||||
|
||||
credential_group() {
|
||||
if getent group diradmin >/dev/null 2>&1; then
|
||||
printf 'diradmin\n'
|
||||
else
|
||||
printf 'root\n'
|
||||
fi
|
||||
}
|
||||
|
||||
sh_single_quote() {
|
||||
local value="$1"
|
||||
value="${value//\'/\'\\\'\'}"
|
||||
printf "'%s'" "$value"
|
||||
}
|
||||
|
||||
write_alt_metadata_env() {
|
||||
local group_name metadata_mode tmp_file
|
||||
group_name="$(credential_group)"
|
||||
metadata_mode="0600"
|
||||
if [ "$group_name" = "diradmin" ]; then
|
||||
metadata_mode="0640"
|
||||
fi
|
||||
|
||||
tmp_file="$(mktemp)"
|
||||
cat > "$tmp_file" <<EOF
|
||||
# Metadata alternatywnej instancji MariaDB zarządzanej przez mysql_upgrade.sh.
|
||||
# Ten plik jest kontraktem dla pluginu DirectAdmin i nie jest natywną konfiguracją MySQL DirectAdmina.
|
||||
SERVICE_NAME=$(sh_single_quote "$SERVICE_NAME")
|
||||
MARIADB_VERSION=$(sh_single_quote "$TARGET_MARIADB_VERSION")
|
||||
MARIADB_RELEASE=$(sh_single_quote "${RESOLVED_MARIADB_RELEASE:-$TARGET_MARIADB_VERSION}")
|
||||
MARIADB_DIR=$(sh_single_quote "$MARIADB_DIR")
|
||||
DATADIR=$(sh_single_quote "$DATADIR")
|
||||
PORT=$(sh_single_quote "$PORT")
|
||||
SOCKET=$(sh_single_quote "$SOCKET")
|
||||
ALT_MYSQL_CONF=$(sh_single_quote "$ALT_MYSQL_CONF")
|
||||
ALT_MY_CNF=$(sh_single_quote "$ALT_MY_CNF")
|
||||
INSTANCE_CNF=$(sh_single_quote "$INSTANCE_CNF")
|
||||
EOF
|
||||
install -m "$metadata_mode" -o root -g "$group_name" "$tmp_file" "$ALT_METADATA_ENV"
|
||||
rm -f "$tmp_file"
|
||||
log "Zaktualizowano metadane alternatywnej instancji: $ALT_METADATA_ENV."
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
cat <<EOF
|
||||
|
||||
Upgrade alternatywnej instancji MariaDB zakończony.
|
||||
|
||||
Poprzedni katalog programu: $CURRENT_MARIADB_DIR
|
||||
Nowy katalog programu: $MARIADB_DIR
|
||||
Katalog danych: $DATADIR
|
||||
Konfiguracja instancji: $INSTANCE_CNF
|
||||
Usługa systemd: $SERVICE_NAME
|
||||
|
||||
Polecenie testowe:
|
||||
$MARIADB_DIR/bin/mariadb --defaults-extra-file=$ALT_MY_CNF -e "SELECT VERSION();"
|
||||
EOF
|
||||
}
|
||||
|
||||
main() {
|
||||
require_root
|
||||
validate_version
|
||||
detect_current_mariadb_dir
|
||||
resolve_download_url
|
||||
download_tarball
|
||||
if systemctl is-active --quiet "$SERVICE_NAME"; then
|
||||
SERVICE_WAS_ACTIVE=1
|
||||
fi
|
||||
systemctl stop "$SERVICE_NAME"
|
||||
extract_tarball
|
||||
rewrite_instance_basedir
|
||||
restart_service
|
||||
run_mariadb_upgrade
|
||||
write_alt_metadata_env
|
||||
UPGRADE_COMMITTED=1
|
||||
print_summary
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PHPMYADMIN_DIR="${PHPMYADMIN_PRIVATE_DIR:-${PHPMYADMIN_DIR:-/var/www/html/alt-mysql-phpmyadmin}}"
|
||||
CONFIG_INC="${PHPMYADMIN_DIR}/config.inc.php"
|
||||
SIGNON_SCRIPT="${PHPMYADMIN_DIR}/da_signon.php"
|
||||
SIGNON_URL_PAGE="${PHPMYADMIN_DIR}/da_login.php"
|
||||
RUNTIME_CONFIG="${PHPMYADMIN_DIR}/runtime/config.json"
|
||||
|
||||
fail() {
|
||||
printf 'phpMyAdmin alt-mysql health: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ -d "$PHPMYADMIN_DIR" ] || fail "phpMyAdmin directory not found: $PHPMYADMIN_DIR"
|
||||
[ -r "$PHPMYADMIN_DIR/index.php" ] || fail "phpMyAdmin index.php is not readable: $PHPMYADMIN_DIR/index.php"
|
||||
[ -r "$CONFIG_INC" ] || fail "config.inc.php is not readable: $CONFIG_INC"
|
||||
[ -r "$SIGNON_SCRIPT" ] || fail "signon script is not readable: $SIGNON_SCRIPT"
|
||||
[ -r "$SIGNON_URL_PAGE" ] || fail "SignonURL page is not readable: $SIGNON_URL_PAGE"
|
||||
grep -Fq "DA_MYSQL_PMA_DEFAULT_CONF" "$CONFIG_INC" || fail "config.inc.php does not configure alt-mysql endpoint"
|
||||
grep -Fq "auth_type'] = 'signon" "$CONFIG_INC" || fail "config.inc.php does not configure signon auth"
|
||||
grep -Fq "SignonURL'] = da_mysql_phpmyadmin_signon_url" "$CONFIG_INC" || fail "config.inc.php does not configure dynamic SignonURL"
|
||||
grep -Fq "da_mysql_login_consume_ticket" "$SIGNON_URL_PAGE" || fail "da_login.php does not consume SSO tickets"
|
||||
grep -Fq "DA_MYSQL_DEFAULT_CONF = '/usr/local/directadmin/conf/alt-mysql.conf'" "$SIGNON_SCRIPT" \
|
||||
|| fail "signon script does not default to alt-mysql.conf"
|
||||
|
||||
if [ -r "$RUNTIME_CONFIG" ] && command -v php >/dev/null 2>&1; then
|
||||
php -r '
|
||||
$path = $argv[1];
|
||||
$data = json_decode(file_get_contents($path), true);
|
||||
if (!is_array($data)) {
|
||||
fwrite(STDERR, "runtime config is not valid JSON\n");
|
||||
exit(2);
|
||||
}
|
||||
if (($data["credentials_file"] ?? "") === "/usr/local/directadmin/conf/mysql.conf") {
|
||||
fwrite(STDERR, "runtime config points to native DirectAdmin mysql.conf\n");
|
||||
exit(3);
|
||||
}
|
||||
' "$RUNTIME_CONFIG" || fail "runtime config validation failed"
|
||||
fi
|
||||
|
||||
echo "phpMyAdmin alt-mysql health: OK"
|
||||
@@ -0,0 +1,486 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PHPMYADMIN_PRIVATE_DIR="${PHPMYADMIN_PRIVATE_DIR:-${PHPMYADMIN_DIR:-/var/www/html/alt-mysql-phpmyadmin}}"
|
||||
PHPMYADMIN_VERSION="${PHPMYADMIN_VERSION:-5.2.3}"
|
||||
PHPMYADMIN_TARBALL_URL="${PHPMYADMIN_TARBALL_URL:-https://files.phpmyadmin.net/phpMyAdmin/5.2.3/phpMyAdmin-5.2.3-all-languages.tar.gz}"
|
||||
PHPMYADMIN_DOWNLOAD_DIR="${PHPMYADMIN_DOWNLOAD_DIR:-/usr/local/src}"
|
||||
PHPMYADMIN_SOURCE_DIR="${PHPMYADMIN_SOURCE_DIR:-}"
|
||||
RUNTIME_DIR="${PHPMYADMIN_PRIVATE_DIR}/runtime"
|
||||
TICKETS_DIR="${RUNTIME_DIR}/tickets"
|
||||
CONFIG_INC="${PHPMYADMIN_PRIVATE_DIR}/config.inc.php"
|
||||
SIGNON_SCRIPT="${PHPMYADMIN_PRIVATE_DIR}/da_signon.php"
|
||||
SIGNON_URL_PAGE="${PHPMYADMIN_PRIVATE_DIR}/da_login.php"
|
||||
BLOWFISH_SECRET_FILE="${RUNTIME_DIR}/blowfish_secret"
|
||||
|
||||
if [ "${PHPMYADMIN_INSTALL_ASSUME_ROOT:-0}" != "1" ] && [ "${EUID:-$(id -u)}" -ne 0 ]; then
|
||||
echo "Skrypt musi być uruchomiony jako root." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log() {
|
||||
printf 'phpMyAdmin alt-mysql: %s\n' "$*"
|
||||
}
|
||||
|
||||
die() {
|
||||
printf 'phpMyAdmin alt-mysql: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
install_private_copy_from_source() {
|
||||
local source_dir="$1"
|
||||
[ -d "$source_dir" ] || die "Katalog źródłowy phpMyAdmin nie istnieje: $source_dir"
|
||||
[ -f "$source_dir/index.php" ] || die "Katalog źródłowy nie wygląda jak phpMyAdmin: $source_dir"
|
||||
|
||||
mkdir -p "$PHPMYADMIN_PRIVATE_DIR"
|
||||
cp -a "$source_dir"/. "$PHPMYADMIN_PRIVATE_DIR"/
|
||||
}
|
||||
|
||||
download_private_copy() {
|
||||
local tarball extracted tmp_dir
|
||||
|
||||
command -v curl >/dev/null 2>&1 || die "Brak curl; ustaw PHPMYADMIN_SOURCE_DIR albo doinstaluj curl."
|
||||
command -v tar >/dev/null 2>&1 || die "Brak tar."
|
||||
|
||||
mkdir -p "$PHPMYADMIN_DOWNLOAD_DIR"
|
||||
tarball="$PHPMYADMIN_DOWNLOAD_DIR/phpMyAdmin-${PHPMYADMIN_VERSION}-all-languages.tar.gz"
|
||||
if [ ! -s "$tarball" ]; then
|
||||
log "Pobieram prywatny phpMyAdmin ${PHPMYADMIN_VERSION}."
|
||||
curl -fL --retry 3 --connect-timeout 20 -o "$tarball" "$PHPMYADMIN_TARBALL_URL"
|
||||
fi
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
tar -xzf "$tarball" -C "$tmp_dir"
|
||||
extracted="$(find "$tmp_dir" -mindepth 1 -maxdepth 1 -type d -name 'phpMyAdmin-*' -print -quit)"
|
||||
[ -n "$extracted" ] || {
|
||||
rm -rf "$tmp_dir"
|
||||
die "Tarball phpMyAdmin nie zawiera oczekiwanego katalogu."
|
||||
}
|
||||
|
||||
install_private_copy_from_source "$extracted"
|
||||
rm -rf "$tmp_dir"
|
||||
}
|
||||
|
||||
ensure_private_copy() {
|
||||
if [ -f "$PHPMYADMIN_PRIVATE_DIR/index.php" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -rf "$PHPMYADMIN_PRIVATE_DIR"
|
||||
if [ -n "$PHPMYADMIN_SOURCE_DIR" ]; then
|
||||
install_private_copy_from_source "$PHPMYADMIN_SOURCE_DIR"
|
||||
else
|
||||
download_private_copy
|
||||
fi
|
||||
}
|
||||
|
||||
generate_secret() {
|
||||
if [ -r "$BLOWFISH_SECRET_FILE" ]; then
|
||||
local existing
|
||||
existing="$(tr -d '\r\n' < "$BLOWFISH_SECRET_FILE")"
|
||||
if [ -n "$existing" ]; then
|
||||
printf '%s\n' "$existing"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
openssl rand -hex 32
|
||||
return 0
|
||||
fi
|
||||
|
||||
tr -dc 'a-f0-9' </dev/urandom | head -c 64
|
||||
printf '\n'
|
||||
}
|
||||
|
||||
write_private_config() {
|
||||
local secret
|
||||
|
||||
mkdir -p "$RUNTIME_DIR" "$TICKETS_DIR"
|
||||
secret="$(generate_secret)"
|
||||
printf '%s\n' "$secret" > "$BLOWFISH_SECRET_FILE"
|
||||
chmod 600 "$BLOWFISH_SECRET_FILE" 2>/dev/null || true
|
||||
cat > "$RUNTIME_DIR/.htaccess" <<'EOF'
|
||||
Require all denied
|
||||
Deny from all
|
||||
EOF
|
||||
cat > "$TICKETS_DIR/.htaccess" <<'EOF'
|
||||
Require all denied
|
||||
Deny from all
|
||||
EOF
|
||||
printf '%s\n' '<!doctype html><title>Forbidden</title>' > "$RUNTIME_DIR/index.html"
|
||||
printf '%s\n' '<!doctype html><title>Forbidden</title>' > "$TICKETS_DIR/index.html"
|
||||
|
||||
cat > "$CONFIG_INC" <<'PHP'
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
const DA_MYSQL_PMA_DEFAULT_CONF = '/usr/local/directadmin/conf/alt-mysql.conf';
|
||||
const DA_MYSQL_PMA_RUNTIME_CONFIG = __DIR__ . '/runtime/config.json';
|
||||
const DA_MYSQL_PMA_BLOWFISH_SECRET_FILE = __DIR__ . '/runtime/blowfish_secret';
|
||||
|
||||
function da_mysql_phpmyadmin_runtime_config(): array
|
||||
{
|
||||
if (!is_readable(DA_MYSQL_PMA_RUNTIME_CONFIG)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$raw = file_get_contents(DA_MYSQL_PMA_RUNTIME_CONFIG);
|
||||
if (!is_string($raw) || $raw === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode($raw, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
function da_mysql_phpmyadmin_parse_conf(string $path): array
|
||||
{
|
||||
$data = [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '33033',
|
||||
'socket' => '',
|
||||
];
|
||||
|
||||
if (!is_readable($path)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$lines = file($path, FILE_IGNORE_NEW_LINES);
|
||||
if ($lines === false) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#' || strpos($line, '=') === false) {
|
||||
continue;
|
||||
}
|
||||
[$key, $value] = explode('=', $line, 2);
|
||||
$key = strtolower(trim($key));
|
||||
$value = trim(trim($value), "\"'");
|
||||
if ($key === 'host' && $value !== '') {
|
||||
$data['host'] = $value;
|
||||
} elseif (($key === 'port' || $key === 'mysql_port') && ctype_digit($value)) {
|
||||
$data['port'] = $value;
|
||||
} elseif (($key === 'socket' || $key === 'mysql_socket') && $value !== '') {
|
||||
$data['socket'] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($data['host'] === '' || $data['host'] === 'localhost') {
|
||||
$data['host'] = '127.0.0.1';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
function da_mysql_phpmyadmin_blowfish_secret(): string
|
||||
{
|
||||
if (is_readable(DA_MYSQL_PMA_BLOWFISH_SECRET_FILE)) {
|
||||
$secret = trim((string)file_get_contents(DA_MYSQL_PMA_BLOWFISH_SECRET_FILE));
|
||||
if ($secret !== '') {
|
||||
return $secret;
|
||||
}
|
||||
}
|
||||
|
||||
return 'alt-mysql-phpmyadmin-secret-change-me';
|
||||
}
|
||||
|
||||
function da_mysql_phpmyadmin_url_path(array $runtimeConfig): string
|
||||
{
|
||||
$path = trim((string)($runtimeConfig['phpmyadmin_url_path'] ?? '/alt-mysql-phpmyadmin'));
|
||||
if ($path === '') {
|
||||
return '/alt-mysql-phpmyadmin';
|
||||
}
|
||||
if ($path[0] !== '/') {
|
||||
$path = '/' . $path;
|
||||
}
|
||||
|
||||
$path = rtrim($path, '/');
|
||||
return $path !== '' ? $path : '/alt-mysql-phpmyadmin';
|
||||
}
|
||||
|
||||
function da_mysql_phpmyadmin_public_base_url(array $runtimeConfig): string
|
||||
{
|
||||
$configured = trim((string)($runtimeConfig['phpmyadmin_public_base_url'] ?? ''));
|
||||
if (preg_match('#^https?://#i', $configured) === 1) {
|
||||
return rtrim($configured, '/');
|
||||
}
|
||||
|
||||
$scheme = 'http';
|
||||
$https = strtolower((string)($_SERVER['HTTPS'] ?? ''));
|
||||
if ($https !== '' && $https !== 'off' && $https !== '0') {
|
||||
$scheme = 'https';
|
||||
} elseif (strtolower((string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https') {
|
||||
$scheme = 'https';
|
||||
} elseif (strtolower((string)($_SERVER['REQUEST_SCHEME'] ?? '')) === 'https') {
|
||||
$scheme = 'https';
|
||||
}
|
||||
|
||||
$host = trim((string)($_SERVER['HTTP_HOST'] ?? ''));
|
||||
if ($host === '') {
|
||||
$host = trim((string)($_SERVER['SERVER_NAME'] ?? 'localhost'));
|
||||
}
|
||||
if ($host === '') {
|
||||
$host = 'localhost';
|
||||
}
|
||||
|
||||
return $scheme . '://' . $host;
|
||||
}
|
||||
|
||||
function da_mysql_phpmyadmin_signon_url(array $runtimeConfig): string
|
||||
{
|
||||
return da_mysql_phpmyadmin_public_base_url($runtimeConfig)
|
||||
. da_mysql_phpmyadmin_url_path($runtimeConfig)
|
||||
. '/da_login.php';
|
||||
}
|
||||
|
||||
$runtimeConfig = da_mysql_phpmyadmin_runtime_config();
|
||||
$credentialsFile = (string)($runtimeConfig['credentials_file'] ?? DA_MYSQL_PMA_DEFAULT_CONF);
|
||||
if ($credentialsFile === '') {
|
||||
$credentialsFile = DA_MYSQL_PMA_DEFAULT_CONF;
|
||||
}
|
||||
$daMysqlServer = da_mysql_phpmyadmin_parse_conf($credentialsFile);
|
||||
|
||||
$cfg = [];
|
||||
$cfg['blowfish_secret'] = da_mysql_phpmyadmin_blowfish_secret();
|
||||
$cfg['TempDir'] = __DIR__ . '/runtime/tmp';
|
||||
$cfg['LoginCookieValidity'] = 900;
|
||||
|
||||
$i = 0;
|
||||
$i++;
|
||||
$cfg['Servers'][$i]['host'] = $daMysqlServer['host'];
|
||||
$cfg['Servers'][$i]['port'] = $daMysqlServer['port'];
|
||||
$cfg['Servers'][$i]['socket'] = $daMysqlServer['host'] === 'localhost' ? $daMysqlServer['socket'] : '';
|
||||
$cfg['Servers'][$i]['auth_type'] = 'signon';
|
||||
$cfg['Servers'][$i]['SignonScript'] = __DIR__ . '/da_signon.php';
|
||||
$cfg['Servers'][$i]['SignonSession'] = 'DA_MYSQL_PHPMYADMIN';
|
||||
$cfg['Servers'][$i]['SignonURL'] = da_mysql_phpmyadmin_signon_url($runtimeConfig);
|
||||
$cfg['Servers'][$i]['LogoutURL'] = da_mysql_phpmyadmin_signon_url($runtimeConfig);
|
||||
$cfg['Servers'][$i]['AllowNoPassword'] = false;
|
||||
$cfg['Servers'][$i]['verbose'] = 'DirectAdmin alt-mysql';
|
||||
$cfg['Servers'][$i]['only_db'] = '';
|
||||
$cfg['ServerDefault'] = 1;
|
||||
$cfg['AllowArbitraryServer'] = false;
|
||||
PHP
|
||||
|
||||
cat > "$SIGNON_URL_PAGE" <<'PHP'
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
const DA_MYSQL_SESSION = 'DA_MYSQL_PHPMYADMIN';
|
||||
const DA_MYSQL_RUNTIME_CONFIG = __DIR__ . '/runtime/config.json';
|
||||
|
||||
function da_mysql_login_runtime_config(): array
|
||||
{
|
||||
if (!is_readable(DA_MYSQL_RUNTIME_CONFIG)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$raw = file_get_contents(DA_MYSQL_RUNTIME_CONFIG);
|
||||
if (!is_string($raw) || $raw === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode($raw, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
function da_mysql_login_url_path(array $runtimeConfig): string
|
||||
{
|
||||
$path = trim((string)($runtimeConfig['phpmyadmin_url_path'] ?? '/alt-mysql-phpmyadmin'));
|
||||
if ($path === '') {
|
||||
return '/alt-mysql-phpmyadmin';
|
||||
}
|
||||
if ($path[0] !== '/') {
|
||||
$path = '/' . $path;
|
||||
}
|
||||
|
||||
$path = rtrim($path, '/');
|
||||
return $path !== '' ? $path : '/alt-mysql-phpmyadmin';
|
||||
}
|
||||
|
||||
function da_mysql_login_ticket_dir(array $runtimeConfig): string
|
||||
{
|
||||
$dir = trim((string)($runtimeConfig['ticket_dir'] ?? ''));
|
||||
if ($dir !== '') {
|
||||
return rtrim($dir, '/');
|
||||
}
|
||||
|
||||
return __DIR__ . '/runtime/tickets';
|
||||
}
|
||||
|
||||
function da_mysql_login_error(string $message): void
|
||||
{
|
||||
http_response_code(400);
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Pragma: no-cache');
|
||||
$safe = htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
echo '<!doctype html><html lang="en"><head><meta charset="utf-8"><title>alt-mysql phpMyAdmin</title></head><body>';
|
||||
echo '<h1>alt-mysql phpMyAdmin</h1><p>' . $safe . '</p>';
|
||||
echo '</body></html>';
|
||||
exit;
|
||||
}
|
||||
|
||||
function da_mysql_login_consume_ticket(array $runtimeConfig): array
|
||||
{
|
||||
$token = strtolower((string)($_GET['ticket'] ?? ''));
|
||||
if (preg_match('/\A[a-f0-9]{64}\z/', $token) !== 1) {
|
||||
da_mysql_login_error('Missing or invalid phpMyAdmin login ticket.');
|
||||
}
|
||||
|
||||
$path = da_mysql_login_ticket_dir($runtimeConfig) . '/' . $token . '.json';
|
||||
if (!is_readable($path)) {
|
||||
da_mysql_login_error('The phpMyAdmin login ticket is not available or has already been used.');
|
||||
}
|
||||
|
||||
$raw = file_get_contents($path);
|
||||
@unlink($path);
|
||||
$ticket = is_string($raw) ? json_decode($raw, true) : null;
|
||||
if (!is_array($ticket)) {
|
||||
da_mysql_login_error('The phpMyAdmin login ticket is invalid.');
|
||||
}
|
||||
|
||||
if ((int)($ticket['expires_at'] ?? 0) < time()) {
|
||||
da_mysql_login_error('The phpMyAdmin login ticket has expired.');
|
||||
}
|
||||
|
||||
$auth = $ticket['auth'] ?? [];
|
||||
if (!is_array($auth) || (string)($auth['user'] ?? '') === '' || (string)($auth['password'] ?? '') === '') {
|
||||
da_mysql_login_error('The phpMyAdmin login ticket does not contain credentials.');
|
||||
}
|
||||
|
||||
return $ticket;
|
||||
}
|
||||
|
||||
function da_mysql_login_redirect_target(array $ticket): string
|
||||
{
|
||||
$route = (string)($ticket['route'] ?? '/database/structure');
|
||||
if (preg_match('#^/[A-Za-z0-9_./-]+$#', $route) !== 1 || strpos($route, '//') !== false) {
|
||||
$route = '/';
|
||||
}
|
||||
|
||||
$query = ['route' => $route];
|
||||
$db = (string)($ticket['db'] ?? ($ticket['auth']['db'] ?? ''));
|
||||
if ($db !== '') {
|
||||
$query['db'] = $db;
|
||||
}
|
||||
|
||||
return 'index.php?' . http_build_query($query);
|
||||
}
|
||||
|
||||
$runtimeConfig = da_mysql_login_runtime_config();
|
||||
$ticket = da_mysql_login_consume_ticket($runtimeConfig);
|
||||
$secure = false;
|
||||
$https = strtolower((string)($_SERVER['HTTPS'] ?? ''));
|
||||
if ($https !== '' && $https !== 'off' && $https !== '0') {
|
||||
$secure = true;
|
||||
} elseif (strtolower((string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https') {
|
||||
$secure = true;
|
||||
}
|
||||
|
||||
session_name(DA_MYSQL_SESSION);
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => da_mysql_login_url_path($runtimeConfig),
|
||||
'secure' => $secure,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
session_start();
|
||||
$_SESSION['DA_MYSQL_PHPMYADMIN_AUTH'] = $ticket['auth'];
|
||||
session_write_close();
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Pragma: no-cache');
|
||||
header('Location: ' . da_mysql_login_redirect_target($ticket), true, 302);
|
||||
exit;
|
||||
PHP
|
||||
|
||||
cat > "$SIGNON_SCRIPT" <<'PHP'
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
const DA_MYSQL_SESSION = 'DA_MYSQL_PHPMYADMIN';
|
||||
const DA_MYSQL_DEFAULT_CONF = '/usr/local/directadmin/conf/alt-mysql.conf';
|
||||
const DA_MYSQL_RUNTIME_CONFIG = __DIR__ . '/runtime/config.json';
|
||||
|
||||
function da_mysql_runtime_config(): array
|
||||
{
|
||||
if (!is_readable(DA_MYSQL_RUNTIME_CONFIG)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$raw = file_get_contents(DA_MYSQL_RUNTIME_CONFIG);
|
||||
if (!is_string($raw) || $raw === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode($raw, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
function da_mysql_signon_session_auth(): array
|
||||
{
|
||||
$payload = $_SESSION['DA_MYSQL_PHPMYADMIN_AUTH'] ?? [];
|
||||
return is_array($payload) ? $payload : [];
|
||||
}
|
||||
|
||||
function da_mysql_signon_read_auth(): array
|
||||
{
|
||||
$previousActive = session_status() === PHP_SESSION_ACTIVE;
|
||||
$previousName = session_name();
|
||||
$previousId = $previousActive ? session_id() : '';
|
||||
|
||||
if ($previousActive && $previousName === DA_MYSQL_SESSION) {
|
||||
return da_mysql_signon_session_auth();
|
||||
}
|
||||
|
||||
if ($previousActive) {
|
||||
session_write_close();
|
||||
}
|
||||
|
||||
session_name(DA_MYSQL_SESSION);
|
||||
session_id('');
|
||||
$ssoSessionId = (string)($_COOKIE[DA_MYSQL_SESSION] ?? '');
|
||||
if ($ssoSessionId !== '' && preg_match('/\A[A-Za-z0-9,-]+\z/', $ssoSessionId) === 1) {
|
||||
session_id($ssoSessionId);
|
||||
}
|
||||
session_start();
|
||||
$payload = da_mysql_signon_session_auth();
|
||||
session_write_close();
|
||||
|
||||
if ($previousActive) {
|
||||
session_name($previousName);
|
||||
if ($previousId !== '') {
|
||||
session_id($previousId);
|
||||
}
|
||||
session_start();
|
||||
} elseif ($previousName !== '') {
|
||||
session_name($previousName);
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
function get_login_credentials($user = ''): array
|
||||
{
|
||||
$payload = da_mysql_signon_read_auth();
|
||||
$username = (string)($payload['user'] ?? '');
|
||||
$password = (string)($payload['password'] ?? '');
|
||||
return [$username, $password];
|
||||
}
|
||||
PHP
|
||||
|
||||
mkdir -p "$RUNTIME_DIR/tmp"
|
||||
chown -R diradmin:diradmin "$RUNTIME_DIR" "$CONFIG_INC" "$SIGNON_SCRIPT" "$SIGNON_URL_PAGE" 2>/dev/null || true
|
||||
chmod 755 "$RUNTIME_DIR" "$TICKETS_DIR" "$RUNTIME_DIR/tmp" 2>/dev/null || true
|
||||
chmod 644 "$CONFIG_INC" "$SIGNON_SCRIPT" "$SIGNON_URL_PAGE" "$RUNTIME_DIR/.htaccess" "$TICKETS_DIR/.htaccess" "$RUNTIME_DIR/index.html" "$TICKETS_DIR/index.html" 2>/dev/null || true
|
||||
}
|
||||
|
||||
ensure_private_copy
|
||||
write_private_config
|
||||
|
||||
log "Prywatna kopia phpMyAdmin dla alt-mysql jest gotowa w ${PHPMYADMIN_PRIVATE_DIR}."
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
exec "$SCRIPT_DIR/phpmyadmin_install.sh" "$@"
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
exec "$SCRIPT_DIR/phpmyadmin_install.sh"
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
CB_DIR="/usr/local/directadmin/custombuild"
|
||||
|
||||
[ "${EUID:-$(id -u)}" -eq 0 ] || {
|
||||
echo "Skrypt musi być uruchomiony jako root." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ -x "${CB_DIR}/build" ] || {
|
||||
echo "Nie znaleziono DirectAdmin CustomBuild w ${CB_DIR}." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
php_slots=()
|
||||
for slot in 1 2 3 4 5 6 7 8 9; do
|
||||
version="$(awk -F= -v key="php${slot}_release" '$1 == key {print $2}' "${CB_DIR}/options.conf" 2>/dev/null | tr -d '[:space:]')"
|
||||
case "${version,,}" in
|
||||
""|no|off) continue ;;
|
||||
esac
|
||||
php_slots+=("$version")
|
||||
done
|
||||
|
||||
[ ${#php_slots[@]} -gt 0 ] || {
|
||||
echo "Nie wykryto aktywnych wersji PHP w CustomBuild." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
for version in "${php_slots[@]}"; do
|
||||
echo "Rebuild PHP ${version}"
|
||||
"${CB_DIR}/build" php "$version"
|
||||
done
|
||||
|
||||
"${CB_DIR}/build" rewrite_confs || true
|
||||
echo "Rekompilacja PHP zakończona. Rozszerzenia mysqli i pdo_mysql są dostarczane przez standardowy build DirectAdmin."
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=./mysql_common.sh
|
||||
source "$SCRIPT_DIR/mysql_common.sh"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Użycie:
|
||||
restore_all.sh <katalog_backupu> [--restore-users]
|
||||
|
||||
Opis:
|
||||
Przywraca wszystkie bazy MySQL z katalogu backupu. Jeśli obecny jest plik
|
||||
`_globals.sql` lub `_globals.sql.gz`, można go odtworzyć flagą
|
||||
`--restore-users`.
|
||||
EOF
|
||||
}
|
||||
|
||||
BACKUP_DIR=""
|
||||
RESTORE_USERS="0"
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--help|-h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--restore-users)
|
||||
RESTORE_USERS="1"
|
||||
;;
|
||||
*)
|
||||
if [ -n "$BACKUP_DIR" ]; then
|
||||
echo "Podano więcej niż jeden katalog backupu." >&2
|
||||
exit 1
|
||||
fi
|
||||
BACKUP_DIR="$arg"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$BACKUP_DIR" ] || {
|
||||
usage
|
||||
exit 1
|
||||
}
|
||||
[ -d "$BACKUP_DIR" ] || {
|
||||
echo "Katalog nie istnieje: $BACKUP_DIR" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
mysql_load_alt_credentials
|
||||
|
||||
if [ "$RESTORE_USERS" = "1" ]; then
|
||||
for globals_file in "$BACKUP_DIR/_globals.sql.gz" "$BACKUP_DIR/_globals.sql"; do
|
||||
[ -f "$globals_file" ] || continue
|
||||
mysql_restore_stream mysql "$globals_file"
|
||||
break
|
||||
done
|
||||
fi
|
||||
|
||||
find "$BACKUP_DIR" -maxdepth 1 -type f \( -name '*.sql' -o -name '*.sql.gz' \) ! -name '_globals.*' | sort | while IFS= read -r file; do
|
||||
[ -n "$file" ] || continue
|
||||
db_name="$(basename "$file" | sed -E 's/\.sql(\.gz)?$//')"
|
||||
db_name="$(mysql_safe_identifier "$db_name")"
|
||||
mysql_exec mysql -e "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$db_name");"
|
||||
mysql_exec mysql -e "CREATE DATABASE $(mysql_quote_identifier "$db_name") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
||||
mysql_restore_stream "$db_name" "$file"
|
||||
echo "Przywrócono ${db_name}"
|
||||
done
|
||||
@@ -0,0 +1,186 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
ROUNDCUBE_PLUGIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
ROUNDCUBE_SETTINGS_FILE="${ROUNDCUBE_SETTINGS_FILE:-$ROUNDCUBE_PLUGIN_DIR/plugin-settings.conf}"
|
||||
ROUNDCUBE_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# shellcheck source=./mysql_common.sh
|
||||
source "$ROUNDCUBE_SCRIPT_DIR/mysql_common.sh"
|
||||
mysql_load_plugin_settings_file "$ROUNDCUBE_SETTINGS_FILE"
|
||||
|
||||
roundcube_read_setting() {
|
||||
local key="$1"
|
||||
local default="$2"
|
||||
local line value
|
||||
[ -r "$ROUNDCUBE_SETTINGS_FILE" ] || {
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
}
|
||||
line="$(grep -E "^${key}=" "$ROUNDCUBE_SETTINGS_FILE" | tail -n 1 || true)"
|
||||
[ -n "$line" ] || {
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
}
|
||||
value="${line#*=}"
|
||||
value="${value%%#*}"
|
||||
value="${value%\"}"
|
||||
value="${value#\"}"
|
||||
value="${value%\'}"
|
||||
value="${value#\'}"
|
||||
value="$(printf '%s' "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||||
printf '%s\n' "${value:-$default}"
|
||||
}
|
||||
|
||||
roundcube_enabled() {
|
||||
case "$(roundcube_read_setting ROUNDCUBE_ENABLED true)" in
|
||||
true|yes|on|1) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
roundcube_config_file() {
|
||||
roundcube_read_setting ROUNDCUBE_CONFIG_FILE /var/www/html/roundcube/config/config.inc.php
|
||||
}
|
||||
|
||||
roundcube_custom_config_file() {
|
||||
roundcube_read_setting ROUNDCUBE_CUSTOM_CONFIG_FILE /usr/local/directadmin/custombuild/custom/roundcube/config.inc.php
|
||||
}
|
||||
|
||||
roundcube_write_custombuild_config() {
|
||||
case "$(roundcube_read_setting ROUNDCUBE_WRITE_CUSTOMBUILD_CONFIG true)" in
|
||||
true|yes|on|1) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
roundcube_db_name() {
|
||||
roundcube_read_setting ROUNDCUBE_DB_NAME roundcube
|
||||
}
|
||||
|
||||
roundcube_db_user() {
|
||||
roundcube_read_setting ROUNDCUBE_DB_USER roundcube
|
||||
}
|
||||
|
||||
roundcube_password_file() {
|
||||
roundcube_read_setting ROUNDCUBE_DB_PASSWORD_FILE /usr/local/directadmin/conf/alt-roundcube.conf
|
||||
}
|
||||
|
||||
roundcube_native_my_cnf() {
|
||||
roundcube_read_setting ROUNDCUBE_NATIVE_MY_CNF /usr/local/directadmin/conf/my.cnf
|
||||
}
|
||||
|
||||
roundcube_endpoint_mode() {
|
||||
roundcube_read_setting ROUNDCUBE_ALT_DSN_MODE tcp
|
||||
}
|
||||
|
||||
roundcube_generate_secret() {
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
openssl rand -base64 36 | tr -d '\n'
|
||||
return 0
|
||||
fi
|
||||
tr -dc 'A-Za-z0-9_@%+=' </dev/urandom | head -c 48
|
||||
}
|
||||
|
||||
roundcube_load_or_create_password() {
|
||||
local file user pass group_name tmp
|
||||
file="$(roundcube_password_file)"
|
||||
user="$(roundcube_db_user)"
|
||||
|
||||
if [ -r "$file" ]; then
|
||||
pass="$(awk -F= '$1 == "password" || $1 == "passwd" { sub(/^[^=]*=/, ""); print; exit }' "$file")"
|
||||
if [ -n "$pass" ]; then
|
||||
printf '%s\n' "$pass"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
pass="$(roundcube_generate_secret)"
|
||||
group_name=""
|
||||
if command -v getent >/dev/null 2>&1 && getent group diradmin >/dev/null 2>&1; then
|
||||
group_name="diradmin"
|
||||
elif command -v getent >/dev/null 2>&1 && getent group root >/dev/null 2>&1; then
|
||||
group_name="root"
|
||||
fi
|
||||
tmp="$(mktemp)"
|
||||
{
|
||||
printf 'user=%s\n' "$user"
|
||||
printf 'password=%s\n' "$pass"
|
||||
} > "$tmp"
|
||||
if [ "${EUID:-$(id -u)}" -eq 0 ] && [ -n "$group_name" ]; then
|
||||
install -m 0640 -o root -g "$group_name" "$tmp" "$file"
|
||||
else
|
||||
install -m 0600 "$tmp" "$file"
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
printf '%s\n' "$pass"
|
||||
}
|
||||
|
||||
roundcube_load_password() {
|
||||
local file pass
|
||||
file="$(roundcube_password_file)"
|
||||
[ -r "$file" ] || return 1
|
||||
pass="$(awk -F= '$1 == "password" || $1 == "passwd" { sub(/^[^=]*=/, ""); print; exit }' "$file")"
|
||||
[ -n "$pass" ] || return 1
|
||||
printf '%s\n' "$pass"
|
||||
}
|
||||
|
||||
roundcube_urlencode() {
|
||||
php -r 'echo rawurlencode($argv[1]);' "$1"
|
||||
}
|
||||
|
||||
roundcube_alt_dsn() {
|
||||
local db user pass host port mode encoded_user encoded_pass
|
||||
mysql_load_alt_runtime
|
||||
db="$(roundcube_db_name)"
|
||||
user="$(roundcube_db_user)"
|
||||
pass="$(roundcube_load_or_create_password)"
|
||||
mode="$(roundcube_endpoint_mode)"
|
||||
encoded_user="$(roundcube_urlencode "$user")"
|
||||
encoded_pass="$(roundcube_urlencode "$pass")"
|
||||
|
||||
case "$mode" in
|
||||
socket)
|
||||
printf 'mysql://%s:%s@unix(%s)/%s\n' "$encoded_user" "$encoded_pass" "$MYSQL_ALT_SOCKET" "$db"
|
||||
;;
|
||||
*)
|
||||
host="127.0.0.1"
|
||||
port="$MYSQL_ALT_PORT"
|
||||
printf 'mysql://%s:%s@%s:%s/%s\n' "$encoded_user" "$encoded_pass" "$host" "$port" "$db"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
roundcube_sql_quote() {
|
||||
local value="$1"
|
||||
value="${value//\\/\\\\}"
|
||||
value="${value//\'/\'\'}"
|
||||
printf "'%s'" "$value"
|
||||
}
|
||||
|
||||
roundcube_create_alt_database_and_user() {
|
||||
local db user pass q_user q_pass
|
||||
mysql_load_alt_credentials
|
||||
mysql_ensure_metadata_schema
|
||||
db="$(roundcube_db_name)"
|
||||
user="$(roundcube_db_user)"
|
||||
pass="$(roundcube_load_or_create_password)"
|
||||
q_user="$(roundcube_sql_quote "$user")"
|
||||
q_pass="$(roundcube_sql_quote "$pass")"
|
||||
|
||||
mysql_safe_identifier "$db" >/dev/null
|
||||
mysql_safe_identifier "$user" >/dev/null
|
||||
|
||||
mysql_exec mysql -e "CREATE DATABASE IF NOT EXISTS $(mysql_quote_identifier "$db") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
||||
mysql_exec mysql -e "CREATE USER IF NOT EXISTS ${q_user}@'localhost' IDENTIFIED BY ${q_pass};"
|
||||
mysql_exec mysql -e "ALTER USER ${q_user}@'localhost' IDENTIFIED BY ${q_pass};"
|
||||
mysql_exec mysql -e "CREATE USER IF NOT EXISTS ${q_user}@'127.0.0.1' IDENTIFIED BY ${q_pass};"
|
||||
mysql_exec mysql -e "ALTER USER ${q_user}@'127.0.0.1' IDENTIFIED BY ${q_pass};"
|
||||
mysql_exec mysql -e "GRANT ALL PRIVILEGES ON $(mysql_quote_identifier "$db").* TO ${q_user}@'localhost';"
|
||||
mysql_exec mysql -e "GRANT ALL PRIVILEGES ON $(mysql_quote_identifier "$db").* TO ${q_user}@'127.0.0.1';"
|
||||
mysql_exec mysql -e "
|
||||
INSERT INTO da_plugin_system_databases (service_name, db_name, db_user, endpoint_mode, config_path, enabled)
|
||||
VALUES ('roundcube', '$(mysql_escape_literal "$db")', '$(mysql_escape_literal "$user")', '$(mysql_escape_literal "$(roundcube_endpoint_mode)")', '$(mysql_escape_literal "$(roundcube_config_file)")', 1)
|
||||
ON DUPLICATE KEY UPDATE db_name=VALUES(db_name), db_user=VALUES(db_user), endpoint_mode=VALUES(endpoint_mode), config_path=VALUES(config_path), enabled=VALUES(enabled), updated_at=CURRENT_TIMESTAMP;
|
||||
"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=./roundcube_common.sh
|
||||
source "$SCRIPT_DIR/roundcube_common.sh"
|
||||
|
||||
fail() {
|
||||
echo "roundcube alt-mysql health: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
roundcube_enabled || {
|
||||
echo "roundcube alt-mysql health: disabled"
|
||||
exit 0
|
||||
}
|
||||
|
||||
CONFIG_FILE="$(roundcube_config_file)"
|
||||
DB_NAME="$(roundcube_db_name)"
|
||||
DB_USER="$(roundcube_db_user)"
|
||||
PASS="$(roundcube_load_password)" || fail "Roundcube password file is missing or invalid: $(roundcube_password_file)"
|
||||
mysql_load_alt_credentials
|
||||
mysql_load_alt_runtime
|
||||
|
||||
[ -r "$CONFIG_FILE" ] || fail "config file is not readable: $CONFIG_FILE"
|
||||
grep -Fq "BEGIN DIRECTADMIN MYSQL PLUGIN ROUNDCUBE" "$CONFIG_FILE" || fail "managed Roundcube DB block is missing"
|
||||
grep -Fq "$DB_NAME" "$CONFIG_FILE" || fail "Roundcube config does not reference expected database"
|
||||
if grep -Fq "/var/lib/mysql/mysql.sock" "$CONFIG_FILE"; then
|
||||
fail "Roundcube config references native DirectAdmin socket"
|
||||
fi
|
||||
if grep -Fq "/usr/local/directadmin/conf/mysql.conf" "$CONFIG_FILE"; then
|
||||
fail "Roundcube config references native DirectAdmin mysql.conf"
|
||||
fi
|
||||
|
||||
MYSQL_PWD="$PASS" "$(mysql_alt_binary mysql)" --protocol=TCP --host=127.0.0.1 --port="$MYSQL_ALT_PORT" --user="$DB_USER" --database="$DB_NAME" -e "SELECT 1" >/dev/null \
|
||||
|| fail "cannot connect to Roundcube database on alt-mariadb as $DB_USER"
|
||||
|
||||
echo "roundcube alt-mysql health: OK"
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=./roundcube_common.sh
|
||||
source "$SCRIPT_DIR/roundcube_common.sh"
|
||||
|
||||
FORCE="${FORCE:-0}"
|
||||
SKIP_NATIVE_DUMP="${SKIP_NATIVE_DUMP:-0}"
|
||||
LOG_FILE="${LOG_FILE:-$ROUNDCUBE_PLUGIN_DIR/data/logs/roundcube-migrate.log}"
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
log() {
|
||||
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
die() {
|
||||
log "ERROR: $*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
roundcube_enabled || die "Roundcube integration is disabled by ROUNDCUBE_ENABLED."
|
||||
|
||||
DB_NAME="$(roundcube_db_name)"
|
||||
NATIVE_MY_CNF="$(roundcube_native_my_cnf)"
|
||||
TMP_DUMP="$(mktemp)"
|
||||
trap 'rm -f "$TMP_DUMP"' EXIT
|
||||
|
||||
roundcube_create_alt_database_and_user
|
||||
|
||||
if [ "$SKIP_NATIVE_DUMP" != "1" ]; then
|
||||
[ -r "$NATIVE_MY_CNF" ] || die "Native DirectAdmin MySQL config not readable: $NATIVE_MY_CNF"
|
||||
NATIVE_MYSQL_BIN="$(mysql_native_binary mysql)" || die "native mysql/mariadb client not found"
|
||||
NATIVE_MYSQLDUMP_BIN="$(mysql_native_binary mysqldump)" || die "native mysqldump/mariadb-dump client not found"
|
||||
|
||||
if ! "$NATIVE_MYSQL_BIN" --defaults-extra-file="$NATIVE_MY_CNF" --batch --skip-column-names information_schema \
|
||||
-e "SELECT 1 FROM SCHEMATA WHERE SCHEMA_NAME='$(mysql_escape_literal "$DB_NAME")' LIMIT 1" | grep -q 1; then
|
||||
if [ "$FORCE" != "1" ]; then
|
||||
die "Native Roundcube database $DB_NAME not found. Use SKIP_NATIVE_DUMP=1 if this is a new Roundcube install."
|
||||
fi
|
||||
log "Native Roundcube database $DB_NAME not found; continuing because FORCE=1."
|
||||
else
|
||||
log "Dumping native Roundcube database: $DB_NAME"
|
||||
"$NATIVE_MYSQLDUMP_BIN" --defaults-extra-file="$NATIVE_MY_CNF" \
|
||||
--single-transaction \
|
||||
--quick \
|
||||
--skip-lock-tables \
|
||||
--routines \
|
||||
--triggers \
|
||||
--events \
|
||||
--default-character-set=utf8mb4 \
|
||||
"$DB_NAME" > "$TMP_DUMP" 2>>"$LOG_FILE" || die "native Roundcube dump failed"
|
||||
|
||||
log "Importing Roundcube database into alt-mariadb: $DB_NAME"
|
||||
mysql_exec mysql -e "DROP DATABASE IF EXISTS $(mysql_quote_identifier "$DB_NAME");"
|
||||
mysql_exec mysql -e "CREATE DATABASE $(mysql_quote_identifier "$DB_NAME") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
||||
mysql_exec "$DB_NAME" < "$TMP_DUMP" >> "$LOG_FILE" 2>&1 || die "Roundcube import into alt-mariadb failed"
|
||||
roundcube_create_alt_database_and_user
|
||||
fi
|
||||
fi
|
||||
|
||||
"$SCRIPT_DIR/roundcube_repair_config.sh"
|
||||
"$SCRIPT_DIR/roundcube_health_check.sh"
|
||||
|
||||
log "Roundcube migration to alt-mariadb completed."
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=./roundcube_common.sh
|
||||
source "$SCRIPT_DIR/roundcube_common.sh"
|
||||
|
||||
rewrite_roundcube_config() {
|
||||
local source_file="$1"
|
||||
local target_file="$2"
|
||||
local dsn="$3"
|
||||
local tmp parent
|
||||
|
||||
[ -f "$source_file" ] || {
|
||||
echo "roundcube: source config file not found: $source_file" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
if [ -e "$target_file" ]; then
|
||||
[ -w "$target_file" ] || {
|
||||
echo "roundcube: config file is not writable: $target_file" >&2
|
||||
return 1
|
||||
}
|
||||
else
|
||||
parent="$(dirname "$target_file")"
|
||||
mkdir -p "$parent"
|
||||
fi
|
||||
|
||||
tmp="$(mktemp)"
|
||||
awk '
|
||||
/BEGIN DIRECTADMIN MYSQL PLUGIN ROUNDCUBE/ { skip=1; next }
|
||||
/END DIRECTADMIN MYSQL PLUGIN ROUNDCUBE/ { skip=0; next }
|
||||
skip != 1 { print }
|
||||
' "$source_file" > "$tmp"
|
||||
|
||||
cat >> "$tmp" <<PHP
|
||||
|
||||
// BEGIN DIRECTADMIN MYSQL PLUGIN ROUNDCUBE
|
||||
\$config['db_dsnw'] = '$dsn';
|
||||
// END DIRECTADMIN MYSQL PLUGIN ROUNDCUBE
|
||||
PHP
|
||||
|
||||
if [ -e "$target_file" ]; then
|
||||
cat "$tmp" > "$target_file"
|
||||
else
|
||||
install -m 0644 "$tmp" "$target_file"
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
roundcube_enabled || {
|
||||
echo "roundcube: integration disabled by ROUNDCUBE_ENABLED."
|
||||
exit 0
|
||||
}
|
||||
|
||||
CONFIG_FILE="$(roundcube_config_file)"
|
||||
[ -f "$CONFIG_FILE" ] || {
|
||||
echo "roundcube: config file not found: $CONFIG_FILE" >&2
|
||||
exit 1
|
||||
}
|
||||
[ -w "$CONFIG_FILE" ] || {
|
||||
echo "roundcube: config file is not writable: $CONFIG_FILE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
DSN="$(roundcube_alt_dsn)"
|
||||
rewrite_roundcube_config "$CONFIG_FILE" "$CONFIG_FILE" "$DSN"
|
||||
|
||||
echo "roundcube: config repaired: $CONFIG_FILE"
|
||||
|
||||
if roundcube_write_custombuild_config; then
|
||||
CUSTOM_CONFIG_FILE="$(roundcube_custom_config_file)"
|
||||
if [ -f "$CUSTOM_CONFIG_FILE" ]; then
|
||||
rewrite_roundcube_config "$CUSTOM_CONFIG_FILE" "$CUSTOM_CONFIG_FILE" "$DSN"
|
||||
else
|
||||
rewrite_roundcube_config "$CONFIG_FILE" "$CUSTOM_CONFIG_FILE" "$DSN"
|
||||
fi
|
||||
echo "roundcube: CustomBuild config repaired: $CUSTOM_CONFIG_FILE"
|
||||
fi
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CPIF="/usr/local/directadmin/data/admin/custom_package_items.conf"
|
||||
SUDOERS_FILE="/etc/sudoers.d/directadmin-alt-mysql-plugin"
|
||||
CRON_FILE="/etc/cron.d/alt-mysql-jobs"
|
||||
CSF_ALLOW_FILE="/etc/csf/csf.allow"
|
||||
CSF_MANAGED_ALLOW_FILE="/etc/csf/alt-mysql-plugin.allow"
|
||||
DA_INTEGRATION_INSTALLER="$(cd "$(dirname "$0")/.." && pwd)/scripts/setup/da_integration_install.sh"
|
||||
CUSTOMBUILD_HOOKS_INSTALLER="$(cd "$(dirname "$0")/.." && pwd)/scripts/setup/custombuild_hooks_install.sh"
|
||||
|
||||
read_plugin_setting() {
|
||||
local key="$1"
|
||||
local default="$2"
|
||||
local file="$PLUGIN_DIR/plugin-settings.conf"
|
||||
local line value
|
||||
|
||||
[ -r "$file" ] || {
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
}
|
||||
|
||||
line="$(grep -E "^${key}=" "$file" | tail -n 1 || true)"
|
||||
if [ -z "$line" ]; then
|
||||
printf '%s\n' "$default"
|
||||
return 0
|
||||
fi
|
||||
|
||||
value="${line#*=}"
|
||||
value="${value%%#*}"
|
||||
value="${value%\"}"
|
||||
value="${value#\"}"
|
||||
value="${value%\'}"
|
||||
value="${value#\'}"
|
||||
value="$(printf '%s' "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||||
printf '%s\n' "${value:-$default}"
|
||||
}
|
||||
|
||||
cleanup_csf_managed_file() {
|
||||
local managed_file="$1"
|
||||
local tmp include_line changed=0
|
||||
|
||||
include_line="Include $managed_file"
|
||||
|
||||
if [ -f "$CSF_ALLOW_FILE" ]; then
|
||||
tmp="$(mktemp)"
|
||||
grep -Fxv "$include_line" "$CSF_ALLOW_FILE" > "$tmp" || true
|
||||
if ! cmp -s "$tmp" "$CSF_ALLOW_FILE"; then
|
||||
cat "$tmp" > "$CSF_ALLOW_FILE"
|
||||
changed=1
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
fi
|
||||
|
||||
if [ -f "$managed_file" ]; then
|
||||
rm -f "$managed_file"
|
||||
changed=1
|
||||
fi
|
||||
|
||||
return "$changed"
|
||||
}
|
||||
|
||||
cleanup_csf_remote_mysql() {
|
||||
local changed=0 configured_managed_allow
|
||||
|
||||
CSF_ALLOW_FILE="$(read_plugin_setting MYSQL_PLUGIN_CSF_ALLOW_FILE "$CSF_ALLOW_FILE")"
|
||||
configured_managed_allow="$(read_plugin_setting MYSQL_PLUGIN_CSF_MANAGED_ALLOW_FILE "$CSF_MANAGED_ALLOW_FILE")"
|
||||
|
||||
cleanup_csf_managed_file "$configured_managed_allow" || changed=1
|
||||
|
||||
if [ "$changed" -eq 1 ] && command -v csf >/dev/null 2>&1; then
|
||||
csf -r >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
if [ -f "$CPIF" ]; then
|
||||
sed -i '/^mysql_enabled=/d' "$CPIF" || true
|
||||
sed -i '/^mysql=/d' "$CPIF" || true
|
||||
sed -i '/^umysql=/d' "$CPIF" || true
|
||||
sed -i '/^mysql_max_databases=/d' "$CPIF" || true
|
||||
sed -i '/^mysql_unlimited=/d' "$CPIF" || true
|
||||
sed -i '/^umysql_max_databases=/d' "$CPIF" || true
|
||||
chown diradmin:diradmin "$CPIF" 2>/dev/null || true
|
||||
chmod 640 "$CPIF" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
rm -f "$SUDOERS_FILE" "$CRON_FILE" || true
|
||||
cleanup_csf_remote_mysql
|
||||
if [ -x "$DA_INTEGRATION_INSTALLER" ]; then
|
||||
"$DA_INTEGRATION_INSTALLER" uninstall || true
|
||||
fi
|
||||
if [ -x "$CUSTOMBUILD_HOOKS_INSTALLER" ]; then
|
||||
"$CUSTOMBUILD_HOOKS_INSTALLER" uninstall || true
|
||||
fi
|
||||
else
|
||||
echo "mysql: warning: uruchom jako root, aby usunąć wpisy z custom_package_items i sudoers" >&2
|
||||
fi
|
||||
|
||||
if [ -f "$PLUGIN_DIR/plugin.conf" ]; then
|
||||
sed -i 's/^active=.*/active=no/' "$PLUGIN_DIR/plugin.conf" || true
|
||||
sed -i 's/^installed=.*/installed=no/' "$PLUGIN_DIR/plugin.conf" || true
|
||||
fi
|
||||
|
||||
echo "mysql: deaktywowany."
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
"$(cd "$(dirname "$0")" && pwd)/install.sh"
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA_DIR="$PLUGIN_DIR/data"
|
||||
JOB_DIR="$DATA_DIR/jobs/pending"
|
||||
PROCESSING_DIR="$DATA_DIR/jobs/processing"
|
||||
DONE_DIR="$DATA_DIR/jobs/done"
|
||||
WORKER_LOCK_DIR="$DATA_DIR/worker.lock"
|
||||
DB_LOCK_DIR="$DATA_DIR/lock"
|
||||
PID_DIR="$DATA_DIR/pids"
|
||||
CANCEL_DIR="$DATA_DIR/cancel"
|
||||
|
||||
mkdir -p "$JOB_DIR" "$PROCESSING_DIR" "$DONE_DIR" "$PID_DIR" "$CANCEL_DIR" "$DB_LOCK_DIR"
|
||||
|
||||
if ! mkdir "$WORKER_LOCK_DIR" 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
rmdir "$WORKER_LOCK_DIR" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
db_lock_path_for_db() {
|
||||
local db_name="$1"
|
||||
local safe
|
||||
safe="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_.-' '_')"
|
||||
printf '%s/%s.lock' "$DB_LOCK_DIR" "$safe"
|
||||
}
|
||||
|
||||
release_db_lock_later() {
|
||||
local lock_path="$1"
|
||||
if [ -z "$lock_path" ]; then
|
||||
return 0
|
||||
fi
|
||||
(
|
||||
sleep 60
|
||||
rm -f "$lock_path" 2>/dev/null || true
|
||||
) >/dev/null 2>&1 &
|
||||
}
|
||||
|
||||
while true; do
|
||||
JOB_FILE="$(ls -1 "$JOB_DIR"/*.env 2>/dev/null | head -n 1 || true)"
|
||||
if [ -z "${JOB_FILE:-}" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
BASE_NAME="$(basename "$JOB_FILE")"
|
||||
RUN_FILE="$PROCESSING_DIR/$BASE_NAME"
|
||||
mv "$JOB_FILE" "$RUN_FILE"
|
||||
JOB_ID="${BASE_NAME%.env}"
|
||||
PID_FILE="$PID_DIR/${JOB_ID}.pid"
|
||||
|
||||
JOB_TYPE="restore"
|
||||
DB_NAME=""
|
||||
DB_LOCK_FILE=""
|
||||
# shellcheck disable=SC1090
|
||||
source "$RUN_FILE" || true
|
||||
JOB_TYPE="${JOB_TYPE:-restore}"
|
||||
DB_NAME="${DB_NAME:-}"
|
||||
DB_LOCK_FILE="${DB_LOCK_FILE:-}"
|
||||
|
||||
LOCK_PATH=""
|
||||
if [ -n "$DB_LOCK_FILE" ]; then
|
||||
case "$DB_LOCK_FILE" in
|
||||
"$DB_LOCK_DIR"/*.lock) LOCK_PATH="$DB_LOCK_FILE" ;;
|
||||
*) LOCK_PATH="" ;;
|
||||
esac
|
||||
fi
|
||||
if [ -z "$LOCK_PATH" ] && [ -n "$DB_NAME" ]; then
|
||||
LOCK_PATH="$(db_lock_path_for_db "$DB_NAME")"
|
||||
fi
|
||||
|
||||
RUNNER="$PLUGIN_DIR/scripts/restore_job.sh"
|
||||
if [ "$JOB_TYPE" = "backup" ]; then
|
||||
RUNNER="$PLUGIN_DIR/scripts/backup_job.sh"
|
||||
fi
|
||||
|
||||
set +e
|
||||
"$RUNNER" "$RUN_FILE" &
|
||||
RUN_PID=$!
|
||||
echo "PID=${RUN_PID}" >> "$RUN_FILE"
|
||||
echo "${RUN_PID}" > "$PID_FILE"
|
||||
wait "$RUN_PID"
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
rm -f "$PID_FILE"
|
||||
|
||||
CANCEL_FLAG="${CANCEL_DIR}/${JOB_ID}.flag"
|
||||
if [ -f "$CANCEL_FLAG" ]; then
|
||||
rm -f "$CANCEL_FLAG"
|
||||
mv "$RUN_FILE" "$DONE_DIR/${JOB_ID}.cancel"
|
||||
elif [ "$EXIT_CODE" -eq 0 ]; then
|
||||
mv "$RUN_FILE" "$DONE_DIR/${JOB_ID}.ok"
|
||||
else
|
||||
mv "$RUN_FILE" "$DONE_DIR/${JOB_ID}.fail"
|
||||
fi
|
||||
|
||||
release_db_lock_later "$LOCK_PATH"
|
||||
done
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user