SHA256
Compare commits
4
Commits
v1.0.0
..
0ade18796f
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
0ade18796f
|
||
|
|
0f61e177f8
|
||
|
|
2fdf065017
|
||
|
|
6239fe5ca6
|
@@ -3,10 +3,10 @@
|
|||||||
A Bash wrapper around Proxmox's `proxmox-backup-client` for Proxmox Backup Server (PBS).
|
A Bash wrapper around Proxmox's `proxmox-backup-client` for Proxmox Backup Server (PBS).
|
||||||
|
|
||||||
It keeps the repository, credentials and the list of paths to back up in a single config
|
It keeps the repository, credentials and the list of paths to back up in a single config
|
||||||
file, injects the configured namespace (`--ns`) into every command that needs it, and lets
|
file, injects the configured namespace (`--ns`) and encryption key (`--keyfile`) into every
|
||||||
you pick a snapshot group, snapshot and archive from an interactive menu instead of typing
|
command that needs them, and lets you pick a snapshot group, snapshot and archive from an
|
||||||
them out. Any command it does not implement itself is passed straight through to
|
interactive menu instead of typing them out. Any command it does not implement itself is
|
||||||
`proxmox-backup-client`.
|
passed straight through to `proxmox-backup-client`.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -23,6 +23,17 @@ curl -fsSL https://git.ccc-rheintal.ch/spacefreak/pbc/raw/branch/master/install.
|
|||||||
This installs the newest tagged version to `/usr/local/bin/pbc`, installs the
|
This installs the newest tagged version to `/usr/local/bin/pbc`, installs the
|
||||||
dependencies, and puts an example config at `/etc/pbc/config.example`.
|
dependencies, and puts an example config at `/etc/pbc/config.example`.
|
||||||
|
|
||||||
|
### Installing a specific version
|
||||||
|
|
||||||
|
Set `PBC_GIT_TAG` to install that tag instead of the newest one:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://git.ccc-rheintal.ch/spacefreak/pbc/raw/branch/master/install.sh | sudo PBC_GIT_TAG=v1.0.0 bash
|
||||||
|
```
|
||||||
|
|
||||||
|
`sudo` drops the environment, so set the variable on the `sudo` command itself (or use
|
||||||
|
`sudo -E`), not before `curl`. Any Git ref works, e.g. a branch name for testing.
|
||||||
|
|
||||||
### Updating
|
### Updating
|
||||||
|
|
||||||
Run the same command again to update `pbc` itself:
|
Run the same command again to update `pbc` itself:
|
||||||
@@ -32,7 +43,7 @@ curl -fsSL https://git.ccc-rheintal.ch/spacefreak/pbc/raw/branch/master/install.
|
|||||||
```
|
```
|
||||||
|
|
||||||
It fetches the newest tag and overwrites `/usr/local/bin/pbc`. Your `/etc/pbc/config` is
|
It fetches the newest tag and overwrites `/usr/local/bin/pbc`. Your `/etc/pbc/config` is
|
||||||
left untouched.
|
left untouched. `PBC_GIT_TAG` works here too, to pin or roll back to a given version.
|
||||||
|
|
||||||
To update only the `proxmox-backup-client` binary:
|
To update only the `proxmox-backup-client` binary:
|
||||||
|
|
||||||
@@ -50,24 +61,15 @@ sudo chmod 640 /etc/pbc/config
|
|||||||
sudo editor /etc/pbc/config
|
sudo editor /etc/pbc/config
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
|
||||||
PBS_SERVER='pbs.domain.tld:8007'
|
|
||||||
PBS_USER='backup@pam:token-name'
|
|
||||||
PBS_PASSWORD='XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
|
|
||||||
PBS_DATASTORE='backup'
|
|
||||||
PBS_NAMESPACE='MyBackups'
|
|
||||||
|
|
||||||
BACKUP=('root.pxar:/' 'data.pxar:/mnt/data')
|
|
||||||
```
|
|
||||||
|
|
||||||
| Key | Description |
|
| Key | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `PBS_SERVER` | PBS host and port |
|
| `PBS_SERVER` | PBS host and port |
|
||||||
| `PBS_USER` | User and API token name, `user@realm:token-name` |
|
| `PBS_USER` | User and API token name, `user@realm:token-name` |
|
||||||
| `PBS_PASSWORD` | The API token secret |
|
| `PBS_PASSWORD` | The API token secret |
|
||||||
| `PBS_DATASTORE` | Datastore to back up to |
|
| `PBS_DATASTORE` | Datastore to back up to |
|
||||||
| `PBS_NAMESPACE` | Namespace inside the datastore |
|
| `PBS_NAMESPACE` | Optional, namespace inside the datastore |
|
||||||
| `BACKUP` | Array of `archive-name.pxar:/path` entries to back up |
|
| `BACKUP` | Array of `archive-name.pxar:/path` entries to back up |
|
||||||
|
| `ENCRYPTION_KEYFILE` | Optional, path to the client encryption key — see [Encryption](#encryption) |
|
||||||
| `PBC` | Optional, path to `proxmox-backup-client` (default `/usr/local/bin/proxmox-backup-client`) |
|
| `PBC` | Optional, path to `proxmox-backup-client` (default `/usr/local/bin/proxmox-backup-client`) |
|
||||||
|
|
||||||
`pbc` never creates the config for you — copy the example and edit it yourself. The file
|
`pbc` never creates the config for you — copy the example and edit it yourself. The file
|
||||||
@@ -93,6 +95,45 @@ Use a different config with `-c`:
|
|||||||
pbc -c ./myconfig list
|
pbc -c ./myconfig list
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Encryption
|
||||||
|
|
||||||
|
Backups can be encrypted client-side, so PBS only ever sees ciphertext. Create a key and
|
||||||
|
point `ENCRYPTION_KEYFILE` at it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo proxmox-backup-client key create /etc/pbc/backup.key --kdf none
|
||||||
|
sudo chmod 600 /etc/pbc/backup.key
|
||||||
|
```
|
||||||
|
|
||||||
|
`pbc` errors out if the key is missing or unreadable, and warns if it is readable by
|
||||||
|
others. As with the config, the file has to be readable for the user that runs `pbc` —
|
||||||
|
`sudo chown youruser /etc/pbc/backup.key` if that is not root.
|
||||||
|
|
||||||
|
**Back the key up somewhere else.** Without it the backups are unrecoverable, and a key
|
||||||
|
stored only on the machine you are backing up is gone exactly when you need it. Print a
|
||||||
|
recovery sheet and keep it off-host:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo proxmox-backup-client key paperkey /etc/pbc/backup.key
|
||||||
|
```
|
||||||
|
|
||||||
|
`--kdf none` leaves the key unprotected on disk, which is what makes unattended backups
|
||||||
|
possible. With a passphrase-protected key (`--kdf scrypt`) the client prompts on every run
|
||||||
|
and `backup-cron` hangs; export `PBS_ENCRYPTION_PASSWORD` in that case.
|
||||||
|
|
||||||
|
The key is passed to `backup`, `backup-cron`, `mount` and `catalog-shell`. It is *not*
|
||||||
|
passed to commands that fall through to `proxmox-backup-client` — `pbc restore` needs both
|
||||||
|
flags spelled out:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pbc restore --ns MyBackups --keyfile /etc/pbc/backup.key \
|
||||||
|
host/myhost/2026-08-24T01:00:00Z root.pxar /mnt/restore
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting `ENCRYPTION_KEYFILE` only affects snapshots made from then on. Older unencrypted
|
||||||
|
snapshots stay readable, and listing works without the key either way — only reading
|
||||||
|
archive contents needs it.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Run `pbc --help` for the synopsis and the list of options, or `pbc -H` to additionally
|
Run `pbc --help` for the synopsis and the list of options, or `pbc -H` to additionally
|
||||||
@@ -150,7 +191,8 @@ pbc mount host/myhost/2026-08-24T01:00:00Z root.pxar /mnt/restore
|
|||||||
```
|
```
|
||||||
|
|
||||||
The target directory is not created for you — create it beforehand and make sure it is
|
The target directory is not created for you — create it beforehand and make sure it is
|
||||||
writable for the user that runs `pbc`. Unmount when done:
|
writable for the user that runs `pbc`. Encrypted archives are decrypted transparently when
|
||||||
|
`ENCRYPTION_KEYFILE` is configured. Unmount when done:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
umount /mnt/restore
|
umount /mnt/restore
|
||||||
@@ -159,6 +201,7 @@ umount /mnt/restore
|
|||||||
### catalog-shell
|
### catalog-shell
|
||||||
|
|
||||||
Open an interactive shell to browse an archive and restore selected files — see below.
|
Open an interactive shell to browse an archive and restore selected files — see below.
|
||||||
|
Encrypted archives are decrypted transparently when `ENCRYPTION_KEYFILE` is configured.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pbc catalog-shell
|
pbc catalog-shell
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ PBS_USER='backup@pam:token-name'
|
|||||||
PBS_PASSWORD='XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
|
PBS_PASSWORD='XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
|
||||||
PBS_DATASTORE='backup'
|
PBS_DATASTORE='backup'
|
||||||
PBS_NAMESPACE='MyBackups'
|
PBS_NAMESPACE='MyBackups'
|
||||||
|
#ENCRYPTION_KEYFILE='/etc/pbc/backup.key'
|
||||||
|
|
||||||
BACKUP=('root.pxar:/' 'data.pxar:/mnt/data')
|
BACKUP=('root.pxar:/' 'data.pxar:/mnt/data')
|
||||||
|
|||||||
+4
-4
@@ -13,7 +13,7 @@ function cleanup_work_dir() {
|
|||||||
function download_files() {
|
function download_files() {
|
||||||
local path rc
|
local path rc
|
||||||
local query_params=''
|
local query_params=''
|
||||||
[ -n "$GIT_TAG" ] && query_params="?ref=$GIT_TAG"
|
[ -n "$PBC_GIT_TAG" ] && query_params="?ref=$PBC_GIT_TAG"
|
||||||
|
|
||||||
while (( $# > 0 )); do
|
while (( $# > 0 )); do
|
||||||
path=$1
|
path=$1
|
||||||
@@ -26,9 +26,9 @@ function download_files() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
GIT_TAG=$(curl -fsSL "$API_ENDPOINT/tags" | grep -o '{"name":"[^"]*"' | sed 's/^{"name":"//;s/"$//' | grep -E '^v[0-9]+' | sort --version-sort | tail -n 1)
|
[ -z "$PBC_GIT_TAG" ] && PBC_GIT_TAG=$(curl -fsSL "$API_ENDPOINT/tags" | grep -o '{"name":"[^"]*"' | sed 's/^{"name":"//;s/"$//' | grep -E '^v[0-9]+' | sort --version-sort | tail -n 1)
|
||||||
if [ -n "$GIT_TAG" ]; then
|
if [ -n "$PBC_GIT_TAG" ]; then
|
||||||
echo "Info: installing pbc version $GIT_TAG"
|
echo "Info: installing pbc version $PBC_GIT_TAG"
|
||||||
else
|
else
|
||||||
echo "Warning: no Git tag found, installing from repository default branch" >&2
|
echo "Warning: no Git tag found, installing from repository default branch" >&2
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -53,17 +53,17 @@ function pbc-version() {
|
|||||||
return $rc
|
return $rc
|
||||||
}
|
}
|
||||||
|
|
||||||
function pbc-backup() { "$PBC" backup --ns "$PBS_NAMESPACE" "${BACKUP[@]}" "$@"; }
|
function pbc-backup() { "$PBC" backup "${PBC_ARGS[@]}" "${KEY_ARGS[@]}" "${BACKUP[@]}" "$@"; }
|
||||||
|
|
||||||
function pbc-list() { "$PBC" list --ns "$PBS_NAMESPACE" "$@"; }
|
function pbc-list() { "$PBC" list "${PBC_ARGS[@]}" "$@"; }
|
||||||
|
|
||||||
function pbc-snapshot-list() { "$PBC" snapshot list --ns "$PBS_NAMESPACE" "$@"; }
|
function pbc-snapshot-list() { "$PBC" snapshot list "${PBC_ARGS[@]}" "$@"; }
|
||||||
|
|
||||||
function pbc-files() { "$PBC" files --ns "$PBS_NAMESPACE" "$@"; }
|
function pbc-files() { "$PBC" files "${PBC_ARGS[@]}" "$@"; }
|
||||||
|
|
||||||
function pbc-catalog-shell() { "$PBC" catalog shell --ns "$PBS_NAMESPACE" "$@"; }
|
function pbc-catalog-shell() { "$PBC" catalog shell "${PBC_ARGS[@]}" "${KEY_ARGS[@]}" "$@"; }
|
||||||
|
|
||||||
function pbc-mount() { "$PBC" mount --ns "$PBS_NAMESPACE" "$@"; }
|
function pbc-mount() { "$PBC" mount "${PBC_ARGS[@]}" "${KEY_ARGS[@]}" "$@"; }
|
||||||
|
|
||||||
function get_group_names() {
|
function get_group_names() {
|
||||||
pbc-list --output-format json | jq -r '.[] | "\(.["backup-type"])/\(.["backup-id"])"'
|
pbc-list --output-format json | jq -r '.[] | "\(.["backup-type"])/\(.["backup-id"])"'
|
||||||
@@ -86,7 +86,6 @@ function apt_get_install() {
|
|||||||
return $rc
|
return $rc
|
||||||
}
|
}
|
||||||
|
|
||||||
WORK_DIR=''
|
|
||||||
function cleanup_work_dir() {
|
function cleanup_work_dir() {
|
||||||
[ -n "$WORK_DIR" ] && [ -d "$WORK_DIR" ] && rm -r "$WORK_DIR"
|
[ -n "$WORK_DIR" ] && [ -d "$WORK_DIR" ] && rm -r "$WORK_DIR"
|
||||||
trap - EXIT
|
trap - EXIT
|
||||||
@@ -143,7 +142,6 @@ function install_proxmox_backup_client() {
|
|||||||
cleanup_work_dir
|
cleanup_work_dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function arr_contains() {
|
function arr_contains() {
|
||||||
local search=$1
|
local search=$1
|
||||||
shift
|
shift
|
||||||
@@ -275,7 +273,6 @@ while (( $# > 0 )); do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
||||||
if [ -f "$cfgfile" ]; then
|
if [ -f "$cfgfile" ]; then
|
||||||
perms=$(stat -c '%a' "$cfgfile")
|
perms=$(stat -c '%a' "$cfgfile")
|
||||||
(( 8#$perms & 0022 )) && echo "Error: $cfgfile is writable by group or others!" >&2 && exit 202
|
(( 8#$perms & 0022 )) && echo "Error: $cfgfile is writable by group or others!" >&2 && exit 202
|
||||||
@@ -307,8 +304,20 @@ else
|
|||||||
[ ! -f "$PBC" ] && echo "Error: $PBC: no such file" >&2 && exit 200
|
[ ! -f "$PBC" ] && echo "Error: $PBC: no such file" >&2 && exit 200
|
||||||
[ ! -x "$PBC" ] && echo "Error: $PBC: not executable" >&2 && exit 200
|
[ ! -x "$PBC" ] && echo "Error: $PBC: not executable" >&2 && exit 200
|
||||||
! command -v jq >/dev/null && echo "Error: jq executable not found!" >&2 && exit 200
|
! command -v jq >/dev/null && echo "Error: jq executable not found!" >&2 && exit 200
|
||||||
|
if [ -n "$ENCRYPTION_KEYFILE" ]; then
|
||||||
|
[ ! -f "$ENCRYPTION_KEYFILE" ] && echo "Error: $ENCRYPTION_KEYFILE: no such file" >&2 && exit 203
|
||||||
|
[ ! -r "$ENCRYPTION_KEYFILE" ] && echo "Error: $ENCRYPTION_KEYFILE: not readable" >&2 && exit 203
|
||||||
|
perms=$(stat -c '%a' "$ENCRYPTION_KEYFILE")
|
||||||
|
(( 8#$perms & 0007 )) && echo "Warning: $ENCRYPTION_KEYFILE is readable by others!" >&2
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
PBC_ARGS=()
|
||||||
|
KEY_ARGS=()
|
||||||
|
|
||||||
|
[ -n "$PBS_NAMESPACE" ] && PBC_ARGS+=('--ns' "$PBS_NAMESPACE")
|
||||||
|
[ -n "$ENCRYPTION_KEYFILE" ] && KEY_ARGS+=('--keyfile' "$ENCRYPTION_KEYFILE")
|
||||||
|
|
||||||
export PBS_REPOSITORY="$PBS_USER@$PBS_SERVER:$PBS_DATASTORE"
|
export PBS_REPOSITORY="$PBS_USER@$PBS_SERVER:$PBS_DATASTORE"
|
||||||
export PBS_PASSWORD="$PBS_PASSWORD"
|
export PBS_PASSWORD="$PBS_PASSWORD"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user