Add reMarkable backup system with web UI
- Docker container with rmapi cloud backup, PDF conversion pipeline - Web UI: file browser, multi-select, delete/move, thumbnail preview - Sync: backup from reMarkable cloud, rmdoc→PDF conversion via rmc+Inkscape - Excluded-files mechanism to prevent deleted items from returning after sync Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
113
remarkable/scripts/backup-official-cloud.sh
Executable file
113
remarkable/scripts/backup-official-cloud.sh
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env bash
|
||||
# reMarkable Backup (offizielle Cloud via rmapi)
|
||||
#
|
||||
# Ziel:
|
||||
# - Inkrementeller Download in "current/".
|
||||
# - Datierte Snapshots in "snapshots/YYYY-MM-DD_HHMMSS/".
|
||||
# - Versionierung via Hardlinks (unveraenderte Dateien belegen kaum Zusatzplatz).
|
||||
#
|
||||
# Nutzung:
|
||||
# ./backup-official-cloud.sh /www/remarkable/notizen /
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
BASE_DIR="${1:-/www/remarkable/notizen}"
|
||||
REMOTE_DIR="${2:-/}"
|
||||
TIMESTAMP="$(date +%F_%H%M%S)"
|
||||
|
||||
CURRENT_DIR="${BASE_DIR}/current"
|
||||
SNAPSHOT_ROOT="${BASE_DIR}/snapshots"
|
||||
SNAPSHOT_DIR="${SNAPSHOT_ROOT}/${TIMESTAMP}"
|
||||
LATEST_LINK="${SNAPSHOT_ROOT}/latest"
|
||||
LOG_DIR="${BASE_DIR}/logs"
|
||||
LOCK_FILE="${BASE_DIR}/.backup.lock"
|
||||
|
||||
# Optional: Pfad, der ein echter Mountpoint sein muss (z. B. /www/remarkable/notizen).
|
||||
# Wenn gesetzt und kein Mountpoint, wird abgebrochen (sicher gegen "auf Root-FS schreiben").
|
||||
MOUNT_CHECK_PATH="${MOUNT_CHECK_PATH:-}"
|
||||
|
||||
# Optional: Anzahl alter Snapshots behalten (0 = unbegrenzt).
|
||||
SNAPSHOT_KEEP="${SNAPSHOT_KEEP:-30}"
|
||||
|
||||
mkdir -p "${CURRENT_DIR}" "${SNAPSHOT_ROOT}" "${LOG_DIR}"
|
||||
|
||||
if [[ -n "${MOUNT_CHECK_PATH}" ]] && ! mountpoint -q "${MOUNT_CHECK_PATH}"; then
|
||||
echo "Fehler: ${MOUNT_CHECK_PATH} ist nicht gemountet. Backup abgebrochen."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v rmapi >/dev/null 2>&1; then
|
||||
echo "Fehler: rmapi nicht im PATH."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v rsync >/dev/null 2>&1; then
|
||||
echo "Fehler: rsync fehlt. Bitte installieren."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec 9>"${LOCK_FILE}"
|
||||
if ! flock -n 9; then
|
||||
echo "Backup laeuft bereits (Lock: ${LOCK_FILE})."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LOG_FILE="${LOG_DIR}/backup-${TIMESTAMP}.log"
|
||||
exec > >(tee -a "${LOG_FILE}") 2>&1
|
||||
|
||||
echo "== reMarkable Backup Start =="
|
||||
echo "Base: ${BASE_DIR}"
|
||||
echo "Remote: ${REMOTE_DIR}"
|
||||
echo "Zeit: ${TIMESTAMP}"
|
||||
|
||||
MGET_FLAGS=(-i)
|
||||
if [[ "${BACKUP_FULL:-}" == "1" ]]; then
|
||||
MGET_FLAGS=()
|
||||
fi
|
||||
if [[ "${BACKUP_MIRROR_DELETE:-}" == "1" ]]; then
|
||||
MGET_FLAGS+=(-d)
|
||||
fi
|
||||
|
||||
echo "1) Aktualisiere current/ via rmapi mget ${MGET_FLAGS[*]:-(none)} ..."
|
||||
rmapi mget "${MGET_FLAGS[@]}" -o "${CURRENT_DIR}" "${REMOTE_DIR}"
|
||||
|
||||
# Leere Ordner anlegen (rmapi mget erstellt keine leeren Verzeichnisse)
|
||||
echo "1b) Lege fehlende Ordner an ..."
|
||||
rmapi find "${REMOTE_DIR}" 2>/dev/null | grep '^\[d\]' | sed 's/^\[d\] *//' | while read -r DIR; do
|
||||
LOCAL_DIR="${CURRENT_DIR}/${DIR}"
|
||||
mkdir -p "${LOCAL_DIR}"
|
||||
done
|
||||
|
||||
echo "2) Erzeuge Snapshot: ${SNAPSHOT_DIR}"
|
||||
mkdir -p "${SNAPSHOT_DIR}"
|
||||
|
||||
if [[ -L "${LATEST_LINK}" ]] && [[ -d "$(readlink -f "${LATEST_LINK}")" ]]; then
|
||||
PREV_SNAPSHOT="$(readlink -f "${LATEST_LINK}")"
|
||||
rsync -a --delete --link-dest="${PREV_SNAPSHOT}" "${CURRENT_DIR}/" "${SNAPSHOT_DIR}/"
|
||||
else
|
||||
rsync -a --delete "${CURRENT_DIR}/" "${SNAPSHOT_DIR}/"
|
||||
fi
|
||||
|
||||
ln -sfn "${SNAPSHOT_DIR}" "${LATEST_LINK}"
|
||||
|
||||
if [[ "${SNAPSHOT_KEEP}" =~ ^[0-9]+$ ]] && (( SNAPSHOT_KEEP > 0 )); then
|
||||
echo "3) Pruefe Retention (keep=${SNAPSHOT_KEEP}) ..."
|
||||
mapfile -t SNAPSHOTS < <(ls -1 "${SNAPSHOT_ROOT}" | grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{6}$' | sort)
|
||||
if (( ${#SNAPSHOTS[@]} > SNAPSHOT_KEEP )); then
|
||||
DELETE_COUNT=$(( ${#SNAPSHOTS[@]} - SNAPSHOT_KEEP ))
|
||||
for OLD in "${SNAPSHOTS[@]:0:DELETE_COUNT}"; do
|
||||
rm -rf "${SNAPSHOT_ROOT}/${OLD}"
|
||||
echo " entfernt: ${SNAPSHOT_ROOT}/${OLD}"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# PDF-Konvertierung
|
||||
if command -v remarkable-convert.sh >/dev/null 2>&1; then
|
||||
echo "4) Konvertiere .rmdoc zu PDF ..."
|
||||
remarkable-convert.sh "${CURRENT_DIR}"
|
||||
fi
|
||||
|
||||
echo "== Backup fertig =="
|
||||
echo "Current: ${CURRENT_DIR}"
|
||||
echo "Snapshot: ${SNAPSHOT_DIR}"
|
||||
Reference in New Issue
Block a user