mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-21 02:21:15 +00:00
feat: Add Cherry Servers cloud provider with openclaw and goose (#286)
Add Cherry Servers as a new cloud provider with: - REST API-based server provisioning - SSH key management via API - Full root access to cloud VPS instances - Hourly billing with no commitments Implementation includes: - cherry/lib/common.sh with Cherry Servers API primitives - cherry/openclaw.sh for OpenClaw deployment - cherry/goose.sh for Goose deployment - cherry/README.md with authentication and usage docs - manifest.json updates (cloud entry + 14 matrix entries) Agent: cloud-scout Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
This commit is contained in:
parent
7aaf55ddb5
commit
004aafaca0
5 changed files with 615 additions and 1 deletions
105
cherry/README.md
Normal file
105
cherry/README.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# Cherry Servers
|
||||
|
||||
Cherry Servers is a European cloud provider offering bare metal and cloud VPS with full root access, hourly billing, and a REST API.
|
||||
|
||||
## Authentication
|
||||
|
||||
All Cherry Servers scripts require a `CHERRY_AUTH_TOKEN` environment variable.
|
||||
|
||||
### Getting your API token
|
||||
|
||||
1. Visit [Cherry Servers Portal](https://portal.cherryservers.com/)
|
||||
2. Click on your profile in the top right
|
||||
3. Navigate to API Tokens
|
||||
4. Create a new token or copy an existing one
|
||||
|
||||
### Setting the token
|
||||
|
||||
```bash
|
||||
export CHERRY_AUTH_TOKEN="your-token-here"
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Optional environment variables:
|
||||
|
||||
- `CHERRY_AUTH_TOKEN` - API authentication token (required)
|
||||
- `CHERRY_DEFAULT_PLAN` - Server plan (default: `cloud_vps_1`)
|
||||
- `CHERRY_DEFAULT_REGION` - Deployment region (default: `eu_nord_1`)
|
||||
- `CHERRY_DEFAULT_IMAGE` - OS image (default: `Ubuntu 24.04 64bit`)
|
||||
- `CHERRY_SERVER_NAME` - Custom server hostname
|
||||
|
||||
## Available Plans
|
||||
|
||||
Cherry Servers offers various cloud VPS and bare metal plans:
|
||||
|
||||
- `cloud_vps_1` - 1 vCPU, 2GB RAM, 40GB SSD (default)
|
||||
- `cloud_vps_2` - 2 vCPU, 4GB RAM, 80GB SSD
|
||||
- `cloud_vps_3` - 4 vCPU, 8GB RAM, 160GB SSD
|
||||
- Bare metal plans available through the API
|
||||
|
||||
View all plans: https://portal.cherryservers.com/
|
||||
|
||||
## Regions
|
||||
|
||||
Available regions:
|
||||
|
||||
- `eu_nord_1` - Lithuania (default)
|
||||
- `eu_west_1` - Netherlands
|
||||
- `us_east_1` - USA East Coast
|
||||
- `us_west_1` - USA West Coast
|
||||
- `ap_southeast_1` - Singapore
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### OpenClaw on Cherry Servers
|
||||
|
||||
```bash
|
||||
export CHERRY_AUTH_TOKEN="your-token"
|
||||
bash <(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/cherry/openclaw.sh)
|
||||
```
|
||||
|
||||
### Goose on Cherry Servers
|
||||
|
||||
```bash
|
||||
export CHERRY_AUTH_TOKEN="your-token"
|
||||
bash <(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/cherry/goose.sh)
|
||||
```
|
||||
|
||||
### Custom configuration
|
||||
|
||||
```bash
|
||||
export CHERRY_AUTH_TOKEN="your-token"
|
||||
export CHERRY_DEFAULT_PLAN="cloud_vps_2"
|
||||
export CHERRY_DEFAULT_REGION="us_east_1"
|
||||
bash <(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/cherry/openclaw.sh)
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
1. **Authentication** - Validates `CHERRY_AUTH_TOKEN` with Cherry Servers API
|
||||
2. **SSH Key** - Generates SSH key pair if needed and registers public key
|
||||
3. **Project** - Fetches your default project ID from Cherry Servers account
|
||||
4. **Provisioning** - Creates cloud VPS with specified plan, region, and image
|
||||
5. **Connectivity** - Waits for SSH access and cloud-init completion
|
||||
6. **Agent Setup** - Installs agent, injects OpenRouter credentials, launches interactive session
|
||||
|
||||
## API Documentation
|
||||
|
||||
- API Docs: https://api.cherryservers.com/doc/
|
||||
- CLI (cherryctl): https://github.com/cherryservers/cherryctl
|
||||
- Portal: https://portal.cherryservers.com/
|
||||
|
||||
## Pricing
|
||||
|
||||
Cherry Servers uses hourly billing with no long-term commitments. Prices vary by plan and region.
|
||||
|
||||
View current pricing: https://www.cherryservers.com/pricing
|
||||
|
||||
## Notes
|
||||
|
||||
- All Cherry Servers instances use `root` user for SSH access
|
||||
- Servers are created with your registered SSH key automatically attached
|
||||
- Full root access and IP-KVM available
|
||||
- Cloud-init is supported for automated setup
|
||||
- Servers can be managed via API, CLI (cherryctl), or web portal
|
||||
64
cherry/goose.sh
Executable file
64
cherry/goose.sh
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
# shellcheck source=cherry/lib/common.sh
|
||||
if [[ -f "${SCRIPT_DIR}/lib/common.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/cherry/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Goose on Cherry Servers"
|
||||
echo ""
|
||||
|
||||
# 1. Resolve Cherry Servers API token
|
||||
ensure_cherry_token
|
||||
|
||||
# 2. Generate + register SSH key
|
||||
ensure_ssh_key
|
||||
|
||||
# 3. Get server name and create server
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
|
||||
# 4. Wait for SSH and cloud-init
|
||||
verify_server_connectivity "${CHERRY_SERVER_IP}"
|
||||
wait_for_cloud_init "${CHERRY_SERVER_IP}" 60
|
||||
|
||||
# 5. Install Goose
|
||||
log_warn "Installing Goose..."
|
||||
run_server "${CHERRY_SERVER_IP}" "CONFIGURE=false curl -fsSL https://github.com/block/goose/releases/latest/download/download_cli.sh | bash"
|
||||
|
||||
# Verify installation succeeded
|
||||
if ! run_server "${CHERRY_SERVER_IP}" "command -v goose &> /dev/null && goose --version &> /dev/null"; then
|
||||
log_error "Goose installation verification failed"
|
||||
log_error "The 'goose' command is not available or not working properly on server ${CHERRY_SERVER_IP}"
|
||||
exit 1
|
||||
fi
|
||||
log_info "Goose installation verified successfully"
|
||||
|
||||
# 6. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
log_warn "Setting up environment variables..."
|
||||
inject_env_vars_ssh "${CHERRY_SERVER_IP}" upload_file run_server \
|
||||
"GOOSE_PROVIDER=openrouter" \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
|
||||
echo ""
|
||||
log_info "Cherry Servers server setup completed successfully!"
|
||||
log_info "Server: ${SERVER_NAME} (ID: ${CHERRY_SERVER_ID}, IP: ${CHERRY_SERVER_IP})"
|
||||
echo ""
|
||||
|
||||
# 7. Start Goose interactively
|
||||
log_warn "Starting Goose..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "${CHERRY_SERVER_IP}" "source ~/.zshrc && goose"
|
||||
345
cherry/lib/common.sh
Executable file
345
cherry/lib/common.sh
Executable file
|
|
@ -0,0 +1,345 @@
|
|||
#!/bin/bash
|
||||
# Cherry Servers-specific functions for Spawn
|
||||
|
||||
# Source shared provider-agnostic functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -f "$SCRIPT_DIR/../../shared/common.sh" ]]; then
|
||||
source "$SCRIPT_DIR/../../shared/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/shared/common.sh)"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# Cherry Servers Configuration
|
||||
# ============================================================
|
||||
|
||||
CHERRY_API_BASE="https://api.cherryservers.com/v1"
|
||||
CHERRY_DEFAULT_PLAN="${CHERRY_DEFAULT_PLAN:-cloud_vps_1}"
|
||||
CHERRY_DEFAULT_REGION="${CHERRY_DEFAULT_REGION:-eu_nord_1}"
|
||||
CHERRY_DEFAULT_IMAGE="${CHERRY_DEFAULT_IMAGE:-Ubuntu 24.04 64bit}"
|
||||
|
||||
# ============================================================
|
||||
# Authentication
|
||||
# ============================================================
|
||||
|
||||
# Get Cherry Servers API token
|
||||
ensure_cherry_token() {
|
||||
local token="${CHERRY_AUTH_TOKEN:-}"
|
||||
|
||||
if [[ -z "$token" ]]; then
|
||||
log_warn "CHERRY_AUTH_TOKEN not found in environment"
|
||||
log_info "Get your API token from: https://portal.cherryservers.com/"
|
||||
printf "Enter your Cherry Servers API token: "
|
||||
read -r token
|
||||
fi
|
||||
|
||||
if [[ -z "$token" ]]; then
|
||||
log_error "API token is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CHERRY_AUTH_TOKEN="$token"
|
||||
export CHERRY_AUTH_TOKEN
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# SSH Key Management
|
||||
# ============================================================
|
||||
|
||||
# Ensure SSH key exists and is registered with Cherry Servers
|
||||
ensure_ssh_key() {
|
||||
check_python_available
|
||||
generate_ssh_key_if_missing
|
||||
|
||||
local ssh_pub_key
|
||||
ssh_pub_key=$(cat ~/.ssh/id_rsa.pub)
|
||||
|
||||
# Check if key already exists
|
||||
log_info "Checking for existing SSH key in Cherry Servers..."
|
||||
|
||||
local existing_keys
|
||||
existing_keys=$(curl -s -X GET \
|
||||
-H "Authorization: Bearer ${CHERRY_AUTH_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${CHERRY_API_BASE}/ssh-keys" 2>&1)
|
||||
|
||||
local key_fingerprint
|
||||
key_fingerprint=$(get_ssh_fingerprint)
|
||||
|
||||
# Check if our key is already registered
|
||||
local key_id
|
||||
key_id=$(printf '%s' "$existing_keys" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
keys = json.load(sys.stdin)
|
||||
fingerprint = '${key_fingerprint}'
|
||||
for key in keys:
|
||||
if key.get('fingerprint', '') == fingerprint:
|
||||
print(key.get('id', ''))
|
||||
break
|
||||
except:
|
||||
pass
|
||||
" 2>&1)
|
||||
|
||||
if [[ -n "$key_id" ]]; then
|
||||
log_info "SSH key already registered (ID: $key_id)"
|
||||
CHERRY_SSH_KEY_ID="$key_id"
|
||||
export CHERRY_SSH_KEY_ID
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Register new SSH key
|
||||
log_info "Registering new SSH key with Cherry Servers..."
|
||||
|
||||
local label="spawn-$(date +%s)"
|
||||
local response
|
||||
response=$(curl -s -X POST \
|
||||
-H "Authorization: Bearer ${CHERRY_AUTH_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"label\": \"$label\", \"key\": \"$ssh_pub_key\"}" \
|
||||
"${CHERRY_API_BASE}/ssh-keys" 2>&1)
|
||||
|
||||
key_id=$(printf '%s' "$response" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
data = json.load(sys.stdin)
|
||||
print(data.get('id', ''))
|
||||
except:
|
||||
pass
|
||||
" 2>&1)
|
||||
|
||||
if [[ -z "$key_id" ]]; then
|
||||
log_error "Failed to register SSH key"
|
||||
log_error "Response: $response"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "SSH key registered successfully (ID: $key_id)"
|
||||
CHERRY_SSH_KEY_ID="$key_id"
|
||||
export CHERRY_SSH_KEY_ID
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Server Management
|
||||
# ============================================================
|
||||
|
||||
# Get project ID (required for server creation)
|
||||
get_cherry_project_id() {
|
||||
check_python_available
|
||||
|
||||
local projects
|
||||
projects=$(curl -s -X GET \
|
||||
-H "Authorization: Bearer ${CHERRY_AUTH_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${CHERRY_API_BASE}/projects" 2>&1)
|
||||
|
||||
local project_id
|
||||
project_id=$(printf '%s' "$projects" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
data = json.load(sys.stdin)
|
||||
if isinstance(data, list) and len(data) > 0:
|
||||
print(data[0].get('id', ''))
|
||||
except:
|
||||
pass
|
||||
" 2>&1)
|
||||
|
||||
if [[ -z "$project_id" ]]; then
|
||||
log_error "No project found in Cherry Servers account"
|
||||
log_error "Create a project at https://portal.cherryservers.com/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s' "$project_id"
|
||||
}
|
||||
|
||||
# Get server name (generate or prompt)
|
||||
get_server_name() {
|
||||
local server_name="${CHERRY_SERVER_NAME:-}"
|
||||
|
||||
if [[ -z "$server_name" ]]; then
|
||||
server_name="spawn-$(date +%s)"
|
||||
fi
|
||||
|
||||
printf '%s' "$server_name"
|
||||
}
|
||||
|
||||
# Create server
|
||||
# Sets CHERRY_SERVER_ID and CHERRY_SERVER_IP as exports
|
||||
create_server() {
|
||||
local hostname="$1"
|
||||
local plan="${CHERRY_DEFAULT_PLAN}"
|
||||
local region="${CHERRY_DEFAULT_REGION}"
|
||||
local image="${CHERRY_DEFAULT_IMAGE}"
|
||||
|
||||
check_python_available
|
||||
|
||||
local project_id
|
||||
project_id=$(get_cherry_project_id)
|
||||
|
||||
log_info "Creating Cherry Servers server..."
|
||||
log_info "Plan: $plan, Region: $region, Image: $image"
|
||||
|
||||
local payload
|
||||
payload=$(python3 -c "
|
||||
import json
|
||||
data = {
|
||||
'plan': '$plan',
|
||||
'region': '$region',
|
||||
'image': '$image',
|
||||
'hostname': '$hostname',
|
||||
'ssh_keys': [${CHERRY_SSH_KEY_ID}]
|
||||
}
|
||||
print(json.dumps(data))
|
||||
")
|
||||
|
||||
local response
|
||||
response=$(curl -s -X POST \
|
||||
-H "Authorization: Bearer ${CHERRY_AUTH_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload" \
|
||||
"${CHERRY_API_BASE}/projects/${project_id}/servers" 2>&1)
|
||||
|
||||
local server_id
|
||||
server_id=$(printf '%s' "$response" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
data = json.load(sys.stdin)
|
||||
print(data.get('id', ''))
|
||||
except:
|
||||
pass
|
||||
" 2>&1)
|
||||
|
||||
if [[ -z "$server_id" ]]; then
|
||||
log_error "Failed to create server"
|
||||
log_error "Response: $response"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Server created with ID: $server_id"
|
||||
CHERRY_SERVER_ID="$server_id"
|
||||
export CHERRY_SERVER_ID
|
||||
|
||||
# Wait for IP assignment
|
||||
log_info "Waiting for IP address assignment..."
|
||||
local ip_address=""
|
||||
local attempts=0
|
||||
local max_attempts=60
|
||||
|
||||
while [[ -z "$ip_address" ]] && [[ $attempts -lt $max_attempts ]]; do
|
||||
sleep "${POLL_INTERVAL}"
|
||||
|
||||
local server_info
|
||||
server_info=$(curl -s -X GET \
|
||||
-H "Authorization: Bearer ${CHERRY_AUTH_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${CHERRY_API_BASE}/servers/${server_id}" 2>&1)
|
||||
|
||||
ip_address=$(printf '%s' "$server_info" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
data = json.load(sys.stdin)
|
||||
addresses = data.get('ip_addresses', [])
|
||||
for addr in addresses:
|
||||
if addr.get('type') == 'primary-ip':
|
||||
print(addr.get('address', ''))
|
||||
break
|
||||
except:
|
||||
pass
|
||||
" 2>&1)
|
||||
|
||||
attempts=$((attempts + 1))
|
||||
done
|
||||
|
||||
if [[ -z "$ip_address" ]]; then
|
||||
log_error "Failed to get server IP address"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Server IP: $ip_address"
|
||||
CHERRY_SERVER_IP="$ip_address"
|
||||
export CHERRY_SERVER_IP
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Execution Functions
|
||||
# ============================================================
|
||||
|
||||
# Run command on server via SSH
|
||||
run_server() {
|
||||
local ip="$1"
|
||||
local command="$2"
|
||||
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||
-o LogLevel=ERROR -o ConnectTimeout=10 \
|
||||
"root@${ip}" "$command"
|
||||
}
|
||||
|
||||
# Upload file to server via SCP
|
||||
upload_file() {
|
||||
local ip="$1"
|
||||
local local_path="$2"
|
||||
local remote_path="$3"
|
||||
|
||||
scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||
-o LogLevel=ERROR -o ConnectTimeout=10 \
|
||||
"$local_path" "root@${ip}:${remote_path}"
|
||||
}
|
||||
|
||||
# Start interactive SSH session
|
||||
interactive_session() {
|
||||
local ip="$1"
|
||||
local command="${2:-}"
|
||||
|
||||
if [[ -n "$command" ]]; then
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||
-o LogLevel=ERROR -t \
|
||||
"root@${ip}" "$command"
|
||||
else
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||
-o LogLevel=ERROR -t \
|
||||
"root@${ip}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Connectivity and Readiness
|
||||
# ============================================================
|
||||
|
||||
# Verify server is accessible via SSH
|
||||
verify_server_connectivity() {
|
||||
local ip="$1"
|
||||
local max_attempts=60
|
||||
local attempt=0
|
||||
|
||||
log_info "Waiting for SSH connectivity..."
|
||||
|
||||
while [[ $attempt -lt $max_attempts ]]; do
|
||||
if ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||
-o LogLevel=ERROR -o ConnectTimeout=5 \
|
||||
"root@${ip}" "echo 'SSH ready'" &> /dev/null; then
|
||||
log_info "SSH connection established"
|
||||
return 0
|
||||
fi
|
||||
|
||||
attempt=$((attempt + 1))
|
||||
sleep "${POLL_INTERVAL}"
|
||||
done
|
||||
|
||||
log_error "Failed to connect to server via SSH"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Wait for cloud-init to complete
|
||||
wait_for_cloud_init() {
|
||||
local ip="$1"
|
||||
local timeout="${2:-300}"
|
||||
|
||||
log_info "Waiting for system initialization..."
|
||||
|
||||
if ! run_server "$ip" "cloud-init status --wait --long" 2>/dev/null; then
|
||||
log_warn "cloud-init wait timed out or not available, proceeding anyway"
|
||||
else
|
||||
log_info "System initialization complete"
|
||||
fi
|
||||
}
|
||||
70
cherry/openclaw.sh
Executable file
70
cherry/openclaw.sh
Executable file
|
|
@ -0,0 +1,70 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Source common functions - try local file first, fall back to remote
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
# shellcheck source=cherry/lib/common.sh
|
||||
if [[ -f "${SCRIPT_DIR}/lib/common.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/cherry/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "OpenClaw on Cherry Servers"
|
||||
echo ""
|
||||
|
||||
# 1. Resolve Cherry Servers API token
|
||||
ensure_cherry_token
|
||||
|
||||
# 2. Generate + register SSH key
|
||||
ensure_ssh_key
|
||||
|
||||
# 3. Get server name and create server
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
|
||||
# 4. Wait for SSH and cloud-init
|
||||
verify_server_connectivity "${CHERRY_SERVER_IP}"
|
||||
wait_for_cloud_init "${CHERRY_SERVER_IP}" 60
|
||||
|
||||
# 5. Install dependencies (Node.js + Bun)
|
||||
log_warn "Installing Node.js and Bun..."
|
||||
run_server "${CHERRY_SERVER_IP}" "curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && apt-get install -y nodejs && curl -fsSL https://bun.sh/install | bash"
|
||||
|
||||
# 6. Install openclaw via bun
|
||||
log_warn "Installing openclaw..."
|
||||
run_server "${CHERRY_SERVER_IP}" "source ~/.bashrc && bun install -g openclaw"
|
||||
log_info "OpenClaw installed"
|
||||
|
||||
# 7. Get OpenRouter API key
|
||||
echo ""
|
||||
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
|
||||
log_info "Using OpenRouter API key from environment"
|
||||
else
|
||||
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
|
||||
fi
|
||||
|
||||
# Get model preference
|
||||
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "Openclaw") || exit 1
|
||||
|
||||
log_warn "Setting up environment variables..."
|
||||
inject_env_vars_ssh "${CHERRY_SERVER_IP}" upload_file run_server \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"ANTHROPIC_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"ANTHROPIC_BASE_URL=https://openrouter.ai/api"
|
||||
|
||||
# 8. Configure openclaw
|
||||
setup_openclaw_config "${OPENROUTER_API_KEY}" "${MODEL_ID}" \
|
||||
"upload_file ${CHERRY_SERVER_IP}" \
|
||||
"run_server ${CHERRY_SERVER_IP}"
|
||||
|
||||
echo ""
|
||||
log_info "Cherry Servers server setup completed successfully!"
|
||||
log_info "Server: ${SERVER_NAME} (ID: ${CHERRY_SERVER_ID}, IP: ${CHERRY_SERVER_IP})"
|
||||
echo ""
|
||||
|
||||
# 9. Start openclaw gateway in background and launch TUI
|
||||
log_warn "Starting openclaw..."
|
||||
run_server "${CHERRY_SERVER_IP}" "source ~/.zshrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 &"
|
||||
sleep 2
|
||||
interactive_session "${CHERRY_SERVER_IP}" "source ~/.zshrc && openclaw tui"
|
||||
|
|
@ -573,6 +573,22 @@
|
|||
},
|
||||
"notes": "Global cloud provider with 25+ datacenters. Hourly billing. Uses AuthClientId/AuthSecret headers for API auth. Async operations via command queue. Requires KAMATERA_API_CLIENT_ID and KAMATERA_API_SECRET from https://console.kamatera.com/keys"
|
||||
},
|
||||
"cherry": {
|
||||
"name": "Cherry Servers",
|
||||
"description": "Cherry Servers bare metal and cloud VPS via REST API",
|
||||
"url": "https://www.cherryservers.com/",
|
||||
"type": "api",
|
||||
"auth": "CHERRY_AUTH_TOKEN",
|
||||
"provision_method": "POST /v1/projects/{project_id}/servers with ssh_keys",
|
||||
"exec_method": "ssh root@IP",
|
||||
"interactive_method": "ssh -t root@IP",
|
||||
"defaults": {
|
||||
"plan": "cloud_vps_1",
|
||||
"region": "eu_nord_1",
|
||||
"image": "Ubuntu 24.04 64bit"
|
||||
},
|
||||
"notes": "European cloud provider with bare metal and VPS. Hourly billing. Full root access. Requires CHERRY_AUTH_TOKEN from https://portal.cherryservers.com/"
|
||||
},
|
||||
"oracle": {
|
||||
"name": "Oracle Cloud Infrastructure",
|
||||
"description": "Oracle Cloud compute instances via OCI CLI",
|
||||
|
|
@ -1101,6 +1117,20 @@
|
|||
"render/gptme": "implemented",
|
||||
"render/opencode": "implemented",
|
||||
"render/plandex": "implemented",
|
||||
"render/kilocode": "implemented"
|
||||
"render/kilocode": "implemented",
|
||||
"cherry/claude": "implemented",
|
||||
"cherry/openclaw": "implemented",
|
||||
"cherry/nanoclaw": "implemented",
|
||||
"cherry/aider": "implemented",
|
||||
"cherry/goose": "implemented",
|
||||
"cherry/codex": "implemented",
|
||||
"cherry/interpreter": "implemented",
|
||||
"cherry/gemini": "implemented",
|
||||
"cherry/amazonq": "implemented",
|
||||
"cherry/cline": "implemented",
|
||||
"cherry/gptme": "implemented",
|
||||
"cherry/opencode": "implemented",
|
||||
"cherry/plandex": "implemented",
|
||||
"cherry/kilocode": "implemented"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue