- docs/architecture/storage.md: new "Nextcloud as PVE-NFS browser" section documenting mount-per-archive + applicable_users model, why mount-level ACL beats Files Access Control on NC 30/31, the manifest shape (with current applicableUsers + enableSharing fields), and the trade-off - docs/runbooks/nextcloud-add-archive.md: 5-step runbook to surface a new directory under /srv/nfs/* to specific NC users via the bootstrap Job - scripts/anca-elements-sync.sh: deployed at /usr/local/bin/anca-elements-sync.sh on the PVE host; fpsync from Synology Anca/Elements to /srv/nfs/anca-elements (idempotent + resumable). The PVE replica is what the NC /anca-elements mount serves; the offsite-sync pipeline excludes this path (committed earlier this session) so we don't write it back to Synology NC usernames are admin/anca/emo (not display names — admin is Viktor). Stale "viktor" references in the manifest example dropped.
112 lines
4.2 KiB
Bash
Executable file
112 lines
4.2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# anca-elements-sync.sh — copy Anca's WD-Elements backup from Synology to PVE NFS
|
|
#
|
|
# Usage:
|
|
# /usr/local/bin/anca-elements-sync.sh
|
|
#
|
|
# Idempotent: re-running after a successful sync is a no-op (only the dry-run
|
|
# verification runs, which reports "sync verified clean" immediately).
|
|
#
|
|
# Resumable: if fpsync was interrupted, resume with:
|
|
# fpsync -r /var/tmp/fpsync \
|
|
# -n 4 -s 4G \
|
|
# -o "-lptgoD -H --no-perms --no-owner --no-group --exclude=@eaDir/ --exclude=*@synoeastream --exclude=.DS_Store --exclude=Thumbs.db" \
|
|
# /mnt/synology-backup/Anca/Elements/ /srv/nfs/anca-elements/
|
|
#
|
|
# NOTE: fpsync -o = rsync options override (what we want)
|
|
# fpsync -O = fpart partition options override (NOT rsync)
|
|
# NOTE: Do NOT use -a or -r in fpsync rsync options — fpsync handles
|
|
# recursion via fpart; -r causes fpsync to warn and skip the slab.
|
|
#
|
|
# Log: /var/log/anca-elements-sync.log
|
|
|
|
set -euo pipefail
|
|
|
|
LOG=/var/log/anca-elements-sync.log
|
|
SRC_HOST=192.168.1.13
|
|
SRC_EXPORT=/volume1/Backup
|
|
SRC_SUBPATH=Anca/Elements
|
|
MOUNT_POINT=/mnt/synology-backup
|
|
DEST=/srv/nfs/anca-elements
|
|
|
|
log() {
|
|
echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $*" | tee -a "$LOG"
|
|
}
|
|
|
|
# ── 1. Ensure destination + mount-point directories exist ────────────────────
|
|
log "Step 1: ensuring directories"
|
|
mkdir -p "$DEST" "$MOUNT_POINT"
|
|
|
|
# ── 2. NFS-mount Synology read-only (skip if already mounted) ───────────────
|
|
MOUNTED_HERE=0
|
|
if mountpoint -q "$MOUNT_POINT"; then
|
|
log "Step 2: $MOUNT_POINT already mounted — skipping"
|
|
else
|
|
log "Step 2: mounting ${SRC_HOST}:${SRC_EXPORT} at $MOUNT_POINT (read-only)"
|
|
mount -t nfs \
|
|
-o ro,vers=4,nolock,soft,timeo=300,retrans=2 \
|
|
"${SRC_HOST}:${SRC_EXPORT}" \
|
|
"$MOUNT_POINT"
|
|
MOUNTED_HERE=1
|
|
log "Step 2: mount successful"
|
|
fi
|
|
|
|
# ── 3. Ensure fpsync (from fpart package) is available ──────────────────────
|
|
log "Step 3: checking for fpsync"
|
|
if ! command -v fpsync >/dev/null 2>&1; then
|
|
log "Step 3: fpsync not found — installing fpart"
|
|
apt-get install -y fpart
|
|
log "Step 3: fpart installed"
|
|
else
|
|
log "Step 3: fpsync already available"
|
|
fi
|
|
|
|
# ── 4. Run fpsync (4-way parallel, no compression — source is already-compressed media) ──
|
|
log "Step 4: starting fpsync"
|
|
log " source : ${MOUNT_POINT}/${SRC_SUBPATH}/"
|
|
log " dest : ${DEST}/"
|
|
log " workers: 4, slab: 4G"
|
|
fpsync \
|
|
-n 4 \
|
|
-s 4G \
|
|
-o "-lptgoD -H --no-perms --no-owner --no-group --exclude=@eaDir/ --exclude=*@synoeastream --exclude=.DS_Store --exclude=Thumbs.db" \
|
|
"${MOUNT_POINT}/${SRC_SUBPATH}/" \
|
|
"${DEST}/" \
|
|
2>&1 | tee -a "$LOG"
|
|
log "Step 4: fpsync completed"
|
|
|
|
# ── 5. Verification dry-run ──────────────────────────────────────────────────
|
|
log "Step 5: running dry-run verification rsync"
|
|
VERIFY_OUT=$(rsync \
|
|
-rlptgoD -H --no-perms --no-owner --no-group \
|
|
--exclude='@eaDir/' --exclude='*@synoeastream' \
|
|
--exclude='.DS_Store' --exclude='Thumbs.db' \
|
|
-n --delete \
|
|
--info=progress2 \
|
|
--out-format='%o %f' \
|
|
"${MOUNT_POINT}/${SRC_SUBPATH}/" \
|
|
"${DEST}/" \
|
|
2>&1 || true)
|
|
|
|
# Count lines that represent actual file changes (send / del. operations)
|
|
CHANGE_COUNT=$(echo "$VERIFY_OUT" | grep -cE '^(send|del\.)' || true)
|
|
|
|
if [ "$CHANGE_COUNT" -eq 0 ]; then
|
|
log "Step 5: sync verified clean — no pending changes"
|
|
else
|
|
log "Step 5: WARNING — verification found ${CHANGE_COUNT} pending change(s). First 50 lines:"
|
|
# Use printf to avoid SIGPIPE from head closing the pipe early (set -o pipefail)
|
|
{ echo "$VERIFY_OUT" | head -50; } >> "$LOG" 2>&1 || true
|
|
fi
|
|
|
|
# ── 6. Unmount (only if we mounted it) ──────────────────────────────────────
|
|
if [ "$MOUNTED_HERE" -eq 1 ]; then
|
|
log "Step 6: unmounting $MOUNT_POINT"
|
|
umount "$MOUNT_POINT"
|
|
rmdir "$MOUNT_POINT"
|
|
log "Step 6: unmounted"
|
|
else
|
|
log "Step 6: mount was pre-existing — leaving in place"
|
|
fi
|
|
|
|
log "Done. Final size: $(du -sh "${DEST}" | cut -f1)"
|