← back to Yolo Agent

setup-yolo-remote.sh

219 lines

#!/bin/bash
# ============================================================================
# YOLO Runner (Yuri) — Remote Machine Setup Script
#
# Deploys a portable YOLO agent on any machine with Claude CLI installed.
# Run this on the TARGET machine after copying yolo-agent/ directory.
#
# Prerequisites on target machine:
#   1. Node.js 18+ (node, npm)
#   2. Claude CLI authenticated (`claude` in PATH)
#   3. PM2 globally installed (`npm i -g pm2`)
#
# Usage:
#   chmod +x setup-yolo-remote.sh
#   ./setup-yolo-remote.sh [PORT] [WORKING_DIR]
#
#   PORT        — dashboard port (default: 9670)
#   WORKING_DIR — cwd for Claude tasks (default: $HOME)
# ============================================================================

set -euo pipefail

PORT="${1:-9670}"
WORK_DIR="${2:-$HOME}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

echo "=== YOLO Runner Setup ==="
echo "  Directory: $SCRIPT_DIR"
echo "  Port:      $PORT"
echo "  Work Dir:  $WORK_DIR"
echo ""

# ── Step 1: Verify prerequisites ──────────────────────────────────────────

echo "[1/6] Checking prerequisites..."

if ! command -v node &>/dev/null; then
  echo "ERROR: Node.js not found. Install with: curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash - && sudo apt install -y nodejs"
  exit 1
fi
NODE_VER=$(node -v)
echo "  Node.js: $NODE_VER"

if ! command -v npm &>/dev/null; then
  echo "ERROR: npm not found."
  exit 1
fi

if ! command -v pm2 &>/dev/null; then
  echo "  PM2 not found — installing globally..."
  npm install -g pm2
fi
echo "  PM2: $(pm2 -v 2>/dev/null || echo 'installed')"

# Find claude binary
CLAUDE_BIN=""
for candidate in /usr/bin/claude /usr/local/bin/claude "$HOME/.claude/bin/claude" "$(which claude 2>/dev/null)"; do
  if [ -x "$candidate" 2>/dev/null ]; then
    CLAUDE_BIN="$candidate"
    break
  fi
done

if [ -z "$CLAUDE_BIN" ]; then
  echo "ERROR: Claude CLI not found. Install from https://claude.ai/download"
  exit 1
fi
echo "  Claude CLI: $CLAUDE_BIN"

# ── Step 2: Install dependencies ──────────────────────────────────────────

echo ""
echo "[2/6] Installing npm dependencies..."
cd "$SCRIPT_DIR"
npm install --production 2>&1 | tail -3

# ── Step 3: Create directory structure ────────────────────────────────────

echo ""
echo "[3/6] Creating task directories..."
mkdir -p "$SCRIPT_DIR/tasks/done"
mkdir -p "$SCRIPT_DIR/tasks/failed"
mkdir -p "$SCRIPT_DIR/tasks/pending"
mkdir -p "$SCRIPT_DIR/tasks/deferred"
mkdir -p "$SCRIPT_DIR/logs"
mkdir -p "$SCRIPT_DIR/history"
echo "  Created: tasks/{done,failed,pending,deferred}, logs, history"

# ── Step 4: Generate local config override ────────────────────────────────

echo ""
echo "[4/6] Generating local config..."

CONFIG_FILE="$SCRIPT_DIR/local-config.json"
cat > "$CONFIG_FILE" << JSONEOF
{
  "port": $PORT,
  "claudeBin": "$CLAUDE_BIN",
  "workingDir": "$WORK_DIR",
  "authUser": "admin",
  "authPass": "DWSecure2024!",
  "cooldownSec": 30,
  "maxTurns": 50,
  "maxConcurrent": 3,
  "timeoutMinutes": 20,
  "allowedTools": ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "WebFetch", "WebSearch"]
}
JSONEOF
echo "  Written: $CONFIG_FILE"

# ── Step 5: Patch server.js to read local config ─────────────────────────

echo ""
echo "[5/6] Checking server.js for local-config support..."

if grep -q "local-config.json" "$SCRIPT_DIR/server.js" 2>/dev/null; then
  echo "  Already patched — skipping."
else
  echo "  Creating wrapper script (yolo-local.js) to override constants..."

  cat > "$SCRIPT_DIR/yolo-local.js" << 'JSEOF'
#!/usr/bin/env node
// ============================================================================
// YOLO Local Wrapper — reads local-config.json and patches env before
// loading the main server.js. Allows portable deployment without editing
// the original server.js.
// ============================================================================

const fs = require('fs');
const path = require('path');

const configPath = path.join(__dirname, 'local-config.json');
if (fs.existsSync(configPath)) {
  const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));

  // Override via environment variables that server.js respects
  if (cfg.port) process.env.YOLO_PORT = String(cfg.port);
  if (cfg.authUser) process.env.YOLO_AUTH_USER = cfg.authUser;
  if (cfg.authPass) process.env.YOLO_AUTH_PASS = cfg.authPass;
  if (cfg.claudeBin) process.env.YOLO_CLAUDE_BIN = cfg.claudeBin;
  if (cfg.workingDir) process.env.YOLO_WORK_DIR = cfg.workingDir;
  if (cfg.maxTurns) process.env.YOLO_MAX_TURNS = String(cfg.maxTurns);
  if (cfg.maxConcurrent) process.env.YOLO_MAX_CONCURRENT = String(cfg.maxConcurrent);
  if (cfg.timeoutMinutes) process.env.YOLO_TIMEOUT_MS = String(cfg.timeoutMinutes * 60 * 1000);
  if (cfg.cooldownSec) process.env.YOLO_COOLDOWN = String(cfg.cooldownSec);
  if (cfg.allowedTools) process.env.YOLO_ALLOWED_TOOLS = cfg.allowedTools.join(',');

  console.log(`[YOLO-Local] Config loaded: port=${cfg.port}, claude=${cfg.claudeBin}, cwd=${cfg.workingDir}`);
}

// Now load the real server
require('./server.js');
JSEOF

  chmod +x "$SCRIPT_DIR/yolo-local.js"
  echo "  Created: yolo-local.js (wrapper with local-config.json support)"
  echo ""
  echo "  NOTE: server.js hardcodes constants at lines 16-28."
  echo "  The wrapper sets env vars, but server.js must read them."
  echo "  You may need to edit server.js lines 16-28 to use:"
  echo ""
  echo "    const PORT = parseInt(process.env.YOLO_PORT) || 9670;"
  echo "    const CLAUDE_BIN = process.env.YOLO_CLAUDE_BIN || '/usr/bin/claude';"
  echo "    const MAX_TURNS = parseInt(process.env.YOLO_MAX_TURNS) || 50;"
  echo "    // etc."
  echo ""
  echo "  OR just edit the constants directly in server.js."
fi

# ── Step 6: Start with PM2 ───────────────────────────────────────────────

echo ""
echo "[6/6] Starting with PM2..."

# Stop existing if running
pm2 delete yolo-agent 2>/dev/null || true

# Determine which script to run
if [ -f "$SCRIPT_DIR/yolo-local.js" ]; then
  ENTRY="yolo-local.js"
else
  ENTRY="server.js"
fi

pm2 start "$SCRIPT_DIR/$ENTRY" \
  --name yolo-agent \
  --cwd "$SCRIPT_DIR" \
  --max-memory-restart 512M \
  --no-autorestart

pm2 save 2>/dev/null || true

echo ""
echo "============================================"
echo "  YOLO Runner (Yuri) is LIVE"
echo "  Dashboard: http://localhost:$PORT/"
echo "  Remote access: ssh -L $PORT:127.0.0.1:$PORT <user>@$(hostname -I | awk '{print $1}')"
echo "  Auth:      admin / DWSecure2024!"
echo "============================================"
echo ""
echo "Quick start:"
echo "  # Add a task"
echo "  echo 'Check disk space and report' > $SCRIPT_DIR/tasks/01_health.md"
echo ""
echo "  # Start the loop"
echo "  curl -u admin:DWSecure2024! -X POST http://localhost:$PORT/api/start"
echo ""
echo "  # Quick one-off run"
echo "  curl -u admin:DWSecure2024! -X POST http://localhost:$PORT/api/run \\"
echo "    -H 'Content-Type: application/json' \\"
echo "    -d '{\"prompt\": \"Check system health and report\"}'"
echo ""
echo "  # Monitor"
echo "  curl -u admin:DWSecure2024! http://localhost:$PORT/api/status"
echo ""
echo "  # View logs"
echo "  pm2 logs yolo-agent --lines 50"
echo ""