diff --git a/README.md b/README.md index 6e16f1b..a0d22ef 100644 --- a/README.md +++ b/README.md @@ -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] diff --git a/bin/gniza b/bin/gniza index 8a16779..2f3cd21 100755 --- a/bin/gniza +++ b/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 diff --git a/lib/ui_main.sh b/lib/ui_main.sh index b95b942..a6e2b74 100644 --- a/lib/ui_main.sh +++ b/lib/ui_main.sh @@ -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 diff --git a/lib/ui_verify.sh b/lib/ui_verify.sh deleted file mode 100644 index 2e95554..0000000 --- a/lib/ui_verify.sh +++ /dev/null @@ -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" -} diff --git a/lib/verify.sh b/lib/verify.sh deleted file mode 100644 index 43b3e11..0000000 --- a/lib/verify.sh +++ /dev/null @@ -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 -} diff --git a/tui/app.py b/tui/app.py index 8a05b72..47aeba3 100644 --- a/tui/app.py +++ b/tui/app.py @@ -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, diff --git a/tui/gniza.tcss b/tui/gniza.tcss index 4a6064f..c900f89 100644 --- a/tui/gniza.tcss +++ b/tui/gniza.tcss @@ -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, diff --git a/tui/screens/__init__.py b/tui/screens/__init__.py index a543d0d..0ffa09f 100644 --- a/tui/screens/__init__.py +++ b/tui/screens/__init__.py @@ -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 diff --git a/tui/screens/main_menu.py b/tui/screens/main_menu.py index 22ac8de..3d32b15 100644 --- a/tui/screens/main_menu.py +++ b/tui/screens/main_menu.py @@ -33,7 +33,6 @@ MENU_ITEMS = [ ("targets", "Targets"), ("remotes", "Remotes"), ("snapshots", "Snapshots"), - ("verify", "Verify"), ("retention", "Retention"), ("schedule", "Schedules"), ("logs", "Logs"), diff --git a/tui/screens/verify.py b/tui/screens/verify.py deleted file mode 100644 index 84d623f..0000000 --- a/tui/screens/verify.py +++ /dev/null @@ -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()