From c7db0337960d0da075eda0649f869e1ed463b2e2 Mon Sep 17 00:00:00 2001 From: A <258483684+la14-1@users.noreply.github.com> Date: Mon, 9 Feb 2026 09:44:16 -0800 Subject: [PATCH] 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) --- kamatera/README.md | 108 +++++++++ kamatera/aider.sh | 50 +++++ kamatera/amazonq.sh | 50 +++++ kamatera/claude.sh | 60 +++++ kamatera/cline.sh | 50 +++++ kamatera/codex.sh | 50 +++++ kamatera/gemini.sh | 51 +++++ kamatera/goose.sh | 49 +++++ kamatera/gptme.sh | 50 +++++ kamatera/interpreter.sh | 50 +++++ kamatera/lib/common.sh | 471 ++++++++++++++++++++++++++++++++++++++++ kamatera/nanoclaw.sh | 53 +++++ kamatera/openclaw.sh | 56 +++++ kamatera/opencode.sh | 48 ++++ kamatera/plandex.sh | 53 +++++ manifest.json | 33 ++- 16 files changed, 1281 insertions(+), 1 deletion(-) create mode 100644 kamatera/README.md create mode 100644 kamatera/aider.sh create mode 100644 kamatera/amazonq.sh create mode 100644 kamatera/claude.sh create mode 100644 kamatera/cline.sh create mode 100644 kamatera/codex.sh create mode 100644 kamatera/gemini.sh create mode 100644 kamatera/goose.sh create mode 100644 kamatera/gptme.sh create mode 100644 kamatera/interpreter.sh create mode 100644 kamatera/lib/common.sh create mode 100644 kamatera/nanoclaw.sh create mode 100644 kamatera/openclaw.sh create mode 100644 kamatera/opencode.sh create mode 100644 kamatera/plandex.sh diff --git a/kamatera/README.md b/kamatera/README.md new file mode 100644 index 00000000..980bf51d --- /dev/null +++ b/kamatera/README.md @@ -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)_ | diff --git a/kamatera/aider.sh b/kamatera/aider.sh new file mode 100644 index 00000000..dc4e1bbb --- /dev/null +++ b/kamatera/aider.sh @@ -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}" diff --git a/kamatera/amazonq.sh b/kamatera/amazonq.sh new file mode 100644 index 00000000..c4cedb2a --- /dev/null +++ b/kamatera/amazonq.sh @@ -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" diff --git a/kamatera/claude.sh b/kamatera/claude.sh new file mode 100644 index 00000000..e8048ef3 --- /dev/null +++ b/kamatera/claude.sh @@ -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" diff --git a/kamatera/cline.sh b/kamatera/cline.sh new file mode 100644 index 00000000..4d02a904 --- /dev/null +++ b/kamatera/cline.sh @@ -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" diff --git a/kamatera/codex.sh b/kamatera/codex.sh new file mode 100644 index 00000000..5e9d9e57 --- /dev/null +++ b/kamatera/codex.sh @@ -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" diff --git a/kamatera/gemini.sh b/kamatera/gemini.sh new file mode 100644 index 00000000..6a619d31 --- /dev/null +++ b/kamatera/gemini.sh @@ -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" diff --git a/kamatera/goose.sh b/kamatera/goose.sh new file mode 100644 index 00000000..c66b75ef --- /dev/null +++ b/kamatera/goose.sh @@ -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" diff --git a/kamatera/gptme.sh b/kamatera/gptme.sh new file mode 100644 index 00000000..f2e370c5 --- /dev/null +++ b/kamatera/gptme.sh @@ -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}" diff --git a/kamatera/interpreter.sh b/kamatera/interpreter.sh new file mode 100644 index 00000000..bee3aa63 --- /dev/null +++ b/kamatera/interpreter.sh @@ -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" diff --git a/kamatera/lib/common.sh b/kamatera/lib/common.sh new file mode 100644 index 00000000..f5d934a6 --- /dev/null +++ b/kamatera/lib/common.sh @@ -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" +} diff --git a/kamatera/nanoclaw.sh b/kamatera/nanoclaw.sh new file mode 100644 index 00000000..d9494db5 --- /dev/null +++ b/kamatera/nanoclaw.sh @@ -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" diff --git a/kamatera/openclaw.sh b/kamatera/openclaw.sh new file mode 100644 index 00000000..086aa702 --- /dev/null +++ b/kamatera/openclaw.sh @@ -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" diff --git a/kamatera/opencode.sh b/kamatera/opencode.sh new file mode 100644 index 00000000..2b6b0dcf --- /dev/null +++ b/kamatera/opencode.sh @@ -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" diff --git a/kamatera/plandex.sh b/kamatera/plandex.sh new file mode 100644 index 00000000..63073629 --- /dev/null +++ b/kamatera/plandex.sh @@ -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" diff --git a/manifest.json b/manifest.json index bd9cea96..512ca77e 100644 --- a/manifest.json +++ b/manifest.json @@ -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" } }