Files
gniza4linux/lib/snapshot.sh
shuki 928d5af54c Initial implementation of gniza4linux backup tool
Complete Linux backup manager with Whiptail TUI and CLI interface.
Adapted from gniza4cp (cPanel backup tool) with target/profile-based
system replacing cPanel-specific features.

- 14 core engine modules (backup, restore, targets, remotes, transfer, etc.)
- 11 Whiptail TUI screens (full CRUD for targets/remotes/schedules)
- CLI entrypoint with subcommands for scripting/cron
- Support for SSH, local, S3, and Google Drive remotes
- rsync --link-dest incremental snapshots
- Root and user mode (XDG paths)
- 70 passing tests
- Config templates, installer, uninstaller

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:15:29 +02:00

157 lines
4.6 KiB
Bash

#!/usr/bin/env bash
# gniza4linux/lib/snapshot.sh — Timestamp naming, list/resolve snapshots, latest symlink
[[ -n "${_GNIZA4LINUX_SNAPSHOT_LOADED:-}" ]] && return 0
_GNIZA4LINUX_SNAPSHOT_LOADED=1
get_remote_target_base() {
local target_name="$1"
local hostname; hostname=$(hostname -f)
echo "${REMOTE_BASE}/${hostname}/targets/${target_name}"
}
get_snapshot_dir() {
local target_name="$1"
echo "$(get_remote_target_base "$target_name")/snapshots"
}
list_remote_snapshots() {
local target_name="$1"
if _is_rclone_mode; then
rclone_list_remote_snapshots "$target_name"
return
fi
if [[ "${REMOTE_TYPE:-ssh}" == "local" ]]; then
local snap_dir; snap_dir=$(get_snapshot_dir "$target_name")
local raw
raw=$(ls -1d "$snap_dir"/[0-9]* 2>/dev/null | grep -v '\.partial$' | sort -r) || true
if [[ -n "$raw" ]]; then
echo "$raw" | xargs -I{} basename {} | sort -r
fi
return
fi
local snap_dir; snap_dir=$(get_snapshot_dir "$target_name")
# List completed snapshots (no .partial suffix), sorted newest first
local raw; raw=$(remote_exec "ls -1d '$snap_dir'/[0-9]* 2>/dev/null | grep -v '\\.partial$' | sort -r" 2>/dev/null) || true
if [[ -n "$raw" ]]; then
echo "$raw" | xargs -I{} basename {} | sort -r
fi
}
get_latest_snapshot() {
local target_name="$1"
if _is_rclone_mode; then
rclone_get_latest_snapshot "$target_name"
return
fi
list_remote_snapshots "$target_name" | head -1
}
resolve_snapshot_timestamp() {
local target_name="$1"
local requested="$2"
if [[ -z "$requested" || "$requested" == "LATEST" || "$requested" == "latest" ]]; then
get_latest_snapshot "$target_name"
elif _is_rclone_mode; then
rclone_resolve_snapshot "$target_name" "$requested"
elif [[ "${REMOTE_TYPE:-ssh}" == "local" ]]; then
local snap_dir; snap_dir=$(get_snapshot_dir "$target_name")
if [[ -d "$snap_dir/$requested" ]]; then
echo "$requested"
else
log_error "Snapshot not found for $target_name: $requested"
return 1
fi
else
# Verify it exists on SSH remote
local snap_dir; snap_dir=$(get_snapshot_dir "$target_name")
if remote_exec "test -d '$snap_dir/$requested'" 2>/dev/null; then
echo "$requested"
else
log_error "Snapshot not found for $target_name: $requested"
return 1
fi
fi
}
update_latest_symlink() {
local target_name="$1"
local timestamp="$2"
if _is_rclone_mode; then
rclone_update_latest "$target_name" "$timestamp"
return
fi
local base; base=$(get_remote_target_base "$target_name")
local snap_dir; snap_dir=$(get_snapshot_dir "$target_name")
if [[ "${REMOTE_TYPE:-ssh}" == "local" ]]; then
ln -sfn "$snap_dir/$timestamp" "$base/latest" || {
log_warn "Failed to update latest symlink for $target_name"
return 1
}
else
remote_exec "ln -sfn '$snap_dir/$timestamp' '$base/latest'" || {
log_warn "Failed to update latest symlink for $target_name"
return 1
}
fi
log_debug "Updated latest symlink for $target_name -> $timestamp"
}
clean_partial_snapshots() {
local target_name="$1"
if _is_rclone_mode; then
rclone_clean_partial_snapshots "$target_name"
return
fi
local snap_dir; snap_dir=$(get_snapshot_dir "$target_name")
if [[ "${REMOTE_TYPE:-ssh}" == "local" ]]; then
local partials
partials=$(ls -1d "$snap_dir"/*.partial 2>/dev/null) || true
if [[ -n "$partials" ]]; then
log_info "Cleaning partial snapshots for $target_name..."
rm -rf "$snap_dir"/*.partial || {
log_warn "Failed to clean partial snapshots for $target_name"
}
fi
return
fi
local partials; partials=$(remote_exec "ls -1d '$snap_dir'/*.partial 2>/dev/null" 2>/dev/null) || true
if [[ -n "$partials" ]]; then
log_info "Cleaning partial snapshots for $target_name..."
remote_exec "rm -rf '$snap_dir'/*.partial" || {
log_warn "Failed to clean partial snapshots for $target_name"
}
fi
}
list_remote_targets() {
if _is_rclone_mode; then
rclone_list_dirs "targets"
return
fi
local hostname; hostname=$(hostname -f)
local targets_dir="${REMOTE_BASE}/${hostname}/targets"
if [[ "${REMOTE_TYPE:-ssh}" == "local" ]]; then
ls -1 "$targets_dir" 2>/dev/null || true
return
fi
remote_exec "ls -1 '$targets_dir' 2>/dev/null" 2>/dev/null || true
}