#!/bin/bash set -euo pipefail usage() { cat <<'EOF' Usage: REMOTE_HOST=150.158.105.6 \ REMOTE_USER=root \ REMOTE_PASSWORD='your-password' \ ./scripts/deploy-remote.sh Optional environment variables: REMOTE_PORT=22 REMOTE_DIR=/root/lingma-proxy-compose REMOTE_BUILD_DIR=/root/lingma-proxy-compose/src REMOTE_PUBLIC_PORT=13123 REMOTE_CONTAINER_NAME=lingma-proxy-uploaded REMOTE_IMAGE_NAME=lingma-proxy-uploaded REMOTE_SESSION_BUNDLE_PATH=/root/lingma-proxy-compose/secrets/lingma-session.b64 LINGMA_REMOTE_BASE_URL=https://lingma.alibabacloud.com LINGMA_SOURCE_TYPE=vsix LINGMA_VSIX_URL=https://tongyi-code.oss-cn-hangzhou.aliyuncs.com/vscode/tongyi-lingma-latest.vsix LINGMA_MARKETPLACE_PUBLISHER=Alibaba-Cloud LINGMA_MARKETPLACE_EXTENSION=tongyi-lingma LINGMA_PROXY_MODEL=org_auto VERIFY_PUBLIC=false EOF } require_env() { local name="$1" if [ -z "${!name:-}" ]; then echo "Missing required environment variable: $name" >&2 usage >&2 exit 1 fi } require_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "Required command not found: $1" >&2 exit 1 } } if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then usage exit 0 fi require_cmd sshpass require_cmd ssh require_cmd scp require_cmd curl require_cmd tar require_env REMOTE_HOST require_env REMOTE_USER require_env REMOTE_PASSWORD SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" REMOTE_PORT="${REMOTE_PORT:-22}" REMOTE_DIR="${REMOTE_DIR:-/root/lingma-proxy-compose}" REMOTE_BUILD_DIR="${REMOTE_BUILD_DIR:-$REMOTE_DIR/src}" REMOTE_PUBLIC_PORT="${REMOTE_PUBLIC_PORT:-13123}" REMOTE_CONTAINER_NAME="${REMOTE_CONTAINER_NAME:-lingma-proxy-uploaded}" REMOTE_IMAGE_NAME="${REMOTE_IMAGE_NAME:-lingma-proxy-uploaded}" REMOTE_SESSION_BUNDLE_PATH="${REMOTE_SESSION_BUNDLE_PATH:-$REMOTE_DIR/secrets/lingma-session.b64}" LINGMA_REMOTE_BASE_URL="${LINGMA_REMOTE_BASE_URL:-https://lingma.alibabacloud.com}" LINGMA_SOURCE_TYPE="${LINGMA_SOURCE_TYPE:-vsix}" LINGMA_VSIX_URL="${LINGMA_VSIX_URL:-https://tongyi-code.oss-cn-hangzhou.aliyuncs.com/vscode/tongyi-lingma-latest.vsix}" LINGMA_MARKETPLACE_PUBLISHER="${LINGMA_MARKETPLACE_PUBLISHER:-Alibaba-Cloud}" LINGMA_MARKETPLACE_EXTENSION="${LINGMA_MARKETPLACE_EXTENSION:-tongyi-lingma}" LINGMA_PROXY_MODEL="${LINGMA_PROXY_MODEL:-org_auto}" VERIFY_PUBLIC="${VERIFY_PUBLIC:-false}" if [ "$REMOTE_BUILD_DIR" = "$REMOTE_DIR" ]; then echo "REMOTE_BUILD_DIR must be different from REMOTE_DIR" >&2 exit 1 fi case "$REMOTE_BUILD_DIR" in "$REMOTE_DIR"/*) REMOTE_BUILD_CONTEXT_SUBDIR="${REMOTE_BUILD_DIR#"$REMOTE_DIR"/}" ;; *) echo "REMOTE_BUILD_DIR must be inside REMOTE_DIR so Docker can access it" >&2 exit 1 ;; esac if [ ! -d "$REPO_ROOT/vendor" ]; then echo "Missing vendor/ directory. Run 'go mod vendor' first." >&2 exit 1 fi SSH_BASE=(sshpass -p "$REMOTE_PASSWORD" ssh -o StrictHostKeyChecking=no -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST") SCP_BASE=(sshpass -p "$REMOTE_PASSWORD" scp -o StrictHostKeyChecking=no -P "$REMOTE_PORT") work_dir="$(mktemp -d)" trap 'rm -rf "$work_dir"' EXIT source_dir="$work_dir/source" archive_path="$work_dir/source.tar.gz" dockerfile_path="$work_dir/Dockerfile.uploaded" env_path="$work_dir/.env.container" remote_archive_path="$REMOTE_DIR/source.tar.gz" mkdir -p "$source_dir" cp -R "$REPO_ROOT/cmd" "$source_dir/cmd" cp -R "$REPO_ROOT/internal" "$source_dir/internal" cp -R "$REPO_ROOT/vendor" "$source_dir/vendor" cp "$REPO_ROOT/go.mod" "$source_dir/go.mod" cp "$REPO_ROOT/go.sum" "$source_dir/go.sum" if [ -d "$REPO_ROOT/pkg" ]; then cp -R "$REPO_ROOT/pkg" "$source_dir/pkg" fi tar -C "$source_dir" -czf "$archive_path" . cat >"$dockerfile_path" <"$env_path" < Ensuring remote deploy directory exists" "${SSH_BASE[@]}" "mkdir -p '$REMOTE_DIR' '$REMOTE_DIR/data' '$REMOTE_DIR/secrets' '$REMOTE_BUILD_DIR'" echo "==> Checking remote build prerequisites" "${SSH_BASE[@]}" "command -v docker >/dev/null 2>&1 && command -v tar >/dev/null 2>&1 && command -v curl >/dev/null 2>&1" echo "==> Checking remote session bundle" "${SSH_BASE[@]}" "test -f '$REMOTE_SESSION_BUNDLE_PATH'" echo "==> Uploading source bundle and runtime files" "${SCP_BASE[@]}" "$archive_path" "$REMOTE_USER@$REMOTE_HOST:$remote_archive_path" "${SCP_BASE[@]}" "$dockerfile_path" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/Dockerfile.uploaded" "${SCP_BASE[@]}" "$env_path" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/.env.container" echo "==> Preparing remote build context" "${SSH_BASE[@]}" "rm -rf '$REMOTE_BUILD_DIR' && mkdir -p '$REMOTE_BUILD_DIR' && tar -xzf '$remote_archive_path' -C '$REMOTE_BUILD_DIR'" echo "==> Rebuilding remote runtime image" "${SSH_BASE[@]}" "cd '$REMOTE_DIR' && docker build -f Dockerfile.uploaded -t '$REMOTE_IMAGE_NAME' ." echo "==> Recreating remote container" "${SSH_BASE[@]}" "cd '$REMOTE_DIR' && docker rm -f '$REMOTE_CONTAINER_NAME' >/dev/null 2>&1 || true && docker run -d --name '$REMOTE_CONTAINER_NAME' --restart unless-stopped --env-file .env.container -p '$REMOTE_PUBLIC_PORT:8095' -v '$REMOTE_DIR/data:/app/data' -v '$REMOTE_DIR/secrets:/secrets:ro' '$REMOTE_IMAGE_NAME' >/dev/null" echo "==> Waiting for remote health endpoint" "${SSH_BASE[@]}" 'for i in $(seq 1 24); do curl -fsS "http://127.0.0.1:'"$REMOTE_PUBLIC_PORT"'/runtime/status" && exit 0; sleep 5; done; docker logs --tail 120 '"$REMOTE_CONTAINER_NAME"' >&2; exit 1' echo echo "==> Remote models" "${SSH_BASE[@]}" "curl -fsS http://127.0.0.1:$REMOTE_PUBLIC_PORT/v1/models" if [ "$VERIFY_PUBLIC" = "true" ]; then echo echo "==> Public runtime check" curl -fsS "http://$REMOTE_HOST:$REMOTE_PUBLIC_PORT/runtime/status" fi echo echo "Deploy complete: http://$REMOTE_HOST:$REMOTE_PUBLIC_PORT"