Files
rustytorch/demos/run-demo.sh
T
2026-03-04 00:08:42 +00:00

653 lines
16 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# RustyTorch++ Medical Demos - Unified Launch Script
#
# Usage:
# ./run-demo.sh # Default: dev mode (landing page)
# ./run-demo.sh --dev # Development mode (hot reload)
# ./run-demo.sh --prod # Production build and run
# ./run-demo.sh --test # Run tests only
# ./run-demo.sh --check # Prerequisites check only
# ./run-demo.sh --cleanup # Kill stuck processes
# ./run-demo.sh --validate-mre # Validate MRE backend (compile + tests)
# ./run-demo.sh --help # Show usage
#
# Exit codes:
# 0 - Success
# 1 - Prerequisites check failed
# 2 - Tests failed
# 3 - Build failed
# 4 - Launch failed
# 5 - Validation failed
set -euo pipefail
# Script directory (demos/)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
UI_DIR="$SCRIPT_DIR/ui"
# Process tracking
TAURI_PID=""
VITE_PID=""
# Ports used by the demo
VITE_PORT=9090
TAURI_PORT=1420
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[OK]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_header() {
echo ""
echo -e "${BOLD}${CYAN}=== $1 ===${NC}"
echo ""
}
# Show usage
show_usage() {
cat << EOF
${BOLD}RustyTorch++ Medical Demos${NC}
${BOLD}USAGE:${NC}
./run-demo.sh [OPTIONS]
${BOLD}OPTIONS:${NC}
--dev Development mode with hot reload (default)
--prod Build and run production binary
--test Run tests only (no UI launch)
--check Check prerequisites only
--cleanup Kill any stuck demo processes
--validate-mre Validate MRE backend (compile + run tests)
--help Show this help message
${BOLD}DEMOS AVAILABLE:${NC}
Virtual Catheter (Hemodynamics) - Navier-Stokes PINN for blood flow
MRE Elastography - Inverse Helmholtz for tissue stiffness
${BOLD}EXAMPLES:${NC}
./run-demo.sh # Start in dev mode (landing page)
./run-demo.sh --test --dev # Run tests, then start dev mode
./run-demo.sh --prod # Build and run production version
./run-demo.sh --validate-mre # Validate MRE backend compiles and tests pass
./run-demo.sh --cleanup # Clean up stuck processes
${BOLD}EXIT CODES:${NC}
0 - Success
1 - Prerequisites check failed
2 - Tests failed
3 - Build failed
4 - Launch failed
5 - Validation failed
EOF
}
# Check if a command exists
command_exists() {
command -v "$1" &> /dev/null
}
# Check if a port is in use
port_in_use() {
local port=$1
if command_exists lsof; then
lsof -i :"$port" &> /dev/null
elif command_exists ss; then
ss -tuln | grep -q ":$port "
elif command_exists netstat; then
netstat -tuln | grep -q ":$port "
else
# Fallback: try to connect
(echo > /dev/tcp/localhost/"$port") 2>/dev/null
fi
}
# Get PID using a port
get_pid_on_port() {
local port=$1
if command_exists lsof; then
lsof -ti :"$port" 2>/dev/null || true
elif command_exists fuser; then
fuser "$port"/tcp 2>/dev/null | awk '{print $1}' || true
else
echo ""
fi
}
# Check prerequisites
check_prerequisites() {
log_header "Checking Prerequisites"
local failed=0
# Check Node.js
if command_exists node; then
local node_version
node_version=$(node --version | sed 's/v//')
local major_version
major_version=$(echo "$node_version" | cut -d. -f1)
if [ "$major_version" -ge 18 ]; then
log_success "Node.js v$node_version (>= 18 required)"
else
log_error "Node.js v$node_version is too old (>= 18 required)"
failed=1
fi
else
log_error "Node.js not found"
failed=1
fi
# Check npm
if command_exists npm; then
local npm_version
npm_version=$(npm --version)
log_success "npm v$npm_version"
else
log_error "npm not found"
failed=1
fi
# Check Rust/cargo
if command_exists cargo; then
local cargo_version
cargo_version=$(cargo --version | awk '{print $2}')
log_success "cargo v$cargo_version"
else
log_error "cargo not found (Rust toolchain required)"
failed=1
fi
# Check rustc
if command_exists rustc; then
local rustc_version
rustc_version=$(rustc --version | awk '{print $2}')
log_success "rustc v$rustc_version"
else
log_error "rustc not found"
failed=1
fi
# Check UI directory exists
if [ -d "$UI_DIR" ]; then
log_success "UI directory found: $UI_DIR"
else
log_error "UI directory not found: $UI_DIR"
failed=1
fi
# Check/install node_modules
if [ -d "$UI_DIR/node_modules" ]; then
log_success "node_modules directory exists"
else
log_warn "node_modules not found, running npm install..."
if ! (cd "$UI_DIR" && npm install); then
log_error "npm install failed"
failed=1
else
log_success "npm install completed"
fi
fi
# Check Tauri CLI
if (cd "$UI_DIR" && npm run tauri -- --version &> /dev/null); then
local tauri_version
tauri_version=$(cd "$UI_DIR" && npm run tauri -- --version 2>/dev/null | tail -1)
log_success "Tauri CLI $tauri_version"
else
log_warn "Tauri CLI not responding, may need to build first"
fi
# Check for port conflicts
if port_in_use $VITE_PORT; then
log_warn "Port $VITE_PORT is already in use (vite dev server port)"
fi
if port_in_use $TAURI_PORT; then
log_warn "Port $TAURI_PORT is already in use (Tauri port)"
fi
if [ $failed -eq 1 ]; then
log_error "Prerequisites check failed"
return 1
fi
log_success "All prerequisites satisfied"
return 0
}
# Run tests
run_tests() {
log_header "Running Tests"
cd "$UI_DIR"
if npm test; then
log_success "All tests passed"
return 0
else
log_error "Tests failed"
return 2
fi
}
# Cleanup function
cleanup() {
log_header "Cleaning Up"
local killed=0
# Kill tracked processes
if [ -n "$TAURI_PID" ] && kill -0 "$TAURI_PID" 2>/dev/null; then
log_info "Stopping Tauri process (PID: $TAURI_PID)..."
kill -TERM "$TAURI_PID" 2>/dev/null || true
sleep 1
kill -KILL "$TAURI_PID" 2>/dev/null || true
killed=$((killed + 1))
fi
if [ -n "$VITE_PID" ] && kill -0 "$VITE_PID" 2>/dev/null; then
log_info "Stopping Vite process (PID: $VITE_PID)..."
kill -TERM "$VITE_PID" 2>/dev/null || true
sleep 1
kill -KILL "$VITE_PID" 2>/dev/null || true
killed=$((killed + 1))
fi
# Kill processes on ports
for port in $VITE_PORT $TAURI_PORT; do
local pid
pid=$(get_pid_on_port "$port")
if [ -n "$pid" ]; then
log_info "Killing process on port $port (PID: $pid)..."
kill -TERM "$pid" 2>/dev/null || true
sleep 0.5
kill -KILL "$pid" 2>/dev/null || true
killed=$((killed + 1))
fi
done
# Kill any remaining tauri-related processes
local tauri_pids
tauri_pids=$(pgrep -f "rtx-hemodynamics-ui" 2>/dev/null || true)
if [ -n "$tauri_pids" ]; then
for pid in $tauri_pids; do
log_info "Killing remaining Tauri process (PID: $pid)..."
kill -TERM "$pid" 2>/dev/null || true
killed=$((killed + 1))
done
fi
# Kill any vite processes in the UI directory
local vite_pids
vite_pids=$(pgrep -f "vite.*$UI_DIR" 2>/dev/null || true)
if [ -n "$vite_pids" ]; then
for pid in $vite_pids; do
log_info "Killing remaining Vite process (PID: $pid)..."
kill -TERM "$pid" 2>/dev/null || true
killed=$((killed + 1))
done
fi
if [ $killed -gt 0 ]; then
log_success "Cleaned up $killed process(es)"
else
log_info "No processes to clean up"
fi
return 0
}
# Validate that services are running
validate_running() {
log_header "Validating Services"
local dev_mode=${1:-false}
local failed=0
# Check vite server (dev mode only)
if [ "$dev_mode" = true ]; then
if port_in_use $VITE_PORT; then
log_success "Vite dev server running on port $VITE_PORT"
else
log_error "Vite dev server not responding on port $VITE_PORT"
failed=1
fi
fi
# Check for Tauri/app process
if pgrep -f "rtx-hemodynamics-ui" > /dev/null 2>&1; then
log_success "Tauri application process running"
else
log_warn "Tauri application process not detected (may still be starting)"
fi
if [ $failed -eq 1 ]; then
return 5
fi
return 0
}
# Wait for port to be available
wait_for_port() {
local port=$1
local timeout=${2:-30}
local elapsed=0
log_info "Waiting for port $port to be ready..."
while ! port_in_use "$port"; do
sleep 1
elapsed=$((elapsed + 1))
if [ $elapsed -ge $timeout ]; then
log_error "Timeout waiting for port $port after ${timeout}s"
return 1
fi
done
log_success "Port $port is ready (waited ${elapsed}s)"
return 0
}
# Launch development mode
launch_dev() {
log_header "Launching Development Mode"
cd "$UI_DIR"
# Check for port conflicts first
if port_in_use $VITE_PORT; then
log_error "Port $VITE_PORT is already in use"
log_info "Run './run-demo.sh --cleanup' to kill stuck processes"
return 4
fi
log_info "Starting Tauri development server..."
log_info "This may take a moment on first run (compiling Rust backend)..."
echo ""
# Start tauri dev (this starts both vite and the Tauri app)
npm run tauri:dev &
TAURI_PID=$!
log_info "Tauri dev started (PID: $TAURI_PID)"
# Wait for vite server to be ready
if ! wait_for_port $VITE_PORT 60; then
log_error "Failed to start Vite dev server"
cleanup
return 4
fi
# Validate services
sleep 2
if ! validate_running true; then
log_warn "Validation incomplete, but services may still be starting"
fi
echo ""
log_success "Demo is running!"
echo ""
echo -e "${BOLD}Frontend:${NC} http://localhost:$VITE_PORT"
echo -e "${BOLD}Process:${NC} PID $TAURI_PID"
echo ""
echo -e "${YELLOW}Press Ctrl+C to stop the demo${NC}"
echo ""
# Wait for the Tauri process
wait $TAURI_PID 2>/dev/null || true
return 0
}
# Launch production mode
launch_prod() {
log_header "Building Production Version"
cd "$UI_DIR"
log_info "Building production bundle..."
log_info "This may take several minutes on first build..."
echo ""
if ! npm run tauri:build; then
log_error "Production build failed"
return 3
fi
log_success "Build completed"
log_header "Launching Production Binary"
# Find the built binary
local binary_path=""
local possible_paths=(
"$UI_DIR/src-tauri/target/release/rtx-hemodynamics-ui"
"$UI_DIR/src-tauri/target/release/bundle/appimage/"*.AppImage
"$UI_DIR/src-tauri/target/release/bundle/deb/"*.deb
)
for path in "${possible_paths[@]}"; do
if [ -f "$path" ] || compgen -G "$path" > /dev/null; then
binary_path=$(compgen -G "$path" | head -1)
break
fi
done
if [ -z "$binary_path" ]; then
log_error "Could not find built binary"
log_info "Check: $UI_DIR/src-tauri/target/release/"
return 4
fi
log_info "Found binary: $binary_path"
log_info "Launching..."
echo ""
# Make executable if needed
chmod +x "$binary_path" 2>/dev/null || true
# Launch the binary
"$binary_path" &
TAURI_PID=$!
log_success "Production app launched (PID: $TAURI_PID)"
echo ""
echo -e "${YELLOW}Press Ctrl+C to stop the demo${NC}"
echo ""
# Wait for the process
wait $TAURI_PID 2>/dev/null || true
return 0
}
# Signal handler
handle_signal() {
echo ""
log_info "Received shutdown signal..."
cleanup
exit 0
}
# Validate MRE backend
validate_mre() {
log_header "Validating MRE Backend"
local failed=0
# Check mre-shared compiles
log_info "Checking mre-shared crate..."
if cargo check -p mre-shared --quiet 2>/dev/null; then
log_success "mre-shared compiles"
else
log_error "mre-shared compilation failed"
failed=1
fi
# Check rtx-mre compiles
log_info "Checking rtx-mre crate..."
if cargo check -p rtx-mre --quiet 2>/dev/null; then
log_success "rtx-mre compiles"
else
log_error "rtx-mre compilation failed"
failed=1
fi
# Check rtx-hemodynamics-server compiles (includes MreService)
log_info "Checking server crate with MreService..."
if cargo check -p rtx-hemodynamics-server --quiet 2>/dev/null; then
log_success "rtx-hemodynamics-server compiles"
else
log_error "rtx-hemodynamics-server compilation failed"
failed=1
fi
# Run unit tests
log_info "Running MRE unit tests..."
if cargo test -p mre-shared -p rtx-mre --quiet 2>/dev/null; then
log_success "All MRE tests passed"
else
log_error "MRE tests failed"
failed=1
fi
if [ $failed -eq 1 ]; then
log_error "MRE validation failed"
return 5
fi
log_success "MRE backend validation complete"
return 0
}
# Main function
main() {
local mode="dev"
local run_test=false
local check_only=false
local cleanup_only=false
local validate_mre_only=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--dev)
mode="dev"
shift
;;
--prod)
mode="prod"
shift
;;
--test)
run_test=true
shift
;;
--check)
check_only=true
shift
;;
--cleanup)
cleanup_only=true
shift
;;
--validate-mre)
validate_mre_only=true
shift
;;
--help|-h)
show_usage
exit 0
;;
*)
log_error "Unknown option: $1"
show_usage
exit 1
;;
esac
done
# Setup signal handlers
trap handle_signal SIGINT SIGTERM
echo ""
echo -e "${BOLD}${CYAN}RustyTorch++ Medical Demos${NC}"
echo ""
# Cleanup only mode
if [ "$cleanup_only" = true ]; then
cleanup
exit 0
fi
# Validate MRE only mode
if [ "$validate_mre_only" = true ]; then
validate_mre
exit $?
fi
# Prerequisites check
if ! check_prerequisites; then
exit 1
fi
# Check only mode
if [ "$check_only" = true ]; then
exit 0
fi
# Run tests if requested
if [ "$run_test" = true ]; then
if ! run_tests; then
exit 2
fi
# If only --test was specified, exit here
if [ "$mode" = "dev" ] && [ $# -eq 0 ]; then
# Check if --dev was explicitly passed
if [[ ! " ${BASH_ARGV[*]} " =~ " --dev " ]]; then
exit 0
fi
fi
fi
# Launch based on mode
case $mode in
dev)
launch_dev
;;
prod)
launch_prod
;;
esac
# Cleanup on exit
cleanup
}
# Run main
main "$@"