#!/bin/bash # Run Chattyness servers in development mode set -eu cd "$(dirname "$0")" # ============================================================================= # Constants # ============================================================================= readonly APP_PORT=3000 readonly OWNER_PORT=3001 readonly LOCKFILE=".run-dev.lock" # Process IDs (set during runtime) OWNER_PID="" APP_PID="" # ============================================================================= # Helper Functions # ============================================================================= usage() { cat </dev/null || true [ -n "$APP_PID" ] && kill "$APP_PID" 2>/dev/null || true [ -n "$OWNER_PID" ] && wait "$OWNER_PID" 2>/dev/null || true [ -n "$APP_PID" ] && wait "$APP_PID" 2>/dev/null || true rm -f "$LOCKFILE" echo "Done." } # Print server URLs based on what's running print_server_info() { local mode="$1" local build_type="Debug" [ "$RELEASE" = "true" ] && build_type="Release" echo "" echo "========================================" echo " Chattyness Development ($mode - $build_type)" echo "========================================" echo "" if run_owner; then echo " Owner Admin: http://127.0.0.1:$OWNER_PORT" fi if run_app; then echo " User App: http://127.0.0.1:$APP_PORT" echo " Realm Admin: http://127.0.0.1:$APP_PORT/admin" fi echo "" if [ "$TARGET" = "both" ]; then echo "Admin CSS lazy-loaded via symlink from owner build." fi echo "Press Ctrl+C to stop" echo "" } # Wait for running child processes wait_for_processes() { if [ "$TARGET" = "both" ]; then wait "$OWNER_PID" "$APP_PID" elif [ "$TARGET" = "owner" ]; then wait "$OWNER_PID" else wait "$APP_PID" fi } # Create symlink for admin CSS (user app loads admin CSS from owner build) setup_admin_css_symlink() { mkdir -p apps/chattyness-app/public local target="../../../target/site-owner/static/chattyness-owner.css" local link="apps/chattyness-app/public/admin.css" if [ ! -L "$link" ]; then ln -sf "$target" "$link" echo "Created symlink: $link -> $target" fi } # Load environment variables from .env file load_env() { if [ -f .env ]; then set -a # shellcheck source=/dev/null source .env set +a fi } # Validate required environment variables check_env() { if [ -z "${DB_CHATTYNESS_OWNER:-}" ]; then echo "Error: DB_CHATTYNESS_OWNER not set" echo "Create a .env file with DB_CHATTYNESS_OWNER=your_password" exit 1 fi if [ -z "${DB_CHATTYNESS_APP:-}" ]; then echo "Error: DB_CHATTYNESS_APP not set" echo "Create a .env file with DB_CHATTYNESS_APP=your_password" exit 1 fi } # ============================================================================= # Lock File Management # ============================================================================= # Check if another instance is running (returns PID or empty) get_running_pid() { if [ -f "$LOCKFILE" ]; then local pid pid=$(cat "$LOCKFILE" 2>/dev/null) if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then echo "$pid" fi fi } # Handle --status flag handle_status() { local pid pid=$(get_running_pid) if [ -n "$pid" ]; then echo "Running (PID: $pid)" exit 0 else echo "Not running" [ -f "$LOCKFILE" ] && rm -f "$LOCKFILE" # Clean stale lock exit 1 fi } # Kill any running instance (shared by -k and -f) kill_running_instance() { local pid pid=$(get_running_pid) if [ -n "$pid" ]; then echo "Killing existing run-dev.sh (PID: $pid)..." kill "$pid" else echo "No running instance found." [ -f "$LOCKFILE" ] && rm -f "$LOCKFILE" # Clean stale lock fi echo "Killing chattyness processes..." killall -9 chattyness-app 2>/dev/null || true killall -9 chattyness-owner 2>/dev/null || true echo "Done." } # Handle --kill flag handle_kill() { kill_running_instance exit 0 } # Handle --force flag (kill existing, then continue) handle_force() { kill_running_instance sleep 1 # Allow lock release } # Acquire exclusive lock acquire_lock() { exec 200>"$LOCKFILE" if ! flock -n 200; then local pid pid=$(cat "$LOCKFILE" 2>/dev/null) echo "" echo "ERROR: Another run-dev.sh instance is already running (PID: $pid)" echo "" echo "Options:" echo " $0 -k Kill the existing instance" echo " $0 -f [args...] Kill existing and start new instance" echo " $0 -s Check status" echo "" exit 1 fi echo $$ >"$LOCKFILE" } # ============================================================================= # Main Commands # ============================================================================= do_watch() { echo "Starting watch mode..." echo "" local release_flag="" [ "$RELEASE" = "true" ] && release_flag="--release" # Build owner first to create CSS (needed if user app needs admin CSS) if run_owner; then echo "Building owner app first (for admin CSS)..." cargo leptos build -p chattyness-owner $release_flag setup_admin_css_symlink fi # Start watch processes if run_owner; then cargo leptos watch -p chattyness-owner $release_flag & OWNER_PID=$! fi if run_app; then cargo leptos watch -p chattyness-app --split $release_flag & APP_PID=$! fi print_server_info "Watch Mode" wait_for_processes } do_build() { local release_flag="" local target_dir="debug" if [ "$RELEASE" = "true" ]; then release_flag="--release" target_dir="release" fi # Build phase if run_owner; then echo "Running: cargo leptos build -p chattyness-owner $release_flag" cargo leptos build -p chattyness-owner $release_flag setup_admin_css_symlink fi if run_app; then echo "" echo "Running: cargo leptos build -p chattyness-app --split $release_flag" cargo leptos build -p chattyness-app --split $release_flag fi # Verify binaries exist if run_owner && [ ! -x "./target/$target_dir/chattyness-owner" ]; then echo "Error: ./target/$target_dir/chattyness-owner not found or not executable" exit 1 fi if run_app && [ ! -x "./target/$target_dir/chattyness-app" ]; then echo "Error: ./target/$target_dir/chattyness-app not found or not executable" exit 1 fi # Start servers if run_owner; then echo "Starting Owner Server on :$OWNER_PORT..." ./target/$target_dir/chattyness-owner & OWNER_PID=$! fi if run_app; then echo "Starting App Server on :$APP_PORT..." ./target/$target_dir/chattyness-app & APP_PID=$! fi print_server_info "Servers" wait_for_processes } # ============================================================================= # Main Entry Point # ============================================================================= # Parse arguments LEPTOS_CMD="build" TARGET="both" FORCE="false" KILL_EXISTING="false" CHECK_STATUS="false" RELEASE="false" for arg in "$@"; do case $arg in build | watch) LEPTOS_CMD="$arg" ;; --user-app) TARGET="app" ;; --owner-app) TARGET="owner" ;; --both) TARGET="both" ;; -f | --force) FORCE="true" ;; -k | --kill) KILL_EXISTING="true" ;; -s | --status) CHECK_STATUS="true" ;; -r | --release) RELEASE="true" ;; --help | -h) usage ;; *) echo "Unknown option: $arg" usage ;; esac done # Handle special flags first (before acquiring lock) [ "$CHECK_STATUS" = "true" ] && handle_status [ "$KILL_EXISTING" = "true" ] && handle_kill [ "$FORCE" = "true" ] && handle_force # Acquire lock and store PID acquire_lock # Set up cleanup trap trap cleanup EXIT INT TERM # Load and validate environment load_env check_env # Run the requested command case "$LEPTOS_CMD" in watch) do_watch ;; build) do_build ;; esac