Remove verify feature and align main menu layout
Remove verify screen, CLI subcommand, and all related functions. Fix logo/menu height alignment on main menu screen. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -100,7 +100,6 @@ Commands:
|
||||
targets list|add|delete|show [--name=NAME] [--folders=PATHS]
|
||||
remotes list|add|delete|show|test [--name=NAME]
|
||||
snapshots list [--target=NAME] [--remote=NAME]
|
||||
verify [--target=NAME] [--remote=NAME] [--all]
|
||||
retention [--target=NAME] [--remote=NAME] [--all]
|
||||
schedule install|show|remove
|
||||
logs [--last] [--tail=N]
|
||||
|
||||
24
bin/gniza
24
bin/gniza
@@ -20,7 +20,6 @@ source "$GNIZA_DIR/lib/remotes.sh"
|
||||
source "$GNIZA_DIR/lib/backup.sh"
|
||||
source "$GNIZA_DIR/lib/restore.sh"
|
||||
source "$GNIZA_DIR/lib/retention.sh"
|
||||
source "$GNIZA_DIR/lib/verify.sh"
|
||||
source "$GNIZA_DIR/lib/schedule.sh"
|
||||
source "$GNIZA_DIR/lib/notify.sh"
|
||||
source "$GNIZA_DIR/lib/ssh.sh"
|
||||
@@ -34,7 +33,6 @@ source "$GNIZA_DIR/lib/ui_remotes.sh"
|
||||
source "$GNIZA_DIR/lib/ui_backup.sh"
|
||||
source "$GNIZA_DIR/lib/ui_restore.sh"
|
||||
source "$GNIZA_DIR/lib/ui_snapshots.sh"
|
||||
source "$GNIZA_DIR/lib/ui_verify.sh"
|
||||
source "$GNIZA_DIR/lib/ui_retention.sh"
|
||||
source "$GNIZA_DIR/lib/ui_logs.sh"
|
||||
source "$GNIZA_DIR/lib/ui_schedule.sh"
|
||||
@@ -88,7 +86,6 @@ Commands:
|
||||
targets list|add|delete|show [--name=NAME] [--folders=PATHS]
|
||||
remotes list|add|delete|show|test [--name=NAME]
|
||||
snapshots list [--target=NAME] [--remote=NAME]
|
||||
verify [--target=NAME] [--remote=NAME] [--all]
|
||||
retention [--target=NAME] [--remote=NAME] [--all]
|
||||
schedule install|show|remove
|
||||
logs [--last] [--tail=N]
|
||||
@@ -338,27 +335,6 @@ run_cli() {
|
||||
esac
|
||||
;;
|
||||
|
||||
verify)
|
||||
local target="" remote="" all=false
|
||||
target=$(_parse_flag "--target" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}") || true
|
||||
remote=$(_parse_flag "--remote" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}") || true
|
||||
_has_flag "--all" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}" && all=true
|
||||
|
||||
if [[ -z "$remote" ]]; then
|
||||
remote=$(list_remotes | head -1)
|
||||
[[ -z "$remote" ]] && die "No remotes configured"
|
||||
fi
|
||||
_save_remote_globals
|
||||
load_remote "$remote" || die "Failed to load remote: $remote"
|
||||
|
||||
if [[ "$all" == "true" || -z "$target" ]]; then
|
||||
verify_all_targets
|
||||
else
|
||||
verify_target_backup "$target"
|
||||
fi
|
||||
_restore_remote_globals
|
||||
;;
|
||||
|
||||
retention)
|
||||
local target="" remote="" all=false
|
||||
target=$(_parse_flag "--target" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}") || true
|
||||
|
||||
@@ -13,11 +13,10 @@ ui_main_menu() {
|
||||
"3" "Targets" \
|
||||
"4" "Remotes" \
|
||||
"5" "Snapshots" \
|
||||
"6" "Verify" \
|
||||
"7" "Retention" \
|
||||
"8" "Schedules" \
|
||||
"9" "Logs" \
|
||||
"10" "Settings" \
|
||||
"6" "Retention" \
|
||||
"7" "Schedules" \
|
||||
"8" "Logs" \
|
||||
"9" "Settings" \
|
||||
"Q" "Quit") || break
|
||||
|
||||
case "$choice" in
|
||||
@@ -26,11 +25,10 @@ ui_main_menu() {
|
||||
3) ui_targets_menu ;;
|
||||
4) ui_remotes_menu ;;
|
||||
5) ui_snapshots_menu ;;
|
||||
6) ui_verify_menu ;;
|
||||
7) ui_retention_menu ;;
|
||||
8) ui_schedule_menu ;;
|
||||
9) ui_logs_menu ;;
|
||||
10) ui_settings_menu ;;
|
||||
6) ui_retention_menu ;;
|
||||
7) ui_schedule_menu ;;
|
||||
8) ui_logs_menu ;;
|
||||
9) ui_settings_menu ;;
|
||||
Q) break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza4linux/lib/ui_verify.sh — Backup verification TUI
|
||||
|
||||
[[ -n "${_GNIZA4LINUX_UI_VERIFY_LOADED:-}" ]] && return 0
|
||||
_GNIZA4LINUX_UI_VERIFY_LOADED=1
|
||||
|
||||
ui_verify_menu() {
|
||||
while true; do
|
||||
local choice
|
||||
choice=$(ui_menu "Verify" \
|
||||
"SINGLE" "Verify single target" \
|
||||
"ALL" "Verify all targets" \
|
||||
"BACK" "Return to main menu") || return 0
|
||||
|
||||
case "$choice" in
|
||||
SINGLE) _ui_verify_single ;;
|
||||
ALL) _ui_verify_all ;;
|
||||
BACK) return 0 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
_ui_verify_single() {
|
||||
if ! has_targets; then
|
||||
ui_msgbox "No targets configured."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local -a items=()
|
||||
local targets
|
||||
targets=$(list_targets)
|
||||
while IFS= read -r t; do
|
||||
items+=("$t" "Target: $t")
|
||||
done <<< "$targets"
|
||||
|
||||
local target
|
||||
target=$(ui_menu "Select Target to Verify" "${items[@]}") || return 0
|
||||
|
||||
local tmpfile
|
||||
tmpfile=$(mktemp /tmp/gniza-verify-XXXXXX.log)
|
||||
|
||||
(
|
||||
echo "10"
|
||||
if gniza --cli verify --target="$target" > "$tmpfile" 2>&1; then
|
||||
echo "100"
|
||||
else
|
||||
echo "100"
|
||||
fi
|
||||
) | ui_gauge "Verifying target: $target"
|
||||
|
||||
if [[ -s "$tmpfile" ]]; then
|
||||
ui_textbox "$tmpfile"
|
||||
else
|
||||
ui_msgbox "Verification of '$target' completed successfully."
|
||||
fi
|
||||
|
||||
rm -f "$tmpfile"
|
||||
}
|
||||
|
||||
_ui_verify_all() {
|
||||
if ! has_targets; then
|
||||
ui_msgbox "No targets configured."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local targets
|
||||
targets=$(list_targets)
|
||||
local count=0 total=0
|
||||
total=$(echo "$targets" | wc -l)
|
||||
|
||||
local output=""
|
||||
while IFS= read -r t; do
|
||||
((count++))
|
||||
local pct=$(( count * 100 / total ))
|
||||
echo "$pct"
|
||||
local result
|
||||
if result=$(gniza --cli verify --target="$t" 2>&1); then
|
||||
output+="$t: OK\n"
|
||||
else
|
||||
output+="$t: FAILED\n$result\n"
|
||||
fi
|
||||
done <<< "$targets" | ui_gauge "Verifying all targets..."
|
||||
|
||||
ui_msgbox "Verification Results:\n\n$output"
|
||||
}
|
||||
196
lib/verify.sh
196
lib/verify.sh
@@ -1,196 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# gniza4linux/lib/verify.sh — Remote backup integrity checks
|
||||
|
||||
[[ -n "${_GNIZA4LINUX_VERIFY_LOADED:-}" ]] && return 0
|
||||
_GNIZA4LINUX_VERIFY_LOADED=1
|
||||
|
||||
verify_target_backup() {
|
||||
local target_name="$1"
|
||||
local snapshot_ts="${2:-}"
|
||||
local errors=0
|
||||
|
||||
# Resolve timestamp
|
||||
local ts; ts=$(resolve_snapshot_timestamp "$target_name" "$snapshot_ts") || return 1
|
||||
|
||||
log_info "Verifying backup for $target_name (snapshot: $ts)..."
|
||||
|
||||
if _is_rclone_mode; then
|
||||
local snap_subpath="targets/${target_name}/snapshots/${ts}"
|
||||
|
||||
# Check .complete marker
|
||||
if ! rclone_exists "${snap_subpath}/.complete"; then
|
||||
log_error "Snapshot missing .complete marker: $snap_subpath"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check meta.json
|
||||
local meta; meta=$(rclone_cat "${snap_subpath}/meta.json" 2>/dev/null) || true
|
||||
if [[ -z "$meta" ]]; then
|
||||
log_warn "meta.json not found in snapshot"
|
||||
((errors++)) || true
|
||||
else
|
||||
log_info " meta.json: present"
|
||||
fi
|
||||
|
||||
# Check manifest.txt
|
||||
if rclone_exists "${snap_subpath}/manifest.txt"; then
|
||||
log_info " manifest.txt: present"
|
||||
else
|
||||
log_warn " manifest.txt: missing"
|
||||
((errors++)) || true
|
||||
fi
|
||||
|
||||
# Count files
|
||||
local file_list; file_list=$(rclone_list_files "$snap_subpath" 2>/dev/null) || true
|
||||
local file_count=0
|
||||
[[ -n "$file_list" ]] && file_count=$(echo "$file_list" | wc -l)
|
||||
if (( file_count == 0 )); then
|
||||
log_warn "No files found in snapshot"
|
||||
((errors++)) || true
|
||||
else
|
||||
log_info " files: $file_count file(s)"
|
||||
fi
|
||||
|
||||
# Report size
|
||||
local size_json; size_json=$(rclone_size "$snap_subpath" 2>/dev/null) || true
|
||||
local bytes=0
|
||||
if [[ -n "$size_json" ]]; then
|
||||
bytes=$(echo "$size_json" | grep -oP '"bytes":\s*\K[0-9]+' || echo 0)
|
||||
fi
|
||||
log_info " size: $(human_size "$bytes")"
|
||||
|
||||
# Check latest.txt
|
||||
local latest; latest=$(rclone_cat "targets/${target_name}/snapshots/latest.txt" 2>/dev/null) || true
|
||||
if [[ -n "$latest" ]]; then
|
||||
log_info " latest -> $latest"
|
||||
else
|
||||
log_warn " latest.txt not set"
|
||||
fi
|
||||
else
|
||||
local snap_dir; snap_dir=$(get_snapshot_dir "$target_name")
|
||||
local snap_path="$snap_dir/$ts"
|
||||
|
||||
if [[ "${REMOTE_TYPE:-ssh}" == "local" ]]; then
|
||||
# Check snapshot directory exists
|
||||
if [[ ! -d "$snap_path" ]]; then
|
||||
log_error "Snapshot directory not found: $snap_path"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check meta.json
|
||||
if [[ -f "$snap_path/meta.json" ]]; then
|
||||
log_info " meta.json: present"
|
||||
else
|
||||
log_warn " meta.json: missing"
|
||||
((errors++)) || true
|
||||
fi
|
||||
|
||||
# Check manifest.txt
|
||||
if [[ -f "$snap_path/manifest.txt" ]]; then
|
||||
log_info " manifest.txt: present"
|
||||
else
|
||||
log_warn " manifest.txt: missing"
|
||||
((errors++)) || true
|
||||
fi
|
||||
|
||||
# Count files
|
||||
local file_count; file_count=$(find "$snap_path" -type f 2>/dev/null | wc -l)
|
||||
if (( file_count == 0 )); then
|
||||
log_warn "No files found in snapshot"
|
||||
((errors++)) || true
|
||||
else
|
||||
log_info " files: $file_count file(s)"
|
||||
fi
|
||||
|
||||
# Report size
|
||||
local total_size; total_size=$(du -sb "$snap_path" 2>/dev/null | cut -f1) || total_size=0
|
||||
log_info " size: $(human_size "${total_size:-0}")"
|
||||
|
||||
# Check latest symlink
|
||||
local base; base=$(get_remote_target_base "$target_name")
|
||||
if [[ -L "$base/latest" ]]; then
|
||||
local latest_target; latest_target=$(readlink "$base/latest" 2>/dev/null)
|
||||
log_info " latest -> $(basename "$latest_target")"
|
||||
else
|
||||
log_warn " latest symlink not set"
|
||||
fi
|
||||
else
|
||||
# SSH remote
|
||||
if ! remote_exec "test -d '$snap_path'" 2>/dev/null; then
|
||||
log_error "Snapshot directory not found: $snap_path"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check meta.json
|
||||
if remote_exec "test -f '$snap_path/meta.json'" 2>/dev/null; then
|
||||
log_info " meta.json: present"
|
||||
else
|
||||
log_warn " meta.json: missing"
|
||||
((errors++)) || true
|
||||
fi
|
||||
|
||||
# Check manifest.txt
|
||||
if remote_exec "test -f '$snap_path/manifest.txt'" 2>/dev/null; then
|
||||
log_info " manifest.txt: present"
|
||||
else
|
||||
log_warn " manifest.txt: missing"
|
||||
((errors++)) || true
|
||||
fi
|
||||
|
||||
# Count files
|
||||
local file_count; file_count=$(remote_exec "find '$snap_path' -type f | wc -l" 2>/dev/null)
|
||||
if [[ "${file_count:-0}" -eq 0 ]]; then
|
||||
log_warn "No files found in snapshot"
|
||||
((errors++)) || true
|
||||
else
|
||||
log_info " files: $file_count file(s)"
|
||||
fi
|
||||
|
||||
# Report size
|
||||
local total_size; total_size=$(remote_exec "du -sb '$snap_path' | cut -f1" 2>/dev/null)
|
||||
log_info " size: $(human_size "${total_size:-0}")"
|
||||
|
||||
# Check latest symlink
|
||||
local base; base=$(get_remote_target_base "$target_name")
|
||||
local latest_target; latest_target=$(remote_exec "readlink '$base/latest' 2>/dev/null" 2>/dev/null)
|
||||
if [[ -n "$latest_target" ]]; then
|
||||
log_info " latest -> $(basename "$latest_target")"
|
||||
else
|
||||
log_warn " latest symlink not set"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if (( errors > 0 )); then
|
||||
log_error "Verification found $errors issue(s) for $target_name"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Verification passed for $target_name"
|
||||
return 0
|
||||
}
|
||||
|
||||
verify_all_targets() {
|
||||
local targets; targets=$(list_remote_targets)
|
||||
local total=0 passed=0 failed=0
|
||||
|
||||
if [[ -z "$targets" ]]; then
|
||||
log_warn "No remote targets found to verify"
|
||||
return 0
|
||||
fi
|
||||
|
||||
while IFS= read -r target_name; do
|
||||
[[ -z "$target_name" ]] && continue
|
||||
((total++)) || true
|
||||
if verify_target_backup "$target_name"; then
|
||||
((passed++)) || true
|
||||
else
|
||||
((failed++)) || true
|
||||
fi
|
||||
done <<< "$targets"
|
||||
|
||||
echo ""
|
||||
log_info "Verification complete: $passed/$total passed, $failed failed"
|
||||
(( failed > 0 )) && return 1
|
||||
return 0
|
||||
}
|
||||
@@ -9,7 +9,6 @@ from tui.screens.target_edit import TargetEditScreen
|
||||
from tui.screens.remotes import RemotesScreen
|
||||
from tui.screens.remote_edit import RemoteEditScreen
|
||||
from tui.screens.snapshots import SnapshotsScreen
|
||||
from tui.screens.verify import VerifyScreen
|
||||
from tui.screens.retention import RetentionScreen
|
||||
from tui.screens.schedule import ScheduleScreen
|
||||
from tui.screens.logs import LogsScreen
|
||||
@@ -31,7 +30,6 @@ class GnizaApp(App):
|
||||
"remotes": RemotesScreen,
|
||||
"remote_edit": RemoteEditScreen,
|
||||
"snapshots": SnapshotsScreen,
|
||||
"verify": VerifyScreen,
|
||||
"retention": RetentionScreen,
|
||||
"schedule": ScheduleScreen,
|
||||
"logs": LogsScreen,
|
||||
|
||||
@@ -29,8 +29,8 @@ Screen {
|
||||
#menu-list {
|
||||
width: 1fr;
|
||||
max-width: 35;
|
||||
height: auto;
|
||||
margin: 1 2;
|
||||
height: 21;
|
||||
margin: 0 2;
|
||||
}
|
||||
|
||||
/* Data tables */
|
||||
@@ -46,7 +46,6 @@ DataTable {
|
||||
#schedule-screen,
|
||||
#backup-screen,
|
||||
#restore-screen,
|
||||
#verify-screen,
|
||||
#retention-screen,
|
||||
#snapshots-screen,
|
||||
#targets-screen,
|
||||
@@ -83,7 +82,6 @@ Select {
|
||||
/* Button rows */
|
||||
#backup-buttons,
|
||||
#restore-buttons,
|
||||
#verify-buttons,
|
||||
#ret-buttons,
|
||||
#targets-buttons,
|
||||
#remotes-buttons,
|
||||
@@ -98,7 +96,6 @@ Select {
|
||||
|
||||
#backup-buttons Button,
|
||||
#restore-buttons Button,
|
||||
#verify-buttons Button,
|
||||
#ret-buttons Button,
|
||||
#targets-buttons Button,
|
||||
#remotes-buttons Button,
|
||||
|
||||
@@ -6,7 +6,6 @@ from tui.screens.target_edit import TargetEditScreen
|
||||
from tui.screens.remotes import RemotesScreen
|
||||
from tui.screens.remote_edit import RemoteEditScreen
|
||||
from tui.screens.snapshots import SnapshotsScreen
|
||||
from tui.screens.verify import VerifyScreen
|
||||
from tui.screens.retention import RetentionScreen
|
||||
from tui.screens.schedule import ScheduleScreen
|
||||
from tui.screens.logs import LogsScreen
|
||||
|
||||
@@ -33,7 +33,6 @@ MENU_ITEMS = [
|
||||
("targets", "Targets"),
|
||||
("remotes", "Remotes"),
|
||||
("snapshots", "Snapshots"),
|
||||
("verify", "Verify"),
|
||||
("retention", "Retention"),
|
||||
("schedule", "Schedules"),
|
||||
("logs", "Logs"),
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
from textual.app import ComposeResult
|
||||
from textual.screen import Screen
|
||||
from textual.widgets import Header, Footer, Static, Button, Select
|
||||
from textual.containers import Vertical, Horizontal
|
||||
from textual import work
|
||||
|
||||
from tui.config import list_conf_dir
|
||||
from tui.backend import stream_cli
|
||||
from tui.widgets import ConfirmDialog, OperationLog
|
||||
|
||||
|
||||
class VerifyScreen(Screen):
|
||||
|
||||
BINDINGS = [("escape", "go_back", "Back")]
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header()
|
||||
targets = list_conf_dir("targets.d")
|
||||
with Vertical(id="verify-screen"):
|
||||
yield Static("Verify Backups", id="screen-title")
|
||||
if not targets:
|
||||
yield Static("No targets configured.")
|
||||
else:
|
||||
yield Static("Target:")
|
||||
yield Select(
|
||||
[(t, t) for t in targets],
|
||||
id="verify-target",
|
||||
prompt="Select target",
|
||||
)
|
||||
with Horizontal(id="verify-buttons"):
|
||||
yield Button("Verify Selected", variant="primary", id="btn-verify")
|
||||
yield Button("Verify All", variant="warning", id="btn-verify-all")
|
||||
yield Button("Back", id="btn-back")
|
||||
yield Footer()
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
if event.button.id == "btn-back":
|
||||
self.app.pop_screen()
|
||||
elif event.button.id == "btn-verify":
|
||||
target_sel = self.query_one("#verify-target", Select)
|
||||
if target_sel.value is Select.BLANK:
|
||||
self.notify("Select a target first", severity="error")
|
||||
return
|
||||
self._do_verify(str(target_sel.value))
|
||||
elif event.button.id == "btn-verify-all":
|
||||
self._do_verify_all()
|
||||
|
||||
@work
|
||||
async def _do_verify(self, target: str) -> None:
|
||||
log_screen = OperationLog(f"Verify: {target}")
|
||||
self.app.push_screen(log_screen)
|
||||
rc = await stream_cli(log_screen.write, "verify", f"--target={target}")
|
||||
if rc == 0:
|
||||
log_screen.write("\n[green]Verification completed successfully.[/green]")
|
||||
else:
|
||||
log_screen.write(f"\n[red]Verification failed (exit code {rc}).[/red]")
|
||||
|
||||
@work
|
||||
async def _do_verify_all(self) -> None:
|
||||
log_screen = OperationLog("Verify All Targets")
|
||||
self.app.push_screen(log_screen)
|
||||
rc = await stream_cli(log_screen.write, "verify", "--all")
|
||||
if rc == 0:
|
||||
log_screen.write("\n[green]All verifications completed.[/green]")
|
||||
else:
|
||||
log_screen.write(f"\n[red]Verification failed (exit code {rc}).[/red]")
|
||||
|
||||
def action_go_back(self) -> None:
|
||||
self.app.pop_screen()
|
||||
Reference in New Issue
Block a user