Files
ServerKit/dev.sh
T
Juan Denis e25cf6f83e Add tunnel mode and enhance servers UI
Add ngrok 'tunnel' mode to dev scripts and documentation, plus a large UI overhaul for the Servers pages.

- Add .gitattributes and new scripts/setup-wsl.sh helper.
- Extend dev.ps1 and dev.sh with a new 'tunnel' mode that starts backend+frontend and launches an ngrok tunnel (supports NGROK_DOMAIN, NGROK_AUTHTOKEN, and sets sensible CORS_ORIGINS). Includes polling of ngrok API, process cleanup, and helpful error messages.
- Update docs/LOCAL_DEVELOPMENT.md with an "Exposing Your Local Control Plane (ngrok)" guide and WSL setup notes.
- Revamp frontend pages: ServerDetail.jsx and Servers.jsx refactored with new header layouts, status/avatars, status pills, telemetry rows, selectable table rows, bulk actions, confirm dialogs, improved add-server modal, new icons (FolderTinyIcon, MoreIcon), utilities (formatLastSeen, clamp), and accessibility/interaction improvements (menus, selection, copy install command).
- Update servers SCSS to support the new layouts and loading states.

These changes add a developer-friendly tunnel mode for remote testing and significantly improve the server management UX in the frontend.
2026-04-29 02:56:23 -04:00

273 lines
7.6 KiB
Bash

#!/usr/bin/env bash
# ServerKit development launcher and validation tool.
#
# Usage:
# ./dev.sh Start backend + frontend (default)
# ./dev.sh backend Backend only
# ./dev.sh frontend Frontend only
# ./dev.sh tunnel Start backend + frontend + expose backend via ngrok
# ./dev.sh validate Run all linters/checks
#
# Environment variables:
# NGROK_DOMAIN Optional reserved ngrok domain (e.g. my-app.ngrok-free.app)
# NGROK_AUTHTOKEN Optional ngrok authtoken (alternatively run `ngrok config add-authtoken`)
set -euo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BACKEND_DIR="$PROJECT_ROOT/backend"
FRONTEND_DIR="$PROJECT_ROOT/frontend"
# Colors
CYAN='\033[0;36m'
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
DIM='\033[2m'
NC='\033[0m'
header() {
echo ""
echo -e "${CYAN}=== $1 ===${NC}"
echo ""
}
pass() {
echo -e " ${GREEN}PASS${NC} $1"
}
fail() {
echo -e " ${RED}FAIL${NC} $1"
}
start_backend() {
header "Starting Backend (http://localhost:5000)"
cd "$BACKEND_DIR"
if [ -f venv/bin/activate ]; then
source venv/bin/activate
fi
python run.py
}
start_frontend() {
header "Starting Frontend (http://localhost:5173)"
cd "$FRONTEND_DIR"
npm run dev
}
start_both() {
echo ""
echo -e "${CYAN}ServerKit Dev Server${NC}"
echo " Backend: http://localhost:5000"
echo " Frontend: http://localhost:5173"
echo ""
cd "$BACKEND_DIR"
if [ -f venv/bin/activate ]; then
source venv/bin/activate
fi
python run.py &
BACKEND_PID=$!
sleep 2
cd "$FRONTEND_DIR"
npm run dev &
FRONTEND_PID=$!
cleanup() {
echo ""
echo -e "${YELLOW}Stopping...${NC}"
kill "$BACKEND_PID" "$FRONTEND_PID" 2>/dev/null || true
wait "$BACKEND_PID" "$FRONTEND_PID" 2>/dev/null || true
echo "Stopped."
}
trap cleanup INT TERM
echo -e "${DIM}Press Ctrl+C to stop...${NC}"
wait
}
start_tunnel() {
if ! command -v ngrok &>/dev/null; then
echo -e "${RED}Error:${NC} ngrok is not installed or not in PATH."
echo ""
echo "Install ngrok:"
echo " - https://ngrok.com/download"
echo " - macOS: brew install ngrok/ngrok/ngrok"
echo " - Linux: snap install ngrok (or download the .tgz)"
echo " - Windows: choco install ngrok (or scoop install ngrok)"
echo ""
echo "Then authenticate once: ngrok config add-authtoken <YOUR_TOKEN>"
exit 1
fi
echo ""
echo -e "${CYAN}ServerKit Dev Server (with ngrok tunnel)${NC}"
echo " Backend: http://localhost:5000"
echo " Frontend: http://localhost:5173"
echo " Tunnel: exposing backend (port 5000) via ngrok"
echo ""
echo -e "${YELLOW}NOTE:${NC} Agents and remote callers should use the public ngrok URL"
echo " printed below as their --server / control plane URL."
echo ""
# Allow CORS from any ngrok domain by default unless caller already set it.
export CORS_ORIGINS="${CORS_ORIGINS:-http://localhost:5173,http://localhost:5000,https://*.ngrok-free.app,https://*.ngrok.app,https://*.ngrok.io}"
cd "$BACKEND_DIR"
if [ -f venv/bin/activate ]; then
source venv/bin/activate
fi
python run.py &
BACKEND_PID=$!
sleep 2
cd "$FRONTEND_DIR"
npm run dev &
FRONTEND_PID=$!
sleep 1
NGROK_ARGS=(http 5000 --log=stdout)
if [ -n "${NGROK_DOMAIN:-}" ]; then
NGROK_ARGS+=("--domain=$NGROK_DOMAIN")
fi
if [ -n "${NGROK_AUTHTOKEN:-}" ]; then
NGROK_ARGS+=("--authtoken=$NGROK_AUTHTOKEN")
fi
ngrok "${NGROK_ARGS[@]}" &
NGROK_PID=$!
# Poll the local ngrok API for the public URL and print it prominently.
(
for _ in $(seq 1 20); do
sleep 1
if command -v curl &>/dev/null; then
URL=$(curl -fsS http://127.0.0.1:4040/api/tunnels 2>/dev/null \
| grep -oE '"public_url":"https://[^"]+' \
| head -n1 \
| sed 's/"public_url":"//')
if [ -n "$URL" ]; then
echo ""
echo -e "${GREEN}=========================================================${NC}"
echo -e "${GREEN} Public tunnel URL: ${NC}${CYAN}$URL${NC}"
echo -e "${GREEN} Use this as your agent --server / control plane URL.${NC}"
echo -e "${GREEN}=========================================================${NC}"
echo ""
break
fi
fi
done
) &
cleanup_tunnel() {
echo ""
echo -e "${YELLOW}Stopping...${NC}"
kill "$BACKEND_PID" "$FRONTEND_PID" "$NGROK_PID" 2>/dev/null || true
wait "$BACKEND_PID" "$FRONTEND_PID" "$NGROK_PID" 2>/dev/null || true
echo "Stopped."
}
trap cleanup_tunnel INT TERM
echo -e "${DIM}Press Ctrl+C to stop...${NC}"
wait
}
run_validate() {
header "ServerKit Validation Suite"
local failed=0
local passed=0
# --- ESLint (warn-only, does not block) ---
echo -e "${YELLOW}Running ESLint...${NC}"
if (cd "$FRONTEND_DIR" && npm run lint 2>&1); then
pass "ESLint"
((passed++))
else
echo -e " ${YELLOW}WARN${NC} ESLint (has warnings/errors - run 'cd frontend && npm run lint' for details)"
((passed++)) # pre-existing issues should not block
fi
# --- Bandit ---
echo -e "${YELLOW}Running Bandit...${NC}"
if command -v bandit &>/dev/null; then
if bandit -r "$BACKEND_DIR/app" --ini "$BACKEND_DIR/.bandit" --severity-level medium 2>&1; then
pass "Bandit (security scan)"
((passed++))
else
fail "Bandit (security scan)"
((failed++))
fi
else
fail "Bandit (not installed — pip install bandit)"
((failed++))
fi
# --- Pytest ---
echo -e "${YELLOW}Running Pytest...${NC}"
if (cd "$BACKEND_DIR" && {
[ -f venv/bin/activate ] && source venv/bin/activate
pytest --tb=short -q 2>&1
}); then
pass "Pytest"
((passed++))
else
fail "Pytest"
((failed++))
fi
# --- Frontend build ---
echo -e "${YELLOW}Running Frontend build...${NC}"
if (cd "$FRONTEND_DIR" && npm run build 2>&1); then
pass "Frontend build"
((passed++))
else
fail "Frontend build"
((failed++))
fi
# --- Summary ---
header "Results"
echo -e " ${GREEN}Passed: $passed${NC}"
if [ "$failed" -gt 0 ]; then
echo -e " ${RED}Failed: $failed${NC}"
return 1
else
echo -e " ${GREEN}All checks passed!${NC}"
fi
}
run_validate_watch() {
run_validate || true
if command -v inotifywait &>/dev/null; then
echo -e "${DIM}Watching for changes... (Ctrl+C to stop)${NC}"
while true; do
inotifywait -r -q -e modify,create,delete \
--include '\.(py|js|jsx|ts|tsx)$' \
"$BACKEND_DIR/app" "$FRONTEND_DIR/src" 2>/dev/null || break
echo -e "\n${YELLOW}Change detected, re-running...${NC}"
sleep 1
run_validate || true
done
else
echo ""
echo -e "${DIM}Install inotify-tools for file watching (apt install inotify-tools).${NC}"
echo -e "${DIM}Running one-shot validation only.${NC}"
fi
}
# --- Main ---
MODE="${1:-start}"
case "$MODE" in
backend) start_backend ;;
frontend) start_frontend ;;
tunnel) start_tunnel ;;
validate) run_validate_watch ;;
start|*) start_both ;;
esac