Скрипт для удобного бекапирования системы на PBS

Да, все верно, ошибка в скрипте, спасибо за найденный баг. Я поправил в после, не использовал эту переменную, поэтому не заметил ошибку.

В качестве логического продолжения темы выкладываю описание сценраия восстанволения конфигурации PVE из бэкапа.

:memo: Вводные:

  1. Сценарий описывает восстановление из бэкапа для однонодовой архитектуры single node (без кластера)
  2. Исходный скрипт pbs_backup.sh автора статьи был модифицирован (все изменения сопровождены комментариями)
  3. Скорректированы настройки в pbs_backup_settings
  4. В моём случае файлы с настройками и скриптами расположены в директории: /root/pbs/scripts
  5. Команды выполняются с учётом использования Namespace=“hosts”, в соответствии с исходной статьёй Романа. При необходимости используйте свои значения.

:inbox_tray: Бэкапирование

:gear: Изменение директорий для бэкапирования в pbs_backup_settings
Из исходной конфигурации автора удалил corosync.pxar:/etc/pve, т.к. буду бэкапить конкретные файлы из этой директории, а не всю директорию.
Целевой вариант:

BACKUPS="\
etc.pxar:/etc \
crontab.pxar:/var/spool/cron/ \
pmxcfs-SQLite-PVE.pxar:/root/pbs/pmxcfs \
etc-pve-configs.pxar:/root/pbs/etc-pve \
scripts.pxar:/root/pbs/scripts \
"

Где:

# etc.pxar:/etc                                  # Все системные конфигурации
# crontab.pxar:/root/pbs/cron                    # Конфиг crontab
# pmxcfs-SQLite-PVE.pxar:/root/pbs/pmxcfs        # БД PVE SQLite (pmxcfs)
# etc-pve-configs_.pxar:/root/pbs/etc-pve        # Конфиги storage, datacenter, user (для просмотра, не для recovery)
# scripts.pxar:/root/pbs/scripts                 # Скрипты для PBS

:gear: Изменение скрипта pbs_backup.sh
Перед командами экспорта содержимого директорий $BACKUPS в PBS добавлено:

# PVE database and "/etc/pve" configs backup
mkdir -p /root/pbs/{pmxcfs,scripts,etc-pve}
cp -a /etc/pve/{storage.cfg,datacenter.cfg,user.cfg} /root/pbs/etc-pve/
sqlite3 /var/lib/pve-cluster/config.db ".backup /root/pbs/pmxcfs/config.db"

Что изменилось:

  1. mkdir - создаём структуру директорий, которая соответствует переменной $BACKUPS
  2. cp -a - копируем файлы конфигурации (storage.cfg,datacenter.cfg,user.cfg) из /etc/pve
  3. sqlite3 - делаем бэкап настроек PVE из БД SQLite штатным средствами SQLite

После процедуры экспорта бэкапов в PBS добавлено удаление временных директорий:

# Clearing the temporary directory with configs
rm -rf /root/pbs/{etc-pve,pmxcfs}

Итоговый скрипт

#!/bin/bash
set -euo pipefail

# Function to check files
check_files() {
    for f in "$@"; do
        if [[ ! -f "$f" ]]; then
            echo "Error: file not found: $f" >&2
            exit 1
        fi
    done
}

# Function to check environment variables
check_env() {
    for var in "$@"; do
        if [[ -z "${!var:-}" ]]; then
            echo "Error: environment variable not set: $var" >&2
            exit 1
        fi
    done
}

dir=$(dirname $0)

check_files "$dir/pbs_backup_secrets" "$dir/pbs_backup_settings"

. $dir/pbs_backup_secrets
. $dir/pbs_backup_settings

check_env "BACKUPS" "PBS_REPOSITORY" "PBS_PASSWORD" "PBS_FINGERPRINT" "DETECTION_MODE"

if [[ -z "$BACKUP_ID" ]]; then
        BACKUP_ID=$(hostname)
fi

ts=$(date -u +%s)
iso=$(date -u -d "@$ts" +"%Y-%m-%dT%H:%M:%SZ")
SNAPSHOT="host/${BACKUP_ID}/${iso}"
if [ -n "$NS" ]; then
        ns_op="--ns ${NS}"
else
        ns_op=""
fi

excludes=""
for item in $EXCLUDE ; do
        excludes="${excludes} --exclude ${item}"
done

BACKUPS="$BACKUPS pbs_backup.conf:$dir/pbs_backup_settings"

export PBS_PASSWORD
export PBS_FINGERPRINT

# PVE database and "/etc/pve" configs backup
mkdir -p /root/pbs/{pmxcfs,scripts,etc-pve}
cp -a /etc/pve/{storage.cfg,datacenter.cfg,user.cfg} /root/pbs/etc-pve/
sqlite3 /var/lib/pve-cluster/config.db ".backup /root/pbs/pmxcfs/config.db"

$PBC \
        backup \
        $BACKUPS \
        $excludes \
        --backup-time ${ts} \
        --backup-id $BACKUP_ID \
        ${ns_op} \
        --change-detection-mode $DETECTION_MODE \
        --repository $PBS_REPOSITORY \
        $EXTRA_ARGS \
                > $LOG_FILE 2>&1

# Clearing the temporary directory with configs
rm -rf /root/pbs/{etc-pve,pmxcfs}

if [[ -n "$LOG_FILE" ]]; then
        $PBC snapshot upload-log $SNAPSHOT $LOG_FILE ${ns_op} --repository ${PBS_REPOSITORY} >/dev/null 2>&1
fi

if [[ -n "$NOTES" ]]; then
        $PBC snapshot notes update $SNAPSHOT $NOTES ${ns_op} --repository ${PBS_REPOSITORY} >/dev/null 2>&1
fi

:counterclockwise_arrows_button: Восстановление

:small_blue_diamond: Подготовить директории для монтирования

mkdir -p /mnt/restore/{etc,pmxcfs,etc-pve,scripts}
mkdir -p /root/pbs/scripts

:small_blue_diamond: Задать параметры PBS-репозитория для текущей сессии в shell

export PBS_REPOSITORY="YOUR_PBS_REPOSITORY"
export PBS_NAMESPACE="hosts"

:small_blue_diamond: Проверить присвоение переменных

echo "PBS_REPOSITORY=$PBS_REPOSITORY / PBS_NAMESPACE=$PBS_NAMESPACE"
Пример вывода:
PBS_REPOSITORY=root@pam@192.168.1.198:backups / PBS_NAMESPACE=hosts

:small_blue_diamond: Вывести список бэкапов

proxmox-backup-client snapshot list --ns hosts
Пример вывода:
Password for "root@pam": ******
fingerprint: 83:cc:c3:63:c1:dd:12:fd:83:3b:f1:47:bc:47:f2:03:90:0d:f8:bf:e0:75:e0:2e:15:4a:97:bb:87:5d:16:ed
Are you sure you want to continue connecting? (y/n): y
fingerprint: 83:cc:c3:63:c1:dd:12:fd:83:3b:f1:47:bc:47:f2:03:90:0d:f8:bf:e0:75:e0:2e:15:4a:97:bb:87:5d:16:ed
Are you sure you want to continue connecting? (y/n): y
 y
┌───────────────────────────────────┬───────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ snapshot                          │      size │ files                                                                                                       
╞═══════════════════════════════════╪═══════════╪═════════════════════════════════════════════════════════════════════════════════════════════════════════════
│ host/sandbox/2026-03-22T08:35:09Z │ 2.469 MiB │ client.log etc-pve-configs.mpxar etc-pve-configs.ppxar etc.mpxar etc.ppxar index.json pbs_backup.conf pmxcfs
├───────────────────────────────────┼───────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ host/sandbox/2026-03-22T09:03:45Z │ 2.469 MiB │ client.log etc-pve-configs.mpxar etc-pve-configs.ppxar etc.mpxar etc.ppxar index.json pbs_backup.conf pmxcfs
├───────────────────────────────────┼───────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ host/sandbox/2026-03-22T09:03:47Z │ 2.469 MiB │ client.log etc-pve-configs.mpxar etc-pve-configs.ppxar etc.mpxar etc.ppxar index.json pbs_backup.conf pmxcfs
├───────────────────────────────────┼───────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ host/sandbox/2026-03-22T09:05:18Z │ 2.469 MiB │ client.log etc-pve-configs.mpxar etc-pve-configs.ppxar etc.mpxar etc.ppxar index.json pbs_backup.conf pmxcfs
├───────────────────────────────────┼───────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ host/sandbox/2026-03-22T09:05:19Z │  2.41 MiB │ client.log etc-pve-configs.mpxar etc-pve-configs.ppxar etc.mpxar etc.ppxar index.json pbs_backup.conf pmxcfs
├───────────────────────────────────┼───────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ host/sandbox/2026-03-22T10:09:43Z │ 2.396 MiB │ client.log etc-pve-configs.mpxar etc-pve-configs.ppxar etc.mpxar etc.ppxar index.json pbs_backup.conf pmxcfs
├───────────────────────────────────┼───────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ host/sandbox/2026-03-22T16:59:19Z │ 2.398 MiB │ client.log crontab.mpxar crontab.ppxar etc-pve-configs.mpxar etc-pve-configs.ppxar etc.mpxar etc.ppxar index
└───────────────────────────────────┴───────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────

:small_blue_diamond: Вывести список файлов в целевом бэкаппе, чтобы убедиться что бэкап читается (необязательный шаг)

proxmox-backup-client snapshot files host/sandbox/2026-03-22T16:59:19Z --ns hosts
Пример вывода:
┌──────────────────────────────┬────────────┬─────────┐
│ filename                     │ crypt-mode │    size │
╞══════════════════════════════╪════════════╪═════════╡
│ client.log.blob              │            │         │
├──────────────────────────────┼────────────┼─────────┤
│ crontab.mpxar.didx           │ none       │     398 │
├──────────────────────────────┼────────────┼─────────┤
│ crontab.ppxar.didx           │ none       │    1182 │
├──────────────────────────────┼────────────┼─────────┤
│ etc-pve-configs.mpxar.didx   │ none       │     540 │
├──────────────────────────────┼────────────┼─────────┤
│ etc-pve-configs.ppxar.didx   │ none       │    1082 │
├──────────────────────────────┼────────────┼─────────┤
│ etc.mpxar.didx               │ none       │  249446 │
├──────────────────────────────┼────────────┼─────────┤
│ etc.ppxar.didx               │ none       │ 2192132 │
├──────────────────────────────┼────────────┼─────────┤
│ index.json.blob              │ none       │     861 │
├──────────────────────────────┼────────────┼─────────┤
│ pbs_backup.conf.blob         │ none       │     834 │
├──────────────────────────────┼────────────┼─────────┤
│ pmxcfs-SQLite-PVE.mpxar.didx │ none       │     258 │
├──────────────────────────────┼────────────┼─────────┤
│ pmxcfs-SQLite-PVE.ppxar.didx │ none       │   61488 │
├──────────────────────────────┼────────────┼─────────┤
│ scripts.mpxar.didx           │ none       │     707 │
├──────────────────────────────┼────────────┼─────────┤
│ scripts.ppxar.didx           │ none       │    5282 │
└──────────────────────────────┴────────────┴─────────┘

:small_blue_diamond: Смонтировать архивы из PBS в PVE (вместо демонстрационного названия бэкапа host/sandbox/2026-03-22T16:59:19Z укажите свой в соответствии с информацией из пунктов выше)

proxmox-backup-client mount host/sandbox/2026-03-22T16:59:19Z etc.pxar /mnt/restore/etc --ns hosts
proxmox-backup-client mount host/sandbox/2026-03-22T16:59:19Z crontab.pxar /mnt/restore/cron --ns hosts
proxmox-backup-client mount host/sandbox/2026-03-22T16:59:19Z pmxcfs-SQLite-PVE.pxar /mnt/restore/pmxcfs --ns hosts
proxmox-backup-client mount host/sandbox/2026-03-22T16:59:19Z etc-pve-configs.pxar /mnt/restore/etc-pve --ns hosts
proxmox-backup-client mount host/sandbox/2026-03-22T16:59:19Z scripts.pxar /mnt/restore/scripts --ns hosts
Пример вывода:
FUSE library version: 3.17.2
FUSE library version: 3.17.2
FUSE library version: 3.17.2
FUSE library version: 3.17.2
FUSE library version: 3.17.2

:small_blue_diamond: Остановить сервис кластера Proxmox VE

systemctl stop pve-cluster

:small_blue_diamond: Восстановить файлы конфигурации PVE из бэкапа

rsync -aHAX --numeric-ids /mnt/restore/etc/ /etc/
rsync -aHAX --numeric-ids /mnt/restore/cron/ /var/spool/cron/
rsync -aHAX --numeric-ids /mnt/restore/pmxcfs/ /var/lib/pve-cluster/
rsync -aHAX --numeric-ids /mnt/restore/scripts/ /root/pbs/scripts/

:small_blue_diamond: Запустить сервис кластера Proxmox VE

systemctl start pve-cluster

:small_blue_diamond: Проверить сервис кластера Proxmox VE

systemctl status pve-cluster
Пример вывода:
● pve-cluster.service - The Proxmox VE cluster filesystem
     Loaded: loaded (/usr/lib/systemd/system/pve-cluster.service; enabled; preset: enabled)
     Active: active (running) since Sun 2026-03-22 20:17:42 MSK; 3s ago
 Invocation: f97a24b3f64049f8b4484838e6733b82
    Process: 2471 ExecStart=/usr/bin/pmxcfs (code=exited, status=0/SUCCESS)
   Main PID: 2472 (pmxcfs)
      Tasks: 5 (limit: 9351)
     Memory: 23.7M (peak: 24.2M)
        CPU: 34ms
     CGroup: /system.slice/pve-cluster.service
             └─2472 /usr/bin/pmxcfs

Mar 22 20:17:41 sandbox systemd[1]: Starting pve-cluster.service - The Proxmox VE cluster filesystem...
Mar 22 20:17:41 sandbox pmxcfs[2471]: [main] notice: resolved node name 'sandbox' to '192.168.1.220' for default node IP address
Mar 22 20:17:41 sandbox pmxcfs[2471]: [main] notice: resolved node name 'sandbox' to '192.168.1.220' for default node IP address
Mar 22 20:17:42 sandbox systemd[1]: Started pve-cluster.service - The Proxmox VE cluster filesystem.

:small_blue_diamond: Размонтировать архивы из PBS

umount /mnt/restore/{etc,pmxcfs,etc-pve,cron,scripts}

:small_blue_diamond: Удалить временные директории

rm -rf /mnt/restore

:warning: Важно
Архив etc-pve-configs.pxar восстанавливать не нужно, т.к. директория /etc/pve является примонтированной FUSE файловой системой (некое представление SQLite БД config.db) и любая попытка перезаписать файлы в этой директории приводит к поломке службы pve-cluster.
Данный архив нужен для того, чтобы была возможность прочитать файлы конфигурации и перенести настройки вручную в PVE при необходимости.

:scroll: Скрипт восстановления бэкапа

Все вышеописанные действия свёл в скрипт.
Что делает:

  1. Предлагает удалить файл скрипта после восстановления бэкапа
  2. Предлагает выполнить обновление системы (отключает Ceph и enterprise репозитории, подключает no-subscription)
  3. Создаёт и удаляет временные директории после восстановления бэкапа
  4. Подключается к PBS, получает последний актуальный бэкап и монтирует его в временные директории в PVE
  5. Выполняет корректное восстановление бэкапа, после чего размонтирует бэкап.
  6. Очищает конфиг файлы для VM и CT, которые вы отдельно можете восстановить из бэкапов.

Как запустить:

  1. Создать файл в любой удобной директории в PVE, например:
nano /home/restore.sh
  1. Сделать файл исполняемым
chmod 700 /home/restore.sh
  1. Скопировать скрипт в файл restore.sh
  2. Скорректировать переменные в шапке скрипта (да, указывать авторизационные данные не секьюрно, но исходим из того, что скрипт исполняем 1 раз, после чего он автоматически удаляется из системы, в связи с чем городить отдельный файл с секретами нецелесообразно)
PBS_PASSWORD="YOUR_PASSWORD"                # PBS password
PBS_REPOSITORY="YOUR_PBS_REPOSITORY"        # PBS Repository
PBS_FINGERPRINT="YOUR_PBS_FINGERPRINT"      # PBS fingerprint
NS="hosts"                                  # PBS Namespace
RESTORE_DIR="/mnt/restore"                  # Temporary restore directory
  1. Запустить скрипт
/home/restore.sh

Сам скрипт:

#!/bin/bash

# ============================
# Paths and settings
# ============================
PBS_PASSWORD="YOUR_PASSWORD"                # PBS password
PBS_REPOSITORY="YOUR_PBS_REPOSITORY"        # PBS Repository
PBS_FINGERPRINT="YOUR_PBS_FINGERPRINT"      # PBS fingerprint
NS="hosts"                                  # PBS Namespace
RESTORE_DIR="/mnt/restore"                  # Temporary restore directory

# ============================
# Step check function
# ============================
check_step() {
    if [ $? -eq 0 ]; then
        echo -e "\e[32m✅  $1\e[0m"
    else
        echo -e "\e[31m❌  $1 (error)\e[0m"
        exit 1
    fi
}

# ============================
# Load PBS settings
# ============================
export PBS_PASSWORD
export PBS_REPOSITORY
export PBS_FINGERPRINT

# ============================
# 1. System update with confirmation
# ============================

# Allow script self-removal after execution
read -p "Delete the script after execution? [y/N]: " selfdel_choice

read -p "Do you want to update the system (apt update && apt upgrade -y)? [y/N]: " update_choice
if [[ "$update_choice" =~ ^[Yy]$ ]]; then
    echo "Preparing system update..."

    # ============================
    # 1a. Disable enterprise repositories
    # ============================

    # Disable Ceph repo by adding Enabled: false (if not already present)
    if grep -q "^Enabled: false" /etc/apt/sources.list.d/ceph.sources 2>/dev/null; then
        echo "✅  Ceph repository already disabled"
    else
        echo "✅  Disabling Ceph enterprise repository..."
        echo "Enabled: false" >> /etc/apt/sources.list.d/ceph.sources
    fi

    # Disable PVE enterprise repo
    if grep -q "^Enabled: false" /etc/apt/sources.list.d/pve-enterprise.sources 2>/dev/null; then
        echo "✅  PVE enterprise repository already disabled"
    else
        echo "✅  Disabling PVE enterprise repository..."
        echo "Enabled: false" >> /etc/apt/sources.list.d/pve-enterprise.sources
    fi

    # ============================
    # 1b. Add or enable public Proxmox repository
    # ============================

    PROXMOX_REPO_FILE="/etc/apt/sources.list.d/proxmox.sources"

    if [ ! -f "$PROXMOX_REPO_FILE" ]; then
        echo "✅  Adding public Proxmox repository..."
        cat <<EOF > "$PROXMOX_REPO_FILE"
Types: deb
URIs: http://download.proxmox.com/debian/pve
Suites: trixie
Components: pve-no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
EOF
    else
        if grep -q "^Enabled: false" "$PROXMOX_REPO_FILE"; then
            echo "✅  Proxmox repository exists but is disabled. Enabling..."
            sed -i '/^Enabled: false/d' "$PROXMOX_REPO_FILE"
        else
            echo "✅  Public Proxmox repository already exists and enabled"
        fi
    fi

    # ============================
    # 1c. Run update and upgrade
    # ============================

    export DEBIAN_FRONTEND=noninteractive
    apt update && apt upgrade -y
    check_step "System update"
else
    echo -e "\e[33m⏭️  Update skipped\e[0m"
fi

# ============================
# 2. Create directories
# ============================
mkdir -p "$RESTORE_DIR"/{etc,pmxcfs,etc-pve,cron,scripts}
mkdir -p /root/pbs/scripts
check_step "Create directories"

# ============================
# 3. Get latest PBS snapshot
# ============================
pbs_output=$(proxmox-backup-client snapshot list --repository "$PBS_REPOSITORY" --ns "$NS" 2>&1)
cmd_status=$?

if [ $cmd_status -ne 0 ]; then
    echo -e "\e[31m❌  Failed to fetch snapshots from PBS\e[0m"
    echo "❌  $pbs_output"
    exit 1
fi

snapshots=$(echo "$pbs_output" \
    | grep '^│' \
    | awk '{print $2}' \
    | grep '^host/' )

if [[ -z "$snapshots" ]]; then
    echo -e "\e[31m❌  Connection to PBS established, no host-type backups found\e[0m"
    exit 1
fi

check_step "Fetch snapshot list (host only)"

latest_snapshot=$(echo "$snapshots" | sort -t'/' -k3 | tail -n 1)
check_step "Determine latest snapshot"
echo "Latest backup: $latest_snapshot"

# ============================
# 3b. Mount archives from PBS
# ============================
proxmox-backup-client mount "$latest_snapshot" etc.pxar "$RESTORE_DIR/etc" --ns "$NS"
check_step "Mount etc"
proxmox-backup-client mount "$latest_snapshot" crontab.pxar "$RESTORE_DIR/cron" --ns "$NS"
check_step "Mount cron"
proxmox-backup-client mount "$latest_snapshot" pmxcfs-SQLite-PVE.pxar "$RESTORE_DIR/pmxcfs" --ns "$NS"
check_step "Mount pmxcfs"
proxmox-backup-client mount "$latest_snapshot" scripts.pxar "$RESTORE_DIR/scripts" --ns "$NS"
check_step "Mount scripts" 

# ============================
# 4. Stop Proxmox VE cluster service
# ============================
systemctl stop pve-cluster
check_step "Stop pve-cluster"

# ============================
# 5. Restore configuration files
# ============================
rsync -aHAX --numeric-ids "$RESTORE_DIR/etc/" /etc/
check_step "Restore etc"
rsync -aHAX --numeric-ids "$RESTORE_DIR/cron/" /var/spool/cron/
check_step "Restore cron"
rsync -aHAX --numeric-ids "$RESTORE_DIR/pmxcfs/" /var/lib/pve-cluster/
check_step "Restore pmxcfs"
rsync -aHAX --numeric-ids "$RESTORE_DIR/scripts/" /root/pbs/scripts/
check_step "Restore scripts"

# ============================
# 6. Unmount archives
# ============================
umount "$RESTORE_DIR/etc"
check_step "Umount etc" 

umount "$RESTORE_DIR/cron"
check_step "Umount cron"

umount "$RESTORE_DIR/pmxcfs"
check_step "Umount pmxcfs"

umount "$RESTORE_DIR/scripts"
check_step "Umount scripts"

# ============================
# 7. Remove temporary directories
# ============================
rm -rf "$RESTORE_DIR"
check_step "Remove temporary directories"

# ============================
# 8. Reload systemd configuration
# ============================
systemctl daemon-reload
check_step "Reload systemd configuration"

# ============================
# 9. Start Proxmox VE cluster service
# ============================
sleep 2
systemctl start pve-cluster
check_step "Start pve-cluster"

# ============================
# 10. Clean VM/CT configurations (without disks)
# ============================
rm -f /etc/pve/lxc/*.conf
check_step "Clean LXC configs"

rm -f /etc/pve/qemu-server/*.conf
check_step "Clean VM configs"

# ============================
# 11. Check cluster status
# ============================
systemctl is-active --quiet pve-cluster
check_step "Check pve-cluster service status (active)"
check_step "Backup restore completed successfully ✅"

# ============================
# 12. Self-delete script
# ============================
if [[ "$selfdel_choice" =~ ^[Yy]$ ]]; then
    script_path="$(realpath "$0")"
    rm -- "$script_path"
    echo -e "\e[32m✅  Script removed\e[0m"
else
    echo -e "\e[33m⏭️  Self-removal skipped\e[0m"
fi

Работу скрипта проверял на PVE версии 9.
После всех действий рекомендую перезагрузить хост.

1 лайк