mirror of
https://github.com/edv-pi/pbs-client-docker.git
synced 2025-06-08 13:30:45 +02:00
Compare commits
10 Commits
31286b4b71
...
c69aa29b60
Author | SHA1 | Date | |
---|---|---|---|
|
c69aa29b60 | ||
|
1f23e92047 | ||
|
4312e6db39 | ||
|
7060d5c4e0 | ||
|
c6d06fa760 | ||
|
6da5b7ba85 | ||
|
92f23512e1 | ||
|
2fc4c03adb | ||
|
a2bafd2696 | ||
|
cb750d4cb4 |
@ -1,31 +0,0 @@
|
||||
#
|
||||
# .gitea/gitea-ci.yaml
|
||||
#
|
||||
|
||||
name: Build And Test
|
||||
run-name: ${{ gitea.actor }} is runs ci pipeline
|
||||
on: [ push ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: https://github.com/docker/setup-buildx-action@v3
|
||||
with:
|
||||
config-inline: |
|
||||
[registry."images.physi.uni-heidelberg.de"]
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: images.physi.uni-heidelberg.de/pibackup
|
||||
- name: Build and push Docker image
|
||||
uses: https://github.com/docker/build-push-action@v6
|
||||
with:
|
||||
context: ./docker
|
||||
push: true
|
||||
#tags: "images.physi.uni-heidelberg.de/pibackup,images.physi.uni-heidelberg.de/pibackup:latest"
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
35
README.md
35
README.md
@ -1,6 +1,8 @@
|
||||
# Proxmox Backup Server: Client Docker
|
||||
|
||||

|
||||
[](https://github.com/Aterfax/pbs-client-docker/actions/workflows/docker-publish.yml)
|
||||

|
||||

|
||||
|
||||
## **tl;dr?**
|
||||
|
||||
@ -21,7 +23,6 @@ For more in depth instructions, see: [Using-the-DockerHub-provided-image](#Using
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [PI Customs](#PI)
|
||||
- [Quickstart](#Quickstart)
|
||||
- [Configuration](#Configuration)
|
||||
- [FAQ](#FAQ)
|
||||
@ -29,14 +30,6 @@ For more in depth instructions, see: [Using-the-DockerHub-provided-image](#Using
|
||||
- [Contributing](#Contributing)
|
||||
- [License](#License)
|
||||
|
||||
## PI
|
||||
|
||||
### Changes
|
||||
|
||||
Encryption can be turned off.
|
||||
Most of the Data is scientific related and does not contain any secrets and it would be a waste of ressources to encrypt that and loose the deduplication of it because older backups of similar data arent encrypted.
|
||||
For sensible data you can and should enable the encryption with its own set of keypairs and keep them stored seperate from the backups.
|
||||
|
||||
## Quickstart
|
||||
|
||||
### Prerequisites
|
||||
@ -47,6 +40,9 @@ For sensible data you can and should enable the encryption with its own set of k
|
||||
|
||||
### Using the DockerHub provided image
|
||||
|
||||
> [!WARNING]
|
||||
> It is possible, but highly discouraged for you to make unencrypted backups by setting `UNENCRYPTED=1` in your ``.env`` file. This will bypass the automatic key generation process but **this is a bad idea** as the backed-up data will be stored in plaintext. This means that the owner of the PBS backup server you are backing up to will have full access to explore the backed-up content.
|
||||
|
||||
* Run the image with the provided docker-compose file after amending it and the ``.env`` file where needed.
|
||||
* If allowing the container to conduct an auto setup, don't set a ``PBS_ENCRYPTION_PASSWORD`` value yet as the container first run will autogenerate one for you.
|
||||
* Supply your desired ``master-public.pem``, ``master-private.pem`` and ``encryption-key.json`` files with a matching ``PBS_ENCRYPTION_PASSWORD`` or allow the container to automatically generate these for you on first run.
|
||||
@ -88,17 +84,9 @@ The following environment variables can be configured to customize the behavior
|
||||
|
||||
| Variable Name | Default Docker Compose Value | Valid Values | Description |
|
||||
|--------------------|------------------------------|------------------------|-----------------------------------------------------------------------------------------------------------------------|
|
||||
| PBS_ENDPOINT | none | fqdn | target PBS-server |
|
||||
| PBS_FINGERPRINT | none | XX:XX:XX:XX... | your fringerprint of your pbs instance |
|
||||
| PBS_DATASTORE | none | string | name of your pbs datastore |
|
||||
| PBS_DATASTORE_ns | none | string | name of your pbs namespace |
|
||||
| CRON_SCHEDULE | none | * * * * * | cron expression to define the shedule for backups |
|
||||
| CRON_BACKUP_ONLY | none | boolean | controls if first backup will be done at the first start |
|
||||
| PBS_API_KEY_NAME or PBS_USER | none | string | credentials for pbs either define an api token or specify user |
|
||||
| PBS_API_KEY_SECRET or PBS_PASSWORD | none | string | password or secret |
|
||||
| TZ | none | IANA's time zone database long | Timezone to use for tuimestamps in backup |
|
||||
| UNENCRYPTED | 0 | boolean | disables encryption if set to 1 |
|
||||
| CHANGE_DETECT_MODE | metadata | metadata, data, legacy | Let you choose the desired mode for detecting file changes between backups |
|
||||
| Variable Name | Default Docker Compose Value | Valid Values | Description |
|
||||
|
||||
|
||||
|
||||
## FAQ
|
||||
|
||||
@ -112,6 +100,11 @@ See also:
|
||||
- https://github.com/Aterfax/pbs-client-docker/issues/8
|
||||
- https://forum.proxmox.com/threads/backup-client-encryption-not-working-inside-docker-container.139054/
|
||||
|
||||
> [!WARNING]
|
||||
> It is possible, but highly discouraged for you to bypass this issue by taking unencrypted backups. You can do this by setting `UNENCRYPTED=1` in your ``.env`` file and this will bypass the automatic key generation process.
|
||||
>
|
||||
>**This is a bad idea** as the backed-up data will be stored in plaintext. This means that the owner of the PBS backup server you are backing up to will have full access to explore the backed-up content.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter issues, check the [Troubleshooting section](TROUBLESHOOTING.md) for solutions to common problems.
|
||||
|
@ -1,32 +1,34 @@
|
||||
# The 4 variables below are required.
|
||||
PBS_ENCRYPTION_PASSWORD="123456789abcdefghijklmn"
|
||||
PBS_ENDPOINT="pbs.mydomain.com"
|
||||
PBS_DATASTORE="test-datastore"
|
||||
CRON_SCHEDULE="0 */4 * * *"
|
||||
PBS_ENCRYPTION_PASSWORD=123456789abcdefghijklmn
|
||||
PBS_ENDPOINT=pbs.mydomain.com
|
||||
PBS_DATASTORE=test-datastore
|
||||
CRON_SCHEDULE=0 */4 * * *
|
||||
# If you want to skip backup on startup, set CRON_BACKUP_ONLY=1 otherwise CRON_BACKUP_ONLY=0
|
||||
CRON_BACKUP_ONLY=0
|
||||
|
||||
# Set UNENCRYPTED=1 to bypass automatic encryption key generation and allow the backups to be unencrypted.
|
||||
# This is a bad idea as the owner of the PBS backup server you are backing up to will have full access to
|
||||
# explore the backed-up content.
|
||||
UNENCRYPTED=0
|
||||
|
||||
# Use of the PBS_API_KEY_NAME and PBS_API_KEY_SECRET is recommended!
|
||||
# If unset, ensure PBS_USER and PBS_PASSWORD are set.
|
||||
PBS_API_KEY_NAME="username@pam!test"
|
||||
PBS_API_KEY_SECRET="4054356a-f1a6-441e-86fc-e338367db185"
|
||||
PBS_API_KEY_NAME=username@pam!test
|
||||
PBS_API_KEY_SECRET=4054356a-f1a6-441e-86fc-e338367db185
|
||||
|
||||
# PBS_USER is not required if PBS_API_KEY_NAME is set.
|
||||
# PBS_PASSWORD is not required if PBS_API_KEY_SECRET is set.
|
||||
PBS_USER=""
|
||||
PBS_PASSWORD=""
|
||||
PBS_USER=
|
||||
PBS_PASSWORD=
|
||||
|
||||
# PBS_DATASTORE_NS is optional but should be set if using namespaces.
|
||||
PBS_DATASTORE_NS="test"
|
||||
PBS_DATASTORE_NS=test
|
||||
|
||||
# PBS_FINGERPRINT is required if using a self signed SSL certificate.
|
||||
PBS_FINGERPRINT=""
|
||||
PBS_FINGERPRINT=
|
||||
|
||||
# Healthchecks.io details - Optional.
|
||||
HEALTHCHECKSUUID="aa7b0de3-2c17-4fce-b051-388a5415e656"
|
||||
HEALTHCHECKSHOSTNAME="https://healthchecks.mydomain.com"
|
||||
HEALTHCHECKSUUID=aa7b0de3-2c17-4fce-b051-388a5415e656
|
||||
HEALTHCHECKSHOSTNAME=https://healthchecks.mydomain.com
|
||||
|
||||
TZ=Etc/UTC
|
||||
|
||||
# Disable Encryption
|
||||
ENCRYPTION=1
|
@ -1,6 +1,7 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
pbs-client:
|
||||
image: tmueller/pbs-client
|
||||
image: aterfax/pbs-client
|
||||
container_name: pbs-client
|
||||
hostname: pbs-client
|
||||
restart: unless-stopped
|
||||
@ -11,17 +12,8 @@ services:
|
||||
- /run:exec
|
||||
volumes:
|
||||
- ./pbsconfig/:/root/.config/proxmox-backup/
|
||||
# Note - if you want to restore backups make sure to change to read write below.
|
||||
# See the 'restore-backup' command inside the container.
|
||||
- ./backups/test1:/backups/test1:ro
|
||||
- ./backups/test2:/backups/test2:ro
|
||||
- ./backups/test3:/backups/test3:ro
|
||||
- type: bind
|
||||
source: /restore
|
||||
target: /restore
|
||||
bind:
|
||||
propagation: rshared
|
||||
cap_add:
|
||||
- SYS_ADMIN
|
||||
devices:
|
||||
- /dev/fuse:/dev/fuse
|
||||
security_opt:
|
||||
- apparmor:unconfined
|
@ -1,73 +0,0 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
# shellcheck shell=bash
|
||||
|
||||
source /etc/s6-overlay/s6-rc.d/setup_check/run_include
|
||||
|
||||
# We need to build this command in case namespaces are in use.
|
||||
MOUNTCMD="proxmox-backup-client mount"
|
||||
LISTCMD="proxmox-backup-client snapshot list"
|
||||
if [ -n "$PBS_DATASTORE_NS" ]; then
|
||||
LISTCMD+=" --ns ${PBS_DATASTORE_NS}"
|
||||
MOUNTCMD+=" --ns ${PBS_DATASTORE_NS}"
|
||||
fi
|
||||
LISTCMD+=" --output-format json"
|
||||
|
||||
data=$(${LISTCMD})
|
||||
host_name=$(hostname)
|
||||
|
||||
# Backups in einer nummerierten Liste ausgeben
|
||||
options=$(echo "$data" | jq -r --arg host "$host_name" '
|
||||
.[] |
|
||||
select(.["backup-id"] == $host) |
|
||||
"\(.["backup-type"])/\($host)/\(.["backup-time"] | tonumber | strftime("%Y-%m-%dT%H:%M:%SZ"))"')
|
||||
|
||||
# Array erstellen und anzeigen
|
||||
echo "VerfĂĽgbare Backups:"
|
||||
IFS=$'\n' read -d '' -r -a backups <<< "$options"
|
||||
for i in "${!backups[@]}"; do
|
||||
echo "$((i + 1)). ${backups[i]}"
|
||||
done
|
||||
|
||||
# Benutzereingabe fĂĽr die Auswahl
|
||||
read -p "Wählen Sie ein Backup aus (Nummer eingeben): " selection
|
||||
|
||||
# ĂśberprĂĽfen, ob die Eingabe gĂĽltig ist
|
||||
if [[ "$selection" -ge 1 && "$selection" -le "${#backups[@]}" ]]; then
|
||||
selected_backup="${backups[$((selection - 1))]}"
|
||||
MOUNTCMD+=" $selected_backup"
|
||||
echo "Ausgewähltes Backup: $selected_backup"
|
||||
|
||||
# Dateien des ausgewählten Backups abrufen
|
||||
backup_index=$((selection - 1))
|
||||
files=$(echo "$data" | jq -r --argjson index "$backup_index" '
|
||||
.[$index].files[].filename | select(test("\\.pxar.didx$|\\.mpxar.didx$"))')
|
||||
|
||||
# Dateien in einer nummerierten Liste ausgeben
|
||||
echo "VerfĂĽgbare Dateien:"
|
||||
IFS=$'\n' read -d '' -r -a file_list <<< "$files"
|
||||
for i in "${!file_list[@]}"; do
|
||||
echo "$((i + 1)). ${file_list[i]}"
|
||||
done
|
||||
|
||||
# Benutzereingabe fĂĽr die Dateiauswahl
|
||||
read -p "Wählen Sie eine Datei aus (Nummer eingeben): " file_selection
|
||||
|
||||
# ĂśberprĂĽfen, ob die Eingabe gĂĽltig ist
|
||||
if [[ "$file_selection" -ge 1 && "$file_selection" -le "${#file_list[@]}" ]]; then
|
||||
selected_file="${file_list[$((file_selection - 1))]}"
|
||||
MOUNTCMD+=" $selected_file"
|
||||
echo "Ausgewählte Datei: $selected_file"
|
||||
echo "DEBUG: $MOUNTCMD"
|
||||
MOUNTCMD+=" /restore"
|
||||
$(${MOUNTCMD})
|
||||
echo "Dont forget to unmount when finished (just umount /path/on/the/host i.e. /restore)"
|
||||
else
|
||||
echo "UngĂĽltige Auswahl. Abbruch."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "UngĂĽltige Auswahl. Abbruch."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
@ -116,7 +116,7 @@ read -rp "Are these details correct? Press Enter to continue or Ctrl+C to cancel
|
||||
#proxmox-backup-client restore host/elsa/2019-12-03T09:35:01Z root.pxar /target/path/
|
||||
# We need to build this command in case namespaces are in use.
|
||||
RESTORECMD="proxmox-backup-client restore ${selected_backup_type}/${selected_backup_id}/${selected_backup_time} ${selected_file} ${restore_path}"
|
||||
if [ -n "$RESTORECMD" ]; then
|
||||
if [ -n "$PBS_DATASTORE_NS" ]; then
|
||||
RESTORECMD+=" --ns ${PBS_DATASTORE_NS}"
|
||||
fi
|
||||
echo -e "\nRestore command:"
|
||||
@ -124,4 +124,4 @@ echo "${RESTORECMD}"
|
||||
|
||||
read -rp "Is this restore command correct? Press Enter to continue or Ctrl+C to cancel..."
|
||||
|
||||
$RESTORECMD
|
||||
$RESTORECMD
|
||||
|
@ -47,21 +47,6 @@ if [ -n "$PBS_DATASTORE_NS" ]; then
|
||||
BACKUPCMD+=" --ns ${PBS_DATASTORE_NS}"
|
||||
fi
|
||||
|
||||
# Add possibility to exlude paths
|
||||
if [ -n "$EXCLUDE" ]; then
|
||||
IFS=', ' read -r -a array <<< "$EXCLUDE"
|
||||
for element in "${array[@]}"
|
||||
do
|
||||
BACKUPCMD+=" --exclude ${element}"
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -n "$CHANGE_DETECT_MODE" ]; then
|
||||
BACKUPCMD+=" --change-detection-mode=${CHANGE_DETECT_MODE}"
|
||||
else
|
||||
BACKUPCMD+=" --change-detection-mode=metadata"
|
||||
fi
|
||||
|
||||
# Source the variables from the setup_check scripting include file.
|
||||
source /etc/s6-overlay/s6-rc.d/setup_check/run_include
|
||||
|
||||
@ -75,7 +60,7 @@ fi
|
||||
# to allow the first backup or we're
|
||||
if [ "$CRON_BACKUP_ONLY" = "0" ] || [ -e "${lastrunfile}" ]; then
|
||||
if [ -n "$HEALTHCHECKSURL" ]; then
|
||||
curl -fsS -m 10 --retry 5 $HEALTHCHECKSURL/start
|
||||
curl -fsS -m 10 --retry 5 -o /dev/null $HEALTHCHECKSURL/start
|
||||
fi
|
||||
|
||||
# Run the actual backup command.
|
||||
@ -87,7 +72,7 @@ if [ "$CRON_BACKUP_ONLY" = "0" ] || [ -e "${lastrunfile}" ]; then
|
||||
|
||||
if [ -n "$HEALTHCHECKSURL" ]; then
|
||||
# We pipe the exit code to healthchecks, if it isn't zero, a warning will fire.
|
||||
curl -fsS -m 10 --retry 5 ${HEALTHCHECKSURL}/${BACKUP_EXIT_CODE}
|
||||
curl -fsS -m 10 --retry 5 -o /dev/null ${HEALTHCHECKSURL}/${BACKUP_EXIT_CODE}
|
||||
fi
|
||||
elif [ "$CRON_BACKUP_ONLY" = "1" ]; then
|
||||
echo "CRON_BACKUP_ONLY=1, skipping container start up initial backup."
|
||||
@ -98,4 +83,4 @@ fi
|
||||
# Set this so backups always happen after the first run via CRON given logic above.
|
||||
# The date may also be useful for something like a health check if I write it...
|
||||
# First run touches the file. Backups will set the date.
|
||||
touch "${lastrunfile}"
|
||||
touch "${lastrunfile}"
|
||||
|
@ -32,7 +32,7 @@ fi
|
||||
|
||||
CRONLOG_FILE="/root/.config/proxmox-backup/cron.log"
|
||||
CRON_FILE="/etc/cron.d/cron-backup"
|
||||
CRON_LINE="${CRON_SCHEDULE} root bash -c '/etc/s6-overlay/s6-rc.d/backup/run_include' >> $CRONLOG_FILE || curl -d \"Archsync failed\" ntfy-server/pbs 2>&1"
|
||||
CRON_LINE="${CRON_SCHEDULE} root bash -c '/etc/s6-overlay/s6-rc.d/backup/run_include' >> $CRONLOG_FILE 2>&1 "
|
||||
TIMEOUT=60
|
||||
|
||||
touch "${CRONLOG_FILE}"
|
||||
|
@ -20,35 +20,39 @@ handle_error() {
|
||||
}
|
||||
trap handle_error ERR
|
||||
|
||||
# Check if encryption is disabled via environment variable
|
||||
if [ "${UNENCRYPTED}" = "1" ]; then
|
||||
echo "Encrypted backups are disabled. Skipping key setup process."
|
||||
echo ""
|
||||
echo "This is a bad idea as the owner of the PBS backup server you are backing up to will have full access to explore the backed-up content."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
client_encryption_keyfile="/root/.config/proxmox-backup/encryption-key.json"
|
||||
master_private_keyfile="/root/.config/proxmox-backup/master-private.pem"
|
||||
master_public_keyfile="/root/.config/proxmox-backup/master-public.pem"
|
||||
|
||||
expect="/usr/bin/expect"
|
||||
|
||||
if [ "$UNENCRYPTED" = "1" ]; then
|
||||
echo "Encryption set to false not gonna create any Keys."
|
||||
# Check if client encryption keyfile exists and do stuff.
|
||||
if [ -f "$client_encryption_keyfile" ]; then
|
||||
echo "Client encryption keyfile exists. Skipping client encryption keyfile creation."
|
||||
else
|
||||
# Check if client encryption keyfile exists and do stuff.
|
||||
if [ -f "$client_encryption_keyfile" ]; then
|
||||
echo "Client encryption keyfile exists. Skipping client encryption keyfile creation."
|
||||
else
|
||||
echo "Client encryption keyfile does not exist. Creating new client encryption keyfile."
|
||||
$expect /etc/s6-overlay/s6-rc.d/key_setup/client_key
|
||||
fi
|
||||
echo "Client encryption keyfile does not exist. Creating new client encryption keyfile."
|
||||
$expect /etc/s6-overlay/s6-rc.d/key_setup/client_key
|
||||
fi
|
||||
|
||||
# Check if both Master keyfiles exist and do stuff.
|
||||
if [ -f "$master_private_keyfile" ] && [ -f "$master_public_keyfile" ]; then
|
||||
echo "Both master private and public keys exist. Skipping client Master keyfiles creation."
|
||||
|
||||
elif [ ! -f "$master_private_keyfile" ] && [ ! -f "$master_public_keyfile" ]; then
|
||||
echo "Both master private and public keys do not exist. Creating master keyfiles new pair."
|
||||
cd /root/.config/proxmox-backup/ && $expect /etc/s6-overlay/s6-rc.d/key_setup/client_master_key
|
||||
|
||||
elif [ ! -f "$master_private_keyfile" ] || [ ! -f "$master_public_keyfile" ]; then
|
||||
echo "One of the master keyfiles is missing. Error! User intervention required. Ensure correct files present, or remove both: \n"
|
||||
echo "$master_private_keyfile \n"
|
||||
echo "$master_oublic_keyfile \n"
|
||||
echo "To allow for automatic key recreation."
|
||||
fi
|
||||
# Check if both Master keyfiles exist and do stuff.
|
||||
if [ -f "$master_private_keyfile" ] && [ -f "$master_public_keyfile" ]; then
|
||||
echo "Both master private and public keys exist. Skipping client Master keyfiles creation."
|
||||
|
||||
elif [ ! -f "$master_private_keyfile" ] && [ ! -f "$master_public_keyfile" ]; then
|
||||
echo "Both master private and public keys do not exist. Creating master keyfiles new pair."
|
||||
cd /root/.config/proxmox-backup/ && $expect /etc/s6-overlay/s6-rc.d/key_setup/client_master_key
|
||||
|
||||
elif [ ! -f "$master_private_keyfile" ] || [ ! -f "$master_public_keyfile" ]; then
|
||||
echo "One of the master keyfiles is missing. Error! User intervention required. Ensure correct files present, or remove both: \n"
|
||||
echo "$master_private_keyfile \n"
|
||||
echo "$master_oublic_keyfile \n"
|
||||
echo "To allow for automatic key recreation."
|
||||
fi
|
@ -30,15 +30,11 @@ if [ -z "$PBS_PASSWORD" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$UNENCRYPTED" ]; then
|
||||
UNENCRYPTED="${UNENCRYPTED}"
|
||||
else
|
||||
UNENCRYPTED="0"
|
||||
# Evaluate each subvariable and replace all spaces with nothing - if not zero length set variable.
|
||||
if [[ ! -z "${HEALTHCHECKSHOSTNAME// }" ]] && [[ ! -z "${HEALTHCHECKSUUID// }" ]]; then
|
||||
HEALTHCHECKSURL="${HEALTHCHECKSHOSTNAME}/ping/${HEALTHCHECKSUUID}"
|
||||
export HEALTHCHECKSURL="${HEALTHCHECKSURL}"
|
||||
fi
|
||||
|
||||
HEALTHCHECKSURL="${HEALTHCHECKSHOSTNAME}/ping/${HEALTHCHECKSUUID}"
|
||||
|
||||
export UNENCRYPTED
|
||||
export HEALTHCHECKSURL="${HEALTHCHECKSURL}"
|
||||
export PBS_PASSWORD="${PBS_PASSWORD}"
|
||||
export PBS_REPOSITORY="${PBS_USER}@${PBS_ENDPOINT}:${PBS_DATASTORE}"
|
||||
export PBS_REPOSITORY="${PBS_USER}@${PBS_ENDPOINT}:${PBS_DATASTORE}"
|
||||
|
Loading…
Reference in New Issue
Block a user