initial commit

This commit is contained in:
2026-08-21 17:44:28 +02:00
commit f6f764b871
3 changed files with 552 additions and 0 deletions
Executable
+313
View File
@@ -0,0 +1,313 @@
#!/usr/bin/env bash
SCRIPT_PATH=$(realpath -s "$0")
SCRIPT_DIR=$(dirname "$SCRIPT_PATH")
SCRIPT=$(basename "$SCRIPT_PATH")
ETC_DIR="/etc/$SCRIPT"
function usage() {
cat <<EOF
Usage: $SCRIPT COMMAND
Helper script for proxmox-backup-client.
Available commands:
backup Run backup and print output to screen.
backup-cron Run backup and suppress output on success.
list List groups, latest snapshots and archives.
snapshot-list [GROUP] List snapshots, sizes and archives.
catalog-shell [SNAPSHOT [ARCHIVE]] Run interactive shell to explore arhive contents
and restore files/dirs selectively.
mount [SNAPSHOT [ARCHIVE [TARGET]]] Mount snapshot archive locally.
install [--update] Install proxmox-backup-client binary and dependencies.
Use --update to update the currently installed binary.
EOF
}
function cleanup_tmpdir() {
rm -r "$tmpdir"
trap - EXIT
}
function pbc-install() {
local update=${1:-false}
if $update || [ ! -f "$PBC" ]; then
local pkgfile=$(wget -q -O - "$PBC_DOWNLOAD_URL/" | grep proxmox-backup-client-static_ | tail -n 1 | sed 's#.*href="##g;s#".*##g;')
[ -z "$pkgfile" ] && echo "Unable to determine current package file!" >&2 && exit 1
tmpdir=$(mktemp -d)
trap cleanup_tmpdir EXIT
echo "Downloading $PBC_DOWNLOAD_URL/$pkgfile ..."
! wget -q -O "$tmpdir/$pkgfile" "$PBC_DOWNLOAD_URL/$pkgfile" && echo "Error downloading package file!" >&2 && exit 2
echo "Extracting $pkgfile ..."
! dpkg-deb -x "$tmpdir/$pkgfile" "$tmpdir/" && echo "Error extracting package file!" >&2 && exit 3
echo "Installing proxmox-backup-client binary to $PBC ..."
! cp "$tmpdir/usr/bin/proxmox-backup-client" "$PBC" && echo "Error copying proxmox-backup-client binary!" >&2 && exit 4
local version=$("$PBC" version | head -n 1)
(( $? != 0 )) && echo "Error running $PBC version ... exited with rc=$?" >&2 && exit 5
echo "Successfully installed proxmox-backup-$version"
else
local version=$("$PBC" version | head -n 1)
(( $? != 0 )) && echo "Error running $PBC version ... exited with rc=$?" >&2 && exit 5
echo "Binary proxmox-backup-client ($version) already installed"
fi
local jq=$(which jq)
if [ -z "$jq" ]; then
echo "Installing package jq ..."
! apt-get install jq && echo "Error installing jq package!" >&2 && exit 6
else
echo "Package jq already installed"
fi
if [ ! -d "$ETC_DIR" ]; then
echo "Creating config directory $ETC_DIR ..."
mkdir -p "$ETC_DIR"
fi
if [ -f "$SCRIPT_DIR/config.example" ]; then
if $update || [ ! -f "$ETC_DIR/config.example" ]; then
echo "Copy example config to $ETC_DIR/config.example ..."
cp "$SCRIPT_DIR/config.example" "$ETC_DIR/config.example"
fi
fi
echo "Installation successful!"
}
function pbc-backup() {
"$PBC" backup --ns "$PBS_NAMESPACE" "${BACKUP[@]}"
return $?
}
function pbc-list() {
"$PBC" list --ns "$PBS_NAMESPACE" "$@"
return $?
}
function pbc-snapshot-list() {
"$PBC" snapshot list --ns "$PBS_NAMESPACE" "$@"
return $?
}
function pbc-files() {
"$PBC" files --ns "$PBS_NAMESPACE" "$@"
return $?
}
function pbc-catalog-shell() {
"$PBC" catalog shell --ns "$PBS_NAMESPACE" "$@"
return $?
}
function pbc-mount() {
"$PBC" mount --ns "$PBS_NAMESPACE" "$@"
return $?
}
function get_group_names() {
pbc-list --output-format json | jq -r '.[] | "\(.["backup-type"])/\(.["backup-id"])"'
}
function get_snapshot_names() {
pbc-snapshot-list --output-format json | jq -r '.[] | "\(.["backup-type"])/\(.["backup-id"])/\(.["backup-time"] | todateiso8601)"'
}
function get_snapshot_files() {
pbc-files "$1" --output-format json | jq -r '.[] | select(.filename | endswith(".pxar.didx")) | .filename | rtrimstr(".didx")' | sort
}
function arr_contains() {
search=$1
shift
[ -z "$search" ] && return 1
while [ -n "$1" ]; do
[ "$1" == "$search" ] && return 0
shift
done
return 1
}
function may_select() {
value=$1
shift
(( $# == 0 )) && echo "Error: no values to select" >&2 && return 1
if [ -z "$value" ]; then
if (( $# == 1 )); then
value=$1
else
sorted=($(printf "%s\n" "$@" | sort))
select value in "${sorted[@]}"; do
[ -n "$value" ] && break
done
fi
elif ! arr_contains "$value" "$@"; then
return 1
fi
echo "$value"
return 0
}
action=''
configfile=''
args=()
while [ -n "$1" ]; do
opt=$1
shift
if [ -z "$action" ]; then
case "$opt" in
backup|backup-cron|list|snapshot-list|catalog-shell|mount|install)
action=$opt
;;
-c|--config)
configfile=$1
shift
[ -z "$configfile" ] && usage && exit 255
;;
-h|--help)
usage
exit 0
;;
*)
usage
exit 255
;;
esac
elif [ "$action" == "install" ]; then
case "$opt" in
-u|--update)
args+=('true')
;;
*)
usage
exit 255
;;
esac
else
args+=("$opt")
fi
done
configfile=${configfile:-$ETC_DIR/config}
if [ "$action" == 'install' ]; then
if [ -f "$configfile" ]; then
source "$configfile" || exit $?
fi
else
source "$configfile" || exit $?
[ ! -f "$PBC" ] && echo "Error: $PBC: no such file" >&2 && exit 1
[ ! -e "$PBC" ] && echo "Error: $PBC: not executable" >&2 && exit 1
fi
PBC=${PBC:-/usr/bin/proxmox-backup-client}
PBC_DOWNLOAD_URL=${PBC_DOWNLOAD_URL:-http://download.proxmox.com/debian/pbs-client/dists/trixie/main/binary-amd64}
export PBS_REPOSITORY="$PBS_USER@${PBS_SERVER:-8007}:$PBS_DATASTORE"
export PBS_PASSWORD="$PBS_PASSWORD"
case "$action" in
backup)
pbc-backup || exit $?
;;
backup-cron)
output=$(pbc-backup 2>&1)
rc=$?
(( $rc != 0 )) && echo "$output"
;;
list)
pbc-list "${args[@]}" || exit $?
;;
snapshot-list)
readarray -t groups < <(get_group_names)
(( ${#groups[@]} == 0 )) && echo "no snapshot groups found" && exit 0
group=${args[0]}
args=("${args[@]:1}")
PS3="Snapshot group [1-${#groups[@]}]: "
group=$(may_select "$group" "${groups[@]}")
(( $? != 0 )) && echo "Error: invalid group" >&2 && exit 1
echo
pbc-snapshot-list "$group" "${args[@]}" || exit $?
;;
catalog-shell)
readarray -t snapshots < <(get_snapshot_names)
(( ${#snapshots[@]} == 0 )) && echo "no snapshots found" && exit 0
snapshot=${args[0]}
args=("${args[@]:1}")
PS3="Snapshot [1-${#snapshots[@]}]: "
snapshot=$(may_select "$snapshot" "${snapshots[@]}")
(( $? != 0 )) && echo "Error: invalid snapshot" >&2 && exit 1
readarray -t archives < <(get_snapshot_files "$snapshot")
(( ${#archives[@]} == 0 )) && echo "no archives found" && exit 0
archive=${args[0]}
args=("${args[@]:1}")
PS3="Archive [1-${#archives[@]}]: "
archive=$(may_select "$archive" "${archives[@]}")
(( $? != 0 )) && echo "Error: invalid archive" >&2 && exit 1
echo
pbc-catalog-shell "$snapshot" "$archive" || exit $?
;;
mount)
readarray -t snapshots < <(get_snapshot_names)
(( ${#snapshots[@]} == 0 )) && echo "no snapshots found" && exit 0
snapshot=${args[0]}
args=("${args[@]:1}")
PS3="Snapshot [1-${#snapshots[@]}]: "
snapshot=$(may_select "$snapshot" "${snapshots[@]}")
(( $? != 0 )) && echo "Error: invalid snapshot" >&2 && exit 1
readarray -t archives < <(get_snapshot_files "$snapshot")
(( ${#archives[@]} == 0 )) && echo "no archives found" && exit 0
archive=${args[0]}
args=("${args[@]:1}")
PS3="Archive [1-${#archives[@]}]: "
archive=$(may_select "$archive" "${archives[@]}")
(( $? != 0 )) && echo "Error: invalid archive" >&2 && exit 1
target=${args[0]}
args=("${args[@]:1}")
if [ -z "$target" ]; then
while [ -z "$target" ]; do
echo -n "Target path: "
read -r target
[ -d "$target" ] && break
echo -e "\nError: $target: no such directory\n" >&2
target=''
done
elif [ ! -d "$target" ]; then
echo "Error: $target: no such directory" >&2
exit 1
fi
echo
pbc-mount "$snapshot" "$archive" "$target" || exit $?
;;
install)
pbc-install "${args[@]}" || exit $?
;;
*)
usage
exit 1
;;
esac