From aa3b012d60bfedc7aff411479f46ba975a3ec18f Mon Sep 17 00:00:00 2001 From: zenfun Date: Mon, 16 Feb 2026 02:37:48 +0800 Subject: [PATCH] feat: add Shipyard Neo quick-start script Add scripts/start-with-neo.sh: one-click launcher that auto-generates Bay config.yaml (anonymous mode, host_port), pulls Ship image, starts Bay (port 8114) with health check, then starts AstrBot in foreground. Ctrl+C stops both services. Supports BAY_PORT env var override. --- scripts/start-with-neo.sh | 249 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100755 scripts/start-with-neo.sh diff --git a/scripts/start-with-neo.sh b/scripts/start-with-neo.sh new file mode 100755 index 000000000..06111d674 --- /dev/null +++ b/scripts/start-with-neo.sh @@ -0,0 +1,249 @@ +#!/usr/bin/env bash +# ────────────────────────────────────────────────────────────── +# start-with-neo.sh — 一键启动 Shipyard Neo Bay + AstrBot +# +# Usage: +# bash scripts/start-with-neo.sh # 默认 Bay :8114 +# BAY_PORT=9000 bash scripts/start-with-neo.sh # 自定义端口 +# ────────────────────────────────────────────────────────────── +set -euo pipefail + +# ── 路径 ────────────────────────────────────────────────────── +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ASTRBOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +# shipyard-neo mono-repo root is one level above AstrBot +NEO_ROOT="$(cd "$ASTRBOT_DIR/.." && pwd)" +BAY_DIR="$NEO_ROOT/pkgs/bay" + +BAY_PORT="${BAY_PORT:-8114}" +BAY_HOST="0.0.0.0" +BAY_PID="" + +# ── 颜色 ────────────────────────────────────────────────────── +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +log() { echo -e "${CYAN}[neo]${NC} $*"; } +ok() { echo -e "${GREEN}[neo]${NC} $*"; } +warn() { echo -e "${YELLOW}[neo]${NC} $*"; } +err() { echo -e "${RED}[neo]${NC} $*" >&2; } + +# ── 清理函数 ────────────────────────────────────────────────── +cleanup() { + log "Shutting down..." + if [[ -n "$BAY_PID" ]] && kill -0 "$BAY_PID" 2>/dev/null; then + log "Stopping Bay (PID $BAY_PID)..." + kill "$BAY_PID" 2>/dev/null || true + wait "$BAY_PID" 2>/dev/null || true + fi + ok "All services stopped." +} +trap cleanup EXIT INT TERM + +# ── 检查前置条件 ────────────────────────────────────────────── +check_prerequisites() { + log "Checking prerequisites..." + + if [[ ! -d "$BAY_DIR" ]]; then + err "Bay directory not found: $BAY_DIR" + err "Expected shipyard-neo mono-repo at: $NEO_ROOT" + exit 1 + fi + + if ! command -v uv &>/dev/null; then + err "'uv' is not installed. Please install it first." + exit 1 + fi + + # Check Docker access (try without sudo first, then with sudo) + if docker info &>/dev/null 2>&1; then + ok "Docker is accessible." + elif sudo docker info &>/dev/null 2>&1; then + warn "Docker requires sudo. Bay may need socket permissions." + warn "If Bay fails to connect to Docker, run: sudo chmod 666 /var/run/docker.sock" + else + err "Docker is not accessible. Please install Docker or fix permissions." + exit 1 + fi + + # Check Bay venv + if [[ ! -d "$BAY_DIR/.venv" ]]; then + log "Bay venv not found. Running 'uv sync' in $BAY_DIR ..." + (cd "$BAY_DIR" && uv sync) + fi + + ok "Prerequisites OK." +} + +# ── 生成 Bay config.yaml(如不存在)──────────────────────────── +ensure_bay_config() { + local config_file="$BAY_DIR/config.yaml" + + if [[ -f "$config_file" ]]; then + ok "Bay config.yaml already exists." + return + fi + + log "Generating Bay config.yaml for local development..." + + cat > "$config_file" << 'BAYCONFIG' +# Bay Local Development Config (auto-generated by start-with-neo.sh) +# For full reference see config.yaml.example + +server: + host: "0.0.0.0" + port: 8114 + +database: + url: "sqlite+aiosqlite:///./bay.db" + echo: false + +driver: + type: docker + image_pull_policy: if_not_present + docker: + socket: "unix:///var/run/docker.sock" + connect_mode: host_port + host_address: "127.0.0.1" + publish_ports: true + host_port: null + network: null + +cargo: + root_path: "/var/lib/bay/cargos" + default_size_limit_mb: 1024 + mount_path: "/workspace" + +security: + api_key: null + allow_anonymous: true + +profiles: + - id: python-default + description: "Standard Python sandbox" + image: "ghcr.io/astrbotdevs/shipyard-neo-ship:latest" + runtime_type: ship + runtime_port: 8123 + resources: + cpus: 1.0 + memory: "1g" + capabilities: + - filesystem + - shell + - python + idle_timeout: 1800 + env: {} + +gc: + enabled: true + run_on_startup: true + interval_seconds: 300 + idle_session: + enabled: true + expired_sandbox: + enabled: true + orphan_cargo: + enabled: true + orphan_container: + enabled: false +BAYCONFIG + + ok "Bay config.yaml created at $config_file" +} + +# ── 拉取 Ship 镜像 ─────────────────────────────────────────── +ensure_ship_image() { + local image="ghcr.io/astrbotdevs/shipyard-neo-ship:latest" + log "Checking Ship image: $image ..." + + if docker image inspect "$image" &>/dev/null 2>&1 || \ + sudo docker image inspect "$image" &>/dev/null 2>&1; then + ok "Ship image is available locally." + else + log "Pulling Ship image (this may take a while)..." + if docker pull "$image" 2>/dev/null || sudo docker pull "$image" 2>/dev/null; then + ok "Ship image pulled successfully." + else + warn "Failed to pull Ship image. Bay will try to pull it on first sandbox creation." + fi + fi +} + +# ── 启动 Bay ────────────────────────────────────────────────── +start_bay() { + log "Starting Bay on :$BAY_PORT ..." + + (cd "$BAY_DIR" && uv run uvicorn app.main:app \ + --host "$BAY_HOST" \ + --port "$BAY_PORT" \ + --reload \ + 2>&1 | sed "s/^/ ${CYAN}[bay]${NC} /") & + BAY_PID=$! + + log "Bay started (PID $BAY_PID), waiting for health check..." + + # Wait for Bay to become healthy + local max_wait=30 + local waited=0 + while [[ $waited -lt $max_wait ]]; do + if curl -sf "http://127.0.0.1:$BAY_PORT/health" &>/dev/null; then + ok "Bay is healthy at http://127.0.0.1:$BAY_PORT" + return + fi + # Check if process is still alive + if ! kill -0 "$BAY_PID" 2>/dev/null; then + err "Bay process died unexpectedly. Check the output above." + exit 1 + fi + sleep 1 + waited=$((waited + 1)) + done + + err "Bay did not become healthy within ${max_wait}s." + err "It may still be starting — check http://127.0.0.1:$BAY_PORT/health" +} + +# ── 打印 AstrBot 配置提示 ──────────────────────────────────── +print_astrbot_config_hint() { + echo "" + echo -e "${GREEN}════════════════════════════════════════════════════════════${NC}" + echo -e "${GREEN} Shipyard Neo Bay is running at http://127.0.0.1:$BAY_PORT ${NC}" + echo -e "${GREEN}════════════════════════════════════════════════════════════${NC}" + echo "" + echo -e " ${CYAN}AstrBot Dashboard 配置指引:${NC}" + echo -e " 1. AI 配置 → Agent Computer Use" + echo -e " • Computer Use Runtime → ${YELLOW}沙箱${NC}" + echo -e " • 沙箱环境驱动器 → ${YELLOW}Shipyard Neo${NC}" + echo -e " • Shipyard Neo API Endpoint → ${YELLOW}http://127.0.0.1:$BAY_PORT${NC}" + echo -e " • Shipyard Neo Access Token → ${YELLOW}(留空,已开启匿名访问)${NC}" + echo -e " • Shipyard Neo Profile → ${YELLOW}python-default${NC}" + echo "" +} + +# ── 启动 AstrBot ────────────────────────────────────────────── +start_astrbot() { + log "Starting AstrBot..." + cd "$ASTRBOT_DIR" + uv run main.py +} + +# ── 主流程 ──────────────────────────────────────────────────── +main() { + echo "" + echo -e "${CYAN}╔══════════════════════════════════════════╗${NC}" + echo -e "${CYAN}║ Shipyard Neo + AstrBot Quick Start ║${NC}" + echo -e "${CYAN}╚══════════════════════════════════════════╝${NC}" + echo "" + + check_prerequisites + ensure_bay_config + ensure_ship_image + start_bay + print_astrbot_config_hint + start_astrbot +} + +main "$@"