Скрипт backup mysql 8.4 для freebsd 14

Itnull

Команда форума
Администратор
Скрипт backup mysql 8.4 для freebsd 14
  • set -eu
  • защитой от параллельного запуска через lockf
  • trap для очистки временного файла
  • проверкой доступности MySQL
  • проверкой доступа к конкретной БД
  • проверкой свободного места
  • ротацией по количеству файлов
  • аккуратным логированием

Bash:
#!/bin/sh
set -eu

# ===== НАСТРОЙКИ =====
DB_NAME="mydatabase"
DB_HOST="127.0.0.1"
DB_PORT="3306"
DB_USER="backup_user"
DB_PASS="strong_password"

BACKUP_DIR="/var/backups/mysql"
LOG_FILE="/var/log/mysql_backup.log"
LOCK_FILE="/var/run/mysql_backup_${DB_NAME}.lock"

KEEP_FILES=7

# Минимум свободного места в KB.
# Например 1048576 = 1 GB
MIN_FREE_KB=1048576

DATE="$(date +"%Y-%m-%d_%H-%M-%S")"
TMP_FILE=""
FINAL_FILE=""
ARCHIVE_FILE=""

MYSQL="/usr/local/bin/mysql"
MYSQLADMIN="/usr/local/bin/mysqladmin"
MYSQLDUMP="/usr/local/bin/mysqldump"
GZIP="/usr/bin/gzip"
DF="/bin/df"
AWK="/usr/bin/awk"
LS="/bin/ls"
TAIL="/usr/bin/tail"
RM="/bin/rm"
MKDIR="/bin/mkdir"
LOCKF="/usr/bin/lockf"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
}

cleanup() {
    rc=$?
    if [ -n "${TMP_FILE}" ] && [ -f "${TMP_FILE}" ]; then
        $RM -f "${TMP_FILE}" || true
        log "Temporary file removed: ${TMP_FILE}"
    fi
    exit $rc
}

fail() {
    log "ERROR: $*"
    exit 1
}

check_binaries() {
    [ -x "$MYSQL" ] || fail "mysql not found: $MYSQL"
    [ -x "$MYSQLADMIN" ] || fail "mysqladmin not found: $MYSQLADMIN"
    [ -x "$MYSQLDUMP" ] || fail "mysqldump not found: $MYSQLDUMP"
    [ -x "$GZIP" ] || fail "gzip not found: $GZIP"
    [ -x "$DF" ] || fail "df not found: $DF"
    [ -x "$AWK" ] || fail "awk not found: $AWK"
    [ -x "$LS" ] || fail "ls not found: $LS"
    [ -x "$TAIL" ] || fail "tail not found: $TAIL"
    [ -x "$RM" ] || fail "rm not found: $RM"
    [ -x "$MKDIR" ] || fail "mkdir not found: $MKDIR"
    [ -x "$LOCKF" ] || fail "lockf not found: $LOCKF"
}

check_free_space() {
    FREE_KB=$($DF -k "$BACKUP_DIR" | $AWK 'NR==2 {print $4}')
    [ -n "$FREE_KB" ] || fail "cannot determine free disk space for $BACKUP_DIR"

    case "$FREE_KB" in
        ''|*[!0-9]*)
            fail "invalid free space value: $FREE_KB"
            ;;
    esac

    if [ "$FREE_KB" -lt "$MIN_FREE_KB" ]; then
        fail "not enough free disk space in $BACKUP_DIR: ${FREE_KB} KB available, required at least ${MIN_FREE_KB} KB"
    fi
}

check_mysql() {
    "$MYSQLADMIN" \
        --host="$DB_HOST" \
        --port="$DB_PORT" \
        --user="$DB_USER" \
        --password="$DB_PASS" \
        ping >/dev/null 2>>"$LOG_FILE" || fail "MySQL is unavailable on ${DB_HOST}:${DB_PORT}"

    "$MYSQL" \
        --host="$DB_HOST" \
        --port="$DB_PORT" \
        --user="$DB_USER" \
        --password="$DB_PASS" \
        --batch --skip-column-names \
        -e "USE \`${DB_NAME}\`; SELECT 1;" >/dev/null 2>>"$LOG_FILE" || fail "cannot access database ${DB_NAME}"
}

rotate_backups() {
    FILES=$($LS -1t "${BACKUP_DIR}/${DB_NAME}"_*.sql.gz 2>/dev/null || true)

    if [ -n "$FILES" ]; then
        echo "$FILES" | $TAIL -n +$((KEEP_FILES + 1)) | while IFS= read -r oldfile; do
            [ -n "$oldfile" ] || continue
            $RM -f "$oldfile" && log "Old backup removed: $oldfile"
        done
    fi
}

do_backup() {
    trap cleanup EXIT HUP INT TERM

    $MKDIR -p "$BACKUP_DIR" || fail "cannot create backup dir: $BACKUP_DIR"

    check_binaries
    check_free_space
    check_mysql

    FINAL_FILE="${BACKUP_DIR}/${DB_NAME}_${DATE}.sql"
    TMP_FILE="${FINAL_FILE}.tmp"
    ARCHIVE_FILE="${FINAL_FILE}.gz"

    log "Backup started for database: ${DB_NAME}"

    "$MYSQLDUMP" \
        --host="$DB_HOST" \
        --port="$DB_PORT" \
        --user="$DB_USER" \
        --password="$DB_PASS" \
        --single-transaction \
        --quick \
        --routines \
        --triggers \
        --events \
        --hex-blob \
        --default-character-set=utf8mb4 \
        "$DB_NAME" > "$TMP_FILE" 2>>"$LOG_FILE" || fail "mysqldump failed for ${DB_NAME}"

    mv "$TMP_FILE" "$FINAL_FILE" || fail "cannot move temporary dump to final file"

    "$GZIP" "$FINAL_FILE" 2>>"$LOG_FILE" || fail "gzip failed for ${FINAL_FILE}"

    log "Backup created: ${ARCHIVE_FILE}"

    rotate_backups

    log "Backup finished for database: ${DB_NAME}"
}

if [ "${1:-}" = "--run" ]; then
    do_backup
    exit 0
fi

$LOCKF -s -t 0 "$LOCK_FILE" "$0" --run
STATUS=$?

if [ "$STATUS" -eq 0 ]; then
    exit 0
fi

log "Backup skipped: another instance is already running"
exit 0

Что здесь добавлено​

set -eu
  • -e завершает скрипт при ошибке команды
  • -u завершает скрипт при обращении к неинициализированной переменной

trap cleanup​

Если скрипт упадёт посередине, временный .tmp будет удалён.


Временный файл​

Сначала дамп пишется в:


mydatabase_2026-03-11_03-00-00.sql.tmp

и только после успешного завершения переименовывается в .sql. Это защищает от битых полуготовых файлов.


Проверка свободного места​

Перед стартом проверяется, что в файловой системе каталога бэкапов есть хотя бы MIN_FREE_KB.

--quick

Для крупных баз это правильнее: mysqldump выгружает строки потоком и меньше нагружает RAM.

Cron
Код:
0 3 * * * /usr/local/bin/mysql_backup_db.sh

Рекомендация по безопасности​


Лучше убрать пароль из скрипта вообще и использовать ~/.my.cnf для пользователя cron:
Код:
[client]
user=backup_user
password=strong_password
host=127.0.0.1
port=3306

Права:
chmod 600 ~/.my.cnf

Тогда из скрипта можно удалить:
Bash:
DB_HOST="127.0.0.1"
DB_PORT="3306"
DB_USER="backup_user"
DB_PASS="strong_password"

и заменить вызовы на такие:
Bash:
"$MYSQLADMIN" ping
"$MYSQL" --batch --skip-column-names -e "USE \`${DB_NAME}\`; SELECT 1;"
"$MYSQLDUMP" --single-transaction --quick --routines --triggers --events --hex-blob --default-character-set=utf8mb4 "$DB_NAME"

Практическая доработка​


Если хочешь избежать ситуации, когда лог растёт бесконечно, можно вынести логирование в syslog через logger вместо файла. Тогда скрипт будет ещё аккуратнее для production.
 

Создайте аккаунт или войдите в систему, чтобы комментировать

Вы должны быть зарегистрированным, чтобы оставить комментарий

Создать аккаунт

Создайте аккаунт в нашем сообществе.

Войти

У вас уже есть аккаунт? Войдите здесь.

Назад
Сверху Снизу