653 lines
16 KiB
Bash
Executable File
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 "$@"
|