mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-09 19:49:58 +00:00
feat: Add Koyeb serverless container platform support (#185)
Add Koyeb as a new cloud provider with CLI-based provisioning. Changes: - Created koyeb/lib/common.sh with provider primitives - Implemented koyeb/claude.sh - Implemented koyeb/aider.sh - Implemented koyeb/openclaw.sh - Added Koyeb entry to manifest.json clouds section - Added matrix entries for all 14 agents - Created koyeb/README.md with setup instructions Koyeb features: - Serverless container platform with per-second billing - Free tier available (no credit card required) - Fast deployment times - Automatic scaling - Global deployment regions Agent: cloud-scout-2 Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
This commit is contained in:
parent
7d2a2543da
commit
e4052189d2
6 changed files with 636 additions and 1 deletions
52
koyeb/README.md
Normal file
52
koyeb/README.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Koyeb
|
||||
|
||||
Koyeb serverless container platform via CLI. [Koyeb](https://www.koyeb.com/)
|
||||
|
||||
## Agents
|
||||
|
||||
#### Claude Code
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/koyeb/claude.sh)
|
||||
```
|
||||
|
||||
#### OpenClaw
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/koyeb/openclaw.sh)
|
||||
```
|
||||
|
||||
#### Aider
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/koyeb/aider.sh)
|
||||
```
|
||||
|
||||
## Non-Interactive Mode
|
||||
|
||||
```bash
|
||||
KOYEB_TOKEN=your-token \
|
||||
OPENROUTER_API_KEY=sk-or-v1-xxxxx \
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/koyeb/claude.sh)
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `KOYEB_TOKEN` | Koyeb API token | _(prompted)_ |
|
||||
| `KOYEB_REGION` | Deployment region | `was` (Washington D.C.) |
|
||||
| `KOYEB_INSTANCE_TYPE` | Instance type | `nano` |
|
||||
| `OPENROUTER_API_KEY` | OpenRouter API key | _(OAuth or prompted)_ |
|
||||
|
||||
## Authentication
|
||||
|
||||
Get your Koyeb API token at: https://app.koyeb.com/account/api
|
||||
|
||||
## Features
|
||||
|
||||
- Serverless container platform with per-second billing
|
||||
- Free tier available (no credit card required)
|
||||
- Fast deployment times
|
||||
- Automatic scaling
|
||||
- Global deployment regions
|
||||
58
koyeb/aider.sh
Normal file
58
koyeb/aider.sh
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#!/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)"
|
||||
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/koyeb/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Aider on Koyeb"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure Koyeb CLI and API token
|
||||
ensure_koyeb_cli
|
||||
ensure_koyeb_token
|
||||
|
||||
# 2. Create service
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Install base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install Aider
|
||||
log_warn "Installing Aider..."
|
||||
run_server "pip install aider-chat 2>/dev/null || pip3 install aider-chat"
|
||||
log_info "Aider installed"
|
||||
|
||||
# 5. 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
|
||||
|
||||
# 6. Get model preference
|
||||
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "Aider") || exit 1
|
||||
|
||||
# 7. Inject environment variables
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
inject_env_vars \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"PATH=\$HOME/.local/bin:\$PATH"
|
||||
|
||||
echo ""
|
||||
log_info "Koyeb service setup completed successfully!"
|
||||
log_info "Service: $KOYEB_SERVICE_NAME (Instance: $KOYEB_INSTANCE_ID)"
|
||||
echo ""
|
||||
|
||||
# 8. Start Aider interactively
|
||||
log_warn "Starting Aider..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "source /root/.bashrc && aider --model openrouter/${MODEL_ID}"
|
||||
109
koyeb/claude.sh
Normal file
109
koyeb/claude.sh
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#!/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)"
|
||||
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/koyeb/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Claude Code on Koyeb"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure Koyeb CLI and API token
|
||||
ensure_koyeb_cli
|
||||
ensure_koyeb_token
|
||||
|
||||
# 2. Create service
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Install base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install Claude Code
|
||||
log_warn "Installing Claude Code..."
|
||||
run_server "curl -fsSL https://claude.ai/install.sh | bash"
|
||||
|
||||
# Verify installation
|
||||
if ! run_server "command -v claude" >/dev/null 2>&1; then
|
||||
log_error "Claude Code installation failed"
|
||||
exit 1
|
||||
fi
|
||||
log_info "Claude Code installed"
|
||||
|
||||
# 5. 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
|
||||
|
||||
# 6. Inject environment variables
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
inject_env_vars \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"ANTHROPIC_BASE_URL=https://openrouter.ai/api" \
|
||||
"ANTHROPIC_AUTH_TOKEN=${OPENROUTER_API_KEY}" \
|
||||
"ANTHROPIC_API_KEY=" \
|
||||
"CLAUDE_CODE_SKIP_ONBOARDING=1" \
|
||||
"CLAUDE_CODE_ENABLE_TELEMETRY=0" \
|
||||
"PATH=\$HOME/.claude/local/bin:\$HOME/.bun/bin:\$PATH"
|
||||
|
||||
# 7. Configure Claude Code settings
|
||||
log_warn "Configuring Claude Code..."
|
||||
|
||||
run_server "mkdir -p /root/.claude"
|
||||
|
||||
# Upload settings.json
|
||||
SETTINGS_TEMP=$(mktemp)
|
||||
chmod 600 "$SETTINGS_TEMP"
|
||||
cat > "$SETTINGS_TEMP" << EOF
|
||||
{
|
||||
"theme": "dark",
|
||||
"editor": "vim",
|
||||
"env": {
|
||||
"CLAUDE_CODE_ENABLE_TELEMETRY": "0",
|
||||
"ANTHROPIC_BASE_URL": "https://openrouter.ai/api",
|
||||
"ANTHROPIC_AUTH_TOKEN": "${OPENROUTER_API_KEY}"
|
||||
},
|
||||
"permissions": {
|
||||
"defaultMode": "bypassPermissions",
|
||||
"dangerouslySkipPermissions": true
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
upload_file "$SETTINGS_TEMP" "/root/.claude/settings.json"
|
||||
rm "$SETTINGS_TEMP"
|
||||
|
||||
# Upload ~/.claude.json global state
|
||||
GLOBAL_STATE_TEMP=$(mktemp)
|
||||
chmod 600 "$GLOBAL_STATE_TEMP"
|
||||
cat > "$GLOBAL_STATE_TEMP" << EOF
|
||||
{
|
||||
"hasCompletedOnboarding": true,
|
||||
"bypassPermissionsModeAccepted": true
|
||||
}
|
||||
EOF
|
||||
|
||||
upload_file "$GLOBAL_STATE_TEMP" "/root/.claude.json"
|
||||
rm "$GLOBAL_STATE_TEMP"
|
||||
|
||||
# Create empty CLAUDE.md
|
||||
run_server "touch /root/.claude/CLAUDE.md"
|
||||
|
||||
echo ""
|
||||
log_info "Koyeb service setup completed successfully!"
|
||||
log_info "Service: $KOYEB_SERVICE_NAME (Instance: $KOYEB_INSTANCE_ID)"
|
||||
echo ""
|
||||
|
||||
# 8. Start Claude Code interactively
|
||||
log_warn "Starting Claude Code..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "source /root/.bashrc && claude"
|
||||
306
koyeb/lib/common.sh
Normal file
306
koyeb/lib/common.sh
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
#!/bin/bash
|
||||
# Common bash functions for Koyeb spawn scripts
|
||||
# Uses Koyeb CLI for provisioning and exec access
|
||||
|
||||
# Bash safety flags
|
||||
set -eo pipefail
|
||||
|
||||
# ============================================================
|
||||
# Provider-agnostic functions
|
||||
# ============================================================
|
||||
|
||||
# Source shared provider-agnostic functions (local or remote fallback)
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -n "$SCRIPT_DIR" && -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
|
||||
|
||||
# Note: Provider-agnostic functions (logging, OAuth, browser, nc_listen) are now in shared/common.sh
|
||||
|
||||
# ============================================================
|
||||
# Koyeb specific functions
|
||||
# ============================================================
|
||||
|
||||
# Ensure Koyeb CLI is installed
|
||||
ensure_koyeb_cli() {
|
||||
if command -v koyeb &>/dev/null; then
|
||||
log_info "Koyeb CLI available"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_warn "Installing Koyeb CLI..."
|
||||
|
||||
# Detect OS and architecture
|
||||
local os=""
|
||||
local arch=""
|
||||
|
||||
case "$(uname -s)" in
|
||||
Darwin) os="darwin" ;;
|
||||
Linux) os="linux" ;;
|
||||
*)
|
||||
log_error "Unsupported operating system: $(uname -s)"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) arch="amd64" ;;
|
||||
arm64|aarch64) arch="arm64" ;;
|
||||
*)
|
||||
log_error "Unsupported architecture: $(uname -m)"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
local install_dir="$HOME/.koyeb/bin"
|
||||
mkdir -p "$install_dir"
|
||||
|
||||
local download_url="https://github.com/koyeb/koyeb-cli/releases/latest/download/koyeb-${os}-${arch}"
|
||||
|
||||
if ! curl -fsSL "$download_url" -o "$install_dir/koyeb"; then
|
||||
log_error "Failed to download Koyeb CLI"
|
||||
log_error "Install manually: https://www.koyeb.com/docs/build-and-deploy/cli/installation"
|
||||
return 1
|
||||
fi
|
||||
|
||||
chmod +x "$install_dir/koyeb"
|
||||
export PATH="$install_dir:$PATH"
|
||||
|
||||
if ! command -v koyeb &>/dev/null; then
|
||||
log_error "Koyeb CLI not found in PATH after installation"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Koyeb CLI installed"
|
||||
}
|
||||
|
||||
# Save Koyeb token to config file
|
||||
_save_koyeb_token() {
|
||||
local token="$1"
|
||||
local config_dir="$HOME/.config/spawn"
|
||||
local config_file="$config_dir/koyeb.json"
|
||||
mkdir -p "$config_dir"
|
||||
printf '{\n "token": "%s"\n}\n' "$(json_escape "$token")" > "$config_file"
|
||||
chmod 600 "$config_file"
|
||||
}
|
||||
|
||||
# Ensure KOYEB_TOKEN is available (env var -> config file -> prompt+save)
|
||||
ensure_koyeb_token() {
|
||||
check_python_available || return 1
|
||||
|
||||
# 1. Check environment variable
|
||||
if [[ -n "${KOYEB_TOKEN:-}" ]]; then
|
||||
log_info "Using Koyeb API token from environment"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local config_file="$HOME/.config/spawn/koyeb.json"
|
||||
|
||||
# 2. Check config file
|
||||
if [[ -f "$config_file" ]]; then
|
||||
local saved_token
|
||||
saved_token=$(python3 -c "import json, sys; print(json.load(open(sys.argv[1])).get('token',''))" "$config_file" 2>/dev/null)
|
||||
if [[ -n "$saved_token" ]]; then
|
||||
export KOYEB_TOKEN="$saved_token"
|
||||
log_info "Using Koyeb API token from $config_file"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. Prompt user for token
|
||||
log_warn "Koyeb API token required"
|
||||
echo ""
|
||||
echo "Get your API token at: https://app.koyeb.com/account/api"
|
||||
echo ""
|
||||
|
||||
local token
|
||||
token=$(safe_read "Enter Koyeb API token: ")
|
||||
if [[ -z "$token" ]]; then
|
||||
log_error "No token provided"
|
||||
return 1
|
||||
fi
|
||||
|
||||
export KOYEB_TOKEN="$token"
|
||||
_save_koyeb_token "$token"
|
||||
log_info "Koyeb API token saved"
|
||||
}
|
||||
|
||||
# Generate a unique server name for Koyeb (must be lowercase alphanumeric + hyphens)
|
||||
get_server_name() {
|
||||
local prefix="${1:-spawn}"
|
||||
local timestamp=$(date +%s)
|
||||
local random_suffix=$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n')
|
||||
echo "${prefix}-${timestamp}-${random_suffix}" | tr '[:upper:]' '[:lower:]'
|
||||
}
|
||||
|
||||
# Create a Koyeb app and service
|
||||
# Sets: KOYEB_APP_NAME, KOYEB_SERVICE_NAME, KOYEB_SERVICE_ID
|
||||
create_server() {
|
||||
local name="${1:-$(get_server_name)}"
|
||||
|
||||
# App and service names must be separate
|
||||
KOYEB_APP_NAME="${name}"
|
||||
KOYEB_SERVICE_NAME="${name}-svc"
|
||||
|
||||
log_warn "Creating Koyeb app: $KOYEB_APP_NAME"
|
||||
|
||||
# Create app first
|
||||
if ! koyeb app create "$KOYEB_APP_NAME" >/dev/null 2>&1; then
|
||||
log_error "Failed to create Koyeb app"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_warn "Creating Koyeb service: $KOYEB_SERVICE_NAME"
|
||||
|
||||
# Create service with Ubuntu 24.04 image
|
||||
# Using a long-running command to keep container alive
|
||||
local create_output
|
||||
create_output=$(koyeb service create "$KOYEB_SERVICE_NAME" \
|
||||
--app "$KOYEB_APP_NAME" \
|
||||
--docker ubuntu:24.04 \
|
||||
--regions was \
|
||||
--instance-type nano \
|
||||
--command '["tail"]' \
|
||||
--args '["-f", "/dev/null"]' \
|
||||
2>&1)
|
||||
|
||||
if echo "$create_output" | grep -q "Error"; then
|
||||
log_error "Failed to create Koyeb service"
|
||||
log_error "$create_output"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Extract service ID from output
|
||||
KOYEB_SERVICE_ID=$(echo "$create_output" | grep -oP 'Service \K[a-f0-9-]+' | head -1)
|
||||
|
||||
if [[ -z "$KOYEB_SERVICE_ID" ]]; then
|
||||
# Fallback: try to get it from service list
|
||||
KOYEB_SERVICE_ID=$(koyeb service list --app "$KOYEB_APP_NAME" 2>/dev/null | grep "$KOYEB_SERVICE_NAME" | awk '{print $1}' | head -1)
|
||||
fi
|
||||
|
||||
log_info "Koyeb service created: $KOYEB_SERVICE_NAME (ID: $KOYEB_SERVICE_ID)"
|
||||
|
||||
# Wait for service to be ready
|
||||
log_warn "Waiting for service to deploy..."
|
||||
local max_attempts=60
|
||||
local attempt=0
|
||||
|
||||
while [[ $attempt -lt $max_attempts ]]; do
|
||||
local status
|
||||
status=$(koyeb service get "$KOYEB_SERVICE_ID" 2>/dev/null | grep "Status:" | awk '{print $2}')
|
||||
|
||||
if [[ "$status" == "healthy" || "$status" == "running" ]]; then
|
||||
log_info "Service is ready"
|
||||
break
|
||||
fi
|
||||
|
||||
if [[ "$status" == "error" || "$status" == "failed" ]]; then
|
||||
log_error "Service deployment failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
attempt=$((attempt + 1))
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ $attempt -ge $max_attempts ]]; then
|
||||
log_error "Timeout waiting for service to be ready"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Get instance ID for exec commands
|
||||
KOYEB_INSTANCE_ID=$(koyeb instances list --service "$KOYEB_SERVICE_ID" 2>/dev/null | grep -v "^ID" | awk '{print $1}' | head -1)
|
||||
|
||||
if [[ -z "$KOYEB_INSTANCE_ID" ]]; then
|
||||
log_error "Failed to get instance ID"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Instance ID: $KOYEB_INSTANCE_ID"
|
||||
}
|
||||
|
||||
# Run a command on the Koyeb service instance
|
||||
run_server() {
|
||||
local cmd="$1"
|
||||
|
||||
if [[ -z "$KOYEB_INSTANCE_ID" ]]; then
|
||||
log_error "No instance ID set. Call create_server first."
|
||||
return 1
|
||||
fi
|
||||
|
||||
koyeb instances exec "$KOYEB_INSTANCE_ID" -- bash -c "$cmd"
|
||||
}
|
||||
|
||||
# Upload a file to the Koyeb instance
|
||||
upload_file() {
|
||||
local local_path="$1"
|
||||
local remote_path="$2"
|
||||
|
||||
if [[ ! -f "$local_path" ]]; then
|
||||
log_error "Local file not found: $local_path"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Read file content and encode
|
||||
local content
|
||||
content=$(cat "$local_path" | base64)
|
||||
|
||||
# Write file on remote instance
|
||||
run_server "echo '$content' | base64 -d > '$remote_path'"
|
||||
}
|
||||
|
||||
# Wait for cloud-init or basic system readiness
|
||||
wait_for_cloud_init() {
|
||||
log_warn "Installing base tools..."
|
||||
|
||||
# Update package lists and install essentials
|
||||
run_server "apt-get update -qq && apt-get install -y -qq curl wget git python3 python3-pip build-essential ca-certificates" || {
|
||||
log_error "Failed to install base tools"
|
||||
return 1
|
||||
}
|
||||
|
||||
log_info "Base tools installed"
|
||||
}
|
||||
|
||||
# Inject environment variables into shell config
|
||||
inject_env_vars() {
|
||||
local shell_rc="/root/.bashrc"
|
||||
|
||||
log_warn "Injecting environment variables..."
|
||||
|
||||
for env_var in "$@"; do
|
||||
# Escape special characters for sed
|
||||
local escaped_var=$(echo "$env_var" | sed 's/[&/\]/\\&/g')
|
||||
run_server "echo 'export $escaped_var' >> $shell_rc"
|
||||
done
|
||||
|
||||
log_info "Environment variables configured"
|
||||
}
|
||||
|
||||
# Start an interactive session
|
||||
interactive_session() {
|
||||
local launch_cmd="${1:-bash}"
|
||||
|
||||
if [[ -z "$KOYEB_INSTANCE_ID" ]]; then
|
||||
log_error "No instance ID set. Call create_server first."
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Starting interactive session..."
|
||||
koyeb instances exec "$KOYEB_INSTANCE_ID" -- bash -c "$launch_cmd"
|
||||
}
|
||||
|
||||
# Cleanup: delete the service and app
|
||||
cleanup_server() {
|
||||
if [[ -n "${KOYEB_SERVICE_NAME:-}" ]]; then
|
||||
log_warn "Deleting service: $KOYEB_SERVICE_NAME"
|
||||
koyeb service delete "$KOYEB_SERVICE_NAME" --force >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
if [[ -n "${KOYEB_APP_NAME:-}" ]]; then
|
||||
log_warn "Deleting app: $KOYEB_APP_NAME"
|
||||
koyeb app delete "$KOYEB_APP_NAME" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
81
koyeb/openclaw.sh
Normal file
81
koyeb/openclaw.sh
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#!/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)"
|
||||
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/koyeb/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "OpenClaw on Koyeb"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure Koyeb CLI and API token
|
||||
ensure_koyeb_cli
|
||||
ensure_koyeb_token
|
||||
|
||||
# 2. Create service
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "$SERVER_NAME"
|
||||
|
||||
# 3. Install base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install bun first (required for openclaw)
|
||||
log_warn "Installing bun..."
|
||||
run_server "curl -fsSL https://bun.sh/install | bash"
|
||||
run_server "export PATH=\"\$HOME/.bun/bin:\$PATH\""
|
||||
log_info "Bun installed"
|
||||
|
||||
# 5. Install openclaw via bun
|
||||
log_warn "Installing openclaw..."
|
||||
run_server "source /root/.bashrc && export PATH=\"\$HOME/.bun/bin:\$PATH\" && bun install -g openclaw"
|
||||
log_info "OpenClaw installed"
|
||||
|
||||
# 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
|
||||
|
||||
# Get model preference
|
||||
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "Openclaw") || exit 1
|
||||
|
||||
# 7. Inject environment variables into shell config
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
inject_env_vars \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"ANTHROPIC_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"ANTHROPIC_BASE_URL=https://openrouter.ai/api" \
|
||||
"PATH=\$HOME/.bun/bin:\$PATH"
|
||||
|
||||
# 8. Configure openclaw
|
||||
log_warn "Configuring openclaw..."
|
||||
|
||||
run_server "rm -rf /root/.openclaw && mkdir -p /root/.openclaw"
|
||||
|
||||
# Generate a random gateway token
|
||||
GATEWAY_TOKEN=$(openssl rand -hex 16)
|
||||
|
||||
OPENCLAW_CONFIG_TEMP=$(mktemp)
|
||||
chmod 600 "$OPENCLAW_CONFIG_TEMP"
|
||||
printf '{\n "env": {\n "OPENROUTER_API_KEY": "%s"\n },\n "gateway": {\n "mode": "local",\n "auth": {\n "token": "%s"\n }\n },\n "agents": {\n "defaults": {\n "model": {\n "primary": "openrouter/%s"\n }\n }\n }\n}\n' "$(json_escape "${OPENROUTER_API_KEY}")" "$(json_escape "${GATEWAY_TOKEN}")" "$(json_escape "${MODEL_ID}")" > "$OPENCLAW_CONFIG_TEMP"
|
||||
|
||||
upload_file "$OPENCLAW_CONFIG_TEMP" "/root/.openclaw/openclaw.json"
|
||||
rm "$OPENCLAW_CONFIG_TEMP"
|
||||
|
||||
echo ""
|
||||
log_info "Koyeb service setup completed successfully!"
|
||||
log_info "Service: $KOYEB_SERVICE_NAME (Instance: $KOYEB_INSTANCE_ID)"
|
||||
echo ""
|
||||
|
||||
# 9. Start openclaw gateway in background and launch TUI
|
||||
log_warn "Starting openclaw..."
|
||||
run_server "source /root/.bashrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 &"
|
||||
sleep 2
|
||||
interactive_session "source /root/.bashrc && openclaw tui"
|
||||
|
|
@ -588,6 +588,21 @@
|
|||
"image": "Ubuntu Server 24.04 LTS R5504 UEFI"
|
||||
},
|
||||
"notes": "GPU cloud provider. Competitive pricing on RTX A6000 ($0.50/hr). Requires HYPERSTACK_API_KEY from https://infrahub.hyperstack.cloud"
|
||||
},
|
||||
"koyeb": {
|
||||
"name": "Koyeb",
|
||||
"description": "Koyeb serverless container platform via CLI",
|
||||
"url": "https://www.koyeb.com/",
|
||||
"type": "cli",
|
||||
"auth": "KOYEB_TOKEN",
|
||||
"provision_method": "koyeb service create with docker image",
|
||||
"exec_method": "koyeb instances exec",
|
||||
"interactive_method": "koyeb instances exec (PTY)",
|
||||
"defaults": {
|
||||
"instance_type": "nano",
|
||||
"region": "was"
|
||||
},
|
||||
"notes": "Serverless container platform. Free tier available (no credit card). Per-second billing. Requires koyeb CLI."
|
||||
}
|
||||
},
|
||||
"matrix": {
|
||||
|
|
@ -926,6 +941,20 @@
|
|||
"hyperstack/gptme": "missing",
|
||||
"hyperstack/opencode": "missing",
|
||||
"hyperstack/plandex": "missing",
|
||||
"hyperstack/kilocode": "missing"
|
||||
"hyperstack/kilocode": "missing",
|
||||
"koyeb/claude": "implemented",
|
||||
"koyeb/openclaw": "implemented",
|
||||
"koyeb/nanoclaw": "missing",
|
||||
"koyeb/aider": "implemented",
|
||||
"koyeb/goose": "missing",
|
||||
"koyeb/codex": "missing",
|
||||
"koyeb/interpreter": "missing",
|
||||
"koyeb/gemini": "missing",
|
||||
"koyeb/amazonq": "missing",
|
||||
"koyeb/cline": "missing",
|
||||
"koyeb/gptme": "missing",
|
||||
"koyeb/opencode": "missing",
|
||||
"koyeb/plandex": "missing",
|
||||
"koyeb/kilocode": "missing"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue