Skip to content

Instantly share code, notes, and snippets.

@Anexgohan
Last active November 14, 2025 01:08
Show Gist options
  • Select an option

  • Save Anexgohan/624cbf28ccd52f987560ded85e6e06fe to your computer and use it in GitHub Desktop.

Select an option

Save Anexgohan/624cbf28ccd52f987560ded85e6e06fe to your computer and use it in GitHub Desktop.
#!/bin/env bash
# This script is used to backup from Proxmox Virtual Environment (PVE) to the Proxmox Backup Server (PBS).
# Can be run manually or scheduled using cron with no user input. Provides output to the terminal and logs to a file.
# Must have a valid API token for the user on the Proxmox Backup Server (PBS) to run this script.
# ==================== Define variables (Edit Below) ==================== #
DRY_RUN=false # true/false, for testing purposes true (only logs will be generated), for production false (actual run)
CLIENT_HOSTNAME="$(hostname)"
PBS_USER="pbsuser" # user name of the PBS user must have DatastoreAdmin privilages on /datastore
PBS_TOKEN_NAME="pbsuser-token" # API Token for above PBS user must have DatastoreAdmin privilages on /datastore
PBS_NAMESPACE="<pve-name>/<namespace>" # namespace in pbs, must be pre-created
BACKUP_PATH="/" # No trailing slash, use full path and no spaces in the path, eg "/home/user"
# On Proxmox Backup Server UI, PBS>Datastore>"Show Connection Information"
PBS_SERVER_IP="192.168.xxx.xxx"
PBS_SERVER_PORT="8007" # Default port is 8007
PBS_DATASTORE="pbs-ct1000p3" # destination datadtore name
PBS_FINGERPRINT="<FINGERPRINT from PBS>"
PBS_PASSWORD_FILE_LOCATION="/home/pbsuser/.secrets/pbskey"
# PBS_PASSWORD_FILE_LOCATION="/etc/pve/priv/storage/${PBS_DATASTORE}.pw"
PATH_FOR_LOGS="/home/pbsuser/pbs/pbs-client/logs"
# if you want to force include a path, set the path below, else leave it empty,
# separate multiple paths with a space, enclosed in single quotes:
# DO NOT REMOVE THE DOUBLE QUOTES ""
FORCE_INCLUDE_PATH="'/etc/pve' '/boot/efi'" # No trailing slash, use full path and no spaces in the path, e.g. "'/etc/pve' '/var/lib/vz'"
# ==================== End of Define variables ==================== #
# ==================== Guides & Reading ==================== #
# NOTE:
# example of empty path and multiple paths respectively:
# FORCE_INCLUDE_PATH=""
# FORCE_INCLUDE_PATH="'/etc/pve' '/var/lib/vz'"
# https://pbs.proxmox.com/docs/backup-client.html#creating-backups
# Links:
# API Tokens:
# https://pbs.proxmox.com/docs/user-management.html#api-tokens
# generate token for user:
# proxmox-backup-manager user generate-token <user> <token-name>
# Excluding Files/Directories from a Backup: (the .pxarexclude file)
# https://pbs.proxmox.com/docs/backup-client.html#excluding-files-directories-from-a-backup
# the backup command syntax:
# "proxmox-backup-client backup ${CLIENT_HOSTNAME}.pxar:${BACKUP_PATH} --ns ${PBS_NAMESPACE} --include-dev '/path/to/include'"
# Run the following commands in pbs shell to get the required information:
# List Users:
# proxmox-backup-manager user list
# List Tokens:
# proxmox-backup-manager user list-token <user>
# Result <PBS_USER>@pbs!<PBS_TOKEN_NAME>
# List Datastores:
# proxmox-backup-manager datastore list
# ==================== Start of Script (Do not Edit Below) ==================== #
# ------------------- export PBS Environment Secrets: ------------------- #
export PBS_PASSWORD_FILE=${PBS_PASSWORD_FILE_LOCATION}
export PBS_FINGERPRINT=${PBS_FINGERPRINT}
export PBS_REPOSITORY=${PBS_USER}@pbs!${PBS_TOKEN_NAME}@${PBS_SERVER_IP}:${PBS_SERVER_PORT}:${PBS_DATASTORE}
# export PBS_REPOSITORY=pbsuser@pbs!pbsuser-token@192.168.100.197:8007:pbs-ct1000p3
# Initialize the backup command
PBS_BACKUP_COMMAND="proxmox-backup-client backup ${CLIENT_HOSTNAME}.pxar:${BACKUP_PATH} --ns ${PBS_NAMESPACE}"
# If FORCE_INCLUDE_PATH is not empty, add --include-dev for each path
if [ -n "$FORCE_INCLUDE_PATH" ]; then
# Use eval to handle paths enclosed in quotes
eval "set -- $FORCE_INCLUDE_PATH"
for path in "$@"; do
PBS_BACKUP_COMMAND="${PBS_BACKUP_COMMAND} --include-dev ${path}"
done
fi
# get the current date and time and store it
date=$(date)
# Get the current time zone in "Region/City" format
current_time_zone=$(timedatectl show --property=Timezone --value)
# get the current date and time in IST and store it, to use as filename for the log file
date_Filename=$(TZ=${current_time_zone} date +'%Y-%m-%d_%I-%M-%S-%p')
# get the current date and time in IST and store it, for display in terminal and printing to the log file
start_date_formatted=$(TZ=${current_time_zone} date +'%Y-%m-%d | %I:%M:%S %p')
# ------------------- Set paths ------------------- #
# format the variable PBS_NAMESPACE to replace "/" or any other symbols with "-"
PBS_NAMESPACE_FORMATED=$(echo $PBS_NAMESPACE | sed 's/[/\|]/-/g')
# create a variable for path to the log file
log_file_path="${PATH_FOR_LOGS}/${date_Filename}_${PBS_NAMESPACE_FORMATED}.log"
# Redirect stdout and stderr to tee
exec > >(tee -a "$log_file_path") 2>&1
# ------------------- Functions ------------------- #
# create a separator function to print a separator line, # Usage: log_separator
log_separator() {
echo "========================================"
echo ""
}
# create a separator function to print a separator line, # Usage: single_separator
single_separator() {
echo "========================================"
}
# create a smaller separator function to print a smaller separator line, # Usage: small_separator
small_separator() {
echo "--------------------"
}
# print starting date and time
log_separator
echo "Starting Update at: $start_date_formatted"
log_separator
# ------------------- Start of backup script ------------------- #
echo
# print stuff:
echo "Backup Source - [$CLIENT_HOSTNAME]"
echo "Backup Directory - [$BACKUP_PATH]"
small_separator
echo "Backup Destination - [Proxmox Backup Server] @ [$PBS_SERVER_IP:$PBS_SERVER_PORT]"
echo "Destination Datastore - [$PBS_DATASTORE] @ [$PBS_SERVER_IP:$PBS_SERVER_PORT]"
small_separator
echo "Repository - [${PBS_USER}@pbs!${PBS_TOKEN_NAME}@${PBS_SERVER_IP}:${PBS_SERVER_PORT}:${PBS_DATASTORE}]"
echo "Datastore - [$PBS_DATASTORE]"
echo "Namespace - [$PBS_NAMESPACE]"
echo "Environment Variables - [PBS_PASSWORD_FILE], [PBS_FINGERPRINT], [PBS_REPOSITORY]"
small_separator
echo "Directories Excluded - [mnt/**], [var/lib/vz**], [**tmp**], [/etc/pve/**] and all mounted drives by Default."
echo "Force Included - [$FORCE_INCLUDE_PATH]"
echo "Ignore List - [.pxarexclude] #Edit this file to exclude patterns."
# echo "Include in .pxarexclude - [nothing]"
small_separator
echo "Skipped mount points - [dev], [proc], [run], [sys], [var/lib/lxcfs] by Default."
echo ""
single_separator
echo "Running the following command:"
small_separator
echo "${PBS_BACKUP_COMMAND}"
log_separator
echo "running backup, please wait..."
# if DRY_RUN is true, run ${PBS_BACKUP_COMMAND} --dry-run, else run ${PBS_BACKUP_COMMAND}, echo the command before running
if [ "$DRY_RUN" = true ]; then
echo "Dry Running: "
# Countdown from 3 to 1
for i in {3..1}; do
echo -ne " --dry-run -> $i\r"
sleep 1
echo -ne " \r"
done
echo "${PBS_BACKUP_COMMAND} --dry-run"
${PBS_BACKUP_COMMAND} --dry-run
elif [ "$DRY_RUN" = false ]; then
echo "running backup, please wait..."
# Countdown from 3 to 1
for i in {3..1}; do
echo -ne " Starting -> $i\r"
sleep 1
echo -ne " \r"
done
echo "${PBS_BACKUP_COMMAND}"
${PBS_BACKUP_COMMAND}
else
echo "Invalid value for DRY_RUN. Exiting."
# Countdown from 3 to 1
for i in {3..0}; do
echo -ne " Exiting, -> $i\r"
sleep 1
echo -ne " \r"
done
exit 1
fi
sleep 2.0s
echo ""
single_separator
echo "Started Update at: $start_date_formatted"
single_separator
# convert the date to IST with format "YYYY-MM-DD | I:MM:SS p" and add it to the log file
finish_date_formatted=$(TZ=${current_time_zone} date +'%Y-%m-%d | %I:%M:%S %p')
echo "Finished Update at: $finish_date_formatted"
single_separator
# since "|" creates issues in the date command, we need to remove it for the math to work
start_date_for_math=$(echo $start_date_formatted | sed 's/| //')
finish_date_for_math=$(echo $finish_date_formatted | sed 's/| //')
# total time taken in the format "H:M:S"
echo "Total time taken: $(date -d @$(( $(date -d "$finish_date_for_math" +%s) - $(date -d "$start_date_for_math" +%s) )) -u +%H:%M:%S)"
log_separator
# print "Please check the log file for more details" to the console along with the path to the log file
echo "Please check the log file for more details:"
small_separator
echo "$log_file_path"
log_separator
# delete the log files older than 30 days and print the files that are being deleted
log_file_dir=$(dirname "$log_file_path")/
echo "Log files older than 30 days, under '$log_file_dir' that were deleted:"
small_separator
find $log_file_dir -type f -mtime +30 -exec sh -c 'echo "$1"; rm -f "$1"' _ {} \;
small_separator
# exit the script
echo ""
echo "exiting"
echo ""
# clear the variables and clear the export variables to avoid any conflicts with other scripts or commands that may use the same variables
unset PBS_PASSWORD_FILE PBS_FINGERPRINT PBS_REPOSITORY date_Filename date start_date_formatted finish_date_formatted start_date_for_math finish_date_for_math log_file_path log_file_dir log_separator single_separator small_separator FORCE_INCLUDE_PATH
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment