Files
Malio-ops/BackupVaultWarden/backup-vaultwarden.sh
2026-03-19 09:57:12 +01:00

235 lines
7.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
umask 077
#######################################
# Chemins fixes du script
#######################################
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="${SCRIPT_DIR}/.env"
LOG_FILE="/var/log/vaultwarden_backup.log"
mkdir -p "$(dirname "$LOG_FILE")"
touch "$LOG_FILE"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
#######################################
# Vérification fichier .env
#######################################
[[ -f "$ENV_FILE" ]] || {
echo "ERROR: Fichier .env introuvable : $ENV_FILE" >&2
exit 1
}
#######################################
# Chargement du .env
#######################################
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
#######################################
# Variables obligatoires
#######################################
: "${DISCORD_WEBHOOK_URL:=}"
: "${DATA_DIR:?Variable DATA_DIR manquante dans .env}"
: "${LOCAL_BACKUP:?Variable LOCAL_BACKUP manquante dans .env}"
: "${REMOTE_USER:?Variable REMOTE_USER manquante dans .env}"
: "${REMOTE_HOST:?Variable REMOTE_HOST manquante dans .env}"
: "${REMOTE_DIR:?Variable REMOTE_DIR manquante dans .env}"
[[ "$REMOTE_DIR" =~ ^[a-zA-Z0-9/_.-]+$ ]] || {
echo "ERROR: Variable REMOTE_DIR invalide dans .env" >&2
exit 1
}
: "${SSH_KEY:?Variable SSH_KEY manquante dans .env}"
: "${BACKUP_REMOTE_SSH_PORT:=22}"
: "${SSH_CONNECT_TIMEOUT:=10}"
: "${BACKUP_KNOWN_HOSTS_STRICT:=yes}"
: "${BACKUP_KNOWN_HOSTS_FILE:=${HOME}/.ssh/known_hosts}"
#######################################
# Variables backup
#######################################
DATE="$(date +'%Y-%m-%d_%H-%M-%S')"
BACKUP_PREFIX="vaultwarden-backup"
BACKUP_NAME="${BACKUP_PREFIX}-${DATE}.tar.gz"
LOCAL_BACKUP_FILE="${LOCAL_BACKUP}/${BACKUP_NAME}"
RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-10}"
[[ "$BACKUP_REMOTE_SSH_PORT" =~ ^[0-9]+$ ]] || {
echo "ERROR: Variable BACKUP_REMOTE_SSH_PORT invalide dans .env" >&2
exit 1
}
[[ "$SSH_CONNECT_TIMEOUT" =~ ^[0-9]+$ ]] || {
echo "ERROR: Variable SSH_CONNECT_TIMEOUT invalide dans .env" >&2
exit 1
}
[[ "$RETENTION_DAYS" =~ ^[0-9]+$ ]] || {
echo "ERROR: Variable BACKUP_RETENTION_DAYS invalide dans .env" >&2
exit 1
}
case "${BACKUP_KNOWN_HOSTS_STRICT,,}" in
yes|y|oui|o|true|1) BACKUP_KNOWN_HOSTS_STRICT="yes" ;;
no|n|non|false|0) BACKUP_KNOWN_HOSTS_STRICT="no" ;;
*)
echo "ERROR: Variable BACKUP_KNOWN_HOSTS_STRICT invalide dans .env" >&2
exit 1
;;
esac
mkdir -p "$(dirname "$BACKUP_KNOWN_HOSTS_FILE")"
chmod 700 "$(dirname "$BACKUP_KNOWN_HOSTS_FILE")" || true
touch "$BACKUP_KNOWN_HOSTS_FILE"
chmod 600 "$BACKUP_KNOWN_HOSTS_FILE" || true
SSH_OPTS=(
-i "$SSH_KEY"
-p "$BACKUP_REMOTE_SSH_PORT"
-o IdentitiesOnly=yes
-o BatchMode=yes
-o ConnectTimeout="$SSH_CONNECT_TIMEOUT"
-o StrictHostKeyChecking="$BACKUP_KNOWN_HOSTS_STRICT"
-o UserKnownHostsFile="$BACKUP_KNOWN_HOSTS_FILE"
)
SCP_OPTS=(
-i "$SSH_KEY"
-P "$BACKUP_REMOTE_SSH_PORT"
-o IdentitiesOnly=yes
-o BatchMode=yes
-o ConnectTimeout="$SSH_CONNECT_TIMEOUT"
-o StrictHostKeyChecking="$BACKUP_KNOWN_HOSTS_STRICT"
-o UserKnownHostsFile="$BACKUP_KNOWN_HOSTS_FILE"
)
mkdir -p "$LOCAL_BACKUP"
#######################################
# Notification Discord
#######################################
send_discord() {
local success="$1"
local details="${2:-}"
local payload=""
[[ -z "$DISCORD_WEBHOOK_URL" ]] && return 0
require_cmd jq || return 0
require_cmd curl || return 0
local icon status_line
if [[ "$success" == "true" ]]; then
icon="🟢"
status_line="✅"
ping=""
else
icon="🔴"
status_line="❌"
ping="@here "
fi
local msg
msg="**${ping}Backup Vaultwarden ${icon}**\n"
msg+="Backup: ${BACKUP_NAME}\n"
msg+="Data transfer: ${status_line}\n"
[[ -n "$details" ]] && msg+="Détails: ${details}"
payload="$(jq -n --arg content "$msg" '{content: $content}')"
curl -fsS -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK_URL" >/dev/null || true
}
#######################################
# Fonction erreur
#######################################
fail() {
local detail="$1"
log "ERROR: $detail"
send_discord "false" "$detail"
exit 1
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "commande requise absente : $1"
}
#######################################
# Verrou d'execution
#######################################
LOCK_DIR="/tmp/vaultwarden_backup.lock.d"
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
fail "Backup deja en cours"
fi
cleanup() {
rm -f "${LOCAL_BACKUP_FILE:-}"
rm -rf -- "$LOCK_DIR"
}
trap cleanup EXIT
#######################################
# Vérifications préalables
#######################################
[[ -d "$DATA_DIR" ]] || fail "Le dossier source n'existe pas : $DATA_DIR"
[[ -f "$SSH_KEY" ]] || fail "La clé SSH est introuvable : $SSH_KEY"
[[ -r "$SSH_KEY" ]] || fail "La clé SSH est non lisible : $SSH_KEY"
[[ ! -L "$SSH_KEY" ]] || fail "La clé SSH ne doit pas être un lien symbolique : $SSH_KEY"
chmod 600 "$SSH_KEY" || true
for cmd in tar ssh scp jq curl find; do
require_cmd "$cmd"
done
log "Début du backup Vaultwarden"
log "Source : $DATA_DIR"
log "Archive locale : $LOCAL_BACKUP_FILE"
log "Destination distante : ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}"
#######################################
# Création du backup
#######################################
tar -czf "$LOCAL_BACKUP_FILE" -C "$(dirname "$DATA_DIR")" "$(basename "$DATA_DIR")" \
|| fail "Erreur lors de la compression du dossier $DATA_DIR"
log "Backup local créé : $LOCAL_BACKUP_FILE"
#######################################
# Création dossier distant
#######################################
ssh "${SSH_OPTS[@]}" "$REMOTE_USER@$REMOTE_HOST" "mkdir -p '$REMOTE_DIR'" \
|| fail "Impossible de créer le dossier distant $REMOTE_DIR"
#######################################
# Envoi du backup
#######################################
scp "${SCP_OPTS[@]}" "$LOCAL_BACKUP_FILE" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/" \
|| fail "Erreur lors de l'envoi du backup vers $REMOTE_HOST"
log "Backup envoyé sur $REMOTE_HOST:$REMOTE_DIR"
#######################################
# Rotation distante - suppression > 10 jours
#######################################
ssh "${SSH_OPTS[@]}" "$REMOTE_USER@$REMOTE_HOST" \
"find '$REMOTE_DIR' -type f -name '${BACKUP_PREFIX}-*.tar.gz' -mtime +$RETENTION_DAYS -delete" \
|| fail "Erreur lors de la rotation distante des sauvegardes"
log "Rotation distante OK"
#######################################
# Nettoyage local
#######################################
rm -f "$LOCAL_BACKUP_FILE" || fail "Impossible de supprimer le backup local $LOCAL_BACKUP_FILE"
#######################################
# Fin
#######################################
log "Backup $BACKUP_NAME terminé et envoyé sur $REMOTE_HOST:$REMOTE_DIR"
send_discord "true" "Backup envoyé avec succès vers $REMOTE_HOST"
echo "Backup $BACKUP_NAME terminé et envoyé sur $REMOTE_HOST:$REMOTE_DIR"