initial commit

This commit is contained in:
2025-12-20 12:42:19 -06:00
commit 8f28d5c47f
4 changed files with 1224 additions and 0 deletions

288
README.md Normal file
View File

@@ -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
```
<b><font color="red">insert instructions on attaching and mounting external drive</font></b>
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

468
airgap_backup.sh Normal file
View File

@@ -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

395
airgap_backup_remote.sh Normal file
View File

@@ -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

73
docs/Backup.md Normal file
View File

@@ -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