diff options
Diffstat (limited to 'docs/how-to/migration-scripts')
| -rwxr-xr-x | docs/how-to/migration-scripts/run-migration-analysis.sh | 716 |
1 files changed, 716 insertions, 0 deletions
diff --git a/docs/how-to/migration-scripts/run-migration-analysis.sh b/docs/how-to/migration-scripts/run-migration-analysis.sh new file mode 100755 index 0000000..2ba25ab --- /dev/null +++ b/docs/how-to/migration-scripts/run-migration-analysis.sh | |||
| @@ -0,0 +1,716 @@ | |||
| 1 | #!/usr/bin/env bash | ||
| 2 | # | ||
| 3 | # run-migration-analysis.sh - Orchestrate the complete ngit-relay to ngit-grasp migration analysis | ||
| 4 | # | ||
| 5 | # This script runs all 5 phases of the migration analysis pipeline in sequence, | ||
| 6 | # with proper error handling, progress reporting, and timing information. | ||
| 7 | # | ||
| 8 | # QUICK START: | ||
| 9 | # # Basic usage (local analysis only - Phases 1, 3, 5) | ||
| 10 | # ./run-migration-analysis.sh --prod-relay wss://relay.ngit.dev --archive-relay wss://archive.relay.ngit.dev | ||
| 11 | # | ||
| 12 | # # Full analysis including git sync check (requires VPS access) | ||
| 13 | # ./run-migration-analysis.sh \ | ||
| 14 | # --prod-relay wss://relay.ngit.dev \ | ||
| 15 | # --archive-relay wss://archive.relay.ngit.dev \ | ||
| 16 | # --prod-git /var/lib/ngit-relay/git \ | ||
| 17 | # --archive-git /var/lib/ngit-relay-archive/git | ||
| 18 | # | ||
| 19 | # USAGE: | ||
| 20 | # ./run-migration-analysis.sh [options] | ||
| 21 | # | ||
| 22 | # REQUIRED OPTIONS: | ||
| 23 | # --prod-relay <url> Production relay WebSocket URL (e.g., wss://relay.ngit.dev) | ||
| 24 | # --archive-relay <url> Archive relay WebSocket URL (e.g., wss://archive.relay.ngit.dev) | ||
| 25 | # | ||
| 26 | # OPTIONAL OPTIONS: | ||
| 27 | # --prod-git <path> Git base directory for prod (enables Phase 2) | ||
| 28 | # --archive-git <path> Git base directory for archive (enables Phase 2) | ||
| 29 | # --service <name> Systemd service name for log extraction (enables Phase 4) | ||
| 30 | # --output <dir> Output directory (default: work/migration-analysis-YYYYMMDD-HHMM) | ||
| 31 | # | ||
| 32 | # PHASE CONTROL: | ||
| 33 | # --skip-phase-1 Skip event fetching (use existing data) | ||
| 34 | # --skip-phase-2 Skip git sync check (use existing data) | ||
| 35 | # --skip-phase-3 Skip categorization (use existing data) | ||
| 36 | # --skip-phase-4 Skip log extraction (use existing data) | ||
| 37 | # --skip-phase-5 Skip final classification | ||
| 38 | # --only-phase-N Run only phase N (1-5) | ||
| 39 | # --from-phase-N Start from phase N (skip earlier phases) | ||
| 40 | # | ||
| 41 | # OTHER OPTIONS: | ||
| 42 | # --dry-run Show what would be executed without running | ||
| 43 | # --continue-on-error Continue to next phase even if current phase fails | ||
| 44 | # --help Show this help message | ||
| 45 | # | ||
| 46 | # PHASES: | ||
| 47 | # Phase 1: Fetch events from both relays (~30s each, local) | ||
| 48 | # Phase 2: Check git sync status (~20 min each, requires VPS) | ||
| 49 | # Phase 3: Categorize and compare results (fast, local) | ||
| 50 | # Phase 4: Extract logs from systemd (requires VPS) | ||
| 51 | # Phase 5: Final classification (fast, local) | ||
| 52 | # | ||
| 53 | # EXAMPLES: | ||
| 54 | # # Dry run to see what would happen | ||
| 55 | # ./run-migration-analysis.sh --prod-relay wss://relay.ngit.dev --archive-relay wss://archive.relay.ngit.dev --dry-run | ||
| 56 | # | ||
| 57 | # # Run only Phase 1 (fetch events) | ||
| 58 | # ./run-migration-analysis.sh --prod-relay wss://relay.ngit.dev --archive-relay wss://archive.relay.ngit.dev --only-phase-1 | ||
| 59 | # | ||
| 60 | # # Resume from Phase 3 using existing Phase 1-2 data | ||
| 61 | # ./run-migration-analysis.sh --prod-relay wss://relay.ngit.dev --archive-relay wss://archive.relay.ngit.dev --from-phase-3 --output work/migration-analysis-20260122-1430 | ||
| 62 | # | ||
| 63 | # # Full analysis on VPS with all features | ||
| 64 | # ./run-migration-analysis.sh \ | ||
| 65 | # --prod-relay wss://relay.ngit.dev \ | ||
| 66 | # --archive-relay wss://archive.relay.ngit.dev \ | ||
| 67 | # --prod-git /var/lib/ngit-relay/git \ | ||
| 68 | # --archive-git /var/lib/ngit-relay-archive/git \ | ||
| 69 | # --service ngit-grasp.service | ||
| 70 | # | ||
| 71 | # SEE ALSO: | ||
| 72 | # docs/how-to/migrate-ngit-relay-to-ngit-grasp.md - Full migration guide | ||
| 73 | # | ||
| 74 | |||
| 75 | set -euo pipefail | ||
| 76 | |||
| 77 | # Get script directory for finding other scripts | ||
| 78 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| 79 | |||
| 80 | # Colors for output (disabled if not a terminal) | ||
| 81 | if [[ -t 1 ]]; then | ||
| 82 | RED='\033[0;31m' | ||
| 83 | GREEN='\033[0;32m' | ||
| 84 | YELLOW='\033[0;33m' | ||
| 85 | BLUE='\033[0;34m' | ||
| 86 | CYAN='\033[0;36m' | ||
| 87 | BOLD='\033[1m' | ||
| 88 | NC='\033[0m' | ||
| 89 | else | ||
| 90 | RED='' | ||
| 91 | GREEN='' | ||
| 92 | YELLOW='' | ||
| 93 | BLUE='' | ||
| 94 | CYAN='' | ||
| 95 | BOLD='' | ||
| 96 | NC='' | ||
| 97 | fi | ||
| 98 | |||
| 99 | # Logging functions | ||
| 100 | log_header() { | ||
| 101 | echo "" | ||
| 102 | echo -e "${BOLD}${CYAN}════════════════════════════════════════════════════════════════${NC}" | ||
| 103 | echo -e "${BOLD}${CYAN} $*${NC}" | ||
| 104 | echo -e "${BOLD}${CYAN}════════════════════════════════════════════════════════════════${NC}" | ||
| 105 | echo "" | ||
| 106 | } | ||
| 107 | |||
| 108 | log_phase() { | ||
| 109 | echo "" | ||
| 110 | echo -e "${BOLD}${BLUE}┌──────────────────────────────────────────────────────────────┐${NC}" | ||
| 111 | echo -e "${BOLD}${BLUE}│ $*${NC}" | ||
| 112 | echo -e "${BOLD}${BLUE}└──────────────────────────────────────────────────────────────┘${NC}" | ||
| 113 | } | ||
| 114 | |||
| 115 | log_info() { | ||
| 116 | echo -e "${BLUE}[INFO]${NC} $*" >&2 | ||
| 117 | } | ||
| 118 | |||
| 119 | log_success() { | ||
| 120 | echo -e "${GREEN}[OK]${NC} $*" >&2 | ||
| 121 | } | ||
| 122 | |||
| 123 | log_warn() { | ||
| 124 | echo -e "${YELLOW}[WARN]${NC} $*" >&2 | ||
| 125 | } | ||
| 126 | |||
| 127 | log_error() { | ||
| 128 | echo -e "${RED}[ERROR]${NC} $*" >&2 | ||
| 129 | } | ||
| 130 | |||
| 131 | log_step() { | ||
| 132 | echo -e "${CYAN} →${NC} $*" >&2 | ||
| 133 | } | ||
| 134 | |||
| 135 | # Default values | ||
| 136 | PROD_RELAY="" | ||
| 137 | ARCHIVE_RELAY="" | ||
| 138 | PROD_GIT="" | ||
| 139 | ARCHIVE_GIT="" | ||
| 140 | SERVICE_NAME="" | ||
| 141 | OUTPUT_DIR="" | ||
| 142 | DRY_RUN=false | ||
| 143 | CONTINUE_ON_ERROR=false | ||
| 144 | |||
| 145 | # Phase control | ||
| 146 | SKIP_PHASE_1=false | ||
| 147 | SKIP_PHASE_2=false | ||
| 148 | SKIP_PHASE_3=false | ||
| 149 | SKIP_PHASE_4=false | ||
| 150 | SKIP_PHASE_5=false | ||
| 151 | ONLY_PHASE="" | ||
| 152 | FROM_PHASE="" | ||
| 153 | |||
| 154 | # Timing | ||
| 155 | declare -A PHASE_TIMES | ||
| 156 | |||
| 157 | usage() { | ||
| 158 | head -80 "$0" | tail -n +3 | sed 's/^# //' | sed 's/^#//' | ||
| 159 | exit 0 | ||
| 160 | } | ||
| 161 | |||
| 162 | # Parse command line arguments | ||
| 163 | parse_args() { | ||
| 164 | while [[ $# -gt 0 ]]; do | ||
| 165 | case "$1" in | ||
| 166 | --prod-relay) | ||
| 167 | PROD_RELAY="$2" | ||
| 168 | shift 2 | ||
| 169 | ;; | ||
| 170 | --archive-relay) | ||
| 171 | ARCHIVE_RELAY="$2" | ||
| 172 | shift 2 | ||
| 173 | ;; | ||
| 174 | --prod-git) | ||
| 175 | PROD_GIT="$2" | ||
| 176 | shift 2 | ||
| 177 | ;; | ||
| 178 | --archive-git) | ||
| 179 | ARCHIVE_GIT="$2" | ||
| 180 | shift 2 | ||
| 181 | ;; | ||
| 182 | --service) | ||
| 183 | SERVICE_NAME="$2" | ||
| 184 | shift 2 | ||
| 185 | ;; | ||
| 186 | --output) | ||
| 187 | OUTPUT_DIR="$2" | ||
| 188 | shift 2 | ||
| 189 | ;; | ||
| 190 | --skip-phase-1) | ||
| 191 | SKIP_PHASE_1=true | ||
| 192 | shift | ||
| 193 | ;; | ||
| 194 | --skip-phase-2) | ||
| 195 | SKIP_PHASE_2=true | ||
| 196 | shift | ||
| 197 | ;; | ||
| 198 | --skip-phase-3) | ||
| 199 | SKIP_PHASE_3=true | ||
| 200 | shift | ||
| 201 | ;; | ||
| 202 | --skip-phase-4) | ||
| 203 | SKIP_PHASE_4=true | ||
| 204 | shift | ||
| 205 | ;; | ||
| 206 | --skip-phase-5) | ||
| 207 | SKIP_PHASE_5=true | ||
| 208 | shift | ||
| 209 | ;; | ||
| 210 | --only-phase-1|--only-phase-2|--only-phase-3|--only-phase-4|--only-phase-5) | ||
| 211 | ONLY_PHASE="${1#--only-phase-}" | ||
| 212 | shift | ||
| 213 | ;; | ||
| 214 | --from-phase-1|--from-phase-2|--from-phase-3|--from-phase-4|--from-phase-5) | ||
| 215 | FROM_PHASE="${1#--from-phase-}" | ||
| 216 | shift | ||
| 217 | ;; | ||
| 218 | --dry-run) | ||
| 219 | DRY_RUN=true | ||
| 220 | shift | ||
| 221 | ;; | ||
| 222 | --continue-on-error) | ||
| 223 | CONTINUE_ON_ERROR=true | ||
| 224 | shift | ||
| 225 | ;; | ||
| 226 | --help|-h) | ||
| 227 | usage | ||
| 228 | ;; | ||
| 229 | *) | ||
| 230 | log_error "Unknown option: $1" | ||
| 231 | echo "Use --help for usage information." | ||
| 232 | exit 1 | ||
| 233 | ;; | ||
| 234 | esac | ||
| 235 | done | ||
| 236 | } | ||
| 237 | |||
| 238 | # Validate required arguments | ||
| 239 | validate_args() { | ||
| 240 | local errors=0 | ||
| 241 | |||
| 242 | if [[ -z "$PROD_RELAY" ]]; then | ||
| 243 | log_error "Missing required option: --prod-relay" | ||
| 244 | errors=1 | ||
| 245 | fi | ||
| 246 | |||
| 247 | if [[ -z "$ARCHIVE_RELAY" ]]; then | ||
| 248 | log_error "Missing required option: --archive-relay" | ||
| 249 | errors=1 | ||
| 250 | fi | ||
| 251 | |||
| 252 | # Validate relay URLs | ||
| 253 | if [[ -n "$PROD_RELAY" && ! "$PROD_RELAY" =~ ^wss?:// ]]; then | ||
| 254 | log_error "Invalid prod relay URL: $PROD_RELAY (must start with ws:// or wss://)" | ||
| 255 | errors=1 | ||
| 256 | fi | ||
| 257 | |||
| 258 | if [[ -n "$ARCHIVE_RELAY" && ! "$ARCHIVE_RELAY" =~ ^wss?:// ]]; then | ||
| 259 | log_error "Invalid archive relay URL: $ARCHIVE_RELAY (must start with ws:// or wss://)" | ||
| 260 | errors=1 | ||
| 261 | fi | ||
| 262 | |||
| 263 | # Validate git paths if provided | ||
| 264 | if [[ -n "$PROD_GIT" && ! -d "$PROD_GIT" ]]; then | ||
| 265 | log_warn "Prod git directory not found: $PROD_GIT" | ||
| 266 | log_warn "Phase 2 will fail unless running on VPS with access to this path." | ||
| 267 | fi | ||
| 268 | |||
| 269 | if [[ -n "$ARCHIVE_GIT" && ! -d "$ARCHIVE_GIT" ]]; then | ||
| 270 | log_warn "Archive git directory not found: $ARCHIVE_GIT" | ||
| 271 | log_warn "Phase 2 will fail unless running on VPS with access to this path." | ||
| 272 | fi | ||
| 273 | |||
| 274 | if [[ $errors -eq 1 ]]; then | ||
| 275 | echo "" | ||
| 276 | echo "Use --help for usage information." | ||
| 277 | exit 1 | ||
| 278 | fi | ||
| 279 | } | ||
| 280 | |||
| 281 | # Check prerequisites | ||
| 282 | check_prerequisites() { | ||
| 283 | local missing=0 | ||
| 284 | |||
| 285 | log_info "Checking prerequisites..." | ||
| 286 | |||
| 287 | # Required tools | ||
| 288 | for tool in nak jq awk sort; do | ||
| 289 | if command -v "$tool" &> /dev/null; then | ||
| 290 | log_step "$tool: found" | ||
| 291 | else | ||
| 292 | log_error "$tool: NOT FOUND" | ||
| 293 | missing=1 | ||
| 294 | fi | ||
| 295 | done | ||
| 296 | |||
| 297 | # Optional tools | ||
| 298 | if command -v journalctl &> /dev/null; then | ||
| 299 | log_step "journalctl: found (Phase 4 available)" | ||
| 300 | else | ||
| 301 | log_step "journalctl: not found (Phase 4 will be skipped)" | ||
| 302 | SKIP_PHASE_4=true | ||
| 303 | fi | ||
| 304 | |||
| 305 | if [[ $missing -eq 1 ]]; then | ||
| 306 | log_error "Missing required tools. Install them and try again." | ||
| 307 | exit 1 | ||
| 308 | fi | ||
| 309 | |||
| 310 | # Check scripts exist | ||
| 311 | for script in 01-fetch-events.sh 10-check-git-sync.sh 20-categorize.sh 21-compare-relays.sh 30-extract-parse-failures.sh 31-extract-purgatory-expiry.sh 40-classify-actions.sh; do | ||
| 312 | if [[ ! -x "$SCRIPT_DIR/$script" ]]; then | ||
| 313 | log_error "Script not found or not executable: $SCRIPT_DIR/$script" | ||
| 314 | missing=1 | ||
| 315 | fi | ||
| 316 | done | ||
| 317 | |||
| 318 | if [[ $missing -eq 1 ]]; then | ||
| 319 | exit 1 | ||
| 320 | fi | ||
| 321 | |||
| 322 | log_success "All prerequisites satisfied" | ||
| 323 | } | ||
| 324 | |||
| 325 | # Determine which phases to run | ||
| 326 | determine_phases() { | ||
| 327 | # Handle --only-phase-N | ||
| 328 | if [[ -n "$ONLY_PHASE" ]]; then | ||
| 329 | for i in 1 2 3 4 5; do | ||
| 330 | if [[ "$i" != "$ONLY_PHASE" ]]; then | ||
| 331 | eval "SKIP_PHASE_$i=true" | ||
| 332 | fi | ||
| 333 | done | ||
| 334 | fi | ||
| 335 | |||
| 336 | # Handle --from-phase-N | ||
| 337 | if [[ -n "$FROM_PHASE" ]]; then | ||
| 338 | for i in 1 2 3 4 5; do | ||
| 339 | if [[ "$i" -lt "$FROM_PHASE" ]]; then | ||
| 340 | eval "SKIP_PHASE_$i=true" | ||
| 341 | fi | ||
| 342 | done | ||
| 343 | fi | ||
| 344 | |||
| 345 | # Auto-skip Phase 2 if git paths not provided | ||
| 346 | if [[ -z "$PROD_GIT" && -z "$ARCHIVE_GIT" ]]; then | ||
| 347 | if [[ "$SKIP_PHASE_2" != "true" ]]; then | ||
| 348 | log_warn "No git paths provided. Phase 2 (git sync check) will be skipped." | ||
| 349 | log_warn "Use --prod-git and --archive-git to enable Phase 2." | ||
| 350 | SKIP_PHASE_2=true | ||
| 351 | fi | ||
| 352 | fi | ||
| 353 | |||
| 354 | # Auto-skip Phase 4 if service not provided | ||
| 355 | if [[ -z "$SERVICE_NAME" ]]; then | ||
| 356 | if [[ "$SKIP_PHASE_4" != "true" ]]; then | ||
| 357 | log_warn "No service name provided. Phase 4 (log extraction) will be skipped." | ||
| 358 | log_warn "Use --service to enable Phase 4." | ||
| 359 | SKIP_PHASE_4=true | ||
| 360 | fi | ||
| 361 | fi | ||
| 362 | } | ||
| 363 | |||
| 364 | # Setup output directory | ||
| 365 | setup_output_dir() { | ||
| 366 | if [[ -z "$OUTPUT_DIR" ]]; then | ||
| 367 | OUTPUT_DIR="work/migration-analysis-$(date +%Y%m%d-%H%M)" | ||
| 368 | fi | ||
| 369 | |||
| 370 | log_info "Output directory: $OUTPUT_DIR" | ||
| 371 | |||
| 372 | if [[ "$DRY_RUN" == "true" ]]; then | ||
| 373 | log_info "[DRY RUN] Would create directory structure" | ||
| 374 | return | ||
| 375 | fi | ||
| 376 | |||
| 377 | mkdir -p "$OUTPUT_DIR"/{prod/raw,archive/raw,comparison,logs,results} | ||
| 378 | |||
| 379 | # Save configuration | ||
| 380 | cat > "$OUTPUT_DIR/config.txt" << EOF | ||
| 381 | # Migration Analysis Configuration | ||
| 382 | # Generated: $(date -Iseconds) | ||
| 383 | |||
| 384 | PROD_RELAY=$PROD_RELAY | ||
| 385 | ARCHIVE_RELAY=$ARCHIVE_RELAY | ||
| 386 | PROD_GIT=$PROD_GIT | ||
| 387 | ARCHIVE_GIT=$ARCHIVE_GIT | ||
| 388 | SERVICE_NAME=$SERVICE_NAME | ||
| 389 | OUTPUT_DIR=$OUTPUT_DIR | ||
| 390 | EOF | ||
| 391 | |||
| 392 | log_success "Created output directory structure" | ||
| 393 | } | ||
| 394 | |||
| 395 | # Run a phase with timing and error handling | ||
| 396 | run_phase() { | ||
| 397 | local phase_num="$1" | ||
| 398 | local phase_name="$2" | ||
| 399 | shift 2 | ||
| 400 | local cmd=("$@") | ||
| 401 | |||
| 402 | local skip_var="SKIP_PHASE_$phase_num" | ||
| 403 | if [[ "${!skip_var}" == "true" ]]; then | ||
| 404 | log_phase "Phase $phase_num: $phase_name [SKIPPED]" | ||
| 405 | return 0 | ||
| 406 | fi | ||
| 407 | |||
| 408 | log_phase "Phase $phase_num: $phase_name" | ||
| 409 | |||
| 410 | if [[ "$DRY_RUN" == "true" ]]; then | ||
| 411 | log_info "[DRY RUN] Would execute:" | ||
| 412 | for c in "${cmd[@]}"; do | ||
| 413 | echo " $c" | ||
| 414 | done | ||
| 415 | return 0 | ||
| 416 | fi | ||
| 417 | |||
| 418 | local start_time | ||
| 419 | start_time=$(date +%s) | ||
| 420 | |||
| 421 | local exit_code=0 | ||
| 422 | |||
| 423 | # Execute the command(s) | ||
| 424 | for c in "${cmd[@]}"; do | ||
| 425 | log_step "Running: $c" | ||
| 426 | if ! eval "$c"; then | ||
| 427 | exit_code=1 | ||
| 428 | if [[ "$CONTINUE_ON_ERROR" == "true" ]]; then | ||
| 429 | log_warn "Command failed, continuing due to --continue-on-error" | ||
| 430 | else | ||
| 431 | log_error "Command failed" | ||
| 432 | break | ||
| 433 | fi | ||
| 434 | fi | ||
| 435 | done | ||
| 436 | |||
| 437 | local end_time | ||
| 438 | end_time=$(date +%s) | ||
| 439 | local duration=$((end_time - start_time)) | ||
| 440 | PHASE_TIMES[$phase_num]=$duration | ||
| 441 | |||
| 442 | if [[ $exit_code -eq 0 ]]; then | ||
| 443 | log_success "Phase $phase_num completed in ${duration}s" | ||
| 444 | else | ||
| 445 | log_error "Phase $phase_num failed after ${duration}s" | ||
| 446 | if [[ "$CONTINUE_ON_ERROR" != "true" ]]; then | ||
| 447 | return 1 | ||
| 448 | fi | ||
| 449 | fi | ||
| 450 | |||
| 451 | return $exit_code | ||
| 452 | } | ||
| 453 | |||
| 454 | # Phase 1: Fetch events | ||
| 455 | run_phase_1() { | ||
| 456 | local cmds=() | ||
| 457 | |||
| 458 | # Fetch from prod relay | ||
| 459 | cmds+=("'$SCRIPT_DIR/01-fetch-events.sh' '$PROD_RELAY' '$OUTPUT_DIR/prod'") | ||
| 460 | |||
| 461 | # Fetch from archive relay | ||
| 462 | cmds+=("'$SCRIPT_DIR/01-fetch-events.sh' '$ARCHIVE_RELAY' '$OUTPUT_DIR/archive'") | ||
| 463 | |||
| 464 | run_phase 1 "Fetch Events (~30s each)" "${cmds[@]}" | ||
| 465 | } | ||
| 466 | |||
| 467 | # Phase 2: Git sync check | ||
| 468 | run_phase_2() { | ||
| 469 | local cmds=() | ||
| 470 | |||
| 471 | if [[ -n "$PROD_GIT" ]]; then | ||
| 472 | cmds+=("'$SCRIPT_DIR/10-check-git-sync.sh' '$OUTPUT_DIR/prod/raw/state-events.json' '$PROD_GIT' '$OUTPUT_DIR/prod' --categorize") | ||
| 473 | else | ||
| 474 | log_warn "Skipping prod git sync check (no --prod-git provided)" | ||
| 475 | fi | ||
| 476 | |||
| 477 | if [[ -n "$ARCHIVE_GIT" ]]; then | ||
| 478 | cmds+=("'$SCRIPT_DIR/10-check-git-sync.sh' '$OUTPUT_DIR/archive/raw/state-events.json' '$ARCHIVE_GIT' '$OUTPUT_DIR/archive' --categorize") | ||
| 479 | else | ||
| 480 | log_warn "Skipping archive git sync check (no --archive-git provided)" | ||
| 481 | fi | ||
| 482 | |||
| 483 | if [[ ${#cmds[@]} -eq 0 ]]; then | ||
| 484 | log_warn "No git paths provided, skipping Phase 2" | ||
| 485 | return 0 | ||
| 486 | fi | ||
| 487 | |||
| 488 | run_phase 2 "Git Sync Check (~20 min each)" "${cmds[@]}" | ||
| 489 | } | ||
| 490 | |||
| 491 | # Phase 3: Categorize and compare | ||
| 492 | run_phase_3() { | ||
| 493 | local cmds=() | ||
| 494 | |||
| 495 | # Check if we have git-sync-status.tsv files (from Phase 2) | ||
| 496 | # If not, we can't run categorization | ||
| 497 | local has_prod_sync=false | ||
| 498 | local has_archive_sync=false | ||
| 499 | |||
| 500 | if [[ -f "$OUTPUT_DIR/prod/git-sync-status.tsv" ]]; then | ||
| 501 | has_prod_sync=true | ||
| 502 | fi | ||
| 503 | |||
| 504 | if [[ -f "$OUTPUT_DIR/archive/git-sync-status.tsv" ]]; then | ||
| 505 | has_archive_sync=true | ||
| 506 | fi | ||
| 507 | |||
| 508 | # Run categorization if we have sync data but no category files | ||
| 509 | if [[ "$has_prod_sync" == "true" && ! -f "$OUTPUT_DIR/prod/category1-complete-match.txt" ]]; then | ||
| 510 | cmds+=("'$SCRIPT_DIR/20-categorize.sh' '$OUTPUT_DIR/prod/git-sync-status.tsv' '$OUTPUT_DIR/prod'") | ||
| 511 | fi | ||
| 512 | |||
| 513 | if [[ "$has_archive_sync" == "true" && ! -f "$OUTPUT_DIR/archive/category1-complete-match.txt" ]]; then | ||
| 514 | cmds+=("'$SCRIPT_DIR/20-categorize.sh' '$OUTPUT_DIR/archive/git-sync-status.tsv' '$OUTPUT_DIR/archive'") | ||
| 515 | fi | ||
| 516 | |||
| 517 | # Run comparison if we have category files | ||
| 518 | if [[ -f "$OUTPUT_DIR/prod/category1-complete-match.txt" && -f "$OUTPUT_DIR/archive/category1-complete-match.txt" ]]; then | ||
| 519 | cmds+=("'$SCRIPT_DIR/21-compare-relays.sh' '$OUTPUT_DIR/prod' '$OUTPUT_DIR/archive' '$OUTPUT_DIR/comparison'") | ||
| 520 | else | ||
| 521 | log_warn "Missing category files for comparison." | ||
| 522 | log_warn "Phase 2 must complete successfully before Phase 3 can compare relays." | ||
| 523 | |||
| 524 | # Create placeholder comparison files if they don't exist | ||
| 525 | if [[ "$DRY_RUN" != "true" ]]; then | ||
| 526 | mkdir -p "$OUTPUT_DIR/comparison" | ||
| 527 | for f in complete-in-both.txt complete-prod-missing-archive.txt complete-prod-incomplete-archive.txt incomplete-in-both.txt in-archive-not-prod.txt; do | ||
| 528 | if [[ ! -f "$OUTPUT_DIR/comparison/$f" ]]; then | ||
| 529 | echo "# Placeholder - Phase 2 data not available" > "$OUTPUT_DIR/comparison/$f" | ||
| 530 | fi | ||
| 531 | done | ||
| 532 | echo "# Comparison not available - Phase 2 data missing" > "$OUTPUT_DIR/comparison/summary.txt" | ||
| 533 | fi | ||
| 534 | fi | ||
| 535 | |||
| 536 | if [[ ${#cmds[@]} -eq 0 ]]; then | ||
| 537 | log_warn "No categorization or comparison needed (already done or missing input)" | ||
| 538 | return 0 | ||
| 539 | fi | ||
| 540 | |||
| 541 | run_phase 3 "Categorize & Compare (fast)" "${cmds[@]}" | ||
| 542 | } | ||
| 543 | |||
| 544 | # Phase 4: Extract logs | ||
| 545 | run_phase_4() { | ||
| 546 | if [[ -z "$SERVICE_NAME" ]]; then | ||
| 547 | log_warn "No service name provided, skipping Phase 4" | ||
| 548 | return 0 | ||
| 549 | fi | ||
| 550 | |||
| 551 | local cmds=() | ||
| 552 | |||
| 553 | cmds+=("'$SCRIPT_DIR/30-extract-parse-failures.sh' '$SERVICE_NAME' '$OUTPUT_DIR/logs'") | ||
| 554 | cmds+=("'$SCRIPT_DIR/31-extract-purgatory-expiry.sh' '$SERVICE_NAME' '$OUTPUT_DIR/logs'") | ||
| 555 | |||
| 556 | run_phase 4 "Extract Logs (VPS required)" "${cmds[@]}" | ||
| 557 | } | ||
| 558 | |||
| 559 | # Phase 5: Final classification | ||
| 560 | run_phase_5() { | ||
| 561 | # Check if we have the minimum required files | ||
| 562 | local can_run=true | ||
| 563 | |||
| 564 | if [[ ! -d "$OUTPUT_DIR/prod" ]]; then | ||
| 565 | log_warn "Missing prod directory" | ||
| 566 | can_run=false | ||
| 567 | fi | ||
| 568 | |||
| 569 | if [[ ! -d "$OUTPUT_DIR/archive" ]]; then | ||
| 570 | log_warn "Missing archive directory" | ||
| 571 | can_run=false | ||
| 572 | fi | ||
| 573 | |||
| 574 | if [[ ! -d "$OUTPUT_DIR/comparison" ]]; then | ||
| 575 | log_warn "Missing comparison directory" | ||
| 576 | can_run=false | ||
| 577 | fi | ||
| 578 | |||
| 579 | # Create logs directory with empty files if missing | ||
| 580 | if [[ "$DRY_RUN" != "true" ]]; then | ||
| 581 | mkdir -p "$OUTPUT_DIR/logs" | ||
| 582 | for f in parse-failures.txt purgatory-expired.txt; do | ||
| 583 | if [[ ! -f "$OUTPUT_DIR/logs/$f" ]]; then | ||
| 584 | echo "# No data - Phase 4 not run" > "$OUTPUT_DIR/logs/$f" | ||
| 585 | fi | ||
| 586 | done | ||
| 587 | fi | ||
| 588 | |||
| 589 | if [[ "$can_run" == "false" ]]; then | ||
| 590 | log_error "Cannot run Phase 5 - missing required input directories" | ||
| 591 | return 1 | ||
| 592 | fi | ||
| 593 | |||
| 594 | run_phase 5 "Final Classification (fast)" "'$SCRIPT_DIR/40-classify-actions.sh' '$OUTPUT_DIR'" | ||
| 595 | } | ||
| 596 | |||
| 597 | # Display summary | ||
| 598 | display_summary() { | ||
| 599 | log_header "Migration Analysis Complete" | ||
| 600 | |||
| 601 | echo "Output Directory: $OUTPUT_DIR" | ||
| 602 | echo "" | ||
| 603 | |||
| 604 | # Phase timing summary | ||
| 605 | echo "Phase Timing:" | ||
| 606 | local total_time=0 | ||
| 607 | for phase in 1 2 3 4 5; do | ||
| 608 | local skip_var="SKIP_PHASE_$phase" | ||
| 609 | if [[ "${!skip_var}" == "true" ]]; then | ||
| 610 | echo " Phase $phase: SKIPPED" | ||
| 611 | elif [[ -n "${PHASE_TIMES[$phase]:-}" ]]; then | ||
| 612 | local t="${PHASE_TIMES[$phase]}" | ||
| 613 | echo " Phase $phase: ${t}s" | ||
| 614 | total_time=$((total_time + t)) | ||
| 615 | else | ||
| 616 | echo " Phase $phase: N/A" | ||
| 617 | fi | ||
| 618 | done | ||
| 619 | echo " ─────────────" | ||
| 620 | echo " Total: ${total_time}s" | ||
| 621 | echo "" | ||
| 622 | |||
| 623 | # Results summary | ||
| 624 | if [[ -f "$OUTPUT_DIR/results/summary.txt" ]]; then | ||
| 625 | echo "Results Summary:" | ||
| 626 | echo "" | ||
| 627 | # Extract key metrics from summary | ||
| 628 | if grep -q "No Action Required" "$OUTPUT_DIR/results/summary.txt"; then | ||
| 629 | grep -A1 "No Action Required" "$OUTPUT_DIR/results/summary.txt" | head -2 | ||
| 630 | fi | ||
| 631 | if grep -q "Action Required" "$OUTPUT_DIR/results/summary.txt"; then | ||
| 632 | grep -A1 "Action Required" "$OUTPUT_DIR/results/summary.txt" | head -2 | ||
| 633 | fi | ||
| 634 | if grep -q "Manual Investigation" "$OUTPUT_DIR/results/summary.txt"; then | ||
| 635 | grep -A1 "Manual Investigation" "$OUTPUT_DIR/results/summary.txt" | head -2 | ||
| 636 | fi | ||
| 637 | echo "" | ||
| 638 | fi | ||
| 639 | |||
| 640 | # Output files | ||
| 641 | echo "Output Files:" | ||
| 642 | echo " $OUTPUT_DIR/results/no-action-required.txt" | ||
| 643 | echo " $OUTPUT_DIR/results/action-required.txt" | ||
| 644 | echo " $OUTPUT_DIR/results/manual-investigation.txt" | ||
| 645 | echo " $OUTPUT_DIR/results/summary.txt" | ||
| 646 | echo "" | ||
| 647 | |||
| 648 | # Next steps | ||
| 649 | echo "Next Steps:" | ||
| 650 | echo " 1. Review results/summary.txt for overview" | ||
| 651 | echo " 2. Address items in results/action-required.txt" | ||
| 652 | echo " 3. Investigate items in results/manual-investigation.txt" | ||
| 653 | echo " 4. Plan migration window when action items are resolved" | ||
| 654 | echo "" | ||
| 655 | } | ||
| 656 | |||
| 657 | # Main | ||
| 658 | main() { | ||
| 659 | parse_args "$@" | ||
| 660 | |||
| 661 | log_header "ngit-relay to ngit-grasp Migration Analysis" | ||
| 662 | |||
| 663 | validate_args | ||
| 664 | check_prerequisites | ||
| 665 | determine_phases | ||
| 666 | setup_output_dir | ||
| 667 | |||
| 668 | # Show configuration | ||
| 669 | log_info "Configuration:" | ||
| 670 | log_step "Prod relay: $PROD_RELAY" | ||
| 671 | log_step "Archive relay: $ARCHIVE_RELAY" | ||
| 672 | [[ -n "$PROD_GIT" ]] && log_step "Prod git: $PROD_GIT" | ||
| 673 | [[ -n "$ARCHIVE_GIT" ]] && log_step "Archive git: $ARCHIVE_GIT" | ||
| 674 | [[ -n "$SERVICE_NAME" ]] && log_step "Service: $SERVICE_NAME" | ||
| 675 | log_step "Output: $OUTPUT_DIR" | ||
| 676 | echo "" | ||
| 677 | |||
| 678 | # Show phase plan | ||
| 679 | log_info "Phase Plan:" | ||
| 680 | for phase in 1 2 3 4 5; do | ||
| 681 | local skip_var="SKIP_PHASE_$phase" | ||
| 682 | if [[ "${!skip_var}" == "true" ]]; then | ||
| 683 | log_step "Phase $phase: SKIP" | ||
| 684 | else | ||
| 685 | log_step "Phase $phase: RUN" | ||
| 686 | fi | ||
| 687 | done | ||
| 688 | echo "" | ||
| 689 | |||
| 690 | if [[ "$DRY_RUN" == "true" ]]; then | ||
| 691 | log_warn "DRY RUN MODE - No changes will be made" | ||
| 692 | echo "" | ||
| 693 | fi | ||
| 694 | |||
| 695 | # Run phases | ||
| 696 | local overall_exit=0 | ||
| 697 | |||
| 698 | run_phase_1 || overall_exit=1 | ||
| 699 | run_phase_2 || overall_exit=1 | ||
| 700 | run_phase_3 || overall_exit=1 | ||
| 701 | run_phase_4 || overall_exit=1 | ||
| 702 | run_phase_5 || overall_exit=1 | ||
| 703 | |||
| 704 | # Display summary | ||
| 705 | if [[ "$DRY_RUN" != "true" ]]; then | ||
| 706 | display_summary | ||
| 707 | fi | ||
| 708 | |||
| 709 | if [[ $overall_exit -ne 0 ]]; then | ||
| 710 | log_warn "Some phases failed. Review output for details." | ||
| 711 | fi | ||
| 712 | |||
| 713 | exit $overall_exit | ||
| 714 | } | ||
| 715 | |||
| 716 | main "$@" | ||