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