commit 8f28d5c47f5de54d27b3defe6adb73a0eefdcd77 Author: Matt Speer Date: Sat Dec 20 12:42:19 2025 -0600 initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a0794c --- /dev/null +++ b/README.md @@ -0,0 +1,288 @@ +# Media Server + +## Description + +The media server currently runs the entire home lab. + +The home lab consists of the server that hosts storage and services for the following content. The information below documents the setup and can be used to recover if needed. +- movies +- TV +- music +- photos +- notes +- documents + +Mount points are used to allow the actual storage location to change as needed as storage demands change. A new external drive can be added and mounted to this directory without the need to modify the configurations of all the self-hosted services that use it. + +## Host Directory Setup + +### Media Content +Create a new directory named `library` and modify permissions to it. This will be used as the mount point for the external drive that stores movies, TV, music, and books. +``` shell +sudo mkdir /library +sudo chmod 777 /library +``` + +insert instructions on attaching and mounting external drive + +Now create separate directories for each type of content. +``` shell +mkdir /library/tv +mkdir /library/movies +mkdir /library/music +mkdir /library/books +``` + +### Paperless Content +Create a new directory named `doc-archive` and modify permissions to it. This w/ill be used as the mount point for the external drive that stores the paperless content. +``` shell +sudo mkdir /doc-archive +sudo chmod 777 /doc-archive +``` + +Now create the sub directories for each use case +``` shell +mkdir /doc-archive/artifacts #Docs and thumbnails +mkdir /doc-archive/export +mkdir /doc-archive/consume +``` +### Usenet Download +Create a directory named `downloads` and modify permissions to it. This directory will be used by SabNZB for files it downloads. +``` shell +mkdir /downloads +sudo chmod 777 /downloads +``` +SabNZB uses two directories in the download process. One stores incomplete downloads and the other stores completed downloads. Create a directory for each in the `/downloads` directory. +``` shell +mkdir /downloads/complete +mkdir /downloads/incomplete +``` +### Host / Container Directory Mapping +The table below displays the host and container directory mapping. This information is useful to know how you can access files and data in the container directly from the host. This information is also contained in the yaml used to create the container. + +| Host Directory | Container Directory | Container Name | Description | +| ---------------------------------------------- | -------------------------- | ---------------------- | ----------------------------------------------------------------------------- | +| /library/tv | /tv | Sonarr | Sonarr moves TV shows to this directory after processing | +| /downloads/complete/tv | /downloads/tv | Sonarr | Location SabNZB places downloaded TV shows and Sonarr monitors for processing | +| /library/movies | /movie | Radarr | Radarr moves movies to this directory after processing | +| /downloads/complete/movies | /downloads/movies | Radarr | Location SabNZB places downloaded movies and Radarr monitors for processing | +| /library/music | /music | Lidarr | Lidarr moves music to this directory after processing | +| /downloads/complete/music | /downloads/music | Lidarr | Location SabNZB places downloaded music and Lidarr monitors for processing | +| /library/books | /books | Readarr | Readarr moves books to this directory after processing | +| /downloads/complete/books | /downloads/books | Readarr | Location SabNZB places downloaded books and Readarr monitors for processing | +| /downloads/complete | /complete | SabNZB | Top level directory for SabNZB completed downloads | +| /downloads/incomplete | /incomplete | SabNZB | Directory used by SabNZB for incomplete downloads | +| | | Plex | | +| | | Immich | | +| | | Caddy | | +| /home/mattspeer/paperless-ngx/redis/redisdata | /data | Paperless-ngx Redis | Redis data | +| /home/mattspeer/paperless-ngx/postgress/pgdata | /var/lib/postgresql/data | Paperless-ngx Postgres | Postgres DB | +| /home/mattspeer/paperless-ngx/web/data | /usr/src/paperless/data | Paperless-ngx web | Paperless-ngx stores index and models here | +| /doc-archive/artifacts | /usr/src/paperless/media | Paperless-ngx web | Paperless-ngx stores documents and thumbnails | +| /doc-archive/export | /usr/src/paperless/export | Paperless-ngx web | Directory used when documents are exported | +| /doc-archive/consume | /usr/src/paperless/consume | Paperless-ngx web | Directory monitored for ingesting documents | + +## Docker Installation + +> [!note] +> Refer to official docker documentation for installation on Ubuntu for more details. The information below was derived from this link. +> [https://docs.docker.com/engine/install/ubuntu/](https://docs.docker.com/engine/install/ubuntu/ "https://docs.docker.com/engine/install/ubuntu/") + +### Setup Repo +Issue the following commands to setup the docker repo +``` shell +sudo apt-get update + +sudo apt-get install ca-certificates curl gnupg + +sudo install -m 0755 -d /etc/apt/keyrings + +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg + +sudo chmod a+r /etc/apt/keyrings/docker.gpg echo \   "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ +   "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ + +sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + +sudo apt-get update +``` +### Install Docker Engine +Execute the following command to install docker engine +``` shell +sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin +``` + +## Portainer Installation + +> [!note] +> The information below was pulled from the following link for reference +> [https://phoenixnap.com/kb/docker-portainer-install](https://phoenixnap.com/kb/docker-portainer-install "https://phoenixnap.com/kb/docker-portainer-install") + +Execute the following command to create the docker volume +``` shell +docker volume create portainer_data +``` + +Now execute the following command to install Portainer server +``` shell +docker run -d -p 9000:9000 --name portainer \ + --restart=always \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v portainer_data:/data \ + portainer/portainer-ce:latest +``` + +Access Portainer at https://localhost:9000 + +## Port to Service Mapping + + +## Software Applications Setup + +### SabNZB + +### Sonarr + +### Radarr + +### Lidarr + +### Readarr + +### Jellyfin + +### Plex + +### Immich + +### Vaultwarden + +### Paperless-ngx +Docker compose +``` yml +# Docker Compose file for running paperless from the Docker Hub. +# This file contains everything paperless needs to run. +# Paperless supports amd64, arm and arm64 hardware. +# +# All compose files of paperless configure paperless in the following way: +# +# - Paperless is (re)started on system boot, if it was running before shutdown. +# - Docker volumes for storing data are managed by Docker. +# - Folders for importing and exporting files are created in the same directory +# as this file and mounted to the correct folders inside the container. +# - Paperless listens on port 8010. +# +# In addition to that, this Docker Compose file adds the following optional +# configurations: +# +# - Instead of SQLite (default), PostgreSQL is used as the database server. +# +# To install and update paperless with this file, do the following: +# +# - Open portainer Stacks list and click 'Add stack' +# - Paste the contents of this file and assign a name, e.g. 'paperless' +# - Click 'Deploy the stack' and wait for it to be deployed +# - Open the list of containers, select paperless_webserver_1 +# - Click 'Console' and then 'Connect' to open the command line inside the container +# - Run 'python3 manage.py createsuperuser' to create a user +# - Exit the console +# +# For more extensive installation and update instructions, refer to the +# documentation. + +version: "3.4" +services: + broker: + image: docker.io/library/redis:7 + restart: unless-stopped + volumes: + - /home/mattspeer/paperless-ngx/redis/redisdata:/data + + db: + image: docker.io/library/postgres:15 + restart: unless-stopped + volumes: + - /home/mattspeer/paperless-ngx/postgress/pgdata:/var/lib/postgresql/data + environment: + POSTGRES_DB: paperless + POSTGRES_USER: paperless + POSTGRES_PASSWORD: paperless + + webserver: + image: ghcr.io/paperless-ngx/paperless-ngx:latest + restart: unless-stopped + depends_on: + - db + - broker + ports: + - "8010:8000" + volumes: + - /home/mattspeer/paperless-ngx/web/data:/usr/src/paperless/data + - /doc-archive/artifacts:/usr/src/paperless/media + - /doc-archive/export:/usr/src/paperless/export + - /doc-archive/consume:/usr/src/paperless/consume + environment: + PAPERLESS_REDIS: redis://broker:6379 + PAPERLESS_DBHOST: db +# The UID and GID of the user used to run paperless in the container. Set this +# to your UID and GID on the host so that you have write access to the +# consumption directory. + USERMAP_UID: 1000 + USERMAP_GID: 1000 +# Additional languages to install for text recognition, separated by a +# whitespace. Note that this is +# different from PAPERLESS_OCR_LANGUAGE (default=eng), which defines the +# language used for OCR. +# The container installs English, German, Italian, Spanish and French by +# default. +# See https://packages.debian.org/search?keywords=tesseract-ocr-&searchon=names&suite=buster +# for available languages. + #PAPERLESS_OCR_LANGUAGES: tur ces +# Adjust this key if you plan to make paperless available publicly. It should +# be a very long sequence of random characters. You don't need to remember it. + #PAPERLESS_SECRET_KEY: change-me +# Use this variable to set a timezone for the Paperless Docker containers. If not specified, defaults to UTC. + #PAPERLESS_TIME_ZONE: America/Los_Angeles +# The default language to use for OCR. Set this to the language most of your +# documents are written in. + #PAPERLESS_OCR_LANGUAGE: eng + +volumes: + data: + media: + pgdata: + redisdata: +``` + +Launch Portainer and create a new stack, paste the above yaml code into ... + +Once the stack is deployed and runnning enter the container's terminal and execute the following command to create the initial super user. +``` shell +python3 manage.py createsuperuser +``` +Paperless-ngx is now ready for login and use +# Backup + +Execute the following commands to backup important configurations +``` bash +sudo rsync -avz /etc/fstab /mnt/5TB-Disk1/backup/server/os +``` +## Ports in Use + +80 Caddy +443 Caddy +2283 Immich +32400 Plex +5055 Overseerr +6595 Lidarr on Steroids +7878 Radarr +8001 Vaultwarden +8010 Paperless-ngx +8020 Stirling PDF +8085 ntfy +8080 Sabnzb +8686 Lidarr +8687 Lidarr on Steriods +8787 Readarr +8989 Sonarr +9000 Portainer \ No newline at end of file diff --git a/airgap_backup.sh b/airgap_backup.sh new file mode 100644 index 0000000..c0f5785 --- /dev/null +++ b/airgap_backup.sh @@ -0,0 +1,468 @@ +#!/bin/bash + +# ============================================================================== +# Script: airgap_backup.sh +# Description: This script securely copies the /etc/fstab file, a backup +# directory, several photo directories, the Caddyfile, and +# ddclient, dnsmasq, home assistant, Nextcloud, and Paperless-ngx +# configurations to a designated mounted drive using rsync. It +# includes pre-run checks to ensure the source files and destination +# are valid, and sends ntfy notifications for each task. +# Usage: ./airgap_backup.sh +# Notes: This script may require sudo/root privileges to access files. +# ============================================================================== + +# Use 'sudo su' to ensure the entire script runs with root privileges. +sudo su + +# ============================================================================== +# Define Source and Destination Directories +# ============================================================================== +# This makes the script easier to read and modify. +FSTAB_SOURCE="/etc/fstab" +BACKUP_SOURCE="/mnt/5TB-Disk1/backup/" # Trailing slash is important to sync contents +CADDYFILE_SOURCE="/home/mattspeer/docker/caddy/Caddyfile" +DDCLIENT_CONF_SOURCE="/etc/ddclient.conf" +DDCLIENT_DEFAULT_SOURCE="/etc/default/ddclient" +DNSMASQ_CONF_SOURCE="/home/mattspeer/docker/dnsmasq/dnsmasq.conf" +HOMEASSISTANT_BACKUP_SOURCE_DIR="/home/mattspeer/docker/homeassistant/backups" +NEXTCLOUD_BACKUP_SOURCE="/mnt/5TB-Disk1/backup/nextcloud" +PAPERLESS_SOURCE="/doc-archive/export" +VAULTWARDEN_SOURCE_DIR="/home/mattspeer/docker/vaultwarden/data/" +BACKUP_MOUNT_POINT="/mnt/airgap_drive" +FSTAB_DESTINATION="$BACKUP_MOUNT_POINT/backup/server/os" +BACKUP_DESTINATION="$BACKUP_MOUNT_POINT/backup" +CADDY_DESTINATION="$BACKUP_MOUNT_POINT/backup/services/caddy" +DDCLIENT_DESTINATION="$BACKUP_MOUNT_POINT/backup/services/ddclient" +DNSMASQ_DESTINATION="$BACKUP_MOUNT_POINT/backup/services/dnsmasq" +HOMEASSISTANT_DESTINATION="$BACKUP_MOUNT_POINT/backup/services/homeassistant" +NEXTCLOUD_DESTINATION="$BACKUP_MOUNT_POINT/backup/services/nextcloud" +PAPERLESS_DESTINATION="$BACKUP_MOUNT_POINT/backup/services/paperless-ngx" +VAULTWARDEN_DESTINATION="$BACKUP_MOUNT_POINT/backup/services/vaultwarden" + +# Photo backup directories +PHOTOS_BACKUP_SOURCE="/photos/backups/" +PHOTOS_LIBRARY_SOURCE="/photos/library/" +PHOTOS_PROFILE_SOURCE="/photos/profile/" +PHOTOS_UPLOAD_SOURCE="/photos/upload/" +PHOTOS_DESTINATION="$BACKUP_MOUNT_POINT/backup/services/immich" # Destination for all photo directories + +# ============================================================================== +# ntfy Notification Configuration +# ============================================================================== +# To use this feature, you must have the 'curl' command installed. +# NTFY_TOPIC is the topic you wish to send notifications to. +# NTFY_SERVER is the URL of your ntfy server. +# NTFY_TOKEN is the authorization token for your ntfy server. +NTFY_TOPIC="Server" +NTFY_SERVER="https://ntfy.speerfam.net" +NTFY_TOKEN="tk_xmr066ooldbydhbuolymobfn9b27f" + +# ============================================================================== +# Pre-flight Checks (for the destination drive) +# ============================================================================== + +# Check if the mount point directory exists and is a mounted filesystem. +# This is a critical check to ensure we are writing to the intended air-gapped +# drive and not to a local directory if the mount failed. +if ! mountpoint -q "$BACKUP_MOUNT_POINT"; then + echo "Error: '$BACKUP_MOUNT_POINT' is not a mount point. Please ensure the drive is mounted." >&2 + # Send a failure notification + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. Destination '$BACKUP_MOUNT_POINT' is not mounted." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +# Check if the destination directories exist and create them if they don't. +# This prevents rsync from failing on the first run. +mkdir -p "$FSTAB_DESTINATION" +mkdir -p "$BACKUP_DESTINATION" +mkdir -p "$PHOTOS_DESTINATION" +mkdir -p "$CADDY_DESTINATION" +mkdir -p "$DDCLIENT_DESTINATION" +mkdir -p "$DNSMASQ_DESTINATION" +mkdir -p "$HOMEASSISTANT_DESTINATION" +mkdir -p "$NEXTCLOUD_DESTINATION" +mkdir -p "$PAPERLESS_DESTINATION" +mkdir -p "$VAULTWARDEN_DESTINATION" + +# ============================================================================== +# Task 1: Sync /etc/fstab +# ============================================================================== +echo "Starting rsync of '$FSTAB_SOURCE' to '$FSTAB_DESTINATION'..." + +# Check if the source file for fstab exists. +if [ ! -f "$FSTAB_SOURCE" ]; then + echo "Error: Source file '$FSTAB_SOURCE' not found." >&2 + # Send a failure notification + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. Source file '$FSTAB_SOURCE' not found." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +rsync -av --progress "$FSTAB_SOURCE" "$FSTAB_DESTINATION" + +# Post-rsync status check for fstab. +if [ $? -eq 0 ]; then + echo "Success: fstab has been successfully copied to '$FSTAB_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: fstab copied to $FSTAB_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + echo "Error: rsync failed to copy fstab. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy fstab." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +# ============================================================================== +# Task 2: Sync backup directory +# ============================================================================== +echo "Starting rsync of '$BACKUP_SOURCE' to '$BACKUP_DESTINATION'..." + +# Check if the source directory for the backup exists. +if [ ! -d "$BACKUP_SOURCE" ]; then + echo "Error: Backup source directory '$BACKUP_SOURCE' not found." >&2 + # Send a failure notification + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. Backup source not found." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +# The trailing slash on BACKUP_SOURCE is crucial. It tells rsync to copy the +# *contents* of the directory, not the directory itself. +rsync -av --progress "$BACKUP_SOURCE" "$BACKUP_DESTINATION" + +# Post-rsync status check for the backup directory. +if [ $? -eq 0 ]; then + echo "Success: Backup directory has been successfully copied to '$BACKUP_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Backup copied to $BACKUP_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + echo "Error: rsync failed to copy the backup directory. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy backup directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +# ============================================================================== +# Task 3: Sync photo directories +# ============================================================================== + +# ============================================================================== +# Subtask 3.1: Sync most recent file in photos/backups directory +# ============================================================================== +echo "Starting rsync of the most recent file in '$PHOTOS_BACKUP_SOURCE' to '$PHOTOS_DESTINATION'..." + +if [ ! -d "$PHOTOS_BACKUP_SOURCE" ]; then + echo "Error: Photos source directory '$PHOTOS_BACKUP_SOURCE' not found." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. Photos source not found." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + # Find the most recent file in the directory. + LATEST_BACKUP_FILE=$(ls -t "$PHOTOS_BACKUP_SOURCE" | head -n 1) + + # Check if a file was found. + if [ -z "$LATEST_BACKUP_FILE" ]; then + echo "Error: No files found in Photos backup directory '$PHOTOS_BACKUP_SOURCE'." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: No files found in Photos backup directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + else + FULL_BACKUP_PATH="$PHOTOS_BACKUP_SOURCE/$LATEST_BACKUP_FILE" + rsync -av --progress "$FULL_BACKUP_PATH" "$PHOTOS_DESTINATION" + if [ $? -eq 0 ]; then + echo "Success: Most recent Photos backup '$LATEST_BACKUP_FILE' copied to '$PHOTOS_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Most recent Photos backup copied to $PHOTOS_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + else + echo "Error: rsync failed to copy photos backup file '$LATEST_BACKUP_FILE'. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy most recent Photos backup." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + fi + fi +fi + +# ============================================================================== +# Subtask 3.2: Sync photos/library directory +# ============================================================================== +echo "Starting rsync of '$PHOTOS_LIBRARY_SOURCE' to '$PHOTOS_DESTINATION'..." +if [ ! -d "$PHOTOS_LIBRARY_SOURCE" ]; then + echo "Error: Photos source directory '$PHOTOS_LIBRARY_SOURCE' not found." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. Photos library source not found." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + rsync -av --progress "$PHOTOS_LIBRARY_SOURCE" "$PHOTOS_DESTINATION" + if [ $? -eq 0 ]; then + echo "Success: Photos library directory copied to '$PHOTOS_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Photos library copied to $PHOTOS_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + else + echo "Error: rsync failed to copy photos library directory. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy photos library directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + fi +fi + +# ============================================================================== +# Subtask 3.3: Sync photos/profile directory +# ============================================================================== +echo "Starting rsync of '$PHOTOS_PROFILE_SOURCE' to '$PHOTOS_DESTINATION'..." +if [ ! -d "$PHOTOS_PROFILE_SOURCE" ]; then + echo "Error: Photos source directory '$PHOTOS_PROFILE_SOURCE' not found." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. Photos profile source not found." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + rsync -av --progress "$PHOTOS_PROFILE_SOURCE" "$PHOTOS_DESTINATION" + if [ $? -eq 0 ]; then + echo "Success: Photos profile directory copied to '$PHOTOS_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Photos profile copied to $PHOTOS_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + else + echo "Error: rsync failed to copy photos profile directory. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy photos profile directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + fi +fi + +# ============================================================================== +# Subtask 3.4: Sync photos/upload directory +# ============================================================================== +echo "Starting rsync of '$PHOTOS_UPLOAD_SOURCE' to '$PHOTOS_DESTINATION'..." +if [ ! -d "$PHOTOS_UPLOAD_SOURCE" ]; then + echo "Error: Photos source directory '$PHOTOS_UPLOAD_SOURCE' not found." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. Photos upload source not found." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + rsync -av --progress "$PHOTOS_UPLOAD_SOURCE" "$PHOTOS_DESTINATION" + if [ $? -eq 0 ]; then + echo "Success: Photos upload directory copied to '$PHOTOS_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Photos upload copied to $PHOTOS_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + else + echo "Error: rsync failed to copy photos upload directory. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy photos upload directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + fi +fi + +# ============================================================================== +# Task 4: Sync Caddyfile +# ============================================================================== +echo "Starting rsync of '$CADDYFILE_SOURCE' to '$CADDY_DESTINATION'..." + +# Check if the source file for the Caddyfile exists. +if [ ! -f "$CADDYFILE_SOURCE" ]; then + echo "Error: Source file '$CADDYFILE_SOURCE' not found." >&2 + # Send a failure notification + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. Caddyfile source not found." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +rsync -av --progress "$CADDYFILE_SOURCE" "$CADDY_DESTINATION" + +# Post-rsync status check for the Caddyfile. +if [ $? -eq 0 ]; then + echo "Success: Caddyfile has been successfully copied to '$CADDY_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Caddyfile copied to $CADDY_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + echo "Error: rsync failed to copy Caddyfile. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy Caddyfile." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +# ============================================================================== +# Task 5: Sync ddclient configuration +# ============================================================================== +echo "Starting rsync of ddclient configuration files to '$DDCLIENT_DESTINATION'..." + +# Check if ddclient.conf exists. +if [ ! -f "$DDCLIENT_CONF_SOURCE" ]; then + echo "Error: Source file '$DDCLIENT_CONF_SOURCE' not found." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. ddclient.conf source not found." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + rsync -av --progress "$DDCLIENT_CONF_SOURCE" "$DDCLIENT_DESTINATION" + if [ $? -eq 0 ]; then + echo "Success: ddclient.conf copied to '$DDCLIENT_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: ddclient.conf copied to $DDCLIENT_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + else + echo "Error: rsync failed to copy ddclient.conf. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy ddclient.conf." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + fi +fi + +# Check if ddclient default configuration exists. +if [ ! -f "$DDCLIENT_DEFAULT_SOURCE" ]; then + echo "Error: Source file '$DDCLIENT_DEFAULT_SOURCE' not found." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. ddclient default source not found." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + rsync -av --progress "$DDCLIENT_DEFAULT_SOURCE" "$DDCLIENT_DESTINATION" + if [ $? -eq 0 ]; then + echo "Success: ddclient default configuration copied to '$DDCLIENT_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: ddclient default copied to $DDCLIENT_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + else + echo "Error: rsync failed to copy ddclient default configuration. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy ddclient default." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + fi +fi + +# ============================================================================== +# Task 6: Sync dnsmasq configuration +# ============================================================================== +echo "Starting rsync of dnsmasq configuration file to '$DNSMASQ_DESTINATION'..." + +# Check if dnsmasq.conf exists. +if [ ! -f "$DNSMASQ_CONF_SOURCE" ]; then + echo "Error: Source file '$DNSMASQ_CONF_SOURCE' not found." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. dnsmasq.conf source not found." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + rsync -av --progress "$DNSMASQ_CONF_SOURCE" "$DNSMASQ_DESTINATION" + if [ $? -eq 0 ]; then + echo "Success: dnsmasq.conf copied to '$DNSMASQ_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: dnsmasq.conf copied to $DNSMASQ_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + else + echo "Error: rsync failed to copy dnsmasq.conf. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy dnsmasq.conf." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + fi +fi + +# ============================================================================== +# Task 7: Sync Home Assistant backup +# ============================================================================== +echo "Starting rsync of the most recent Home Assistant backup file to '$HOMEASSISTANT_DESTINATION'..." + +# Check if the Home Assistant backup source directory exists. +if [ ! -d "$HOMEASSISTANT_BACKUP_SOURCE_DIR" ]; then + echo "Error: Home Assistant backup directory '$HOMEASSISTANT_BACKUP_SOURCE_DIR' not found." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. Home Assistant backup directory not found." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + # Find the most recent file in the directory. + LATEST_BACKUP_FILE=$(ls -t "$HOMEASSISTANT_BACKUP_SOURCE_DIR" | head -n 1) + + # Check if a file was found. + if [ -z "$LATEST_BACKUP_FILE" ]; then + echo "Error: No files found in Home Assistant backup directory '$HOMEASSISTANT_BACKUP_SOURCE_DIR'." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: No files found in Home Assistant backup directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + else + FULL_BACKUP_PATH="$HOMEASSISTANT_BACKUP_SOURCE_DIR/$LATEST_BACKUP_FILE" + rsync -av --progress "$FULL_BACKUP_PATH" "$HOMEASSISTANT_DESTINATION" + if [ $? -eq 0 ]; then + echo "Success: Most recent Home Assistant backup '$LATEST_BACKUP_FILE' copied to '$HOMEASSISTANT_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Home Assistant backup copied to $HOMEASSISTANT_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + else + echo "Error: rsync failed to copy Home Assistant backup file '$LATEST_BACKUP_FILE'. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy Home Assistant backup." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + fi + fi +fi + +# ============================================================================== +# Task 8: Sync Nextcloud backup +# ============================================================================== +echo "Starting Nextcloud backup process..." + +# Step 1: Execute the Docker command to create the backup. +echo "Running Nextcloud AIO daily backup script via Docker..." +sudo docker exec --env DAILY_BACKUP=1 nextcloud-aio-mastercontainer /daily-backup.sh +if [ $? -ne 0 ]; then + echo "Error: Docker command failed to create Nextcloud backup." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Docker command failed to create Nextcloud backup." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi +echo "Nextcloud backup script completed successfully." +curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Nextcloud daily backup created. Starting rsync." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + + +# Step 2: Rsync the Nextcloud backup directory to the destination. +echo "Starting rsync of '$NEXTCLOUD_BACKUP_SOURCE' to '$NEXTCLOUD_DESTINATION'..." + +# Check if the Nextcloud backup source directory exists. +if [ ! -d "$NEXTCLOUD_BACKUP_SOURCE" ]; then + echo "Error: Nextcloud backup source directory '$NEXTCLOUD_BACKUP_SOURCE' not found." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. Nextcloud backup source not found." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +# The trailing slash on NEXTCLOUD_BACKUP_SOURCE is crucial. It tells rsync to +# copy the *contents* of the directory, not the directory itself. +rsync -av --progress "$NEXTCLOUD_BACKUP_SOURCE" "$NEXTCLOUD_DESTINATION" + +# Post-rsync status check for the Nextcloud backup directory. +if [ $? -eq 0 ]; then + echo "Success: Nextcloud backup directory has been successfully copied to '$NEXTCLOUD_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Nextcloud backup copied to $NEXTCLOUD_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + echo "Error: rsync failed to copy the Nextcloud backup directory. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy Nextcloud backup directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +# ============================================================================== +# Task 9: Sync Paperless-ngx backup +# ============================================================================== +echo "Starting Paperless-ngx backup process..." + +# Step 1: Execute the Docker command to create the backup. +echo "Running Paperless-ngx document exporter via Docker..." +sudo docker compose exec -T webserver document_exporter ../export -z +if [ $? -ne 0 ]; then + echo "Error: Docker command failed to create Paperless-ngx backup." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Docker command failed to create Paperless-ngx backup." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi +echo "Paperless-ngx backup script completed successfully." +curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Paperless-ngx backup created. Starting rsync." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + +# Step 2: Rsync the Paperless-ngx backup directory to the destination. +echo "Starting rsync of '$PAPERLESS_SOURCE' to '$PAPERLESS_DESTINATION'..." + +# Check if the Paperless-ngx backup source directory exists. +if [ ! -d "$PAPERLESS_SOURCE" ]; then + echo "Error: Paperless-ngx backup source directory '$PAPERLESS_SOURCE' not found." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. Paperless-ngx backup source not found." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +rsync -av --progress "$PAPERLESS_SOURCE" "$PAPERLESS_DESTINATION" + +# Post-rsync status check for the Paperless-ngx backup directory. +if [ $? -eq 0 ]; then + echo "Success: Paperless-ngx backup directory has been successfully copied to '$PAPERLESS_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Paperless-ngx backup copied to $PAPERLESS_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + echo "Error: rsync failed to copy the Paperless-ngx backup directory. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy Paperless-ngx backup directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +# ============================================================================== +# Task 10: Sync Vaultwarden backup +# ============================================================================== +echo "Starting Vaultwarden backup process..." + +# Step 1: Execute the Docker command to create the database backup. +echo "Running Vaultwarden backup via Docker..." +sudo docker exec -it vaultwarden /vaultwarden backup +if [ $? -ne 0 ]; then + echo "Error: Docker command failed to create Vaultwarden database backup." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Docker command failed to create Vaultwarden database backup." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi +echo "Vaultwarden database backup created successfully." +curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Vaultwarden database backup created. Starting rsync." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + +# Step 2: Rsync the Vaultwarden directories and files to the destination. +echo "Starting rsync of Vaultwarden data to '$VAULTWARDEN_DESTINATION'..." + +# Define the Vaultwarden source files and directories to be backed up +# This approach makes the script more readable and scalable. +VAULTWARDEN_SOURCES=( + "$VAULTWARDEN_SOURCE_DIR/attachments" + "$VAULTWARDEN_SOURCE_DIR/sends" + "$VAULTWARDEN_SOURCE_DIR/rsa_key.pem" + "$VAULTWARDEN_SOURCE_DIR/rsa_key.pub" + "$VAULTWARDEN_SOURCE_DIR/icon_cache" +) + +# Check if the Vaultwarden source directory exists. +if [ ! -d "$VAULTWARDEN_SOURCE_DIR" ]; then + echo "Error: Vaultwarden backup source directory '$VAULTWARDEN_SOURCE_DIR' not found." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. Vaultwarden backup source not found." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +# Loop through each source and rsync it. +for source in "${VAULTWARDEN_SOURCES[@]}"; do + rsync -av --progress "$source" "$VAULTWARDEN_DESTINATION" + if [ $? -eq 0 ]; then + echo "Success: '$source' copied to '$VAULTWARDEN_DESTINATION'." + else + echo "Error: rsync failed to copy '$source'. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy Vaultwarden data." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 + fi +done + +echo "Success: Vaultwarden backup has been successfully copied to '$VAULTWARDEN_DESTINATION'." +curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Vaultwarden backup copied to $VAULTWARDEN_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + +echo "All rsync tasks completed." +exit 0 diff --git a/airgap_backup_remote.sh b/airgap_backup_remote.sh new file mode 100644 index 0000000..96cca55 --- /dev/null +++ b/airgap_backup_remote.sh @@ -0,0 +1,395 @@ +#!/bin/bash + +# ============================================================================== +# Script: airgap_backup_remote.sh +# Description: This script securely copies the /etc/fstab file, a backup +# directory, several photo directories, the Caddyfile, and +# ddclient, dnsmasq, home assistant, Nextcloud, and Paperless-ngx +# configurations from a remote server to a designated mounted drive +# using rsync over SSH. It includes pre-run checks to ensure the +# source files and destination are valid, and sends ntfy +# notifications for each task. +# Usage: ./airgap_backup_remote.sh +# Notes: This script requires SSH access to the remote server without a +# password prompt (e.g., using SSH keys). It also requires the +# 'mattspeer' user to be able to run rsync with sudo on the remote +# host without a password. +# ============================================================================== + +# ============================================================================== +# Define Source and Destination Directories +# ============================================================================== +# This makes the script easier to read and modify. +REMOTE_USER="mattspeer" +REMOTE_HOST="192.168.4.20" +FSTAB_SOURCE="/etc/fstab" +BACKUP_SOURCE="/mnt/5TB-Disk1/backup/" # Trailing slash is important to sync contents +CADDYFILE_SOURCE="/home/mattspeer/docker/caddy/Caddyfile" +DDCLIENT_CONF_SOURCE="/etc/ddclient.conf" +DDCLIENT_DEFAULT_SOURCE="/etc/default/ddclient" +DNSMASQ_CONF_SOURCE="/home/mattspeer/docker/dnsmasq/dnsmasq.conf" +HOMEASSISTANT_BACKUP_SOURCE_DIR="/home/mattspeer/docker/homeassistant/backups" +NEXTCLOUD_BACKUP_SOURCE="/mnt/5TB-Disk1/backup/nextcloud" +PAPERLESS_SOURCE="/doc-archive/export" +VAULTWARDEN_SOURCE_DIR="/home/mattspeer/docker/vaultwarden/data/" +BACKUP_MOUNT_POINT="/Volumes/2TB SSD" +FSTAB_DESTINATION="$BACKUP_MOUNT_POINT/backup/server/os/fstab/" +BACKUP_DESTINATION="$BACKUP_MOUNT_POINT/backup" +CADDY_DESTINATION="$BACKUP_MOUNT_POINT/backup/server/services/caddy/" +DDCLIENT_DESTINATION="$BACKUP_MOUNT_POINT/backup/server/services/ddclient/" +DNSMASQ_DESTINATION="$BACKUP_MOUNT_POINT/backup/server/services/dnsmasq/" +HOMEASSISTANT_DESTINATION="$BACKUP_MOUNT_POINT/backup/server/services/homeassistant" +NEXTCLOUD_DESTINATION="$BACKUP_MOUNT_POINT/backup/server/services/nextcloud" +PAPERLESS_DESTINATION="$BACKUP_MOUNT_POINT/backup/server/services/paperless-ngx" +VAULTWARDEN_DESTINATION="$BACKUP_MOUNT_POINT/backup/server/services/vaultwarden/" + +# Photo backup directories +PHOTOS_BACKUP_SOURCE="/photos/backups" +PHOTOS_LIBRARY_SOURCE="/photos/library" +PHOTOS_PROFILE_SOURCE="/photos/profile" +PHOTOS_UPLOAD_SOURCE="/photos/upload" +PHOTOS_DESTINATION="$BACKUP_MOUNT_POINT/backup/server/services/immich/" # Destination for all photo directories + +# ============================================================================== +# ntfy Notification Configuration +# ============================================================================== +# To use this feature, you must have the 'curl' command installed. +# NTFY_TOPIC is the topic you wish to send notifications to. +# NTFY_SERVER is the URL of your ntfy server. +# NTFY_TOKEN is the authorization token for your ntfy server. +NTFY_TOPIC="Server" +NTFY_SERVER="https://ntfy.speerfam.net" +NTFY_TOKEN="tk_xmr066ooldbydhbuolymobfn9b27f" + +# ============================================================================== +# Pre-flight Checks (for the destination drive) +# ============================================================================== + +# Check if the mount point directory exists and is a mounted filesystem. +# This is a critical check to ensure we are writing to the intended air-gapped +# drive and not to a local directory if the mount failed. This version uses +# a command compatible with macOS. +if ! mount | grep -q "$BACKUP_MOUNT_POINT"; then + echo "Error: '$BACKUP_MOUNT_POINT' is not a mount point. Please ensure the drive is mounted." >&2 + # Send a failure notification + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. Destination '$BACKUP_MOUNT_POINT' is not mounted." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +# Check if the destination directories exist and create them if they don't. +# This prevents rsync from failing on the first run. +mkdir -p "$FSTAB_DESTINATION" +mkdir -p "$BACKUP_DESTINATION" +mkdir -p "$PHOTOS_DESTINATION" +mkdir -p "$CADDY_DESTINATION" +mkdir -p "$DDCLIENT_DESTINATION" +mkdir -p "$DNSMASQ_DESTINATION" +mkdir -p "$HOMEASSISTANT_DESTINATION" +mkdir -p "$NEXTCLOUD_DESTINATION" +mkdir -p "$PAPERLESS_DESTINATION" +mkdir -p "$VAULTWARDEN_DESTINATION" + +# ============================================================================== +# Task 1: Sync /etc/fstab +# ============================================================================== +echo "Starting rsync of '$FSTAB_SOURCE' to '$FSTAB_DESTINATION'..." + +# Use -e "ssh -t sudo rsync" to run rsync with sudo privileges on the remote host. +# The --rsync-path flag allows you to specify a different rsync binary on the remote +# host. Here we use 'sudo rsync' to gain elevated permissions. +rsync -av --progress --rsync-path="sudo rsync" "$REMOTE_USER@$REMOTE_HOST:$FSTAB_SOURCE" "$FSTAB_DESTINATION" + +# Post-rsync status check for fstab. +if [ $? -eq 0 ]; then + echo "Success: fstab has been successfully copied to '$FSTAB_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: fstab copied to $FSTAB_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + echo "Error: rsync failed to copy fstab. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy fstab." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +# ============================================================================== +# Task 2: Sync backup directory +# ============================================================================== +# This task has been commented out. To re-enable it, remove the '#' from the +# beginning of each line below. +# echo "Starting rsync of '$BACKUP_SOURCE' to '$BACKUP_DESTINATION'..." +# rsync -av --progress --rsync-path="sudo rsync" "$REMOTE_USER@$REMOTE_HOST:$BACKUP_SOURCE" "$BACKUP_DESTINATION" +# if [ $? -eq 0 ]; then +# echo "Success: Backup directory has been successfully copied to '$BACKUP_DESTINATION'." +# curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Backup copied to $BACKUP_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +# else +# echo "Error: rsync failed to copy the backup directory. Please check permissions." >&2 +# curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy backup directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +# exit 1 +# fi + +# ============================================================================== +# Task 3: Sync photo directories +# ============================================================================== + +# ============================================================================== +# Subtask 3.1: Sync most recent file in photos/backups directory +# ============================================================================== +echo "Starting rsync of the most recent file in '$PHOTOS_BACKUP_SOURCE' to '$PHOTOS_DESTINATION..." + +# Use ssh to run the 'ls' command on the remote host to find the most recent file. +LATEST_BACKUP_FILE=$(ssh "$REMOTE_USER@$REMOTE_HOST" "ls -t '$PHOTOS_BACKUP_SOURCE' | head -n 1") + +# Check if a file was found. +if [ -z "$LATEST_BACKUP_FILE" ]; then + echo "Error: No files found in Photos backup directory '$PHOTOS_BACKUP_SOURCE' on the remote host." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: No files found in Photos backup directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + FULL_REMOTE_PATH="$PHOTOS_BACKUP_SOURCE/$LATEST_BACKUP_FILE" + rsync -av --progress --rsync-path="sudo rsync" "$REMOTE_USER@$REMOTE_HOST:$FULL_REMOTE_PATH" "$PHOTOS_DESTINATION" + if [ $? -eq 0 ]; then + echo "Success: Most recent Immich database backup '$LATEST_BACKUP_FILE' copied to '$PHOTOS_DESTINATION/backups'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Most recent Immich database backup copied to $PHOTOS_DESTINATION/backups" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + else + echo "Error: rsync failed to copy Immich database backup file '$LATEST_BACKUP_FILE'. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy most recent Immich database backup." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + fi +fi + +# ============================================================================== +# Subtask 3.2: Sync photos/library directory +# ============================================================================== +echo "Starting rsync of '$PHOTOS_LIBRARY_SOURCE' to '$PHOTOS_DESTINATION'..." + +# rsync -av --progress "$REMOTE_USER@$REMOTE_HOST:$PHOTOS_LIBRARY_SOURCE" "$PHOTOS_DESTINATION" +rsync -av --progress --rsync-path="sudo rsync" "$REMOTE_USER@$REMOTE_HOST:$PHOTOS_LIBRARY_SOURCE" "$PHOTOS_DESTINATION" +if [ $? -eq 0 ]; then + echo "Success: Photos library directory copied to '$PHOTOS_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Photos library copied to $PHOTOS_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + echo "Error: rsync failed to copy photos library directory. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy photos library directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +fi + +# ============================================================================== +# Subtask 3.3: Sync photos/profile directory +# ============================================================================== +echo "Starting rsync of '$PHOTOS_PROFILE_SOURCE' to '$PHOTOS_DESTINATION'..." +# rsync -av --progress "$REMOTE_USER@$REMOTE_HOST:$PHOTOS_PROFILE_SOURCE" "$PHOTOS_DESTINATION" +rsync -av --progress --rsync-path="sudo rsync" "$REMOTE_USER@$REMOTE_HOST:$PHOTOS_PROFILE_SOURCE" "$PHOTOS_DESTINATION" +if [ $? -eq 0 ]; then + echo "Success: Photos profile directory copied to '$PHOTOS_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Photos profile copied to $PHOTOS_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + echo "Error: rsync failed to copy photos profile directory. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy photos profile directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +fi + +# ============================================================================== +# Subtask 3.4: Sync photos/upload directory +# ============================================================================== +echo "Starting rsync of '$PHOTOS_UPLOAD_SOURCE' to '$PHOTOS_DESTINATION'..." +# rsync -av --progress "$REMOTE_USER@$REMOTE_HOST:$PHOTOS_UPLOAD_SOURCE" "$PHOTOS_DESTINATION" +rsync -av --progress --rsync-path="sudo rsync" "$REMOTE_USER@$REMOTE_HOST:$PHOTOS_UPLOAD_SOURCE" "$PHOTOS_DESTINATION" +if [ $? -eq 0 ]; then + echo "Success: Photos upload directory copied to '$PHOTOS_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Photos upload copied to $PHOTOS_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + echo "Error: rsync failed to copy photos upload directory. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy photos upload directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +fi + +# ============================================================================== +# Task 4: Sync Caddyfile +# ============================================================================== +echo "Starting rsync of '$CADDYFILE_SOURCE' to '$CADDY_DESTINATION'..." + +rsync -av --progress "$REMOTE_USER@$REMOTE_HOST:$CADDYFILE_SOURCE" "$CADDY_DESTINATION" + +# Post-rsync status check for the Caddyfile. +if [ $? -eq 0 ]; then + echo "Success: Caddyfile has been successfully copied to '$CADDY_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Caddyfile copied to $CADDY_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + echo "Error: rsync failed to copy Caddyfile. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy Caddyfile." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +# ============================================================================== +# Task 5: Sync ddclient configuration +# ============================================================================== +echo "Starting rsync of ddclient configuration files to '$DDCLIENT_DESTINATION'..." + +rsync -av --progress --rsync-path="sudo rsync" "$REMOTE_USER@$REMOTE_HOST:$DDCLIENT_CONF_SOURCE" "$DDCLIENT_DESTINATION" +if [ $? -eq 0 ]; then + echo "Success: ddclient.conf copied to '$DDCLIENT_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: ddclient.conf copied to $DDCLIENT_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + echo "Error: rsync failed to copy ddclient.conf. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy ddclient.conf." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +fi + +rsync -av --progress --rsync-path="sudo rsync" "$REMOTE_USER@$REMOTE_HOST:$DDCLIENT_DEFAULT_SOURCE" "$DDCLIENT_DESTINATION" +if [ $? -eq 0 ]; then + echo "Success: ddclient default configuration copied to '$DDCLIENT_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: ddclient default copied to $DDCLIENT_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + echo "Error: rsync failed to copy ddclient default configuration. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy ddclient default." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +fi + +# ============================================================================== +# Task 6: Sync dnsmasq configuration +# ============================================================================== +echo "Starting rsync of dnsmasq configuration file to '$DNSMASQ_DESTINATION'..." + +rsync -av --progress "$REMOTE_USER@$REMOTE_HOST:$DNSMASQ_CONF_SOURCE" "$DNSMASQ_DESTINATION" + +if [ $? -eq 0 ]; then + echo "Success: dnsmasq.conf copied to '$DNSMASQ_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: dnsmasq.conf copied to $DNSMASQ_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + echo "Error: rsync failed to copy dnsmasq.conf. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy dnsmasq.conf." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +fi + +# ============================================================================== +# Task 7: Sync Home Assistant backup +# ============================================================================== +echo "Starting rsync of the most recent Home Assistant backup file to '$HOMEASSISTANT_DESTINATION'..." + +# Use ssh to run the 'ls' command on the remote host to find the most recent file. +LATEST_BACKUP_FILE=$(ssh "$REMOTE_USER@$REMOTE_HOST" "ls -t '$HOMEASSISTANT_BACKUP_SOURCE_DIR' | head -n 1") + +# Check if a file was found. +if [ -z "$LATEST_BACKUP_FILE" ]; then + echo "Error: No files found in Home Assistant backup directory '$HOMEASSISTANT_BACKUP_SOURCE_DIR' on the remote host." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: No files found in Home Assistant backup directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + FULL_REMOTE_PATH="$HOMEASSISTANT_BACKUP_SOURCE_DIR/$LATEST_BACKUP_FILE" + rsync -av --progress "$REMOTE_USER@$REMOTE_HOST:$FULL_REMOTE_PATH" "$HOMEASSISTANT_DESTINATION" + if [ $? -eq 0 ]; then + echo "Success: Most recent Home Assistant backup '$LATEST_BACKUP_FILE' copied to '$HOMEASSISTANT_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Home Assistant backup copied to $HOMEASSISTANT_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + else + echo "Error: rsync failed to copy Home Assistant backup file '$LATEST_BACKUP_FILE'. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy Home Assistant backup." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + fi +fi + +# ============================================================================== +# Task 8: Sync Nextcloud backup +# ============================================================================== +echo "Starting Nextcloud backup process..." + +# Step 1: Execute the Docker command to create the backup. +echo "Running Nextcloud AIO daily backup script via Docker..." +ssh "$REMOTE_USER@$REMOTE_HOST" "sudo docker exec --env DAILY_BACKUP=1 nextcloud-aio-mastercontainer /daily-backup.sh" +if [ $? -ne 0 ]; then + echo "Error: Docker command failed to create Nextcloud backup." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Docker command failed to create Nextcloud backup." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi +echo "Nextcloud backup script completed successfully." +curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Nextcloud daily backup created. Starting rsync." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + + +# Step 2: Rsync the Nextcloud backup directory to the destination. +echo "Starting rsync of '$NEXTCLOUD_BACKUP_SOURCE' to '$NEXTCLOUD_DESTINATION'..." + +# The trailing slash on NEXTCLOUD_BACKUP_SOURCE is crucial. It tells rsync to +# copy the *contents* of the directory, not the directory itself. +rsync -av --progress "$REMOTE_USER@$REMOTE_HOST:$NEXTCLOUD_BACKUP_SOURCE" "$NEXTCLOUD_DESTINATION" + +# Post-rsync status check for the Nextcloud backup directory. +if [ $? -eq 0 ]; then + echo "Success: Nextcloud backup directory has been successfully copied to '$NEXTCLOUD_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Nextcloud backup copied to $NEXTCLOUD_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + echo "Error: rsync failed to copy the Nextcloud backup directory. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy Nextcloud backup directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +# ============================================================================== +# Task 9: Sync Paperless-ngx backup +# ============================================================================== +echo "Starting Paperless-ngx backup process..." + +# Step 1: Execute the Docker command to create the backup. +echo "Running Paperless-ngx document exporter via Docker..." +ssh "$REMOTE_USER@$REMOTE_HOST" "sudo docker compose exec -T paperless-ngx-webserver-1 document_exporter ../export -z" +if [ $? -ne 0 ]; then + echo "Error: Docker command failed to create Paperless-ngx backup." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Docker command failed to create Paperless-ngx backup." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi +echo "Paperless-ngx backup script completed successfully." +curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Paperless-ngx backup created. Starting rsync." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + +# Step 2: Rsync the Paperless-ngx backup directory to the destination. +echo "Starting rsync of '$PAPERLESS_SOURCE' to '$PAPERLESS_DESTINATION'..." + +rsync -av --progress "$REMOTE_USER@$REMOTE_HOST:$PAPERLESS_SOURCE" "$PAPERLESS_DESTINATION" + +# Post-rsync status check for the Paperless-ngx backup directory. +if [ $? -eq 0 ]; then + echo "Success: Paperless-ngx backup directory has been successfully copied to '$PAPERLESS_DESTINATION'." + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Paperless-ngx backup copied to $PAPERLESS_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 +else + echo "Error: rsync failed to copy the Paperless-ngx backup directory. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy Paperless-ngx backup directory." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +# ============================================================================== +# Task 10: Sync Vaultwarden backup +# ============================================================================== +echo "Starting Vaultwarden backup process..." + +# Step 1: Execute the Docker command to create the database backup. +echo "Running Vaultwarden backup via Docker..." +ssh "$REMOTE_USER@$REMOTE_HOST" "sudo docker exec -it vaultwarden /vaultwarden backup" +if [ $? -ne 0 ]; then + echo "Error: Docker command failed to create Vaultwarden database backup." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Docker command failed to create Vaultwarden database backup." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi +echo "Vaultwarden database backup created successfully." +curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Vaultwarden database backup created. Starting rsync." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + +# Step 2: Rsync the Vaultwarden directories and files to the destination. +echo "Starting rsync of Vaultwarden data to '$VAULTWARDEN_DESTINATION'..." + +# Define the Vaultwarden source files and directories to be backed up +# This approach makes the script more readable and scalable. +VAULTWARDEN_SOURCES=( + "$VAULTWARDEN_SOURCE_DIR/attachments" + "$VAULTWARDEN_SOURCE_DIR/sends" + "$VAULTWARDEN_SOURCE_DIR/rsa_key.pem" + "$VAULTWARDEN_SOURCE_DIR/rsa_key.pub" + "$VAULTWARDEN_SOURCE_DIR/icon_cache" +) + +# Check if the Vaultwarden source directory exists. +if [ ! -d "$VAULTWARDEN_SOURCE_DIR" ]; then + echo "Error: Vaultwarden backup source directory '$VAULTWARDEN_SOURCE_DIR' not found." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: Script failed. Vaultwarden backup source not found." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 +fi + +# Loop through each source and rsync it. +for source in "${VAULTWARDEN_SOURCES[@]}"; do + rsync -av --progress "$REMOTE_USER@$REMOTE_HOST:$source" "$VAULTWARDEN_DESTINATION" + if [ $? -eq 0 ]; then + echo "Success: '$source' copied to '$VAULTWARDEN_DESTINATION'." + else + echo "Error: rsync failed to copy '$source'. Please check permissions." >&2 + curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Error: rsync failed to copy Vaultwarden data." "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + exit 1 + fi +done + +echo "Success: Vaultwarden backup has been successfully copied to '$VAULTWARDEN_DESTINATION'." +curl -H "Authorization: Bearer $NTFY_TOKEN" -d "Success: Vaultwarden backup copied to $VAULTWARDEN_DESTINATION" "$NTFY_SERVER/$NTFY_TOPIC" > /dev/null 2>&1 + +echo "All rsync tasks completed." +exit 0 diff --git a/docs/Backup.md b/docs/Backup.md new file mode 100644 index 0000000..43c4254 --- /dev/null +++ b/docs/Backup.md @@ -0,0 +1,73 @@ +# Backup + +> [!important] +> The master backup drive contains all backups along with some historical backups +Each thumb drive located in Matt and Jennifer's vehicle contain only the latest backup, excluding photos. + +## Process Overview + +1. Create a backup of each service / product. + * Home Assistant + * [[Immich]] + * Jellyfin + * [[Lidarr]] + * [[Media PC]] + * [[Overseerr]] + * Paperless-ngx + * Plex + * Portainer + * Radarr + * Readarr + * Sabnzbd + * Sendgrid + * Sonarr + * Watchtower + +2. Ensure all backups of the services above are placed into the "backup" folder. +3. Connect the master backup drive, that is kept in the safe, to the computer. +4. Copy all backups to the master backup drive. +5. Delete any historical backups on the master backup pdrive according to the [[#Backup Retention]]. + > [!caution] Some backups should not be deleted. +6. Remove the master backup drive and place it back into the safe. +7. Connect the thumb drive located in Jennifer's SUV to the computer. +8. Delete all current backups on the thumb drive. +9. Copy all backups, except photos, to the thumb drive. +10. Remove the thumb drive and place it back in Jennifer's SUV +11. Connect the thumb drive located in Matt's truck to the computer. +12. Delete all current backups on the thumb drive. +13. Copy all backups, except photos, to the thumb drive. +14. Remove the thumb drive and place it back in Matt's truck. + +## Backup Schedule +| Service / Product | Backup Frequency | +| ---- | ---- | +| Bitwarden | Monthly | +| Google Drive | Monthly | +| Google Photos | Monthly | +| Media PC | Monthly | + +## Backup Locations & Retention + +### Master Backup SSD + +This retention schedule applies only to the master backup drive. + +| Service / Product | Backups to retain | +| ----------------- | ---------------------------- | +| Bitwarden | 10 most recent | +| Google Drive | 5 most recent | +| Google Photos | 1 - don't delete any history | +| Media PC | 10 most recent | + +### Thumb Drives + +| Service / Product | Backups to retain | +| ----------------- | ------------------- | +| Bitwarden | Most recent | +| Google Drive | Most recent | +| Google Photos | 0 - Not copied here | +| Media PC | Most recent | + +## OS Files + +/etc/fstab \ No newline at end of file