[SOLVED] Daily restore of a file using Cron

sunseeker2k5

Member
Jul 25, 2023
24
7
8
Good day everyone!

I have a script that retrieves the latest VM snapshot from my Proxmox Backup Server datastore and uploads a file from inside that snapshot via FTP.
It's basically a copy of my homeassistant db file for external use. I plan to use scp later but for now it's easier to test via FTP.

Environment:
  • Proxmox Backup Server 3.4.3
  • Datastore: DS221
  • Script works perfectly and fast when run interactively from a root shell.
  • Fails when run from cron or via env -i HOME=/root /bin/bash script.sh.

What works interactively (after providing my password at the initial run):
proxmox-backup-client snapshot list vm/101 --repository DS221 --output-format json
No password prompt after second run or even after reboot, correct output.


What fails in cron / env -i:
  • Either prompts for password password for "root@pam" or
  • throws : Error: permission check failed
  • No /root/.cache/proxmox-backup/tickets file is created during cron run (or on interactive run)
What I’ve tried:
  1. API token authentication
    • /root/.config/proxmox-backup/credentials contains:
PBS_REPOSITORY="DS221"​
PBS_TOKEN_NAME="root@pam!pbscron"​
PBS_TOKEN_SECRET="xxxxx"​
ACL shows token has permissions:acl:1:/datastore/DS221:root@pam!pbscron:Admin,DatastoreAdmin​
Password file approach
  • Created /root/.config/proxmox-backup/pbs_pass with 600 perms.
  • PBS 3.4.3 CLI throws parameter verification failed - 'password-file': schema does not allow additional properties.

Question:How can I run proxmox-backup-client commands non-interactively in PBS 3.4.3 from cron, using either tokens or stored credentials, without it prompting for a password? Do I need PBS 4.0 ?

Here is the working interactive script:
Bash:
#!/bin/bash
set -euo pipefail

PBS_REPO="DS221"
VM_GROUP="vm/101"

# FTP connection details
FTP_SERVER="192.168.1.10"
FTP_USER="xxx"
FTP_PASS="xxx"
FTP_PATH="/media/xxx"

TMP_DISK_MOUNT=$(mktemp -d /tmp/ha_disk_mount.XXXXXX)
LOOPDEV=""

cleanup() {
    echo "Cleaning up..."
    mountpoint -q "$TMP_DISK_MOUNT" && umount "$TMP_DISK_MOUNT" || true
    [ -n "$LOOPDEV" ] && losetup -d "$LOOPDEV" || true
    rm -rf "$TMP_DISK_MOUNT"
}
trap cleanup EXIT

echo "Finding latest snapshot and archive..."
read -r SNAP_TYPE SNAP_ID SNAP_TIME_UNIX ARCHIVE < <(
  proxmox-backup-client snapshot list "$VM_GROUP" \
    --repository "$PBS_REPO" \
    --output-format json \
    | jq -r '
        sort_by(.["backup-time"])[-1] as $snap |
        [
          $snap["backup-type"],
          $snap["backup-id"],
          $snap["backup-time"],
          (
            $snap.files[]? | select(.filename=="drive-scsi0.img.fidx") | .filename
          )
        ] | @tsv
    '
)

SNAP_TIME_ISO=$(date -u -d @"$SNAP_TIME_UNIX" +"%Y-%m-%dT%H:%M:%SZ")
SNAPSHOT="$SNAP_TYPE/$SNAP_ID/$SNAP_TIME_ISO"

if [ -z "$ARCHIVE" ]; then
    echo "No main disk archive found in latest snapshot."
    exit 1
fi

echo "Latest snapshot: $SNAPSHOT"
echo "Main disk archive: $ARCHIVE"

echo "Mapping disk archive from PBS..."
MAP_OUTPUT=$(proxmox-backup-client map "$SNAPSHOT" "$ARCHIVE" --repository "$PBS_REPO" 2>&1)
echo "$MAP_OUTPUT"

LOOPDEV=$(echo "$MAP_OUTPUT" | grep -o '/dev/loop[0-9]\+')
if [ -z "$LOOPDEV" ]; then
    echo "Failed to map disk image."
    exit 1
fi
echo "Loop device: $LOOPDEV"

echo "Detecting correct partition..."
PART=$(lsblk -ln -o NAME,TYPE "$LOOPDEV" | awk '$2=="part"{print "/dev/"$1}' \
    | while read -r p; do
        MNT=$(mktemp -d)
        if mount -o ro "$p" "$MNT" 2>/dev/null; then
            if [ -d "$MNT/supervisor/homeassistant" ]; then
                umount "$MNT"
                rm -rf "$MNT"
                echo "$p"
                break
            fi
            umount "$MNT" 2>/dev/null
        fi
        rm -rf "$MNT"
    done)

if [ -z "$PART" ]; then
    echo "Could not find correct data partition."
    exit 1
fi
echo "Mounting partition $PART..."
mount -o ro "$PART" "$TMP_DISK_MOUNT"

echo "Searching for Home Assistant DB..."
DB_FILE_FOUND=$(find "$TMP_DISK_MOUNT/supervisor/homeassistant" -maxdepth 1 -type f -name "home-assistant_v2.db" | head -n 1 || true)
if [ -z "$DB_FILE_FOUND" ]; then
    echo "Database file not found inside mounted partition."
    exit 1
fi
echo "Found DB file at: $DB_FILE_FOUND"

echo "Uploading DB to FTP..."
curl --ftp-create-dirs -T "$DB_FILE_FOUND" "ftp://$FTP_USER:$FTP_PASS@$FTP_SERVER//$FTP_PATH/home-assistant_v2.db"
if [ $? -ne 0 ]; then
    echo "FTP upload failed!"
else
    echo "FTP upload completed successfully."
fi
 
Last edited:
Answering my own question :

PBS 3.4.3 API token authentication requires a specific format that wasn't being used correctly, and cron environments don't load the PBS credentials file.

Removed dependency on credentials file - Instead of relying on /root/.config/proxmox-backup/credentials, I passed authentication directly via command line parameters

Fixed PBS 3.4.3 token authentication syntax- The key was using the correct repository format: user@pbs!token@host:store and setting PBS_PASSWORD environment variable.

Updated cron environment variables- Added the required variables to crontab:
PBS_REPOSITORY=DS221
PBS_TOKEN_NAME=root@pam!pbscron
PBS_TOKEN_SECRET=your_token_secret
PBS_HOST=your_pbs_server_ip (I couldn't get it to work without IP even if I am executing it on the pbs server)

Python:
#!/bin/bash

# PBS Configuration - these should be set as environment variables in cron
# Example cron entry:
# PBS_REPOSITORY=DS221
# PBS_TOKEN_NAME=root@pam!pbscron
# PBS_TOKEN_SECRET=your_actual_secret
# 0 2 * * * /path/to/your/script.sh

# Validate required environment variables first
if [ -z "${PBS_REPOSITORY:-}" ] || [ -z "${PBS_TOKEN_NAME:-}" ] || [ -z "${PBS_TOKEN_SECRET:-}" ]; then
    echo "Error: Required PBS environment variables not set:"
    echo "PBS_REPOSITORY, PBS_TOKEN_NAME, PBS_TOKEN_SECRET must be defined"
    exit 1
fi

# Now it's safe to set strict error handling
set -euo pipefail

VM_GROUP="vm/101"

# For local PBS server, just use the repository name
PBS_REPO_WITH_AUTH="$PBS_REPOSITORY"

# FTP connection details
FTP_SERVER="192.168.1.10"
FTP_USER="xxx"
FTP_PASS="xxx"
FTP_PATH="/media/xxx"

TMP_DISK_MOUNT=$(mktemp -d /tmp/ha_disk_mount.XXXXXX)
LOOPDEV=""

cleanup() {
    echo "Cleaning up..."
    mountpoint -q "$TMP_DISK_MOUNT" && umount "$TMP_DISK_MOUNT" || true
    [ -n "$LOOPDEV" ] && losetup -d "$LOOPDEV" || true
    rm -rf "$TMP_DISK_MOUNT"
}

trap cleanup EXIT

# Set environment variables for API token authentication
export PBS_REPOSITORY="$PBS_REPOSITORY"
export PBS_FINGERPRINT=""  # Not needed for local server
export PBS_PASSWORD="$PBS_TOKEN_SECRET"

echo "Finding latest snapshot and archive..."
read -r SNAP_TYPE SNAP_ID SNAP_TIME_UNIX ARCHIVE < <(
    proxmox-backup-client snapshot list "$VM_GROUP" \
        --repository "$PBS_REPOSITORY" \
        --output-format json \
    | jq -r '
        sort_by(.["backup-time"])[-1] as $snap |
        [
            $snap["backup-type"],
            $snap["backup-id"],
            $snap["backup-time"],
            (
                $snap.files[]? | select(.filename=="drive-scsi0.img.fidx") | .filename
            )
        ] | @tsv
    '
)

SNAP_TIME_ISO=$(date -u -d @"$SNAP_TIME_UNIX" +"%Y-%m-%dT%H:%M:%SZ")
SNAPSHOT="$SNAP_TYPE/$SNAP_ID/$SNAP_TIME_ISO"

if [ -z "$ARCHIVE" ]; then
    echo "No main disk archive found in latest snapshot."
    exit 1
fi

echo "Latest snapshot: $SNAPSHOT"
echo "Main disk archive: $ARCHIVE"

echo "Mapping disk archive from PBS..."
MAP_OUTPUT=$(proxmox-backup-client map "$SNAPSHOT" "$ARCHIVE" \
    --repository "$PBS_REPOSITORY" 2>&1)

echo "$MAP_OUTPUT"
LOOPDEV=$(echo "$MAP_OUTPUT" | grep -o '/dev/loop[0-9]\+')

if [ -z "$LOOPDEV" ]; then
    echo "Failed to map disk image."
    exit 1
fi

echo "Loop device: $LOOPDEV"

echo "Detecting correct partition..."
PART=$(lsblk -ln -o NAME,TYPE "$LOOPDEV" | awk '$2=="part"{print "/dev/"$1}' \
    | while read -r p; do
        MNT=$(mktemp -d)
        if mount -o ro "$p" "$MNT" 2>/dev/null; then
            if [ -d "$MNT/supervisor/homeassistant" ]; then
                umount "$MNT"
                rm -rf "$MNT"
                echo "$p"
                break
            fi
            umount "$MNT" 2>/dev/null
        fi
        rm -rf "$MNT"
    done)

if [ -z "$PART" ]; then
    echo "Could not find correct data partition."
    exit 1
fi

echo "Mounting partition $PART..."
mount -o ro "$PART" "$TMP_DISK_MOUNT"

echo "Searching for Home Assistant DB..."
DB_FILE_FOUND=$(find "$TMP_DISK_MOUNT/supervisor/homeassistant" -maxdepth 1 -type f -name "home-assistant_v2.db" | head -n 1 || true)

if [ -z "$DB_FILE_FOUND" ]; then
    echo "Database file not found inside mounted partition."
    exit 1
fi

echo "Found DB file at: $DB_FILE_FOUND"

echo "Uploading DB to FTP..."
curl --ftp-create-dirs -T "$DB_FILE_FOUND" "ftp://$FTP_USER:$FTP_PASS@$FTP_SERVER//$FTP_PATH/home-assistant_v2.db"

if [ $? -ne 0 ]; then
    echo "FTP upload failed!"
    exit 1
else
    echo "FTP upload completed successfully."
fi
 
Last edited: