mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-20 01:11:18 +00:00
feat: Add Kamatera cloud provider with all 13 agents (#96)
Adds Kamatera (25+ global datacenters, REST API, hourly billing) as a new cloud provider. Implements all 13 agent scripts with full lifecycle: create, wait, destroy, SSH, upload. Agent: team-lead Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a49c0874a9
commit
c7db033796
16 changed files with 1281 additions and 1 deletions
108
kamatera/README.md
Normal file
108
kamatera/README.md
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
# Kamatera
|
||||
|
||||
Kamatera cloud servers via REST API. [Kamatera](https://www.kamatera.com/)
|
||||
|
||||
## Agents
|
||||
|
||||
#### Claude Code
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/kamatera/claude.sh)
|
||||
```
|
||||
|
||||
#### OpenClaw
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/kamatera/openclaw.sh)
|
||||
```
|
||||
|
||||
#### NanoClaw
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/kamatera/nanoclaw.sh)
|
||||
```
|
||||
|
||||
#### Aider
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/kamatera/aider.sh)
|
||||
```
|
||||
|
||||
#### Goose
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/kamatera/goose.sh)
|
||||
```
|
||||
|
||||
#### Codex CLI
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/kamatera/codex.sh)
|
||||
```
|
||||
|
||||
#### Open Interpreter
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/kamatera/interpreter.sh)
|
||||
```
|
||||
|
||||
#### Gemini CLI
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/kamatera/gemini.sh)
|
||||
```
|
||||
|
||||
#### Amazon Q CLI
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/kamatera/amazonq.sh)
|
||||
```
|
||||
|
||||
#### Cline
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/kamatera/cline.sh)
|
||||
```
|
||||
|
||||
#### gptme
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/kamatera/gptme.sh)
|
||||
```
|
||||
|
||||
#### OpenCode
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/kamatera/opencode.sh)
|
||||
```
|
||||
|
||||
#### Plandex
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/kamatera/plandex.sh)
|
||||
```
|
||||
|
||||
## Non-Interactive Mode
|
||||
|
||||
```bash
|
||||
KAMATERA_SERVER_NAME=dev-mk1 \
|
||||
KAMATERA_API_CLIENT_ID=your-client-id \
|
||||
KAMATERA_API_SECRET=your-api-secret \
|
||||
OPENROUTER_API_KEY=sk-or-v1-xxxxx \
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/kamatera/claude.sh)
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|---|---|---|
|
||||
| `KAMATERA_API_CLIENT_ID` | Kamatera API Client ID | _(prompted)_ |
|
||||
| `KAMATERA_API_SECRET` | Kamatera API Secret | _(prompted)_ |
|
||||
| `KAMATERA_SERVER_NAME` | Server name | _(prompted)_ |
|
||||
| `KAMATERA_DATACENTER` | Datacenter location | `EU` |
|
||||
| `KAMATERA_CPU` | CPU type and cores (e.g., `2B`) | `2B` |
|
||||
| `KAMATERA_RAM` | RAM in MB | `2048` |
|
||||
| `KAMATERA_DISK` | Disk configuration | `size=20` |
|
||||
| `KAMATERA_IMAGE` | OS image | `ubuntu_server_24.04_64-bit` |
|
||||
| `KAMATERA_BILLING` | Billing cycle (`hourly` or `monthly`) | `hourly` |
|
||||
| `OPENROUTER_API_KEY` | OpenRouter API key | _(prompted via OAuth)_ |
|
||||
50
kamatera/aider.sh
Normal file
50
kamatera/aider.sh
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
# shellcheck source=kamatera/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/kamatera/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Aider on Kamatera"
|
||||
echo ""
|
||||
|
||||
ensure_kamatera_token
|
||||
generate_ssh_key_if_missing "${HOME}/.ssh/id_ed25519"
|
||||
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
verify_server_connectivity "${KAMATERA_SERVER_IP}"
|
||||
|
||||
log_warn "Waiting for init script to complete..."
|
||||
generic_ssh_wait "root" "${KAMATERA_SERVER_IP}" "${SSH_OPTS} -o ConnectTimeout=5" "test -f /root/.cloud-init-complete" "init script" 60 5
|
||||
|
||||
log_warn "Installing Aider..."
|
||||
run_server "${KAMATERA_SERVER_IP}" "pip install aider-chat 2>/dev/null || pip3 install aider-chat"
|
||||
log_info "Aider installed"
|
||||
|
||||
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
|
||||
|
||||
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "Aider") || exit 1
|
||||
|
||||
log_warn "Setting up environment variables..."
|
||||
inject_env_vars_ssh "${KAMATERA_SERVER_IP}" upload_file run_server \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
|
||||
echo ""
|
||||
log_info "Kamatera server setup completed successfully!"
|
||||
log_info "Server: ${SERVER_NAME} (IP: ${KAMATERA_SERVER_IP})"
|
||||
echo ""
|
||||
|
||||
log_warn "Starting Aider..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "${KAMATERA_SERVER_IP}" "source ~/.zshrc && aider --model openrouter/${MODEL_ID}"
|
||||
50
kamatera/amazonq.sh
Normal file
50
kamatera/amazonq.sh
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
# shellcheck source=kamatera/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/kamatera/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Amazon Q CLI on Kamatera"
|
||||
echo ""
|
||||
|
||||
ensure_kamatera_token
|
||||
generate_ssh_key_if_missing "${HOME}/.ssh/id_ed25519"
|
||||
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
verify_server_connectivity "${KAMATERA_SERVER_IP}"
|
||||
|
||||
log_warn "Waiting for init script to complete..."
|
||||
generic_ssh_wait "root" "${KAMATERA_SERVER_IP}" "${SSH_OPTS} -o ConnectTimeout=5" "test -f /root/.cloud-init-complete" "init script" 60 5
|
||||
|
||||
log_warn "Installing Amazon Q CLI..."
|
||||
run_server "${KAMATERA_SERVER_IP}" "curl -fsSL https://desktop-release.q.us-east-1.amazonaws.com/latest/amazon-q-cli-install.sh | bash"
|
||||
log_info "Amazon Q CLI installed"
|
||||
|
||||
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 "${KAMATERA_SERVER_IP}" upload_file run_server \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
|
||||
echo ""
|
||||
log_info "Kamatera server setup completed successfully!"
|
||||
log_info "Server: ${SERVER_NAME} (IP: ${KAMATERA_SERVER_IP})"
|
||||
echo ""
|
||||
|
||||
log_warn "Starting Amazon Q..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "${KAMATERA_SERVER_IP}" "source ~/.zshrc && q chat"
|
||||
60
kamatera/claude.sh
Normal file
60
kamatera/claude.sh
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
# shellcheck source=kamatera/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/kamatera/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Claude Code on Kamatera"
|
||||
echo ""
|
||||
|
||||
ensure_kamatera_token
|
||||
generate_ssh_key_if_missing "${HOME}/.ssh/id_ed25519"
|
||||
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
verify_server_connectivity "${KAMATERA_SERVER_IP}"
|
||||
|
||||
log_warn "Waiting for init script to complete..."
|
||||
generic_ssh_wait "root" "${KAMATERA_SERVER_IP}" "${SSH_OPTS} -o ConnectTimeout=5" "test -f /root/.cloud-init-complete" "init script" 60 5
|
||||
|
||||
log_warn "Verifying Claude Code installation..."
|
||||
if ! run_server "${KAMATERA_SERVER_IP}" "command -v claude" >/dev/null 2>&1; then
|
||||
log_warn "Claude Code not found, installing manually..."
|
||||
run_server "${KAMATERA_SERVER_IP}" "curl -fsSL https://claude.ai/install.sh | bash"
|
||||
fi
|
||||
log_info "Claude Code is installed"
|
||||
|
||||
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 "${KAMATERA_SERVER_IP}" upload_file run_server \
|
||||
"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"
|
||||
|
||||
setup_claude_code_config "${OPENROUTER_API_KEY}" \
|
||||
"upload_file ${KAMATERA_SERVER_IP}" \
|
||||
"run_server ${KAMATERA_SERVER_IP}"
|
||||
|
||||
echo ""
|
||||
log_info "Kamatera server setup completed successfully!"
|
||||
log_info "Server: ${SERVER_NAME} (IP: ${KAMATERA_SERVER_IP})"
|
||||
echo ""
|
||||
|
||||
log_warn "Starting Claude Code..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "${KAMATERA_SERVER_IP}" "source ~/.zshrc && claude"
|
||||
50
kamatera/cline.sh
Normal file
50
kamatera/cline.sh
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
# shellcheck source=kamatera/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/kamatera/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Cline on Kamatera"
|
||||
echo ""
|
||||
|
||||
ensure_kamatera_token
|
||||
generate_ssh_key_if_missing "${HOME}/.ssh/id_ed25519"
|
||||
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
verify_server_connectivity "${KAMATERA_SERVER_IP}"
|
||||
|
||||
log_warn "Waiting for init script to complete..."
|
||||
generic_ssh_wait "root" "${KAMATERA_SERVER_IP}" "${SSH_OPTS} -o ConnectTimeout=5" "test -f /root/.cloud-init-complete" "init script" 60 5
|
||||
|
||||
log_warn "Installing Cline..."
|
||||
run_server "${KAMATERA_SERVER_IP}" "npm install -g cline"
|
||||
log_info "Cline installed"
|
||||
|
||||
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 "${KAMATERA_SERVER_IP}" upload_file run_server \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
|
||||
echo ""
|
||||
log_info "Kamatera server setup completed successfully!"
|
||||
log_info "Server: ${SERVER_NAME} (IP: ${KAMATERA_SERVER_IP})"
|
||||
echo ""
|
||||
|
||||
log_warn "Starting Cline..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "${KAMATERA_SERVER_IP}" "source ~/.zshrc && cline"
|
||||
50
kamatera/codex.sh
Normal file
50
kamatera/codex.sh
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
# shellcheck source=kamatera/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/kamatera/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Codex CLI on Kamatera"
|
||||
echo ""
|
||||
|
||||
ensure_kamatera_token
|
||||
generate_ssh_key_if_missing "${HOME}/.ssh/id_ed25519"
|
||||
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
verify_server_connectivity "${KAMATERA_SERVER_IP}"
|
||||
|
||||
log_warn "Waiting for init script to complete..."
|
||||
generic_ssh_wait "root" "${KAMATERA_SERVER_IP}" "${SSH_OPTS} -o ConnectTimeout=5" "test -f /root/.cloud-init-complete" "init script" 60 5
|
||||
|
||||
log_warn "Installing Codex CLI..."
|
||||
run_server "${KAMATERA_SERVER_IP}" "npm install -g @openai/codex"
|
||||
log_info "Codex CLI installed"
|
||||
|
||||
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 "${KAMATERA_SERVER_IP}" upload_file run_server \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
|
||||
echo ""
|
||||
log_info "Kamatera server setup completed successfully!"
|
||||
log_info "Server: ${SERVER_NAME} (IP: ${KAMATERA_SERVER_IP})"
|
||||
echo ""
|
||||
|
||||
log_warn "Starting Codex..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "${KAMATERA_SERVER_IP}" "source ~/.zshrc && codex"
|
||||
51
kamatera/gemini.sh
Normal file
51
kamatera/gemini.sh
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
# shellcheck source=kamatera/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/kamatera/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Gemini CLI on Kamatera"
|
||||
echo ""
|
||||
|
||||
ensure_kamatera_token
|
||||
generate_ssh_key_if_missing "${HOME}/.ssh/id_ed25519"
|
||||
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
verify_server_connectivity "${KAMATERA_SERVER_IP}"
|
||||
|
||||
log_warn "Waiting for init script to complete..."
|
||||
generic_ssh_wait "root" "${KAMATERA_SERVER_IP}" "${SSH_OPTS} -o ConnectTimeout=5" "test -f /root/.cloud-init-complete" "init script" 60 5
|
||||
|
||||
log_warn "Installing Gemini CLI..."
|
||||
run_server "${KAMATERA_SERVER_IP}" "npm install -g @google/gemini-cli"
|
||||
log_info "Gemini CLI installed"
|
||||
|
||||
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 "${KAMATERA_SERVER_IP}" upload_file run_server \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"GEMINI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
|
||||
echo ""
|
||||
log_info "Kamatera server setup completed successfully!"
|
||||
log_info "Server: ${SERVER_NAME} (IP: ${KAMATERA_SERVER_IP})"
|
||||
echo ""
|
||||
|
||||
log_warn "Starting Gemini..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "${KAMATERA_SERVER_IP}" "source ~/.zshrc && gemini"
|
||||
49
kamatera/goose.sh
Normal file
49
kamatera/goose.sh
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
# shellcheck source=kamatera/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/kamatera/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Goose on Kamatera"
|
||||
echo ""
|
||||
|
||||
ensure_kamatera_token
|
||||
generate_ssh_key_if_missing "${HOME}/.ssh/id_ed25519"
|
||||
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
verify_server_connectivity "${KAMATERA_SERVER_IP}"
|
||||
|
||||
log_warn "Waiting for init script to complete..."
|
||||
generic_ssh_wait "root" "${KAMATERA_SERVER_IP}" "${SSH_OPTS} -o ConnectTimeout=5" "test -f /root/.cloud-init-complete" "init script" 60 5
|
||||
|
||||
log_warn "Installing Goose..."
|
||||
run_server "${KAMATERA_SERVER_IP}" "CONFIGURE=false curl -fsSL https://github.com/block/goose/releases/latest/download/download_cli.sh | bash"
|
||||
log_info "Goose installed"
|
||||
|
||||
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 "${KAMATERA_SERVER_IP}" upload_file run_server \
|
||||
"GOOSE_PROVIDER=openrouter" \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
|
||||
echo ""
|
||||
log_info "Kamatera server setup completed successfully!"
|
||||
log_info "Server: ${SERVER_NAME} (IP: ${KAMATERA_SERVER_IP})"
|
||||
echo ""
|
||||
|
||||
log_warn "Starting Goose..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "${KAMATERA_SERVER_IP}" "source ~/.zshrc && goose"
|
||||
50
kamatera/gptme.sh
Normal file
50
kamatera/gptme.sh
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
# shellcheck source=kamatera/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/kamatera/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "gptme on Kamatera"
|
||||
echo ""
|
||||
|
||||
ensure_kamatera_token
|
||||
generate_ssh_key_if_missing "${HOME}/.ssh/id_ed25519"
|
||||
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
verify_server_connectivity "${KAMATERA_SERVER_IP}"
|
||||
|
||||
log_warn "Waiting for init script to complete..."
|
||||
generic_ssh_wait "root" "${KAMATERA_SERVER_IP}" "${SSH_OPTS} -o ConnectTimeout=5" "test -f /root/.cloud-init-complete" "init script" 60 5
|
||||
|
||||
log_warn "Installing gptme..."
|
||||
run_server "${KAMATERA_SERVER_IP}" "pip install gptme 2>/dev/null || pip3 install gptme"
|
||||
log_info "gptme installed"
|
||||
|
||||
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
|
||||
|
||||
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "gptme") || exit 1
|
||||
|
||||
log_warn "Setting up environment variables..."
|
||||
inject_env_vars_ssh "${KAMATERA_SERVER_IP}" upload_file run_server \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
|
||||
echo ""
|
||||
log_info "Kamatera server setup completed successfully!"
|
||||
log_info "Server: ${SERVER_NAME} (IP: ${KAMATERA_SERVER_IP})"
|
||||
echo ""
|
||||
|
||||
log_warn "Starting gptme..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "${KAMATERA_SERVER_IP}" "source ~/.zshrc && gptme -m openrouter/${MODEL_ID}"
|
||||
50
kamatera/interpreter.sh
Normal file
50
kamatera/interpreter.sh
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
# shellcheck source=kamatera/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/kamatera/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Open Interpreter on Kamatera"
|
||||
echo ""
|
||||
|
||||
ensure_kamatera_token
|
||||
generate_ssh_key_if_missing "${HOME}/.ssh/id_ed25519"
|
||||
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
verify_server_connectivity "${KAMATERA_SERVER_IP}"
|
||||
|
||||
log_warn "Waiting for init script to complete..."
|
||||
generic_ssh_wait "root" "${KAMATERA_SERVER_IP}" "${SSH_OPTS} -o ConnectTimeout=5" "test -f /root/.cloud-init-complete" "init script" 60 5
|
||||
|
||||
log_warn "Installing Open Interpreter..."
|
||||
run_server "${KAMATERA_SERVER_IP}" "pip install open-interpreter 2>/dev/null || pip3 install open-interpreter"
|
||||
log_info "Open Interpreter installed"
|
||||
|
||||
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 "${KAMATERA_SERVER_IP}" upload_file run_server \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
|
||||
echo ""
|
||||
log_info "Kamatera server setup completed successfully!"
|
||||
log_info "Server: ${SERVER_NAME} (IP: ${KAMATERA_SERVER_IP})"
|
||||
echo ""
|
||||
|
||||
log_warn "Starting Open Interpreter..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "${KAMATERA_SERVER_IP}" "source ~/.zshrc && interpreter"
|
||||
471
kamatera/lib/common.sh
Normal file
471
kamatera/lib/common.sh
Normal file
|
|
@ -0,0 +1,471 @@
|
|||
#!/bin/bash
|
||||
# Common bash functions for Kamatera spawn scripts
|
||||
|
||||
# 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
|
||||
|
||||
# ============================================================
|
||||
# Kamatera specific functions
|
||||
# ============================================================
|
||||
|
||||
readonly KAMATERA_API_BASE="https://cloudcli.cloudwm.com"
|
||||
|
||||
# Configurable timeout/delay constants
|
||||
INSTANCE_STATUS_POLL_DELAY=${INSTANCE_STATUS_POLL_DELAY:-5}
|
||||
KAMATERA_COMMAND_TIMEOUT=${KAMATERA_COMMAND_TIMEOUT:-600} # 10 minutes for async commands
|
||||
|
||||
# Kamatera API wrapper - uses AuthClientId/AuthSecret headers instead of Bearer token
|
||||
kamatera_api() {
|
||||
local method="$1"
|
||||
local endpoint="$2"
|
||||
local body="${3:-}"
|
||||
local max_retries="${4:-3}"
|
||||
|
||||
local attempt=1
|
||||
local interval=2
|
||||
local max_interval=30
|
||||
|
||||
while [[ "$attempt" -le "$max_retries" ]]; do
|
||||
local args=(
|
||||
-s
|
||||
-w "\n%{http_code}"
|
||||
-X "$method"
|
||||
-H "AuthClientId: ${KAMATERA_API_CLIENT_ID}"
|
||||
-H "AuthSecret: ${KAMATERA_API_SECRET}"
|
||||
-H "Content-Type: application/json"
|
||||
)
|
||||
|
||||
if [[ -n "$body" ]]; then
|
||||
args+=(-d "$body")
|
||||
fi
|
||||
|
||||
local response
|
||||
response=$(curl "${args[@]}" "${KAMATERA_API_BASE}${endpoint}" 2>&1)
|
||||
local curl_exit_code=$?
|
||||
|
||||
local http_code
|
||||
http_code=$(printf '%s' "$response" | tail -1)
|
||||
local response_body
|
||||
response_body=$(printf '%s' "$response" | head -n -1)
|
||||
|
||||
if [[ "$curl_exit_code" -ne 0 ]]; then
|
||||
if ! _api_should_retry_on_error "$attempt" "$max_retries" "$interval" "$max_interval" "Kamatera API network error"; then
|
||||
log_error "Kamatera API network error after $max_retries attempts"
|
||||
return 1
|
||||
fi
|
||||
interval=$((interval * 2))
|
||||
if [[ "$interval" -gt "$max_interval" ]]; then
|
||||
interval="$max_interval"
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$http_code" == "429" ]] || [[ "$http_code" == "503" ]]; then
|
||||
if ! _api_handle_transient_http_error "$http_code" "$attempt" "$max_retries" "$interval" "$max_interval"; then
|
||||
printf '%s' "$response_body"
|
||||
return 1
|
||||
fi
|
||||
interval=$((interval * 2))
|
||||
if [[ "$interval" -gt "$max_interval" ]]; then
|
||||
interval="$max_interval"
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
printf '%s' "$response_body"
|
||||
return 0
|
||||
done
|
||||
|
||||
log_error "Kamatera API retry logic exhausted"
|
||||
return 1
|
||||
}
|
||||
|
||||
ensure_kamatera_token() {
|
||||
check_python_available || return 1
|
||||
|
||||
if [[ -n "${KAMATERA_API_CLIENT_ID:-}" ]] && [[ -n "${KAMATERA_API_SECRET:-}" ]]; then
|
||||
log_info "Using Kamatera API credentials from environment"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local config_dir="$HOME/.config/spawn"
|
||||
local config_file="$config_dir/kamatera.json"
|
||||
|
||||
if [[ -f "$config_file" ]]; then
|
||||
local saved_client_id saved_secret
|
||||
saved_client_id=$(python3 -c "import json, sys; print(json.load(open(sys.argv[1])).get('api_client_id',''))" "$config_file" 2>/dev/null)
|
||||
saved_secret=$(python3 -c "import json, sys; print(json.load(open(sys.argv[1])).get('api_secret',''))" "$config_file" 2>/dev/null)
|
||||
if [[ -n "$saved_client_id" ]] && [[ -n "$saved_secret" ]]; then
|
||||
export KAMATERA_API_CLIENT_ID="$saved_client_id"
|
||||
export KAMATERA_API_SECRET="$saved_secret"
|
||||
log_info "Using Kamatera API credentials from $config_file"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_warn "Kamatera API Credentials Required"
|
||||
log_warn "Get your API keys from: https://console.kamatera.com/keys"
|
||||
echo ""
|
||||
|
||||
local client_id
|
||||
client_id=$(validated_read "Enter your Kamatera API Client ID: " validate_api_token) || return 1
|
||||
|
||||
local secret
|
||||
secret=$(validated_read "Enter your Kamatera API Secret: " validate_api_token) || return 1
|
||||
|
||||
export KAMATERA_API_CLIENT_ID="$client_id"
|
||||
export KAMATERA_API_SECRET="$secret"
|
||||
|
||||
# Validate credentials by listing server options (lightweight call)
|
||||
local response
|
||||
response=$(kamatera_api POST "/service/server/info" '{"name":"__test__"}')
|
||||
# A valid response (even if no server found) means auth succeeded
|
||||
# An auth error returns a specific error message
|
||||
if printf '%s' "$response" | grep -qi "authentication failed\|unauthorized\|invalid.*auth"; then
|
||||
log_error "Authentication failed: Invalid Kamatera API credentials"
|
||||
log_warn "Remediation steps:"
|
||||
log_warn " 1. Verify credentials at: https://console.kamatera.com/keys"
|
||||
log_warn " 2. Ensure the API key has appropriate permissions"
|
||||
unset KAMATERA_API_CLIENT_ID
|
||||
unset KAMATERA_API_SECRET
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "API credentials validated"
|
||||
|
||||
mkdir -p "$config_dir"
|
||||
cat > "$config_file" << EOF
|
||||
{
|
||||
"api_client_id": "$client_id",
|
||||
"api_secret": "$secret"
|
||||
}
|
||||
EOF
|
||||
chmod 600 "$config_file"
|
||||
log_info "API credentials saved to $config_file"
|
||||
}
|
||||
|
||||
get_server_name() {
|
||||
local server_name
|
||||
server_name=$(get_resource_name "KAMATERA_SERVER_NAME" "Enter server name: ") || return 1
|
||||
|
||||
if ! validate_server_name "$server_name"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$server_name"
|
||||
}
|
||||
|
||||
# Wait for an async Kamatera command to complete
|
||||
# Kamatera API returns command IDs for long-running operations
|
||||
# We poll the queue endpoint until the command completes
|
||||
# Usage: wait_for_command COMMAND_IDS [TIMEOUT]
|
||||
wait_for_command() {
|
||||
local command_ids="$1"
|
||||
local timeout="${2:-$KAMATERA_COMMAND_TIMEOUT}"
|
||||
|
||||
local elapsed=0
|
||||
log_warn "Waiting for Kamatera command to complete (timeout: ${timeout}s)..."
|
||||
|
||||
while [[ "$elapsed" -lt "$timeout" ]]; do
|
||||
local queue_response
|
||||
queue_response=$(kamatera_api GET "/service/queue?id=${command_ids}")
|
||||
|
||||
local status
|
||||
status=$(python3 -c "
|
||||
import json, sys
|
||||
data = json.loads(sys.stdin.read())
|
||||
if isinstance(data, list) and len(data) > 0:
|
||||
print(data[0].get('status', ''))
|
||||
elif isinstance(data, dict):
|
||||
print(data.get('status', ''))
|
||||
else:
|
||||
print('')
|
||||
" <<< "$queue_response" 2>/dev/null)
|
||||
|
||||
if [[ "$status" == "complete" ]]; then
|
||||
log_info "Command completed successfully"
|
||||
printf '%s' "$queue_response"
|
||||
return 0
|
||||
elif [[ "$status" == "error" ]]; then
|
||||
local error_log
|
||||
error_log=$(python3 -c "
|
||||
import json, sys
|
||||
data = json.loads(sys.stdin.read())
|
||||
if isinstance(data, list) and len(data) > 0:
|
||||
print(data[0].get('log', 'Unknown error'))
|
||||
elif isinstance(data, dict):
|
||||
print(data.get('log', 'Unknown error'))
|
||||
" <<< "$queue_response" 2>/dev/null)
|
||||
log_error "Command failed: $error_log"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_warn "Command status: ${status:-pending} (elapsed: ${elapsed}s)"
|
||||
sleep "$INSTANCE_STATUS_POLL_DELAY"
|
||||
elapsed=$((elapsed + INSTANCE_STATUS_POLL_DELAY))
|
||||
done
|
||||
|
||||
log_error "Command timed out after ${timeout}s"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Generate a random password meeting Kamatera requirements
|
||||
# (10-20 chars, uppercase, lowercase, digit)
|
||||
generate_server_password() {
|
||||
local password
|
||||
if command -v openssl &>/dev/null; then
|
||||
password="Sp$(openssl rand -hex 8)1"
|
||||
elif [[ -r /dev/urandom ]]; then
|
||||
password="Sp$(od -An -N8 -tx1 /dev/urandom | tr -d ' \n')1"
|
||||
else
|
||||
password="Sp$(date +%s)Rn1"
|
||||
fi
|
||||
printf '%s' "$password"
|
||||
}
|
||||
|
||||
create_server() {
|
||||
local name="$1"
|
||||
local datacenter="${KAMATERA_DATACENTER:-EU}"
|
||||
local cpu="${KAMATERA_CPU:-2B}"
|
||||
local ram="${KAMATERA_RAM:-2048}"
|
||||
local disk="${KAMATERA_DISK:-size=20}"
|
||||
local image="${KAMATERA_IMAGE:-ubuntu_server_24.04_64-bit}"
|
||||
local billing="${KAMATERA_BILLING:-hourly}"
|
||||
|
||||
log_warn "Creating Kamatera server '$name' (datacenter: $datacenter, cpu: $cpu, ram: ${ram}MB)..."
|
||||
|
||||
# Generate password for the server
|
||||
local password
|
||||
password=$(generate_server_password)
|
||||
|
||||
# Read SSH public key if available
|
||||
local ssh_key=""
|
||||
local pub_path="${HOME}/.ssh/id_ed25519.pub"
|
||||
if [[ -f "$pub_path" ]]; then
|
||||
ssh_key=$(cat "$pub_path")
|
||||
fi
|
||||
|
||||
# Build init script
|
||||
local script_content
|
||||
script_content=$(cat << 'INIT_EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq curl unzip git zsh
|
||||
# Install Bun
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
# Install Claude Code
|
||||
curl -fsSL https://claude.ai/install.sh | bash
|
||||
# Configure PATH
|
||||
echo 'export PATH="${HOME}/.claude/local/bin:${HOME}/.bun/bin:${PATH}"' >> /root/.bashrc
|
||||
echo 'export PATH="${HOME}/.claude/local/bin:${HOME}/.bun/bin:${PATH}"' >> /root/.zshrc
|
||||
# Signal completion
|
||||
touch /root/.cloud-init-complete
|
||||
INIT_EOF
|
||||
)
|
||||
|
||||
local body
|
||||
body=$(python3 -c "
|
||||
import json, sys
|
||||
ssh_key = sys.stdin.read().strip()
|
||||
body = {
|
||||
'name': '$name',
|
||||
'password': '$password',
|
||||
'passwordValidate': '$password',
|
||||
'ssh-key': ssh_key,
|
||||
'datacenter': '$datacenter',
|
||||
'image': '$image',
|
||||
'cpu': '$cpu',
|
||||
'ram': $ram,
|
||||
'disk': '$disk',
|
||||
'dailybackup': 'no',
|
||||
'managed': 'no',
|
||||
'network': 'name=wan,ip=auto',
|
||||
'quantity': 1,
|
||||
'billingcycle': '$billing',
|
||||
'poweronaftercreate': 'yes',
|
||||
'script-file': '''$(printf '%s' "$script_content" | sed "s/'/'\\''/g")'''
|
||||
}
|
||||
print(json.dumps(body))
|
||||
" <<< "$ssh_key")
|
||||
|
||||
local response
|
||||
response=$(kamatera_api POST "/service/server" "$body")
|
||||
|
||||
# Parse command ID from response (Kamatera returns array of command IDs)
|
||||
local command_ids
|
||||
command_ids=$(python3 -c "
|
||||
import json, sys
|
||||
data = json.loads(sys.stdin.read())
|
||||
if isinstance(data, list):
|
||||
print(','.join(str(x) for x in data))
|
||||
elif isinstance(data, (int, float)):
|
||||
print(int(data))
|
||||
else:
|
||||
print(data)
|
||||
" <<< "$response" 2>/dev/null)
|
||||
|
||||
if [[ -z "$command_ids" ]]; then
|
||||
log_error "Failed to create Kamatera server"
|
||||
log_error "API Response: $response"
|
||||
log_warn "Common issues:"
|
||||
log_warn " - Insufficient account balance"
|
||||
log_warn " - Datacenter unavailable (try different KAMATERA_DATACENTER)"
|
||||
log_warn " - Invalid image name"
|
||||
log_warn "Remediation: Check https://console.kamatera.com/"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Server creation command submitted: $command_ids"
|
||||
|
||||
# Wait for the command to complete
|
||||
local queue_result
|
||||
queue_result=$(wait_for_command "$command_ids" 600) || return 1
|
||||
|
||||
# Extract server name from the completed command
|
||||
KAMATERA_SERVER_NAME_ACTUAL="$name"
|
||||
export KAMATERA_SERVER_NAME_ACTUAL
|
||||
|
||||
# Get server info to retrieve IP address
|
||||
log_warn "Retrieving server IP address..."
|
||||
local max_info_attempts=30
|
||||
local info_attempt=1
|
||||
while [[ "$info_attempt" -le "$max_info_attempts" ]]; do
|
||||
local info_response
|
||||
info_response=$(kamatera_api POST "/service/server/info" "{\"name\":\"$name\"}")
|
||||
|
||||
KAMATERA_SERVER_IP=$(python3 -c "
|
||||
import json, sys
|
||||
data = json.loads(sys.stdin.read())
|
||||
if isinstance(data, list) and len(data) > 0:
|
||||
server = data[0]
|
||||
else:
|
||||
server = data
|
||||
networks = server.get('networks', [])
|
||||
for net in networks:
|
||||
net_name = net.get('network', '')
|
||||
if net_name.startswith('wan'):
|
||||
ips = net.get('ips', [])
|
||||
if ips:
|
||||
print(ips[0])
|
||||
sys.exit(0)
|
||||
# Fallback: try power_on field or any IP
|
||||
power = server.get('power', '')
|
||||
if power == 'on':
|
||||
for net in networks:
|
||||
ips = net.get('ips', [])
|
||||
if ips:
|
||||
print(ips[0])
|
||||
sys.exit(0)
|
||||
" <<< "$info_response" 2>/dev/null)
|
||||
|
||||
if [[ -n "$KAMATERA_SERVER_IP" ]]; then
|
||||
export KAMATERA_SERVER_IP
|
||||
log_info "Server active: IP=$KAMATERA_SERVER_IP"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_warn "Waiting for server IP... (attempt $info_attempt/$max_info_attempts)"
|
||||
sleep "$INSTANCE_STATUS_POLL_DELAY"
|
||||
info_attempt=$((info_attempt + 1))
|
||||
done
|
||||
|
||||
log_error "Failed to retrieve server IP address"
|
||||
return 1
|
||||
}
|
||||
|
||||
verify_server_connectivity() {
|
||||
local ip="$1"
|
||||
local max_attempts=${2:-30}
|
||||
# shellcheck disable=SC2154
|
||||
generic_ssh_wait "root" "$ip" "$SSH_OPTS -o ConnectTimeout=5" "echo ok" "SSH connectivity" "$max_attempts" 5
|
||||
}
|
||||
|
||||
run_server() {
|
||||
local ip="$1"; local cmd="$2"
|
||||
# shellcheck disable=SC2086
|
||||
ssh $SSH_OPTS "root@$ip" "$cmd"
|
||||
}
|
||||
|
||||
upload_file() {
|
||||
local ip="$1"; local local_path="$2"; local remote_path="$3"
|
||||
# shellcheck disable=SC2086
|
||||
scp $SSH_OPTS "$local_path" "root@$ip:$remote_path"
|
||||
}
|
||||
|
||||
interactive_session() {
|
||||
local ip="$1"; local cmd="$2"
|
||||
ssh -t $SSH_OPTS "root@$ip" "$cmd"
|
||||
}
|
||||
|
||||
destroy_server() {
|
||||
local server_name="$1"
|
||||
log_warn "Terminating server $server_name..."
|
||||
local response
|
||||
response=$(kamatera_api POST "/service/server/terminate" "{\"name\":\"$server_name\",\"force\":true}")
|
||||
|
||||
# Parse command ID and wait for completion
|
||||
local command_ids
|
||||
command_ids=$(python3 -c "
|
||||
import json, sys
|
||||
data = json.loads(sys.stdin.read())
|
||||
if isinstance(data, list):
|
||||
print(','.join(str(x) for x in data))
|
||||
elif isinstance(data, (int, float)):
|
||||
print(int(data))
|
||||
else:
|
||||
print(data)
|
||||
" <<< "$response" 2>/dev/null)
|
||||
|
||||
if [[ -n "$command_ids" ]]; then
|
||||
wait_for_command "$command_ids" 120 || true
|
||||
fi
|
||||
log_info "Server $server_name terminated"
|
||||
}
|
||||
|
||||
list_servers() {
|
||||
local response
|
||||
response=$(kamatera_api POST "/service/server/info" '{}')
|
||||
python3 -c "
|
||||
import json, sys
|
||||
data = json.loads(sys.stdin.read())
|
||||
if not isinstance(data, list):
|
||||
data = [data] if data else []
|
||||
if not data:
|
||||
print('No servers found')
|
||||
sys.exit(0)
|
||||
print(f\"{'NAME':<25} {'DATACENTER':<15} {'POWER':<10} {'IP':<16} {'CPU':<8} {'RAM':<8}\")
|
||||
print('-' * 82)
|
||||
for s in data:
|
||||
name = s.get('name', 'N/A')
|
||||
dc = s.get('datacenter', 'N/A')
|
||||
power = s.get('power', 'N/A')
|
||||
ip = 'N/A'
|
||||
for net in s.get('networks', []):
|
||||
if net.get('network', '').startswith('wan'):
|
||||
ips = net.get('ips', [])
|
||||
if ips:
|
||||
ip = ips[0]
|
||||
break
|
||||
cpu = s.get('cpu', 'N/A')
|
||||
ram = s.get('ram', 'N/A')
|
||||
print(f'{name:<25} {dc:<15} {power:<10} {ip:<16} {str(cpu):<8} {str(ram):<8}')
|
||||
" <<< "$response"
|
||||
}
|
||||
53
kamatera/nanoclaw.sh
Normal file
53
kamatera/nanoclaw.sh
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
# shellcheck source=kamatera/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/kamatera/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "NanoClaw on Kamatera"
|
||||
echo ""
|
||||
|
||||
ensure_kamatera_token
|
||||
generate_ssh_key_if_missing "${HOME}/.ssh/id_ed25519"
|
||||
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
verify_server_connectivity "${KAMATERA_SERVER_IP}"
|
||||
|
||||
log_warn "Waiting for init script to complete..."
|
||||
generic_ssh_wait "root" "${KAMATERA_SERVER_IP}" "${SSH_OPTS} -o ConnectTimeout=5" "test -f /root/.cloud-init-complete" "init script" 60 5
|
||||
|
||||
log_warn "Installing NanoClaw..."
|
||||
run_server "${KAMATERA_SERVER_IP}" "source ~/.bashrc && npm install -g tsx && git clone https://github.com/gavrielc/nanoclaw.git ~/nanoclaw && cd ~/nanoclaw && npm install && npm run build"
|
||||
log_info "NanoClaw installed"
|
||||
|
||||
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 "${KAMATERA_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"
|
||||
|
||||
# Write .env file for NanoClaw
|
||||
run_server "${KAMATERA_SERVER_IP}" "printf 'ANTHROPIC_API_KEY=%s\n' '${OPENROUTER_API_KEY}' > ~/nanoclaw/.env"
|
||||
|
||||
echo ""
|
||||
log_info "Kamatera server setup completed successfully!"
|
||||
log_info "Server: ${SERVER_NAME} (IP: ${KAMATERA_SERVER_IP})"
|
||||
echo ""
|
||||
|
||||
log_warn "Starting NanoClaw..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "${KAMATERA_SERVER_IP}" "source ~/.zshrc && cd ~/nanoclaw && npm run dev"
|
||||
56
kamatera/openclaw.sh
Normal file
56
kamatera/openclaw.sh
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
# shellcheck source=kamatera/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/kamatera/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "OpenClaw on Kamatera"
|
||||
echo ""
|
||||
|
||||
ensure_kamatera_token
|
||||
generate_ssh_key_if_missing "${HOME}/.ssh/id_ed25519"
|
||||
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
verify_server_connectivity "${KAMATERA_SERVER_IP}"
|
||||
|
||||
log_warn "Waiting for init script to complete..."
|
||||
generic_ssh_wait "root" "${KAMATERA_SERVER_IP}" "${SSH_OPTS} -o ConnectTimeout=5" "test -f /root/.cloud-init-complete" "init script" 60 5
|
||||
|
||||
log_warn "Installing openclaw..."
|
||||
run_server "${KAMATERA_SERVER_IP}" "source ~/.bashrc && bun install -g openclaw"
|
||||
log_info "OpenClaw installed"
|
||||
|
||||
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
|
||||
|
||||
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "Openclaw") || exit 1
|
||||
|
||||
log_warn "Setting up environment variables..."
|
||||
inject_env_vars_ssh "${KAMATERA_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"
|
||||
|
||||
setup_openclaw_config "${OPENROUTER_API_KEY}" "${MODEL_ID}" \
|
||||
"upload_file ${KAMATERA_SERVER_IP}" \
|
||||
"run_server ${KAMATERA_SERVER_IP}"
|
||||
|
||||
echo ""
|
||||
log_info "Kamatera server setup completed successfully!"
|
||||
log_info "Server: ${SERVER_NAME} (IP: ${KAMATERA_SERVER_IP})"
|
||||
echo ""
|
||||
|
||||
log_warn "Starting openclaw..."
|
||||
run_server "${KAMATERA_SERVER_IP}" "source ~/.zshrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 &"
|
||||
sleep 2
|
||||
interactive_session "${KAMATERA_SERVER_IP}" "source ~/.zshrc && openclaw tui"
|
||||
48
kamatera/opencode.sh
Normal file
48
kamatera/opencode.sh
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
# shellcheck source=kamatera/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/kamatera/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "OpenCode on Kamatera"
|
||||
echo ""
|
||||
|
||||
ensure_kamatera_token
|
||||
generate_ssh_key_if_missing "${HOME}/.ssh/id_ed25519"
|
||||
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
verify_server_connectivity "${KAMATERA_SERVER_IP}"
|
||||
|
||||
log_warn "Waiting for init script to complete..."
|
||||
generic_ssh_wait "root" "${KAMATERA_SERVER_IP}" "${SSH_OPTS} -o ConnectTimeout=5" "test -f /root/.cloud-init-complete" "init script" 60 5
|
||||
|
||||
log_warn "Installing OpenCode..."
|
||||
run_server "${KAMATERA_SERVER_IP}" "$(opencode_install_cmd)"
|
||||
log_info "OpenCode installed"
|
||||
|
||||
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 "${KAMATERA_SERVER_IP}" upload_file run_server \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
|
||||
echo ""
|
||||
log_info "Kamatera server setup completed successfully!"
|
||||
log_info "Server: ${SERVER_NAME} (IP: ${KAMATERA_SERVER_IP})"
|
||||
echo ""
|
||||
|
||||
log_warn "Starting OpenCode..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "${KAMATERA_SERVER_IP}" "source ~/.zshrc && opencode"
|
||||
53
kamatera/plandex.sh
Normal file
53
kamatera/plandex.sh
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
# shellcheck source=kamatera/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/kamatera/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Plandex on Kamatera"
|
||||
echo ""
|
||||
|
||||
ensure_kamatera_token
|
||||
generate_ssh_key_if_missing "${HOME}/.ssh/id_ed25519"
|
||||
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
verify_server_connectivity "${KAMATERA_SERVER_IP}"
|
||||
|
||||
log_warn "Waiting for init script to complete..."
|
||||
generic_ssh_wait "root" "${KAMATERA_SERVER_IP}" "${SSH_OPTS} -o ConnectTimeout=5" "test -f /root/.cloud-init-complete" "init script" 60 5
|
||||
|
||||
log_warn "Installing Plandex..."
|
||||
run_server "${KAMATERA_SERVER_IP}" "curl -sL https://plandex.ai/install.sh | bash"
|
||||
|
||||
if ! run_server "${KAMATERA_SERVER_IP}" "command -v plandex &> /dev/null && plandex version &> /dev/null"; then
|
||||
log_error "Plandex installation verification failed"
|
||||
exit 1
|
||||
fi
|
||||
log_info "Plandex installed"
|
||||
|
||||
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 "${KAMATERA_SERVER_IP}" upload_file run_server \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
|
||||
echo ""
|
||||
log_info "Kamatera server setup completed successfully!"
|
||||
log_info "Server: ${SERVER_NAME} (IP: ${KAMATERA_SERVER_IP})"
|
||||
echo ""
|
||||
|
||||
log_warn "Starting Plandex..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "${KAMATERA_SERVER_IP}" "source ~/.zshrc && plandex"
|
||||
|
|
@ -511,6 +511,24 @@
|
|||
"image": "Ubuntu 24.04"
|
||||
},
|
||||
"notes": "Major European cloud provider. Uses signature-based auth (Application Key + Secret + Consumer Key). Requires OVH_PROJECT_ID for Public Cloud. Create credentials at https://api.ovh.com/createToken/"
|
||||
},
|
||||
"kamatera": {
|
||||
"name": "Kamatera",
|
||||
"description": "Kamatera cloud servers via REST API with 25+ global datacenters",
|
||||
"url": "https://www.kamatera.com/",
|
||||
"type": "api",
|
||||
"auth": "KAMATERA_API_CLIENT_ID + KAMATERA_API_SECRET",
|
||||
"provision_method": "POST /service/server with script-file init",
|
||||
"exec_method": "ssh root@IP",
|
||||
"interactive_method": "ssh -t root@IP",
|
||||
"defaults": {
|
||||
"cpu": "2B",
|
||||
"ram": 2048,
|
||||
"disk": "size=20",
|
||||
"datacenter": "EU",
|
||||
"image": "ubuntu_server_24.04_64-bit"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"matrix": {
|
||||
|
|
@ -773,6 +791,19 @@
|
|||
"ovh/cline": "missing",
|
||||
"ovh/gptme": "missing",
|
||||
"ovh/opencode": "missing",
|
||||
"ovh/plandex": "missing"
|
||||
"ovh/plandex": "missing",
|
||||
"kamatera/claude": "implemented",
|
||||
"kamatera/openclaw": "implemented",
|
||||
"kamatera/nanoclaw": "implemented",
|
||||
"kamatera/aider": "implemented",
|
||||
"kamatera/goose": "implemented",
|
||||
"kamatera/codex": "implemented",
|
||||
"kamatera/interpreter": "implemented",
|
||||
"kamatera/gemini": "implemented",
|
||||
"kamatera/amazonq": "implemented",
|
||||
"kamatera/cline": "implemented",
|
||||
"kamatera/gptme": "implemented",
|
||||
"kamatera/opencode": "implemented",
|
||||
"kamatera/plandex": "implemented"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue