#!/usr/bin/env bash # verify.sh — GuruRMM local pre-merge verification. # Builds/checks a component the SAME way the server-side pipeline does, so you don't # merge code that fails the post-merge build on .30. Does NOT trigger the production # build (that's the webhook on push to main) — this is the local gate only. # # Usage: # bash verify.sh server [--check] # SQLX_OFFLINE cargo build (or check) + .sqlx freshness warning # bash verify.sh agent [--check] # cargo build (or check) + per-OS compile-gate warning # bash verify.sh dashboard # npm install + npm run build # bash verify.sh all [--check] # server + agent + dashboard # bash verify.sh migrations # report highest migration number + any duplicate numbers # # See: projects/msp-tools/guru-rmm/docs/BUILD.md # projects/msp-tools/guru-rmm/deploy/build-pipeline/README.md set -uo pipefail # Resolve repo root (4 levels up: .claude/skills/gururmm-build/scripts/ -> repo root). CLAUDETOOLS_ROOT="${CLAUDETOOLS_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)}" REPO_DIR="$CLAUDETOOLS_ROOT/projects/msp-tools/guru-rmm" _logerr() { bash "$CLAUDETOOLS_ROOT/.claude/scripts/log-skill-error.sh" "gururmm-build" "$@" >/dev/null 2>&1 || true; } # ASCII markers (no emojis — fleet rule). OK="[OK]"; ERR="[ERROR]"; WARN="[WARNING]"; INFO="[INFO]" die() { echo "$ERR $*" >&2; _logerr "$*"; exit 1; } [ -d "$REPO_DIR" ] || die "guru-rmm checkout not found at $REPO_DIR (submodule not initialized?)" # Host OS family. case "$(uname -s 2>/dev/null)" in Linux*) OS=linux ;; Darwin*) OS=mac ;; MINGW*|MSYS*|CYGWIN*) OS=windows ;; *) OS=unknown ;; esac CMD="${1:-}"; shift || true MODE="build" for a in "$@"; do case "$a" in --check) MODE="check" ;; *) die "unknown flag: $a" ;; esac done [ -n "$CMD" ] || die "usage: verify.sh {server|agent|dashboard|all|migrations} [--check]" have() { command -v "$1" >/dev/null 2>&1; } # Names of files changed vs origin/main (committed) + uncommitted, restricted to a path prefix. changed_under() { local prefix="$1" { git -C "$REPO_DIR" diff --name-only origin/main...HEAD -- "$prefix" 2>/dev/null git -C "$REPO_DIR" status --porcelain -- "$prefix" 2>/dev/null | awk '{print $2}' } | sort -u } verify_server() { echo "$INFO === server: $MODE (host=$OS) ===" have cargo || die "cargo not on PATH — install the Rust toolchain to verify server" # sqlx offline-cache freshness: the server build runs SQLX_OFFLINE=true against # server/.sqlx. If server/ Rust changed but .sqlx did not, the build can fail on a # cache mismatch — warn loudly (not fatal; queries may be unchanged). local srv_rs srv_sqlx srv_rs=$(changed_under "server" | grep -E '\.rs$' | wc -l | tr -d ' ') srv_sqlx=$(changed_under "server/.sqlx" | wc -l | tr -d ' ') if [ "$srv_rs" -gt 0 ] && [ "$srv_sqlx" -eq 0 ]; then echo "$WARN server/*.rs changed but server/.sqlx was not regenerated." echo "$WARN If you added/changed SQL queries or migrations, run (in server/, DB reachable):" echo "$WARN cargo sqlx prepare && git add server/.sqlx && git commit" echo "$WARN Otherwise the post-merge server build may fail on the offline cache." fi local sub="build --release"; [ "$MODE" = check ] && sub="check" ( cd "$REPO_DIR/server" && SQLX_OFFLINE=true cargo $sub ) local rc=$? [ $rc -eq 0 ] || { _logerr "server cargo $sub failed (rc=$rc)" --context "os=$OS"; echo "$ERR server $MODE FAILED (rc=$rc)"; return 1; } echo "$OK server $MODE passed" } verify_agent() { echo "$INFO === agent: $MODE (host=$OS) ===" have cargo || die "cargo not on PATH — install the Rust toolchain to verify agent" # Compile-gate warning: platform-gated code is only verified on its own OS. if [ "$OS" = windows ]; then echo "$WARN On Windows, cargo cannot verify Linux-gated agent code (#[cfg(target_os=\"linux\")];" echo "$WARN openssl-sys won't cross-build). The Linux agent build on .30 is the real gate —" echo "$WARN build on Linux too, or watch /var/log/gururmm-build-linux.log after merge." elif [ "$OS" = linux ]; then echo "$INFO Linux host: this gates Linux + cross-platform agent code. Windows-only paths" echo "$INFO (MSI/tray/legacy/x86) are gated only by the Beast/Pluto Windows build." fi local sub="build --release"; [ "$MODE" = check ] && sub="check" ( cd "$REPO_DIR/agent" && cargo $sub ) local rc=$? [ $rc -eq 0 ] || { _logerr "agent cargo $sub failed (rc=$rc)" --context "os=$OS"; echo "$ERR agent $MODE FAILED (rc=$rc)"; return 1; } echo "$OK agent $MODE passed (host-OS gate only — see compile-gate warning)" } verify_dashboard() { echo "$INFO === dashboard: build (host=$OS) ===" have npm || die "npm not on PATH — install Node to verify dashboard" # 'npm install' (not ci): tolerates the package.json version-bump skew the pipeline makes. ( cd "$REPO_DIR/dashboard" && npm install --no-audit --no-fund && npm run build ) local rc=$? [ $rc -eq 0 ] || { _logerr "dashboard npm build failed (rc=$rc)" --context "os=$OS"; echo "$ERR dashboard build FAILED (rc=$rc)"; return 1; } [ -f "$REPO_DIR/dashboard/dist/index.html" ] || { _logerr "dashboard build produced no dist/index.html"; echo "$ERR dashboard build produced no dist/index.html"; return 1; } echo "$OK dashboard build passed (dist/index.html present)" } check_migrations() { echo "$INFO === migrations: numbering check ===" local mdir="" for cand in "server/migrations" "migrations"; do [ -d "$REPO_DIR/$cand" ] && { mdir="$REPO_DIR/$cand"; break; } done [ -n "$mdir" ] || die "no migrations directory found (looked for server/migrations, migrations)" echo "$INFO migrations dir: ${mdir#$REPO_DIR/}" # Highest NNN prefix and any duplicate numbers. local nums nums=$(ls -1 "$mdir" 2>/dev/null | grep -E '^[0-9]+_' | sed -E 's/^([0-9]+)_.*/\1/' | sort) [ -n "$nums" ] || { echo "$WARN no NNN_ migrations found in $mdir"; return 0; } local highest next dupes highest=$(printf '%s\n' "$nums" | tail -1) next=$(printf '%03d' $((10#$highest + 1))) echo "$OK highest migration on this checkout: $highest -> next free number: $next" dupes=$(printf '%s\n' "$nums" | uniq -d) if [ -n "$dupes" ]; then echo "$ERR duplicate migration number(s) detected: $dupes" printf '%s\n' "$dupes" | while read -r d; do ls -1 "$mdir" | grep -E "^${d}_" | sed "s/^/$ERR /" done _logerr "duplicate migration number(s): $dupes" return 1 fi echo "$INFO Before naming a new migration, also check the highest number on origin/main" echo "$INFO (another branch may have claimed $next): git fetch && ls $(printf '%s' "${mdir#$REPO_DIR/}")" } rc=0 case "$CMD" in server) verify_server || rc=1 ;; agent) verify_agent || rc=1 ;; dashboard) verify_dashboard || rc=1 ;; migrations) check_migrations || rc=1 ;; all) verify_server || rc=1 verify_agent || rc=1 verify_dashboard || rc=1 ;; *) die "unknown component: $CMD (use server|agent|dashboard|all|migrations)" ;; esac if [ $rc -eq 0 ]; then echo "$OK verify.sh $CMD: PASS" else echo "$ERR verify.sh $CMD: FAIL" fi exit $rc