Skip to content

Instantly share code, notes, and snippets.

@killerbees19
Last active June 10, 2025 17:48
Show Gist options
  • Save killerbees19/422bda4549763fa8a800e78d510caaf1 to your computer and use it in GitHub Desktop.
Save killerbees19/422bda4549763fa8a800e78d510caaf1 to your computer and use it in GitHub Desktop.
Proxmox VE (host only) backup

Tested at PVE 8.x (with PBS 3.x)

Installation

  • Save the script pve-backup.sh anywhere (i.e. /usr/local/sbin/pve-backup.sh)

cURL configuration

Tested with FTPS, but it should also work with WebDAV or SFTP.

  • Modify CURL_URL (and optionally CURL_ARGS) to fit your needs.
  • Create a ~/.netrc file, set permissions to 0600 and store your credentials:
machine backup-destination.example.net
login yourstupidremoteusername
password yoursecretpassword

PBS configuration

  • Create default configration directory for proxmox-backup-client mkdir -p ~/.config/proxmox-backup
  • Create ~/.config/proxmox-backup/default-credentials.sh, set permissions to 0600 and set required variables:
export PBS_REPOSITORY="user@realm!token@server:datastore"
export PBS_PASSWORD="secretpassword"
#export PBS_FINGERPRINT=...

Optional: Create and view new encryption key

proxmox-backup-client key create --kdf none
jq < ~/.config/proxmox-backup/encryption-key.json

Optional: Use existing encryption key

Create a symlink to an encryption key from a PBS entry in storage.cfg:

ln -s /etc/pve/priv/storage/example.enc ~/.config/proxmox-backup/encryption-key.json

Alternatively update the path at PBC_ENCRYPTION.

Usage examples

Create TAR archive and upload it with cURL, silent (cronjob) mode:

./pve-backup.sh

Create TAR archive and just output the filename, without uploading or deleting it:

./pve-backup.sh -l

Send backup to PBS, silent (cronjob) mode:

./pve-backup.sh -p

Send backup to PBS, verbose mode:

PBS_LOG=info ./pve-backup.sh -p

Restore from PBS

Tip

If you're using namespaces, don't forget to append --ns "foo/bar" as last argument to proxmox-backup-client.

Note

Load your credentials before executing proxmox-backup-client commands.

source ~/.config/proxmox-backup/default-credentials.sh

List all available backups for this host:

proxmox-backup-client snapshot list "host/$(hostname -s)"

View pvereport from backup:

proxmox-backup-client restore host/example/2025-01-01T00:00:00Z pvereport.log /tmp/pvereport.txt - | less

Reinstall all missing packages:

proxmox-backup-client restore host/example/2025-01-01T00:00:00Z dpkg-selections.conf /tmp/dpkg.selections
apt update && apt-cache dumpavail | dpkg --merge-avail
dpkg --set-selections < /tmp/dpkg.selections
apt update && apt-get dselect-upgrade

Restore a directory to a temporary location:

proxmox-backup-client restore host/example/2025-01-01T00:00:00Z etc.pxar /tmp/test
#!/bin/bash
# [email protected] (2025-06-08)
set -euf -o pipefail
########################################################################
####################### START OF CONFIGURATION #########################
########################################################################
# Custom arguments for various commands.
TAR_ARGS=()
PBC_ARGS=()
# dpkg --get-selections: Set to an empty string to disable this feature.
DPKG_FILE="/tmp/dpkg.selections"
# pvereport: Set to an empty string to disable this feature.
PVE_FILE="/tmp/pvereport.txt"
# List of common Debian directories to be included in TAR files and PBS snapshots.
# It's recommended to use absolute paths and drop the leading slash from each element.
# NOTE: DON'T LIST INDIVIDUAL FILES HERE! THIS IS NOT SUPPORTED BY PROXMOX-BACKUP-CLIENT.
BACKUP_DIRS=(etc root usr/local var/cache/debconf var/spool/cron var/lib/vz/snippets)
# TAR filename and temporary location. Supports compressed file extensions like gz/bz2/xz/...
TAR_FILE="/tmp/backup_$(hostname -s).$(date --utc +'%Y%m%d-%H%M%S').tar.xz"
# Upload URL and arguments for cURL. Could be FTP or any other protocol supported by cURL. (SFTP, HTTP, ...)
# If you're only using --local or --pbs, you could set this to an empty string to disable this feature.
# It's recommended to store credentials in the ~/.netrc file! But this requires --netrc in CURL_ARGS.
CURL_URL="ftp://backup-destination.example.net/pve-backup/$(date --utc +'%Y')/"
CURL_ARGS=(--netrc --ssl-reqd --ftp-create-dirs)
# Custom command and arguments to wake-up device before uploading files.
# Multiple commands are possible with something like this: bash -c '...'
CURL_WOL=()
PBC_WOL=()
###
# [OPTIONAL] Load PBS credentials from external file, for simpler reuse.
# Only required if PBS_REPOSITORY and PBS_PASSWORD are not exported yet!
# Alternatively you could set these variables in this script...
#
# > export PBS_REPOSITORY="user!token@server"
# > export PBS_PASSWORD="secretpassword"
#
# DON'T FORGET TO PROTECT THE FILE! (chmod 0600 ...)
#
# There are many environment variables for proxmox-backup-client, see full list:
#
# https://pbs.proxmox.com/docs/backup-client.html#environment-variables
###
PBC_CREDENTIALS=~/.config/proxmox-backup/default-credentials.sh
###
# [OPTIONAL] Path to encryption key for PBS, if not already set by PBC_CREDENTIALS file.
#
# - Option 1) Create a new key in the default location: proxmox-backup-client key create --kdf none
# - Option 2) Use existing key from PVE storage configuration: ln -s /etc/pve/priv/storage/example.enc ~/.config/proxmox-backup/encryption-key.json
#
# proxmox-backup-client will search the default location without further configuration!
# Only change this variable, if your key is located elsewhere or you're super paranoid.
###
PBC_ENCRYPTION=~/.config/proxmox-backup/encryption-key.json
####
## [OPTIONAL] Custom PBS namespace, if not already set by PBC_CREDENTIALS file.
## Don't forget the --ns option when calling proxmox-backup-client directly!
####
#PBC_NAMESPACE=
########################################################################
######################## END OF CONFIGURATION ##########################
########################################################################
function stderr
{
printf '%s\n' "$*" >&2
}
function errexit
{
stderr "$(basename "$0"):" "$@"
exit 1
}
function usage
{
cat <<- EOF >&2
Usage: $0 [OPTION]
-l, --local Do not upload TAR file, just output the filename and exit.
-p, --pbs Do not create TAR file, use proxmox-backup-client instead.
EOF
exit 1
}
#shellcheck disable=SC2317
function cleanup
{
for file in "${cleanupFiles[@]}"
do
if [[ -n "$file" && -f "$file" ]]
then
rm -f "$file"
fi
done
}
function prepareDPKGFile
{
if [[ -n "$DPKG_FILE" ]]
then
cleanupFiles+=("$DPKG_FILE")
dpkg --get-selections > "$DPKG_FILE"
fi
}
function preparePVEFile
{
if [[ -n "$PVE_FILE" ]]
then
cleanupFiles+=("$PVE_FILE")
touch "$PVE_FILE"
chmod 0600 "$PVE_FILE"
pvereport 2>/dev/null 1> "$PVE_FILE" ||:
fi
}
function taskTAR
{
[[ -z "$CURL_URL" && "$local" -ne 1 ]] && errexit "cURL upload disabled! Check your configuration..."
[[ -z "$TAR_FILE" ]] && errexit "TAR_FILE is empty! Check your configuration..."
cleanupFiles+=("$TAR_FILE")
touch "$TAR_FILE"
chmod 0600 "$TAR_FILE"
tar --create --auto-compress --file="$TAR_FILE" --directory="/" "${TAR_ARGS[@]}" "${TAR_SRCFILES[@]}"
if [[ "$local" -eq 1 ]]
then
cleanupFiles=("${cleanupFiles[@]/$TAR_FILE}")
printf '%s\n' "$TAR_FILE"
exit 0
fi
checksumTAR="$TAR_FILE.sha512"
cleanupFiles+=("$checksumTAR")
(cd "$(dirname "$TAR_FILE")" && sha512sum -- "$(basename "$TAR_FILE")" > "$checksumTAR") ||:
[[ -s "$checksumTAR" ]] || errexit "Could not calculate TAR checksum!"
[[ -n "${CURL_WOL[*]}" ]] && "${CURL_WOL[@]}" >/dev/null
curl --silent --show-error --upload-file "{$TAR_FILE,$checksumTAR}" "${CURL_ARGS[@]}" "$CURL_URL"
}
function taskPBS
{
if [[ -n "${PBC_CREDENTIALS:-}" && -f "$PBC_CREDENTIALS" ]]
then
source "$PBC_CREDENTIALS"
fi
local src
local name
local -a pbcargs
local -a backupspec
for src in "${PBC_SRCDIRS[@]}"
do
src=$(sed -r 's#^#/#;s#^[/]{2,}#/#g' <<< "$src")
name=$(sed -r 's/[^A-Z0-9_]/-/ig;s/[-]{2,}/-/g;s/^[-]+//' <<< "$src")
if [[ ! -d "$src" ]]
then
stderr "[WARNING] $src is not a directory!"
continue
fi
backupspec+=("${name}.pxar:${src}")
done
[[ "${#backupspec[@]}" -eq 0 ]] && errexit "Directory list for PBS is empty!"
[[ -n "$DPKG_FILE" ]] && backupspec+=("dpkg-selections.conf:$DPKG_FILE")
[[ -n "$PVE_FILE" ]] && backupspec+=("pvereport.log:$PVE_FILE")
[[ -n "${PBC_NAMESPACE:-}" ]] && pbcargs+=(--ns "$PBC_NAMESPACE")
[[ -n "${PBC_ENCRYPTION:-}" ]] && pbcargs+=(--keyfile "$PBC_ENCRYPTION")
pbcargs+=(--backup-type "host" --all-file-systems 1)
# Disable "info" output.
PBS_LOG=${PBS_LOG:-error}
export PBS_LOG
[[ -n "${PBC_WOL[*]}" ]] && "${PBC_WOL[@]}" >/dev/null
proxmox-backup-client backup "${backupspec[@]}" "${PBC_ARGS[@]}" "${pbcargs[@]}"
}
PBC_SRCDIRS=("${BACKUP_DIRS[@]}")
TAR_SRCFILES=("${BACKUP_DIRS[@]}")
[[ -n "$DPKG_FILE" ]] && TAR_SRCFILES+=("${DPKG_FILE:1}")
[[ -n "$PVE_FILE" ]] && TAR_SRCFILES+=("${PVE_FILE:1}")
pbs=0
local=0
while [[ $# -gt 0 ]]
do
case "$1" in
-l|--local)
local=1
;;
-p|--pbs)
pbs=1
;;
*)
usage
;;
esac
shift
done
if [[ "$local" -eq 1 && "$pbs" -eq 1 ]]
then
errexit '-l (--local) cannot be used with -p (--pbs)!'
fi
cleanupFiles=()
trap cleanup EXIT
prepareDPKGFile
preparePVEFile
if [[ "$pbs" -eq 1 ]]
then
taskPBS
else
taskTAR
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment