Backup your complete Pi to a Synology NAS
Create ED25519 keys on RPi
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_rpi-backup -C "rpi-backup"
Code language: JavaScript (javascript)
From your user home directory
Take care your .ssh folder is readable
chmod 700 ~/.ssh
chmod 700 ~/.ssh
cd .ssh
Code language: CSS (css)
nano config
Enter the following (creating an alias with all kinds of presets to the SSH connection)
Host nas
HostName 192.168.1.3
User <USER-NAME>
Port 2222 #SSH-port of your Synology NAS
IdentityFile ~/.ssh/id_ed25519_rpi-backup
ConnectTimeout 10
ServerAliveInterval 60
ServerAliveCountMax 3
Code language: PHP (php)
Je configuratiebestand:
Host nas
HostName 192.168.1.3
User Erik
Port 2222
IdentityFile /home/erik/.ssh/id_ed25519_rpi-media
ConnectTimeout 10
ServerAliveInterval 60
ServerAliveCountMax 3
Analyse:
Host nas
:- Dit stelt een alias in voor het adres van je NAS. Je kunt nu eenvoudig
ssh nas
typen in plaats vanssh -p 2222 Erik@192.168.1.3
.
- Dit stelt een alias in voor het adres van je NAS. Je kunt nu eenvoudig
HostName 192.168.1.3
:- Dit is correct. Het verwijst naar het IP-adres van je NAS.
User Erik
:- Zorg ervoor dat de gebruiker
Erik
bestaat op je NAS en dat SSH voor deze gebruiker is geconfigureerd.
- Zorg ervoor dat de gebruiker
Port 2222
:- Dit lijkt correct, ervan uitgaande dat je NAS geconfigureerd is om SSH-verkeer te accepteren op poort
2222
.
- Dit lijkt correct, ervan uitgaande dat je NAS geconfigureerd is om SSH-verkeer te accepteren op poort
IdentityFile /home/erik/.ssh/id_ed25519_rpi-media
:- Controleer of dit bestand bestaat, goed geconfigureerd is en de juiste permissies heeft:bashKopiërenBewerken
ls -l /home/erik/.ssh/id_ed25519_rpi-media
Het bestand moet de permissie-rw-------
hebben (600).
- Controleer of dit bestand bestaat, goed geconfigureerd is en de juiste permissies heeft:bashKopiërenBewerken
ConnectTimeout 10
:- Dit bepaalt hoe lang de SSH-client wacht bij het maken van een verbinding. 10 seconden is prima.
ServerAliveInterval 60
enServerAliveCountMax 3
:- Deze instellingen zorgen ervoor dat de verbinding actief blijft. Na 3 minuten (60 seconden x 3) zonder activiteit wordt de verbinding gesloten. Dit is een goede instelling.
cat ~/.ssh/id_ed25519_rpi-backup.pub
Code language: JavaScript (javascript)
copy de line which looks something like,
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH... rpi-backup
SSH into your Synology NAS, watch it in the next line! And take care of being at your users home folder
The > means, empty the existing file and replace with ….
The >> means, add at the and of the existing file …..
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH... rpi-backup" <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-typology-acc-color">>></mark> .ssh/authorized_keys
Code language: JavaScript (javascript)
chmod 600 ~/.ssh/authorized_keys
Code language: JavaScript (javascript)
Testing,
ssh nas
#!/bin/bash
set -e
set -u
#echo "Starting script"
#set -x # Print commands as they execute
#echo "After set commands"
# Configuration
HOSTNAME=$(hostname)
LOG="/var/log/rpi-system-backup.log"
LOG_MAX_SIZE=10M
LOG_BACKUP_COUNT=7
LOCK_TIMEOUT=3600 # 1 hour timeout for lock file
REMOTE_PATH="/volume1/RPi-archive/${HOSTNAME}"
# Backup exclusions
EXCLUDE_LIST=(
"/proc"
"/sys"
"/dev"
"/tmp"
"/run"
"/lost+found"
"/var/log"
"/var/cache/apt/archives"
"/home/*/.cache"
"/media"
"/mnt"
"/var/lib/docker/overlay2"
"/var/lib/docker/containers"
)
# Services to pause during backup
CRITICAL_SERVICES=(
"mariadb"
"postgres"
)
# Docker containers to pause
CRITICAL_CONTAINERS=(
"immich_postgres"
"immich_redis"
"immich_server"
)
# Retention configuration
DAILY_RETENTION=7
WEEKLY_RETENTION=4
MONTHLY_RETENTION=12
YEARLY_RETENTION=2
# Lock file
LOCK_FILE="/tmp/rpi_system_backup.lock"
# Logging function
log_message() {
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "$timestamp - $1" | tee -a "$LOG"
}
LOG_MAX_SIZE=10M
LOG_BACKUP_COUNT=7
rotate_logs() {
if [ -f "$LOG" ]; then
size=$(stat -f%z "$LOG" 2>/dev/null || stat -c%s "$LOG" 2>/dev/null)
max_size=$(numfmt --from=iec $LOG_MAX_SIZE)
if [ "$size" -gt "$max_size" ]; then
for ((i=LOG_BACKUP_COUNT-1; i>=1; i--)); do
[ -f "$LOG.$i" ] && mv "$LOG.$i" "$LOG.$((i+1))"
done
mv "$LOG" "$LOG.1"
touch "$LOG"
fi
fi
}
# Enhanced lock management with timeout
check_lock() {
if [ -f "$LOCK_FILE" ]; then
pid=$(cat "$LOCK_FILE")
if kill -0 "$pid" 2>/dev/null; then
lock_age=$(($(date +%s) - $(stat -c %Y "$LOCK_FILE")))
if [ $lock_age -gt $LOCK_TIMEOUT ]; then
log_message "Lock file is stale (age: ${lock_age}s). Removing."
rm -f "$LOCK_FILE"
else
log_message "Another backup process is running (PID: $pid)"
exit 1
fi
fi
fi
echo $$ > "$LOCK_FILE"
}
cleanup() {
rm -f "$LOCK_FILE"
log_message "Cleanup completed"
}
trap cleanup EXIT INT TERM
# Check requirements
check_tools() {
for tool in rsync ssh docker; do
if ! command -v $tool &> /dev/null; then
log_message "ERROR: $tool is not installed."
exit 1
fi
done
}
# Create backup directories
create_backup_structure() {
log_message "Creating backup directory structure"
if ! ssh root "
mkdir -p '${REMOTE_PATH}/current' \
'${REMOTE_PATH}/daily' \
'${REMOTE_PATH}/weekly' \
'${REMOTE_PATH}/monthly' \
'${REMOTE_PATH}/yearly'
"; then
log_message "Failed to create backup structure"
exit 1
fi
}
# Generate exclude parameters
generate_exclude_params() {
local exclude_params=""
for item in "${EXCLUDE_LIST[@]}"; do
exclude_params+="--exclude=${item} "
done
echo "$exclude_params"
}
# Check available space
check_space() {
local required_space=$(du -sx --exclude=/proc --exclude=/sys --exclude=/dev / 2>/dev/null | awk '{print $1}')
local available_space
if ! available_space=$(ssh root \
"df -k '${REMOTE_PATH}' | tail -1 | awk '{print \$4}'"); then
log_message "ERROR: Failed to check remote space"
return 1
fi
if [ -z "$available_space" ] || [ -z "$required_space" ]; then
log_message "ERROR: Failed to determine space requirements"
return 1
fi
if [ "$available_space" -lt "$required_space" ]; then
log_message "ERROR: Insufficient space. Required: ${required_space}KB, Available: ${available_space}KB"
return 1
fi
return 0
}
# Handle services
handle_services() {
local action=$1
local failed=0
log_message "${action^}ing critical services and containers"
# System services
for service in "${CRITICAL_SERVICES[@]}"; do
if systemctl is-active --quiet "$service"; then
if ! systemctl "$action" "$service"; then
log_message "Failed to $action $service"
failed=1
fi
fi
done
# Docker containers
if command -v docker >/dev/null 2>&1; then
for container in "${CRITICAL_CONTAINERS[@]}"; do
if docker ps -q -f name="$container" >/dev/null; then
if [ "$action" = "stop" ]; then
if ! docker inspect --format '{{.State.Paused}}' "$container" | grep -q "true"; then
docker pause "$container" || failed=1
fi
else
if docker inspect --format '{{.State.Paused}}' "$container" | grep -q "true"; then
docker unpause "$container" || failed=1
fi
fi
fi
done
fi
return $failed
}
# Backup rotation
rotate_backups() {
local date_suffix=$(date +%Y%m%d)
local daily_backup="${REMOTE_PATH}/daily/backup_${date_suffix}"
if ssh root "[ -d '$daily_backup' ]"; then
log_message "Daily backup for ${date_suffix} already exists, skipping rotation"
return 0
fi
ssh root "
cd '${REMOTE_PATH}/daily' && ls -1t | tail -n +$((DAILY_RETENTION + 1)) | xargs -r rm -rf;
cd '${REMOTE_PATH}/weekly' && ls -1t | tail -n +$((WEEKLY_RETENTION + 1)) | xargs -r rm -rf;
cd '${REMOTE_PATH}/monthly' && ls -1t | tail -n +$((MONTHLY_RETENTION + 1)) | xargs -r rm -rf;
cd '${REMOTE_PATH}/yearly' && ls -1t | tail -n +$((YEARLY_RETENTION + 1)) | xargs -r rm -rf
"
sleep 5
ssh root "cp -al '${REMOTE_PATH}/current' '${REMOTE_PATH}/daily/backup_${date_suffix}'"
}
# Cleanup old backups
cleanup_old_backups() {
log_message "Starting cleanup of old backups"
local cleanup_commands="
find '${REMOTE_PATH}/daily' -maxdepth 1 -type d -mtime +${DAILY_RETENTION} -exec rm -rf {} \;
find '${REMOTE_PATH}/weekly' -maxdepth 1 -type d -mtime +$((WEEKLY_RETENTION * 7)) -exec rm -rf {} \;
find '${REMOTE_PATH}/monthly' -maxdepth 1 -type d -mtime +$((MONTHLY_RETENTION * 30)) -exec rm -rf {} \;
find '${REMOTE_PATH}/yearly' -maxdepth 1 -type d -mtime +$((YEARLY_RETENTION * 365)) -exec rm -rf {} \;
"
if ! ssh root "$cleanup_commands"; then
log_message "Failed to cleanup old backups"
return 1
fi
log_message "Cleanup of old backups completed"
return 0
}
# Define rsync options
RSYNC_OPTS="-aAX --delete --timeout=120 --no-specials --copy-unsafe-links --partial --quiet --no-acls"
DRY_RUN=false
for arg in "$@"; do
case $arg in
--dry-run)
DRY_RUN=true
;;
esac
done
if $DRY_RUN; then
RSYNC_OPTS+=" --dry-run"
fi
# Perform backup
perform_backup() {
local exclude_params=$(generate_exclude_params)
local rsync_output=$(mktemp)
nice -n 19 ionice -c2 -n7 sudo rsync $RSYNC_OPTS \
$exclude_params \
/ "root:${REMOTE_PATH}/current/" \
--rsync-path="/bin/rsync" 2>"$rsync_output"
local status=$?
if [ $status -ne 0 ]; then
log_message "ERROR: Backup failed (code $status): $(cat $rsync_output)"
rm "$rsync_output"
return 1
fi
rm "$rsync_output"
return 0
}
# Main execution
main() {
log_message "Starting backup process"
rotate_logs
check_lock
check_tools
create_backup_structure
if ! check_space; then
log_message "Space check failed"
exit 1
fi
if ! handle_services stop; then
log_message "Failed to stop services"
handle_services start
exit 1
fi
if perform_backup; then
if rotate_backups; then
log_message "Backup and rotation completed successfully"
else
log_message "Backup succeeded but rotation failed"
exit 1
fi
else
log_message "Backup failed"
exit 1
fi
if ! handle_services start; then
log_message "Failed to restart services"
exit 1
fi
}
# Run main function
main
Code language: PHP (php)
Met deze instellingen zal je Pi niet vastlopen als de NFS share niet beschikbaar is tijdens het opstarten. door de combinatie van noauto
met x-systemd.automount
zal het systeem de share automatisch mounten zodra er toegang toe nodig is.
Het werkt zo:
noauto
: voorkomt dat het direct bij boot gemount wordtx-systemd.automount
: zorgt ervoor dat systemd een “automount point” creëert dat de share automatisch mount zodra er toegang toe nodig is
Zonder x-systemd.automount
zou je inderdaad gelijk hebben – dan zou noauto
betekenen dat je het handmatig moet mounten. Maar deze combinatie geeft je het beste van beide werelden:
Robuuste failback als de share tijdelijk onbereikbaar is
Geen vertraging tijdens boot als de share niet bereikbaar is
Automatisch mounten zodra je het nodig hebt
192.168.xx.xx:/volume1/raspiBackup /mnt/backup nfs noauto,x-systemd.automount,soft,timeo=15,retrans=2 0 0
Code language: JavaScript (javascript)
sudo systemctl daemon-reload
sudo systemctl restart remote-fs.target
Code language: CSS (css)
Get and install the RaspiBackup application, walk through the options and configure as you want.
Watch it, this is the right one (look at the difference hyperlinks the two sources provided)
sudo curl -o install -L https://raspibackup.linux-tips-and-tricks.de/install; sudo bash ./install
Code language: JavaScript (javascript)
Start the backup for the first time.
sudo raspiBackup -m detailed
And to take care of automatic backup, check via the next settings/configuration file if it is of your liking (and as how you entered it during installation)
sudo nano /etc/systemd/system/raspiBackup.timer
[Unit]
Description=Timer for raspiBackup.service to start backup
[Timer]
OnCalendar=*-*-* 02:00:42
# Create a backup every day at 02:00 and 42 seconds
Unit=raspiBackup.service
[Install]
WantedBy=multi-user.target
Code language: PHP (php)
After you made changes you have to tell the system t reload the service again
sudo systemctl daemon-reload
sudo systemctl enable raspiBackup.timer
sudo systemctl start raspiBackup.timer
Code language: CSS (css)
sources (references) & credits
Basically all input/information came from the websites below. So credits and thanks to those content creators and subject matter experts. The only reason I mainly copy/paste their content is to guarantee I have a backup for myself and because multiple times I had to change and adapt. So archiving the “scripts” as I executed it succesfully is inportant for me.
https://boldt.blog/rasperry-pi-backup-to-synology-nas-with-raspibackup/
https://www.linux-tips-and-tricks.de/en/installation
https://pimylifeup.com/cron-jobs-and-crontab/
https://claude.ai