Compare commits
2
Commits
76d2d806a9
...
d99e4b1e52
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d99e4b1e52 | ||
|
|
9ef4b9ea8b |
@@ -1,5 +1,5 @@
|
|||||||
$TTL 3600
|
$TTL 3600
|
||||||
@ IN SOA dns106.ovh.net. tech.ovh.net. (2085478106 86400 3600 3600000 300)
|
@ IN SOA dns106.ovh.net. tech.ovh.net. (2086784538 86400 3600 3600000 300)
|
||||||
IN NS dns106.ovh.net.
|
IN NS dns106.ovh.net.
|
||||||
IN NS ns106.ovh.net.
|
IN NS ns106.ovh.net.
|
||||||
IN MX 100 mx3.mail.ovh.net.
|
IN MX 100 mx3.mail.ovh.net.
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
A practical tutorial for people who moved to a ZimaBoard for the faster CPU and extra RAM, but want to keep using their existing Synology as a rock-solid backup target.
|
||||||
|
|
||||||
|
Disclaimer: this is a setup that works well for me. I take no responsibility for your data or hardware — test on your own gear, verify the results yourself, and be very careful with any command that writes to a disk.
|
||||||
|
|
||||||
|
The idea
|
||||||
|
Your ZimaBoard is now the workhorse — apps, containers, everything runs there. Your Synology is perfect for what it’s genuinely great at: reliable, cheap, roomy storage you write to and rarely read. This tutorial wires the two together so the ZimaBoard pushes versioned, space-efficient backups to the Synology every night, automatically.
|
||||||
|
|
||||||
|
What you get:
|
||||||
|
|
||||||
|
Dated snapshots — one folder per run (e.g. 2026-08-06_000006), each a complete browsable copy of your data at that moment.
|
||||||
|
|
||||||
|
Almost no extra space per snapshot — unchanged files are hardlinked, so keeping 14 daily snapshots costs roughly one copy + the daily changes, not 14 full copies. (In practice: one snapshot looks like 517 GB, but 14 of them plus the live mirror total just 542 GB on disk.)
|
||||||
|
|
||||||
|
Only changed data crosses the network each night — quick after the first run.
|
||||||
|
|
||||||
|
A locked-down backup key that can’t get a shell or touch anything outside the backup folder.
|
||||||
|
|
||||||
|
How it works (two steps, and why)
|
||||||
|
Rather than one big rsync, the nightly job does two things:
|
||||||
|
|
||||||
|
Snapshot — runs on the Synology itself: copy the previous backup into a new dated folder with cp -al (a hardlink copy). Done locally this is near-instant.
|
||||||
|
|
||||||
|
Mirror — runs over SSH from the ZimaBoard: a plain rsync into a single CURRENT/ folder.
|
||||||
|
|
||||||
|
Snapshot first, mirror second — that way, if a mirror is ever interrupted, the previous night’s snapshot is already complete and safe.
|
||||||
|
|
||||||
|
The Synology ends up looking like:
|
||||||
|
|
||||||
|
/volume2/Backups/
|
||||||
|
├── CURRENT/ ← the live mirror (updated in place each night)
|
||||||
|
├── 2026-08-05_000006/ ← dated hardlinked snapshot
|
||||||
|
├── 2026-08-06_000006/ ← dated hardlinked snapshot
|
||||||
|
└── ... ← up to however many you keep
|
||||||
|
|
||||||
|
Restore from a dated snapshot, never from CURRENT — CURRENT is the live mirror and could be mid-update.
|
||||||
|
|
||||||
|
Why the snapshot runs on the Synology, not over a mounted share: cp -al creates one hardlink per file. Run locally on the Synology, hardlinking a large tree takes a couple of minutes. Run across an SMB/network mount, each hardlink becomes a separate network round-trip — for a large file count that turns minutes into many hours. So we trigger the snapshot on the Synology. This one detail makes or breaks the whole thing.
|
||||||
|
|
||||||
|
Step 1 — Turn on rsync-over-SSH on the Synology
|
||||||
|
In DSM:
|
||||||
|
|
||||||
|
Control Panel → File Services → rsync → enable the rsync service. Note the SSH encryption port shown (often non-standard). Your backups use this port.
|
||||||
|
|
||||||
|
Leave the “rsync account” option off (that’s an unencrypted mode you don’t need).
|
||||||
|
|
||||||
|
Make sure your DSM user is in the administrators group (Control Panel → User & Group) — DSM only allows admin users to SSH in by default.
|
||||||
|
|
||||||
|
Enable user home service (Control Panel → User & Group → Advanced), so your user has a home directory with a ~/.ssh folder.
|
||||||
|
|
||||||
|
DSM’s SSH is strict about a couple of things:
|
||||||
|
|
||||||
|
~/.ssh must be 700 and ~/.ssh/authorized_keys must be 600, or DSM quietly ignores your key and falls back to password auth.
|
||||||
|
|
||||||
|
If rapid failed attempts get your source blocked, clear it under Control Panel → Security → Account (Auto Block).
|
||||||
|
|
||||||
|
Step 2 — Create a key and restrict it with rrsync
|
||||||
|
On the ZimaBoard, generate a key with no passphrase (an automated job can’t type one). Put it somewhere that survives reboots — on ZimaOS, keep it on your data pool, e.g. under /media/YOURPOOL/scripts/.ssh/:
|
||||||
|
|
||||||
|
ssh-keygen -t ed25519 -f /media/YOURPOOL/scripts/.ssh/synology_backup -N ""
|
||||||
|
|
||||||
|
rrsync is the official wrapper that confines a key to rsync-only, in one folder. Fetch it into your DSM user’s home (over SSH to the Synology):
|
||||||
|
|
||||||
|
mkdir -p ~/bin
|
||||||
|
curl -o ~/bin/rrsync https://raw.githubusercontent.com/RsyncProject/rsync/master/support/rrsync
|
||||||
|
chmod +x ~/bin/rrsync
|
||||||
|
|
||||||
|
(head -1 ~/bin/rrsync tells you if it wants python3 or perl — DSM has both.)
|
||||||
|
|
||||||
|
Install the public key on the Synology with a forced command, so this key can only run rsync into your backup folder — no shell, no other paths:
|
||||||
|
|
||||||
|
# on the Synology, append (>> not >) to ~/.ssh/authorized_keys:
|
||||||
|
cat >> ~/.ssh/authorized_keys <<'EOF'
|
||||||
|
command="/var/services/homes/YOURUSER/bin/rrsync '/volume2/Backups'",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-ed25519 AAAA...your_public_key... zimaboard
|
||||||
|
EOF
|
||||||
|
chmod 700 ~/.ssh
|
||||||
|
chmod 600 ~/.ssh/authorized_keys
|
||||||
|
|
||||||
|
Use plain rrsync '...', not -wo — write-only mode blocks reading the destination, which the snapshot approach and any restore need.
|
||||||
|
|
||||||
|
Test: an rsync of a small file should work; a plain ssh with this key should not give you a shell. That refusal is the lockdown doing its job.
|
||||||
|
|
||||||
|
Step 3 — The snapshot script (lives on the Synology)
|
||||||
|
Put this on the Synology as ~/bin/snapshot.sh (adjust path and retention):
|
||||||
|
|
||||||
|
#!/bin/sh
|
||||||
|
BACKUP_ROOT="/volume2/Backups"
|
||||||
|
RETENTION=14
|
||||||
|
TS=$(date +%Y-%m-%d_%H%M%S)
|
||||||
|
cd "$BACKUP_ROOT" || exit 1
|
||||||
|
[ -d CURRENT ] || { echo "No CURRENT yet, skipping"; exit 0; }
|
||||||
|
echo "Snapshotting CURRENT -> $TS"
|
||||||
|
mkdir -p "$TS"
|
||||||
|
cp -al CURRENT/. "$TS/" || { rm -rf "$TS"; echo "cp -al failed"; exit 1; }
|
||||||
|
TOTAL=$(ls -1d [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]_* 2>/dev/null | wc -l)
|
||||||
|
if [ "$TOTAL" -gt "$RETENTION" ]; then
|
||||||
|
ls -1d [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]_* | sort | head -n $((TOTAL-RETENTION)) \
|
||||||
|
| while read -r old; do echo "Pruning $old"; rm -rf "$old"; done
|
||||||
|
fi
|
||||||
|
echo "Done: $TS"
|
||||||
|
|
||||||
|
chmod +x ~/bin/snapshot.sh and test it once by hand — it should finish in a couple of minutes and leave a dated folder.
|
||||||
|
|
||||||
|
Trigger it remotely with a second restricted key, so the nightly job can run it without a shell. Generate a second key on the ZimaBoard (synology_snapshot), and install its public key on the Synology forced to run only this script:
|
||||||
|
|
||||||
|
command="/var/services/homes/YOURUSER/bin/snapshot.sh",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-ed25519 AAAA...second_public_key... zimaboard
|
||||||
|
|
||||||
|
Now SSHing in with that key just runs the snapshot and disconnects.
|
||||||
|
|
||||||
|
Step 4 — The nightly backup script (on the ZimaBoard)
|
||||||
|
#!/bin/sh
|
||||||
|
SSH="ssh -p PORT -o StrictHostKeyChecking=accept-new"
|
||||||
|
SNAPKEY=/media/YOURPOOL/scripts/.ssh/synology_snapshot
|
||||||
|
MIRRORKEY=/media/YOURPOOL/scripts/.ssh/synology_backup
|
||||||
|
USER_HOST="youruser@your.synology.ip"
|
||||||
|
|
||||||
|
# 1. Snapshot CURRENT server-side (fast, local cp -al)
|
||||||
|
$SSH -i "$SNAPKEY" "$USER_HOST" || { echo "snapshot failed"; exit 1; }
|
||||||
|
|
||||||
|
# 2. Mirror your data -> CURRENT
|
||||||
|
rsync -aH --delete --stats \
|
||||||
|
-e "$SSH -i $MIRRORKEY" \
|
||||||
|
/media/YOURPOOL/ "$USER_HOST":/CURRENT/
|
||||||
|
|
||||||
|
Adjust the pool path, port, and add any excludes you need (see the traps below).
|
||||||
|
|
||||||
|
Scheduling on ZimaOS: ZimaOS clears the system crontab on reboot, so don’t rely on plain cron. Run the schedule from a small Docker container instead (e.g. the lightweight Ofelia scheduler, deployed as a Portainer stack) — Docker state lives on your pool and survives reboots, so the schedule sticks. Point one Ofelia job at your backup script on your chosen time (e.g. daily at 00:00).
|
||||||
|
|
||||||
|
Two traps worth knowing before your first run
|
||||||
|
1. rsync include/exclude order is first-match-wins. If you exclude a big directory but want to keep a subfolder of it, the --include for that subfolder must come before the broad --exclude, or it never takes effect and you silently lose that data. Correct order:
|
||||||
|
|
||||||
|
--include='/somedir/'
|
||||||
|
--include='/somedir/keepthis/***'
|
||||||
|
--exclude='/somedir/**'
|
||||||
|
|
||||||
|
2. Some objects can’t be rsynced, and that’s normal. Live special files (block devices, sockets, certain container-runtime internals under Docker’s overlay2) make rsync return exit 23 (“some files not transferred”) — the real files still went across fine. If you back up your whole pool, exclude Docker’s runtime layers (keep docker/volumes, skip docker/overlay2 and friends), plus the SMB recycle bin (.trash) and @eaDir metadata. Treat exit codes 23 and 24 (files vanished mid-copy — normal on a live system) as OK in your script; anything else is a real failure.
|
||||||
|
|
||||||
|
Verify it — the important part
|
||||||
|
Check hardlinks are real (snapshots share storage instead of duplicating). On the Synology:
|
||||||
|
|
||||||
|
find "/volume2/Backups/<a-dated-snapshot>/<some/known/file/path>" -type f -printf '%n %p\n' | head
|
||||||
|
|
||||||
|
Link count 2 or more = shared with CURRENT, working correctly. 1 = something’s full-copying.
|
||||||
|
|
||||||
|
Check the real total size — the decisive space test. On the Synology:
|
||||||
|
|
||||||
|
du -sh "/volume2/Backups"
|
||||||
|
|
||||||
|
du counts hardlinked files only once, so CURRENT + all snapshots together should total roughly one copy plus accumulated changes — not N copies. (Example: 14 snapshots + mirror = 542 GB, versus 517 GB for a single snapshot. The 25 GB difference is two weeks of real changed data.) Note: File Station → Properties on one snapshot shows the full ~517 GB because it counts each hardlink as a full file — that number is misleading. du -sh on the whole Backups folder is the true figure.
|
||||||
|
|
||||||
|
Check the data you expect is actually there:
|
||||||
|
|
||||||
|
ls "/volume2/Backups/CURRENT/"
|
||||||
|
ls "/volume2/Backups/CURRENT/<a/subfolder/you/care/about>"
|
||||||
|
|
||||||
|
Restoring
|
||||||
|
One file: browse the right dated snapshot on the Synology (File Station or SSH) and copy it out — plain hardlinked folders, no special tooling.
|
||||||
|
|
||||||
|
Full restore: rsync a dated snapshot back to the ZimaBoard using your normal Synology login (the restricted keys are for pushing):
|
||||||
|
|
||||||
|
rsync -aH --info=progress2 -e "ssh -p PORT" \ "youruser@synology:/volume2/Backups/2026-MM-DD_HHMMSS/" /media/YOURPOOL/
|
||||||
|
|
||||||
|
Fix ownership afterward if an app expects specific file owners (databases, web apps, etc.).
|
||||||
|
|
||||||
|
Getting the most out of the Synology as a target
|
||||||
|
Throughput will be modest, and that’s expected. A single rsync stream over gigabit, with lots of small files, runs well under line speed because per-file overhead dominates. A link-aggregation bond doesn’t speed up a single stream. The first full run takes a while; every run after is fast because only changes move.
|
||||||
|
|
||||||
|
Keep the versioning on the Synology (Step 3). It’s the difference between a 2-minute snapshot and an hours-long one.
|
||||||
|
|
||||||
|
If a run ever fails with “another instance is already accessing this directory,” a killed transfer left a process running on the Synology holding the lock. SSH in, ps aux | grep rsync, and kill the leftover rrsync/rsync --server PIDs — but not the SynoRsync … --daemon one (that’s DSM’s own service).
|
||||||
|
|
||||||
|
Expect a “login at an unusual time” notice if you back up at night — that’s DSM (via Active Insight) noticing your own nightly login. Either widen the “usual” window in Active Insight or accept the notice; don’t disable login alerts entirely.
|
||||||
|
|
||||||
|
Keep the disks healthy (SMART checks, periodic scrubs). This is where your safety net lives.
|
||||||
|
|
||||||
|
One habit to keep
|
||||||
|
Verify on disk. Exit codes and “done” messages don’t prove your data is there. Check the destination contents, the link counts, and the real du size yourself now and then. It takes a minute and it’s the difference between hoping you have a backup and knowing you do.
|
||||||
Binary file not shown.
@@ -92,7 +92,7 @@ borg.delmar.bzh {
|
|||||||
gzip
|
gzip
|
||||||
minimum_length 1024
|
minimum_length 1024
|
||||||
}
|
}
|
||||||
reverse_proxy patrick:8080
|
reverse_proxy carlo:8081
|
||||||
}
|
}
|
||||||
|
|
||||||
cap.delmar.bzh {
|
cap.delmar.bzh {
|
||||||
@@ -241,7 +241,7 @@ hmr.delmar.bzh {
|
|||||||
gzip
|
gzip
|
||||||
minimum_length 1024
|
minimum_length 1024
|
||||||
}
|
}
|
||||||
reverse_proxy bob:17080
|
reverse_proxy bob:39084
|
||||||
}
|
}
|
||||||
|
|
||||||
homepage.delmar.bzh {
|
homepage.delmar.bzh {
|
||||||
@@ -299,7 +299,7 @@ jellyfin.delmar.bzh {
|
|||||||
gzip
|
gzip
|
||||||
minimum_length 1024
|
minimum_length 1024
|
||||||
}
|
}
|
||||||
reverse_proxy patrick:8096
|
reverse_proxy carlo:8096
|
||||||
}
|
}
|
||||||
|
|
||||||
jellyseerr.delmar.bzh {
|
jellyseerr.delmar.bzh {
|
||||||
@@ -308,7 +308,7 @@ jellyseerr.delmar.bzh {
|
|||||||
gzip
|
gzip
|
||||||
minimum_length 1024
|
minimum_length 1024
|
||||||
}
|
}
|
||||||
reverse_proxy patrick:5055
|
reverse_proxy carlo:5055
|
||||||
}
|
}
|
||||||
|
|
||||||
kontadenn.delmar.bzh {
|
kontadenn.delmar.bzh {
|
||||||
@@ -366,7 +366,7 @@ mmgr.delmar.bzh {
|
|||||||
gzip
|
gzip
|
||||||
minimum_length 1024
|
minimum_length 1024
|
||||||
}
|
}
|
||||||
reverse_proxy bikinibottom:38274
|
reverse_proxy carlo:38274
|
||||||
}
|
}
|
||||||
|
|
||||||
mmm.delmar.bzh {
|
mmm.delmar.bzh {
|
||||||
@@ -659,7 +659,7 @@ wizarr.delmar.bzh {
|
|||||||
gzip
|
gzip
|
||||||
minimum_length 1024
|
minimum_length 1024
|
||||||
}
|
}
|
||||||
reverse_proxy patrick:5690
|
reverse_proxy carlo:5690
|
||||||
}
|
}
|
||||||
|
|
||||||
www.delmar.bzh {
|
www.delmar.bzh {
|
||||||
@@ -701,5 +701,5 @@ zik.delmar.bzh {
|
|||||||
gzip
|
gzip
|
||||||
minimum_length 1024
|
minimum_length 1024
|
||||||
}
|
}
|
||||||
reverse_proxy patrick:4533
|
reverse_proxy carlo:4533
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user