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:
shuki
2026-03-06 01:57:55 +02:00
parent 8149976170
commit 3d5a9cdfdd
10 changed files with 10 additions and 394 deletions

View File

@@ -100,7 +100,6 @@ Commands:
targets list|add|delete|show [--name=NAME] [--folders=PATHS] targets list|add|delete|show [--name=NAME] [--folders=PATHS]
remotes list|add|delete|show|test [--name=NAME] remotes list|add|delete|show|test [--name=NAME]
snapshots list [--target=NAME] [--remote=NAME] snapshots list [--target=NAME] [--remote=NAME]
verify [--target=NAME] [--remote=NAME] [--all]
retention [--target=NAME] [--remote=NAME] [--all] retention [--target=NAME] [--remote=NAME] [--all]
schedule install|show|remove schedule install|show|remove
logs [--last] [--tail=N] logs [--last] [--tail=N]

View File

@@ -20,7 +20,6 @@ source "$GNIZA_DIR/lib/remotes.sh"
source "$GNIZA_DIR/lib/backup.sh" source "$GNIZA_DIR/lib/backup.sh"
source "$GNIZA_DIR/lib/restore.sh" source "$GNIZA_DIR/lib/restore.sh"
source "$GNIZA_DIR/lib/retention.sh" source "$GNIZA_DIR/lib/retention.sh"
source "$GNIZA_DIR/lib/verify.sh"
source "$GNIZA_DIR/lib/schedule.sh" source "$GNIZA_DIR/lib/schedule.sh"
source "$GNIZA_DIR/lib/notify.sh" source "$GNIZA_DIR/lib/notify.sh"
source "$GNIZA_DIR/lib/ssh.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_backup.sh"
source "$GNIZA_DIR/lib/ui_restore.sh" source "$GNIZA_DIR/lib/ui_restore.sh"
source "$GNIZA_DIR/lib/ui_snapshots.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_retention.sh"
source "$GNIZA_DIR/lib/ui_logs.sh" source "$GNIZA_DIR/lib/ui_logs.sh"
source "$GNIZA_DIR/lib/ui_schedule.sh" source "$GNIZA_DIR/lib/ui_schedule.sh"
@@ -88,7 +86,6 @@ Commands:
targets list|add|delete|show [--name=NAME] [--folders=PATHS] targets list|add|delete|show [--name=NAME] [--folders=PATHS]
remotes list|add|delete|show|test [--name=NAME] remotes list|add|delete|show|test [--name=NAME]
snapshots list [--target=NAME] [--remote=NAME] snapshots list [--target=NAME] [--remote=NAME]
verify [--target=NAME] [--remote=NAME] [--all]
retention [--target=NAME] [--remote=NAME] [--all] retention [--target=NAME] [--remote=NAME] [--all]
schedule install|show|remove schedule install|show|remove
logs [--last] [--tail=N] logs [--last] [--tail=N]
@@ -338,27 +335,6 @@ run_cli() {
esac 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) retention)
local target="" remote="" all=false local target="" remote="" all=false
target=$(_parse_flag "--target" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}") || true target=$(_parse_flag "--target" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}") || true

View File

@@ -13,11 +13,10 @@ ui_main_menu() {
"3" "Targets" \ "3" "Targets" \
"4" "Remotes" \ "4" "Remotes" \
"5" "Snapshots" \ "5" "Snapshots" \
"6" "Verify" \ "6" "Retention" \
"7" "Retention" \ "7" "Schedules" \
"8" "Schedules" \ "8" "Logs" \
"9" "Logs" \ "9" "Settings" \
"10" "Settings" \
"Q" "Quit") || break "Q" "Quit") || break
case "$choice" in case "$choice" in
@@ -26,11 +25,10 @@ ui_main_menu() {
3) ui_targets_menu ;; 3) ui_targets_menu ;;
4) ui_remotes_menu ;; 4) ui_remotes_menu ;;
5) ui_snapshots_menu ;; 5) ui_snapshots_menu ;;
6) ui_verify_menu ;; 6) ui_retention_menu ;;
7) ui_retention_menu ;; 7) ui_schedule_menu ;;
8) ui_schedule_menu ;; 8) ui_logs_menu ;;
9) ui_logs_menu ;; 9) ui_settings_menu ;;
10) ui_settings_menu ;;
Q) break ;; Q) break ;;
esac esac
done done

View File

@@ -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"
}

View File

@@ -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
}

View File

@@ -9,7 +9,6 @@ from tui.screens.target_edit import TargetEditScreen
from tui.screens.remotes import RemotesScreen from tui.screens.remotes import RemotesScreen
from tui.screens.remote_edit import RemoteEditScreen from tui.screens.remote_edit import RemoteEditScreen
from tui.screens.snapshots import SnapshotsScreen from tui.screens.snapshots import SnapshotsScreen
from tui.screens.verify import VerifyScreen
from tui.screens.retention import RetentionScreen from tui.screens.retention import RetentionScreen
from tui.screens.schedule import ScheduleScreen from tui.screens.schedule import ScheduleScreen
from tui.screens.logs import LogsScreen from tui.screens.logs import LogsScreen
@@ -31,7 +30,6 @@ class GnizaApp(App):
"remotes": RemotesScreen, "remotes": RemotesScreen,
"remote_edit": RemoteEditScreen, "remote_edit": RemoteEditScreen,
"snapshots": SnapshotsScreen, "snapshots": SnapshotsScreen,
"verify": VerifyScreen,
"retention": RetentionScreen, "retention": RetentionScreen,
"schedule": ScheduleScreen, "schedule": ScheduleScreen,
"logs": LogsScreen, "logs": LogsScreen,

View File

@@ -29,8 +29,8 @@ Screen {
#menu-list { #menu-list {
width: 1fr; width: 1fr;
max-width: 35; max-width: 35;
height: auto; height: 21;
margin: 1 2; margin: 0 2;
} }
/* Data tables */ /* Data tables */
@@ -46,7 +46,6 @@ DataTable {
#schedule-screen, #schedule-screen,
#backup-screen, #backup-screen,
#restore-screen, #restore-screen,
#verify-screen,
#retention-screen, #retention-screen,
#snapshots-screen, #snapshots-screen,
#targets-screen, #targets-screen,
@@ -83,7 +82,6 @@ Select {
/* Button rows */ /* Button rows */
#backup-buttons, #backup-buttons,
#restore-buttons, #restore-buttons,
#verify-buttons,
#ret-buttons, #ret-buttons,
#targets-buttons, #targets-buttons,
#remotes-buttons, #remotes-buttons,
@@ -98,7 +96,6 @@ Select {
#backup-buttons Button, #backup-buttons Button,
#restore-buttons Button, #restore-buttons Button,
#verify-buttons Button,
#ret-buttons Button, #ret-buttons Button,
#targets-buttons Button, #targets-buttons Button,
#remotes-buttons Button, #remotes-buttons Button,

View File

@@ -6,7 +6,6 @@ from tui.screens.target_edit import TargetEditScreen
from tui.screens.remotes import RemotesScreen from tui.screens.remotes import RemotesScreen
from tui.screens.remote_edit import RemoteEditScreen from tui.screens.remote_edit import RemoteEditScreen
from tui.screens.snapshots import SnapshotsScreen from tui.screens.snapshots import SnapshotsScreen
from tui.screens.verify import VerifyScreen
from tui.screens.retention import RetentionScreen from tui.screens.retention import RetentionScreen
from tui.screens.schedule import ScheduleScreen from tui.screens.schedule import ScheduleScreen
from tui.screens.logs import LogsScreen from tui.screens.logs import LogsScreen

View File

@@ -33,7 +33,6 @@ MENU_ITEMS = [
("targets", "Targets"), ("targets", "Targets"),
("remotes", "Remotes"), ("remotes", "Remotes"),
("snapshots", "Snapshots"), ("snapshots", "Snapshots"),
("verify", "Verify"),
("retention", "Retention"), ("retention", "Retention"),
("schedule", "Schedules"), ("schedule", "Schedules"),
("logs", "Logs"), ("logs", "Logs"),

View File

@@ -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()