From 704f7ecb0cc798d22f24531583ac6549f7ad1366 Mon Sep 17 00:00:00 2001 From: thomasabishop Date: Sun, 11 May 2025 17:57:24 +0100 Subject: [PATCH] add generic vps-to-local backup script --- old/copy_forgejo_db_backup.sh | 26 ++++++++ old/slack_notifier.sh | 87 +++++++++++++++++++++++++ old/storagebox_alpha_backup_old.sh | 84 ++++++++++++++++++++++++ vps_backups/copy_vps_backup_to_local.sh | 30 +++++++++ 4 files changed, 227 insertions(+) create mode 100755 old/copy_forgejo_db_backup.sh create mode 100755 old/slack_notifier.sh create mode 100755 old/storagebox_alpha_backup_old.sh create mode 100755 vps_backups/copy_vps_backup_to_local.sh diff --git a/old/copy_forgejo_db_backup.sh b/old/copy_forgejo_db_backup.sh new file mode 100755 index 0000000..94a7ea2 --- /dev/null +++ b/old/copy_forgejo_db_backup.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +source /home/thomas/.env + +# Copy backup of Forgejo database on VPS to local device + +LOCAL_MOUNTPOINT="/media/my-passport" +RCHAT_NOTIFIER="${HOME}/repos/utilities/rocketchat_notifier.sh" + +mountpoint -q ${LOCAL_MOUNTPOINT} + +if [ $? -eq 1 ]; then + $RCHAT_NOTIFIER "backups" "error" \ + "Could not transfer Forgejo DB backup from VPS. Local backup disk (/media/my-passport) not mounted." + exit +fi + +scp 'thomas@systemsobscure.net:/home/thomas/backups/forgejo/*.sql.gz' "${LOCAL_MOUNTPOINT}/vps_backups/mysql/forgejo/" + +if [ $? -eq 0 ]; then + BACKUP_FILE=$(ls -t "${LOCAL_MOUNTPOINT}/vps_backups/mysql/forgejo" | head -1) + $RCHAT_NOTIFIER "backups" "success" "Copied Forgejo DB backup from VPS. File: ${BACKUP_FILE}" +else + $RCHAT_NOTIFIER "backups" "error" \ + "Could not transfer Forgejo DB backup from VPS." +fi diff --git a/old/slack_notifier.sh b/old/slack_notifier.sh new file mode 100755 index 0000000..654563b --- /dev/null +++ b/old/slack_notifier.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +# Send error and success notifications to Slack channels + +# Env vars: +# --- Webhook URLs for given channel, eg $SLACK_WEBHOOK_TEST, $SLACK_WEBHOOK_EOLAS +# --- sourced from `.env` file in Zsh path + +# Parameters: +# --- $1 = Slack channel, +# --- $2 = type 'error' | 'success' +# --- $3 = Message +# --- $4 = (Opt) Error details +# --- $5 = (Opt) Error source + +# Usage: +# --- ./slack_notifier.sh test 'SUCCESS: ...' +# --- ./slack_notifier.sh test 'ERROR: ... ' 'Error details' 'source' + +declare -A CHANNEL_TO_WEBHOOK +CHANNEL_TO_WEBHOOK["test"]=$SLACK_WEBHOOK_TEST +CHANNEL_TO_WEBHOOK["backups"]=$SLACK_WEBHOOK_BACKUPS +CHANNEL_TO_WEBHOOK["eolas"]=$SLACK_WEBHOOK_EOLAS +CHANNEL_TO_WEBHOOK["website"]=$SLACK_WEBHOOK_SYSTEMS_OBSCURE +CHANNEL_TO_WEBHOOK["time-tracking"]=$SLACK_WEBHOOK_TIME_TRACKING + +WEBHOOK=${CHANNEL_TO_WEBHOOK[$1]} + +ERROR_BLOCKS=$( + jq -n \ + --arg channel "$1" \ + --arg message "$3" \ + --arg details "$4" \ + --arg source "$5" \ + '{ + channel: $channel, + blocks: ([ + { + type: "section", + text: { + type: "plain_text", + text: "🔴 \($message)" + } + }, + { + type: "section", + text: { + type: "mrkdwn", + text: "```\n\($details)\n```" + } + }, + { + "type": "context", + "elements": [ + { + "type": "plain_text", + text: $source + } + ] + } + ]) + }' +) + +# Initialise sound playback + +# mpv --volume=0 --start=0 --length=0.1 "${HOME}/dotfiles/sounds/star-trek-computer-success.mp3" \ +# >/dev/null 2>&1 +# sleep 1 + +# Process notification +if [ "$2" != "error" ]; then + curl -X POST \ + -H 'Content-type: application/json' \ + --data '{"text":"🟢 '"$3"'"}' \ + "$WEBHOOK" + # mpv --volume=100 "${HOME}/dotfiles/sounds/star-trek-computer-success.mp3" \ + >/dev/null 2>&1 + +else + curl -X POST \ + -H 'Content-type: application/json' \ + --json "$ERROR_BLOCKS" \ + "$WEBHOOK" + # mpv --volume=100 "${HOME}/dotfiles/sounds/star-trek-computer-error.mp3" \ + >/dev/null 2>&1 +fi diff --git a/old/storagebox_alpha_backup_old.sh b/old/storagebox_alpha_backup_old.sh new file mode 100755 index 0000000..5daceaa --- /dev/null +++ b/old/storagebox_alpha_backup_old.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +# Backup Hetzner Storagebox (Alpha) used by VPS for service data. + +NAS_DEVICE_NAME="Hetzner Storagebox (Alpha)" +RCHAT_NOTIFIER="${HOME}/repos/utilities/rocketchat_notifier.sh" +SOURCE_MOUNTPOINT="/media/hetzner-storagebox-alpha" +LOCAL_MOUNTPOINT="/media/my-passport" + +function clean_up() { + # Dismount NAS + echo "INFO Dismounting ${NAS_DEVICE_NAME}." + sudo umount /media/hetzner-storagebox-alpha/ + + # Turn on VPN + echo "INFO Re-connecting Mullvad VPN." + mullvad connect + exit +} + +# Turn off VPN +echo "INFO Attempting backup of ${NAS_DEVICE_NAME} NAS device." +echo "INFO Checking VPN status." + +mullvad status | grep "Disconnected" 2>&1 +STATUS=$? +if [ $STATUS -eq 0 ]; then + echo "INFO VPN is not connected. Proceeding." +else + echo "INFO Mullvad VPN is connected. Disconnecting." + mullvad disconnect +fi + +# Check source disk is mounted +if mountpoint -q ${LOCAL_MOUNTPOINT}; then + echo "INFO Local disk /media/my-passport mounted. Proceeding." +else + echo "INFO Local disk not mounted. Mounting /media/my-passport." + sudo mount ${LOCAL_MOUNTPOINT} + if mountpoint -q ${LOCAL_MOUNTPOINT}; then + echo "INFO Mounted /media/my-passport" + else + $RCHAT_NOTIFIER "backups" "error" \ + "Could not complete scheduled backup of ${NAS_DEVICE_NAME}. Local backup disk (/media/my-passport) not mounted." + clean_up + fi +fi + +# Mount NAS locally +sudo mount -t cifs -v //u455282.your-storagebox.de/backup /media/hetzner-storagebox-alpha -o \ + credentials=/etc/hetzner-storagebox-alpha-credentials,uid=1000,gid=1000,file_mode=0775,dir_mode=0775,vers=3.0 + +if mountpoint -q ${SOURCE_MOUNTPOINT}; then + echo "INFO Mounted ${NAS_DEVICE_NAME} device to /media/hetzner-storagebox-alpha" +else + $RCHAT_NOTIFIER "backups" "error" \ + "Could not complete scheduled ${NAS_DEVICE_NAME} backup. Source disk (/media/hetzner-storagebox-alpha) not mounted." + clean_up +fi + +# Create backup (ignoring lock files) + +echo "INFO Beginning backup." + +sudo rsync -avzP --delete --no-perms \ + --exclude="*.lock" \ + --exclude="*.bleve/store/root.bolt" \ + --exclude="mongo/db/mongod.lock" \ + "$SOURCE_MOUNTPOINT/" "$LOCAL_MOUNTPOINT/vps_backups/hetzner_storagebox_alpha" + +STATUS=$? +if [ $STATUS -eq 0 ]; then + $RCHAT_NOTIFIER "backups" "success" \ + "Created backup of ${NAS_DEVICE_NAME}." + # 23 = partial backup, 24 = some vanished files, both != major failure +elif [ $STATUS -eq 23 ] || [ $STATUS -eq 24 ]; then + $RCHAT_NOTIFIER "backups" "success" \ + "Created backup of ${NAS_DEVICE_NAME}. A few files could not be copied." +else + $RCHAT_NOTIFIER "backups" "error" \ + "Failed to create backup of ${NAS_DEVICE_NAME}: problem with rsync (exit code $STATUS)." +fi + +clean_up diff --git a/vps_backups/copy_vps_backup_to_local.sh b/vps_backups/copy_vps_backup_to_local.sh new file mode 100755 index 0000000..46789ba --- /dev/null +++ b/vps_backups/copy_vps_backup_to_local.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Copy backups of databases on VPS to local machine + +# Parameters +# --- $1 = Service name, e.g. "forgejo" +# --- $2 = Database location on VPS, e.g. "thomas@systemsobscure.net:/home/thomas/backups/forgejo/*.sql.gz" +# --- $3 = Backup location path on VPS, e.g. "/vps_backups/mysql/forgejo/" + +source "$HOME/.env" + +LOCAL_MOUNTPOINT="/media/my-passport" +RCHAT_NOTIFIER="${HOME}/repos/utilities/rocketchat_notifier.sh" + +mountpoint -q ${LOCAL_MOUNTPOINT} + +if [ $? -eq 1 ]; then + $RCHAT_NOTIFIER "backups" "error" \ + "Could not transfer $1 DB backup from VPS. Local backup disk (/media/my-passport) not mounted." + exit +fi + +scp "thomas@systemsobscure.net:${2}" "${LOCAL_MOUNTPOINT}/${3}" + +if [ $? -eq 0 ]; then + BACKUP_FILE=$(ls -t "${LOCAL_MOUNTPOINT}/${3}" | head -1) + $RCHAT_NOTIFIER "backups" "success" "Copied $1 DB backup from VPS. File: ${BACKUP_FILE}" +else + $RCHAT_NOTIFIER "backups" "error" \ + "Could not transfer $1 DB backup from VPS." +fi