#!/bin/sh
# --------------- [ PREREQUISITES ] ---------------
if [ -n "$BACKUP_ENCRYPTION_KEY" ]; then
EXTENSION="tar.xz.enc"
else
EXTENSION="tar.xz"
fi
TIMESTAMP=$(date +"%F_%H-%M-%S")
BACKUP_FILENAME="vaultwarden_backup_${TIMESTAMP}"
# New line defenition
NL='
'
# Error statuses:
# 0 = Success
# 1 = Network/API error
# 2 = File too large
send_telegram_message() {
local message="$1"
if [ -n "$TG_TOKEN" ] && [ -n "$TG_CHAT_ID" ]; then
if [ "$VWDUMP_DEBUG" = "true" ]; then
echo "[DEBUG] Sending Telegram message..."
fi
# Add timeout and retry logic
local max_retries=3
local retry_count=0
while [ $retry_count -lt $max_retries ]; do
response=$(curl -S -s --max-time 30 --connect-timeout 10 -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
-d "chat_id=${TG_CHAT_ID}" \
-d "text=${message}" \
-d "parse_mode=HTML" 2>&1)
# Check if curl command succeeded
if [ $? -eq 0 ] && echo "$response" | grep -q '"ok":true'; then
if [ "$VWDUMP_DEBUG" = "true" ]; then echo "[DEBUG] Telegram message sent successfully"; fi
return 0
else
echo "[ERROR] Telegram message send failed. Details: $response" >&2
fi
retry_count=$((retry_count + 1))
if [ $retry_count -lt $max_retries ]; then
echo "[INFO] Retrying Telegram message send (attempt $((retry_count + 1))/$max_retries)..." >&2
sleep 5
fi
done
echo "[ERROR] Failed to send Telegram message after $max_retries attempts" >&2
return 1
fi
return 1
}
send_telegram_file() {
local file_path="$1"
local caption="$2"
if [ -n "$TG_TOKEN" ] && [ -n "$TG_CHAT_ID" ] && [ -f "$file_path" ]; then
# Check file size, Telegram limit is 50MB
# See: https://core.telegram.org/bots/api#sending-files
file_size=$(stat -c%s "$file_path" 2>/dev/null || stat -f%z "$file_path" 2>/dev/null)
if [ "$file_size" -ge 52428800 ]; then # 50MB in bytes
echo "[ERROR] File too large for Telegram ($(echo "$file_size" | awk '{print int($1/1024/1024)}')MB)" >&2
return 2
fi
echo "[INFO] Uploading file to Telegram ($(echo "$file_size" | awk '{print int($1/1024)}')KB)..."
# Add timeout and retry logic for file uploads
local max_retries=2
local retry_count=0
while [ $retry_count -lt $max_retries ]; do
response=$(curl -S -s --max-time 120 --connect-timeout 30 -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendDocument" \
-F "chat_id=${TG_CHAT_ID}" \
-F "document=@${file_path}" \
-F "caption=${caption}" \
-F "parse_mode=HTML" 2>&1)
# Check if curl command succeeded
if [ $? -eq 0 ] && echo "$response" | grep -q '"ok":true'; then
if [ "$VWDUMP_DEBUG" = "true" ]; then echo "[DEBUG] Telegram file upload successful"; fi
return 0
else
echo "[ERROR] Telegram file upload failed. Details: $response" >&2
if echo "$response" | grep -q "Request Entity Too Large\|File too large"; then return 2; fi
fi
retry_count=$((retry_count + 1))
if [ $retry_count -lt $max_retries ]; then
echo "[INFO] Retrying Telegram file upload (attempt $((retry_count + 1))/$max_retries)..." >&2
sleep 10
fi
done
echo "[ERROR] Failed to upload file to Telegram after $max_retries attempts" >&2
return 1
fi
return 1
}
# ------------------ [ PRINT CONFIGURATION ] ------------------
if [ "$VWDUMP_DEBUG" = "true" ]; then
echo "[DEBUG] ----- VWDUMP CONFIGURATION -----"
echo "[DEBUG] Cron Schedule: ${CRON_TIME}"
echo "[DEBUG] User ID (UID): ${UID}"
echo "[DEBUG] Group ID (GID): ${GID}"
echo "[DEBUG] Delete After: ${DELETE_AFTER} days"
if [ -n "$BACKUP_ENCRYPTION_KEY" ]; then
echo "[DEBUG] Encryption: Enabled"
echo "[DEBUG] PBKDF2 Iterations: ${PBKDF2_ITERATIONS}"
else
echo "[DEBUG] Encryption: Disabled"
fi
if [ -n "$TG_TOKEN" ] && [ -n "$TG_CHAT_ID" ]; then
echo "[DEBUG] Telegram Notifications: Enabled"
echo "[DEBUG] Telegram Chat ID: ${TG_CHAT_ID}"
echo "[DEBUG] Telegram Token: [set, masked]"
echo "[DEBUG] Disable Warnings: ${DISABLE_WARNINGS}"
echo "[DEBUG] Disable Uploads: ${DISABLE_TELEGRAM_UPLOAD}"
else
echo "[DEBUG] Telegram Notifications: Disabled"
fi
echo "[DEBUG] --------------------------------"
fi
# ------------------ [ BACKUP ] ------------------
cd /data || exit 1 # Exit with error if opening vw data file fails
# Ensure backup directory exists
mkdir -p /backups
BACKUP_LOCATION="/backups/${BACKUP_FILENAME}.${EXTENSION}"
BACKUP_ITEMS="db.sqlite3 rsa_key* config.json attachments sends"
WARNING_STATUS=""
FILES_TO_BACKUP=""
WARNING=""
# Verify which items are available to be backed up
for ITEM in $BACKUP_ITEMS; do
if [ -e "$ITEM" ] || [ -d "$ITEM" ]; then
FILES_TO_BACKUP="$FILES_TO_BACKUP $ITEM"
else # if an item is missing, raise warning
WARNING="$WARNING $ITEM"
fi
done
# Print and set warnings
if [ -n "$WARNING" ]; then
echo "[WARNING] The following expected files/directories are missing:$WARNING" >&2
if [ "$DISABLE_WARNINGS" != "true" ]; then
WARNING_STATUS="â ī¸ Missing files:$WARNING"
fi
fi
# Exit if there are no files to back up
if [ -z "$FILES_TO_BACKUP" ]; then
OUTPUT="No files to back up"
ERROR_MSG="â ī¸ Vaultwarden Backup Warning${NL}No files found to back up"
send_telegram_message "$ERROR_MSG"
echo "[$(date +"%F %r")] ${OUTPUT}."
exit 0
fi
# --- [ Backup Preparation ] ---
echo "[INFO] Creating backup..."
# Prepare a temporary directory for temp backup files
if [ "$VWDUMP_DEBUG" = "true" ]; then
echo "[DEBUG] Preparing a backup temp directory"
fi
TEMP_DIR="/tmp/vw_backup_$$"
mkdir -p "$TEMP_DIR"
# Always use safe .backup for sqlite3 if it exists
if [ -f "db.sqlite3" ]; then
if [ "$VWDUMP_DEBUG" = "true" ]; then
echo "[DEBUG] Creating SQLite backup..."
fi
sqlite3 db.sqlite3 ".backup '$TEMP_DIR/db.sqlite3'"
fi
# Copy all other files to the temp directory, preserving permissions
for ITEM in $FILES_TO_BACKUP; do
if [ "$ITEM" != "db.sqlite3" ]; then
cp -a "$ITEM" "$TEMP_DIR/"
fi
done
# --- [ Archive Creation ] ---
# Create tar archive from temp directory
if [ -n "$BACKUP_ENCRYPTION_KEY" ]; then
# Create encrypted backup
if [ "$VWDUMP_DEBUG" = "true" ]; then
echo "[DEBUG] Creating archive..."
fi
tar -Jcf - -C "$TEMP_DIR" . | openssl enc -e -aes256 -salt -pbkdf2 -iter "$PBKDF2_ITERATIONS" -pass pass:"$BACKUP_ENCRYPTION_KEY" -out "$BACKUP_LOCATION"
BACKUP_SUCCESS=$?
else
# Create unencrypted backup
if [ "$VWDUMP_DEBUG" = "true" ]; then
echo "[DEBUG] Creating archive..."
fi
tar -Jcf "$BACKUP_LOCATION" -C "$TEMP_DIR" .
BACKUP_SUCCESS=$?
fi
# --- [ Cleanup ] ---
echo "[INFO] Cleaning up..."
rm -rf "$TEMP_DIR"
# --- [ Build clean up message ] ---
CLEANUP_STATUS_MSG=""
if [ -n "$DELETE_AFTER" ] && [ "$DELETE_AFTER" -gt 0 ]; then
cd /backups
TO_DELETE=$(find . -iname "*.${EXTENSION}" -type f -mtime +$DELETE_AFTER)
DELETED_COUNT=0
if [ -n "$TO_DELETE" ]; then DELETED_COUNT=$(echo "$TO_DELETE" | wc -l); fi
if [ "$DELETED_COUNT" -gt 0 ]; then
if [ "$DELETED_COUNT" -eq 1 ]; then
archive_word="archive"
file_word="file"
else
archive_word="archives"
file_word="files"
fi
echo "$TO_DELETE" | xargs rm -f
OUTPUT_SUFFIX=", deleted ${DELETED_COUNT} ${archive_word} older than ${DELETE_AFTER} days"
CLEANUP_STATUS_MSG="đī¸ Deleted ${DELETED_COUNT} old backup ${file_word} (older than ${DELETE_AFTER} days)."
else
OUTPUT_SUFFIX=", no archives older than ${DELETE_AFTER} days to delete"
fi
fi
# --- [ Telegram Notification ] ---
if [ $BACKUP_SUCCESS -eq 0 ]; then
file_size=$(stat -c%s "$BACKUP_LOCATION" 2>/dev/null || stat -f%z "$file_path" 2>/dev/null)
file_size_kb=$(echo "$file_size" | awk '{print int($1/1024)}')
if [ "$file_size_kb" -ge 1024 ]; then
size_display="$(echo "$file_size_kb" | awk '{print int($1/1024)}')MB"
else
size_display="${file_size_kb}KB"
fi
if [ -n "$BACKUP_ENCRYPTION_KEY" ]; then
# Encrypted backup notification logic
decrypt_cmd="openssl enc -d -aes256 -salt -pbkdf2 -iter ${PBKDF2_ITERATIONS} -in ${BACKUP_FILENAME}.${EXTENSION} | tar xJ -C restore-dir"
file_caption="â
Vaultwarden Backup Complete${NL}${NL}đ File: ${BACKUP_FILENAME}.${EXTENSION}
${NL}đ Size: ${size_display}"
if [ -n "$WARNING_STATUS" ]; then file_caption="${file_caption}${NL}${WARNING_STATUS}"; fi
if [ -n "$CLEANUP_STATUS_MSG" ]; then file_caption="${file_caption}${NL}${NL}${CLEANUP_STATUS_MSG}"; fi
file_caption="${file_caption}${NL}${NL}đ Decrypt with:${NL}${decrypt_cmd}
"
if [ "$DISABLE_TELEGRAM_UPLOAD" = "true" ]; then
echo "[INFO] Telegram file upload is disabled by user setting."
upload_result=3 # Use a unique code for "disabled by user"
else
send_telegram_file "$BACKUP_LOCATION" "$file_caption"
upload_result=$?
fi
if [ $upload_result -eq 0 ]; then
OUTPUT="New backup created and sent to Telegram"
else
# Build the fallback text-only notification
TELEGRAM_MSG="â
Vaultwarden Backup Complete${NL}"
TELEGRAM_MSG="${TELEGRAM_MSG}${NL}đ File: ${BACKUP_FILENAME}.${EXTENSION}
"
TELEGRAM_MSG="${TELEGRAM_MSG}${NL}đ Size: ${size_display}"
if [ $upload_result -eq 2 ]; then
TELEGRAM_MSG="${TELEGRAM_MSG}${NL}â ī¸ File too large for Telegram, saved locally only."
OUTPUT="New backup created (too large for Telegram)"
elif [ $upload_result -eq 3 ]; then
TELEGRAM_MSG="${TELEGRAM_MSG}${NL}âšī¸ File upload disabled by user, saved locally only."
OUTPUT="New backup created (upload disabled by user)"
else
TELEGRAM_MSG="${TELEGRAM_MSG}${NL}â File upload failed, saved locally only."
OUTPUT="New backup created (Telegram upload failed)"
fi
if [ -n "$WARNING_STATUS" ]; then TELEGRAM_MSG="${TELEGRAM_MSG}${NL}${WARNING_STATUS}"; fi
if [ -n "$CLEANUP_STATUS_MSG" ]; then TELEGRAM_MSG="${TELEGRAM_MSG}${NL}${NL}${CLEANUP_STATUS_MSG}"; fi
TELEGRAM_MSG="${TELEGRAM_MSG}${NL}${NL}đ Decrypt with:${NL}${decrypt_cmd}
"
send_telegram_message "$TELEGRAM_MSG"
fi
else
# Unencrypted backup notification logic
TELEGRAM_MSG="â
Vaultwarden Backup Complete"
TELEGRAM_MSG="${TELEGRAM_MSG}${NL}đ File: ${BACKUP_FILENAME}.${EXTENSION}
"
TELEGRAM_MSG="${TELEGRAM_MSG}${NL}đ Size: ${size_display}"
if [ -n "$WARNING_STATUS" ]; then TELEGRAM_MSG="${TELEGRAM_MSG}${NL}${WARNING_STATUS}"; fi
if [ -n "$CLEANUP_STATUS_MSG" ]; then TELEGRAM_MSG="${TELEGRAM_MSG}${NL}${NL}${CLEANUP_STATUS_MSG}"; fi
if send_telegram_message "$TELEGRAM_MSG"; then
OUTPUT="New backup created (notification sent)"
else
OUTPUT="New backup created (notification failed)"
fi
fi
else
OUTPUT="Failed to create backup"
ERROR_MSG="â Vaultwarden Backup Failed${NL}Could not create archive: ${BACKUP_FILENAME}"
if [ -n "$WARNING_STATUS" ]; then ERROR_MSG="${ERROR_MSG}${NL}${WARNING_STATUS}"; fi
send_telegram_message "$ERROR_MSG"
fi
# ------------------ [ EXIT ] ------------------
# Append the cleanup message to the final console log message
echo "[$(date +"%F %r")] ${OUTPUT}${OUTPUT_SUFFIX}."