OpenClawDashboard/local-dev.sh
Mannu 01ab630085 feat(dev): deploy.sh + local-dev.sh + bridge remote mode
deploy.sh:
  Validated explicit-deploy workflow. Pre-flight checks (local build,
  uncommitted changes, server reachability) run on Mac before touching
  server. Code pushed to server via 'git push ssh://...' over the
  existing SSH connection — no Mac SSH server required. Server does
  git reset --hard to the pushed commit, reinstalls deps if
  package.json changed, rebuilds dashboard, restarts services, verifies
  health. Full troubleshooting guide in file header.

local-dev.sh:
  Runs bridge (:3457) and dashboard (:3101) locally on Mac while
  reaching Tiger via SSH. Separate ports + separate SQLite DB keep it
  isolated from prod (still live on :3100/:3456). Hot-reload in both
  layers. Clean Ctrl-C shutdown.

bridge remote mode:
  Added TIGER_REMOTE=true support in bridge/src/tiger.ts and chat.ts.
  When set, 'docker exec tiger-openclaw' calls are prefixed with
  'ssh $TIGER_REMOTE_SSH'. Backward-compatible: VPS leaves TIGER_REMOTE
  unset and runs docker locally as before.

Workflow moving forward:
  • Edit locally on Mac
  • ./local-dev.sh to test against real Tiger
  • git commit small + often
  • ./deploy.sh to push to production
2026-04-19 01:24:23 +05:30

180 lines
7.7 KiB
Bash
Executable file

#!/bin/bash
# ═══════════════════════════════════════════════════════════════════
# local-dev.sh — Run bridge + dashboard locally on Mac
#
# WHAT IT DOES:
# Starts two Node.js processes:
# - Bridge on :3457 (runs in TIGER_REMOTE=true mode, talks to
# the real tiger-openclaw container on VPS via SSH)
# - Dashboard on :3101 (Next.js dev mode with hot-reload)
#
# ARCHITECTURE:
#
# [Mac] Dashboard :3101 ──HTTP──▶ [Mac] Bridge :3457
# │
# ├── SQLite (local ./data/tiger-local.db)
# │
# └──SSH──▶ [VPS] docker exec tiger-openclaw
# │
# └── OpenClaw + Tiger
#
# SAFETY:
# - Uses separate ports (3101, 3457) so production on :3100, :3456
# keeps running normally.
# - Uses a SEPARATE SQLite file (data/tiger-local.db) so local chat
# history doesn't mix with production.
# - Read-only for Tiger state: your local bridge can invoke Tiger and
# READ its workspace, but there's no "commit my local chat history
# to server" step. Production Tiger is untouched.
#
# USAGE:
# ./local-dev.sh # start both services
# ./local-dev.sh --bridge # only bridge
# ./local-dev.sh --dashboard # only dashboard
# Ctrl-C # stop everything cleanly
#
# REQUIREMENTS:
# - Node 20+ (already have this)
# - bridge/node_modules installed (cd bridge && npm install, once)
# - dashboard/node_modules installed (cd dashboard && npm install, once)
# - SSH access to root@100.75.128.45 (already set up via Tailscale)
# ═══════════════════════════════════════════════════════════════════
set -uo pipefail
LOCAL_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$LOCAL_PATH"
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
RED='\033[0;31m'
NC='\033[0m'
# Parse flags
MODE="both"
for arg in "$@"; do
case $arg in
--bridge) MODE="bridge" ;;
--dashboard) MODE="dashboard" ;;
--help|-h)
echo "Usage: $0 [--bridge | --dashboard]"
exit 0
;;
esac
done
# ─── Pre-flight ──────────────────────────────────────────────────────
# Check ssh to server (we need it if bridge is starting)
if [ "$MODE" != "dashboard" ]; then
if ! ssh -o ConnectTimeout=5 -o BatchMode=yes root@100.75.128.45 'echo ok' >/dev/null 2>&1; then
echo -e "${RED}${NC} Can't SSH to server. Bridge needs it for remote docker exec."
echo " Try: ssh root@100.75.128.45 manually; check tailscale status"
exit 1
fi
echo -e "${GREEN}${NC} SSH to server works"
fi
# Check node_modules are installed
if [ "$MODE" != "dashboard" ] && [ ! -d bridge/node_modules ]; then
echo -e "${YELLOW}${NC} bridge/node_modules missing. Installing…"
(cd bridge && npm install --no-audit --no-fund) || { echo "npm install failed"; exit 1; }
fi
if [ "$MODE" != "bridge" ] && [ ! -d dashboard/node_modules ]; then
echo -e "${YELLOW}${NC} dashboard/node_modules missing. Installing…"
(cd dashboard && npm install --no-audit --no-fund) || { echo "npm install failed"; exit 1; }
fi
# Check port conflicts (we use 3101 and 3457 to stay clear of prod's 3100/3456)
for port in 3101 3457; do
if lsof -ti:$port >/dev/null 2>&1; then
echo -e "${RED}${NC} Port $port is already in use. Kill the process first:"
echo " lsof -ti:$port | xargs kill"
exit 1
fi
done
# ─── PID tracking so Ctrl-C cleans up children ──────────────────────
PIDS=()
cleanup() {
echo
echo -e "${YELLOW}Shutting down…${NC}"
for pid in "${PIDS[@]}"; do
kill "$pid" 2>/dev/null || true
done
wait 2>/dev/null
echo -e "${GREEN}${NC} All services stopped"
exit 0
}
trap cleanup INT TERM
# ─── Start bridge ────────────────────────────────────────────────────
start_bridge() {
echo -e "${BLUE}[bridge]${NC} Starting on :3457 in REMOTE mode…"
cd "$LOCAL_PATH/bridge"
# Environment for local bridge:
# TIGER_REMOTE=true → prefix docker commands with SSH
# TIGER_REMOTE_SSH=... → which host to SSH to
# TIGER_BRIDGE_PORT=3457 → don't collide with prod :3456
# TIGER_DB_DIR=./data → separate SQLite file from prod
# TIGER_BRIDGE_TOKEN=dev-local-token → auth for dev (server uses different token)
export TIGER_REMOTE=true
export TIGER_REMOTE_SSH=root@100.75.128.45
export TIGER_BRIDGE_PORT=3457
export TIGER_BRIDGE_HOST=127.0.0.1
export TIGER_BRIDGE_TOKEN=dev-local-token
export TIGER_DB_DIR="$LOCAL_PATH/data"
# Use tsx directly (same as systemd does on server) so changes to src/
# reload automatically without rebuilding.
node --import tsx src/index.ts 2>&1 | sed -u "s/^/$(printf "${BLUE}[bridge]${NC} ")/" &
PIDS+=($!)
cd "$LOCAL_PATH"
}
# ─── Start dashboard ─────────────────────────────────────────────────
start_dashboard() {
echo -e "${GREEN}[dashboard]${NC} Starting on :3101…"
cd "$LOCAL_PATH/dashboard"
# Environment for local dashboard:
# PORT=3101 → don't collide with prod :3100
# TIGER_BRIDGE_URL=localhost:3457 → talk to OUR local bridge, not prod
# TIGER_BRIDGE_TOKEN=dev-local-token → match local bridge's auth
export PORT=3101
export TIGER_BRIDGE_URL=http://localhost:3457
export TIGER_BRIDGE_TOKEN=dev-local-token
# next dev → hot-reload, fast compile
npm run dev 2>&1 | sed -u "s/^/$(printf "${GREEN}[dashboard]${NC} ")/" &
PIDS+=($!)
cd "$LOCAL_PATH"
}
# ─── Start requested services ──────────────────────────────────────
echo
echo "═══════════════════════════════════════════════════════════════"
echo "Local dev environment starting"
echo "═══════════════════════════════════════════════════════════════"
[ "$MODE" = "both" ] || [ "$MODE" = "bridge" ] && start_bridge
[ "$MODE" = "both" ] || [ "$MODE" = "dashboard" ] && start_dashboard
sleep 2
echo
echo -e "${GREEN}═══════════════════════════════════════════════════════════════${NC}"
[ "$MODE" != "bridge" ] && echo -e " Dashboard: ${GREEN}http://localhost:3101${NC}"
[ "$MODE" != "dashboard" ] && echo -e " Bridge: ${BLUE}http://localhost:3457${NC} (TIGER_REMOTE mode)"
echo -e " Production: https://agent.manohargupta.com (${YELLOW}untouched${NC})"
echo -e " Stop with: ${YELLOW}Ctrl-C${NC}"
echo -e "${GREEN}═══════════════════════════════════════════════════════════════${NC}"
echo
# Wait for any child to exit (usually Ctrl-C triggers cleanup first)
wait "${PIDS[@]}"