mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-06 08:10:48 +00:00
fix: harden agent reliability + security across all clouds (#1468)
* docs: add spawn delete command to README Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: harden openclaw across all clouds — validation, reliability, performance Fixes multiple issues causing openclaw to break on most clouds: Bugs fixed: - Double-prefixed model ID (openrouter/openrouter/auto) in config generation - AWS gateway starting without env vars (missing .zshrc source) - DigitalOcean sourcing .spawnrc instead of .zshrc for gateway - Destructive rm -rf ~/.openclaw on re-runs (now mkdir -p) Validation added: - API key checked against OpenRouter /auth/key endpoint with re-prompt on failure - Model ID verified against OpenRouter model list with re-prompt loop - openrouter/auto and openrouter/free bypass model check Reliability improvements: - Standardized gateway launch with </dev/null & disown across all 9 clouds - Gateway log auto-displayed on startup timeout for diagnostics - 2GB swap added to cloud-init to prevent OOM on small VMs - Portable install timeout (10 min) with macOS gtimeout fallback Performance: - Reordered spawn_agent: OAuth runs while VM provisions (saves 30-60s) - Fly.io: bumped to 2GB RAM + 2 shared CPUs for openclaw - Fly.io: tries bun first (faster), falls back to npm Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: skip sudo in gh install when running as root (Fly.io containers) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review — skip validation in tests, quote escaped cmd, escape model_id - verify_openrouter_key and verify_openrouter_model skip network calls when SPAWN_SKIP_API_VALIDATION, BUN_ENV=test, or NODE_ENV=test is set - install_agent timeout wrapper now quotes the escaped command for defense in depth - model_id in openclaw JSON now uses json_escape() for consistency Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove double-escaping in install_agent that broke shell operators install_agent() was wrapping commands with printf '%q' + bash -c before passing them to the run callback. But run callbacks (run_server, run_sprite, ssh_run_server) already handle escaping for remote transport. The double- escaping turned && || > | into literal characters, causing 'source' to treat the entire command as a single filename. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use local github-auth.sh instead of curling from main When running from a local checkout, base64-encode the local github-auth.sh and send it inline to the remote machine. This ensures fixes (like the sudo skip for root) take effect immediately without waiting for a merge to main. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: handle github-auth errors gracefully instead of terminating GitHub CLI setup is optional — failures should not abort the spawn session. Guard both run_callback calls in offer_github_auth with || log_warn so the script continues even if gh install fails. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use GOOGLE_GEMINI_BASE_URL to route Gemini CLI through OpenRouter Gemini CLI ignores OPENAI_BASE_URL — it uses GEMINI_API_KEY to talk directly to Google's API. The OpenRouter key is not a valid Google API key, so all requests fail with "API key not valid". Use GOOGLE_GEMINI_BASE_URL to redirect Gemini CLI to OpenRouter's endpoint. Fixes all 9 cloud gemini scripts + manifest.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: guard optional spawn_agent hooks so failures don't kill the session With set -eo pipefail, any unguarded failure terminates the script. Several optional operations in spawn_agent were unguarded: - agent_configure: config file uploads (agent works with defaults) - agent_save_connection: convenience JSON for spawn list - agent_pre_launch: gateway daemons, startup hooks - agent_pre_provision: pre-provision prompts - .spawnrc shell hooks: hooking env vars into .bashrc/.zshrc These now log warnings and continue instead of aborting. Critical steps (cloud_authenticate, agent_install, cloud_provision) still exit on failure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: audit and fix env vars, escaping, and error handling across all agents Audit findings from 3 parallel agents, fixes applied: **Env vars (4 agents fixed across 9 clouds each = 36 scripts):** - Amazon Q: remove fake OPENAI_* vars (Q uses AWS auth, can't use OpenRouter) - Cline: replace OPENAI_* env vars with `cline auth -p openrouter` command - Open Interpreter: drop OPENAI_* vars, use only OPENROUTER_API_KEY (native support via --model flag) - NanoClaw: add ANTHROPIC_BASE_URL to .env file (was missing, requests went to Anthropic directly) **Escaping:** - execute_agent_non_interactive: replace printf '%q' with single-quote wrapping to avoid double-escaping on Fly.io **Manifest updated** for amazonq, cline, interpreter entries. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use setsid to detach openclaw gateway daemon from SSH sessions The gateway daemon launch (`nohup openclaw gateway ... & disown`) hangs on all clouds because SSH/exec channels wait for child FDs to close. setsid creates a new session, fully detaching the daemon so the channel can close immediately. Falls back to nohup where setsid is unavailable. Consolidates the daemon launch into a shared start_openclaw_gateway() function used by all 9 cloud scripts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: configure npm global prefix for non-root clouds (AWS, GCP, OVH) AWS Lightsail, GCP, and OVH SSH as non-root users (ubuntu/login user), so `npm install -g` fails with EACCES on /usr/local/lib/node_modules/. Fix: configure npm prefix to ~/.npm-global during cloud-init/setup and add ~/.npm-global/bin to the SSH PATH prefix so agent install commands find globally-installed npm binaries without sudo. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove broken OpenRouter routing from Gemini CLI scripts Gemini CLI uses Google's native API format (/v1beta/models/:streamGenerateContent), not the OpenAI-compatible format (/v1/chat/completions). No base URL override can bridge this — the request formats are fundamentally incompatible. Same situation as Amazon Q (uses vendor-specific auth/API). Removed GEMINI_API_KEY and GOOGLE_GEMINI_BASE_URL from all 9 scripts + manifest. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: auto-install AWS CLI and gcloud SDK when missing Instead of printing manual install instructions and exiting, both CLIs now auto-install: - AWS: downloads official .pkg (macOS) or .zip (Linux) installer - GCP: uses brew cask on macOS, Google's tarball installer on Linux Falls back to manual instructions if auto-install fails. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: nanoclaw — install Docker on Linux, fix hardcoded /root/ path Two issues broke NanoClaw on all clouds: 1. .env upload hardcoded /root/nanoclaw/.env — fails on non-root clouds (AWS=ubuntu, GCP=user, OVH=ubuntu). Now uses upload_config_file with $HOME which expands on the remote side. 2. NanoClaw requires a container runtime. On Linux it uses Docker, but Docker was never installed. Added Docker install via get.docker.com to all cloud scripts (with sudo where SSH user is non-root). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address security review findings from PR #1463 - Reject symlinked github-auth.sh before base64-encoding (falls back to remote URL) - Hide API key from process list using curl -K - instead of -H in verify_openrouter_key Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: quote OPENROUTER_API_KEY in cline auth to prevent command injection Unquoted variable in `cline auth -p openrouter -k ${OPENROUTER_API_KEY}` allows shell metacharacters in the key to execute arbitrary commands on the remote server. Wrapping in escaped double quotes prevents expansion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b603e05043
commit
8ee54d01a8
49 changed files with 253 additions and 208 deletions
|
|
@ -14,11 +14,10 @@ log_info "Amazon Q on AWS Lightsail"
|
|||
echo ""
|
||||
|
||||
agent_install() { install_agent "Amazon Q CLI" "curl -fsSL https://desktop-release.q.us-east-1.amazonaws.com/latest/amazon-q-cli-install.sh | bash" cloud_run; }
|
||||
# Amazon Q uses AWS Builder ID auth — cannot route through OpenRouter.
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
agent_launch_cmd() { echo 'source ~/.zshrc && q chat'; }
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ echo ""
|
|||
agent_install() { install_agent "Cline" "npm install -g cline" cloud_run; }
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
agent_configure() {
|
||||
log_step "Authenticating Cline with OpenRouter..."
|
||||
cloud_run "source ~/.zshrc && cline auth -p openrouter -k \"${OPENROUTER_API_KEY}\""
|
||||
}
|
||||
agent_launch_cmd() { echo 'source ~/.zshrc && cline'; }
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,7 @@ agent_install() {
|
|||
}
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
agent_launch_cmd() { printf 'export PATH="$HOME/.local/bin:$PATH"; source ~/.zshrc && interpreter --model %s --api_key %s' "${MODEL_ID}" "${OPENROUTER_API_KEY}"; }
|
||||
|
||||
|
|
|
|||
|
|
@ -31,13 +31,38 @@ INSTANCE_STATUS_POLL_DELAY=${INSTANCE_STATUS_POLL_DELAY:-5} # Delay between ins
|
|||
|
||||
ensure_aws_cli() {
|
||||
if ! command -v aws &>/dev/null; then
|
||||
_log_diagnostic \
|
||||
"AWS CLI is required but not installed" \
|
||||
"aws command not found in PATH" \
|
||||
--- \
|
||||
"Install the AWS CLI: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" \
|
||||
"Or on macOS: brew install awscli"
|
||||
return 1
|
||||
log_step "Installing AWS CLI..."
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
# macOS: download the .pkg installer
|
||||
local _aws_tmp
|
||||
_aws_tmp=$(mktemp -d)
|
||||
curl -fsSL "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "${_aws_tmp}/AWSCLIV2.pkg" \
|
||||
&& sudo installer -pkg "${_aws_tmp}/AWSCLIV2.pkg" -target / \
|
||||
&& rm -rf "${_aws_tmp}" \
|
||||
&& log_info "AWS CLI installed" \
|
||||
|| {
|
||||
rm -rf "${_aws_tmp}"
|
||||
log_error "Auto-install failed. Install manually:"
|
||||
log_error " brew install awscli"
|
||||
log_error " or: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
|
||||
return 1
|
||||
}
|
||||
else
|
||||
# Linux: download the zip installer
|
||||
local _aws_tmp
|
||||
_aws_tmp=$(mktemp -d)
|
||||
curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "${_aws_tmp}/awscliv2.zip" \
|
||||
&& unzip -q "${_aws_tmp}/awscliv2.zip" -d "${_aws_tmp}" \
|
||||
&& sudo "${_aws_tmp}/aws/install" \
|
||||
&& rm -rf "${_aws_tmp}" \
|
||||
&& log_info "AWS CLI installed" \
|
||||
|| {
|
||||
rm -rf "${_aws_tmp}"
|
||||
log_error "Auto-install failed. Install manually:"
|
||||
log_error " https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
fi
|
||||
# Verify credentials are configured
|
||||
if ! aws sts get-caller-identity &>/dev/null; then
|
||||
|
|
@ -120,9 +145,11 @@ npm install -g n && n 22 && ln -sf /usr/local/bin/node /usr/bin/node && ln -sf /
|
|||
su - ubuntu -c 'curl -fsSL https://bun.sh/install | bash'
|
||||
# Install Claude Code
|
||||
su - ubuntu -c 'curl -fsSL https://claude.ai/install.sh | bash'
|
||||
# Configure npm global prefix so ubuntu can npm install -g without sudo
|
||||
su - ubuntu -c 'mkdir -p ~/.npm-global/bin && npm config set prefix ~/.npm-global'
|
||||
# Configure PATH
|
||||
echo 'export PATH="${HOME}/.claude/local/bin:${HOME}/.local/bin:${HOME}/.bun/bin:${PATH}"' >> /home/ubuntu/.bashrc
|
||||
echo 'export PATH="${HOME}/.claude/local/bin:${HOME}/.local/bin:${HOME}/.bun/bin:${PATH}"' >> /home/ubuntu/.zshrc
|
||||
echo 'export PATH="${HOME}/.npm-global/bin:${HOME}/.claude/local/bin:${HOME}/.local/bin:${HOME}/.bun/bin:${PATH}"' >> /home/ubuntu/.bashrc
|
||||
echo 'export PATH="${HOME}/.npm-global/bin:${HOME}/.claude/local/bin:${HOME}/.local/bin:${HOME}/.bun/bin:${PATH}"' >> /home/ubuntu/.zshrc
|
||||
chown ubuntu:ubuntu /home/ubuntu/.bashrc /home/ubuntu/.zshrc
|
||||
touch /home/ubuntu/.cloud-init-complete
|
||||
chown ubuntu:ubuntu /home/ubuntu/.cloud-init-complete
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ log_info "NanoClaw on AWS Lightsail"
|
|||
echo ""
|
||||
|
||||
agent_install() {
|
||||
log_step "Installing Docker (required by NanoClaw on Linux)..."
|
||||
cloud_run "command -v docker >/dev/null || (curl -fsSL https://get.docker.com | sudo sh && sudo usermod -aG docker \$(whoami))"
|
||||
log_step "Installing tsx..."
|
||||
cloud_run "source ~/.bashrc && bun install -g tsx"
|
||||
log_step "Cloning and building nanoclaw..."
|
||||
|
|
@ -27,13 +29,9 @@ agent_env_vars() {
|
|||
"ANTHROPIC_BASE_URL=https://openrouter.ai/api"
|
||||
}
|
||||
agent_configure() {
|
||||
log_step "Configuring nanoclaw..."
|
||||
local dotenv_temp
|
||||
dotenv_temp=$(mktemp)
|
||||
trap 'rm -f "${dotenv_temp}"' EXIT
|
||||
chmod 600 "${dotenv_temp}"
|
||||
printf 'ANTHROPIC_API_KEY=%s\n' "${OPENROUTER_API_KEY}" > "${dotenv_temp}"
|
||||
cloud_upload "${dotenv_temp}" "/root/nanoclaw/.env"
|
||||
local dotenv_content
|
||||
dotenv_content=$(printf 'ANTHROPIC_API_KEY=%s\nANTHROPIC_BASE_URL=https://openrouter.ai/api\n' "${OPENROUTER_API_KEY}")
|
||||
upload_config_file cloud_upload cloud_run "${dotenv_content}" "\$HOME/nanoclaw/.env"
|
||||
}
|
||||
agent_launch_cmd() { echo 'cd ~/nanoclaw && source ~/.zshrc && npm run dev'; }
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ agent_env_vars() {
|
|||
}
|
||||
agent_configure() { setup_openclaw_config "${OPENROUTER_API_KEY}" "${MODEL_ID}" cloud_upload cloud_run; }
|
||||
agent_pre_launch() {
|
||||
cloud_run "source ~/.zshrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 </dev/null & disown"
|
||||
start_openclaw_gateway cloud_run
|
||||
wait_for_openclaw_gateway cloud_run
|
||||
}
|
||||
agent_launch_cmd() { echo 'source ~/.zshrc && openclaw tui'; }
|
||||
|
|
|
|||
|
|
@ -16,11 +16,10 @@ agent_install() {
|
|||
install_agent "Amazon Q CLI" "curl -fsSL https://desktop-release.q.us-east-1.amazonaws.com/latest/amazon-q-cli-install.sh | bash" cloud_run
|
||||
}
|
||||
|
||||
# Amazon Q uses AWS Builder ID auth — cannot route through OpenRouter.
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,12 @@ agent_install() {
|
|||
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
|
||||
agent_configure() {
|
||||
log_step "Authenticating Cline with OpenRouter..."
|
||||
cloud_run "source ~/.zshrc && cline auth -p openrouter -k \"${OPENROUTER_API_KEY}\""
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -22,9 +22,7 @@ agent_install() {
|
|||
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ log_info "NanoClaw on Daytona"
|
|||
echo ""
|
||||
|
||||
agent_install() {
|
||||
log_step "Installing Docker (required by NanoClaw on Linux)..."
|
||||
cloud_run "command -v docker >/dev/null || (curl -fsSL https://get.docker.com | sudo sh && sudo usermod -aG docker \$(whoami))"
|
||||
log_step "Installing tsx..."
|
||||
cloud_run "source ~/.bashrc && bun install -g tsx"
|
||||
log_step "Cloning and building nanoclaw..."
|
||||
|
|
@ -28,13 +30,9 @@ agent_env_vars() {
|
|||
}
|
||||
|
||||
agent_configure() {
|
||||
log_step "Configuring nanoclaw..."
|
||||
local dotenv_temp
|
||||
dotenv_temp=$(mktemp)
|
||||
chmod 600 "${dotenv_temp}"
|
||||
track_temp_file "${dotenv_temp}"
|
||||
printf 'ANTHROPIC_API_KEY=%s\n' "${OPENROUTER_API_KEY}" > "${dotenv_temp}"
|
||||
cloud_upload "${dotenv_temp}" "/root/nanoclaw/.env"
|
||||
local dotenv_content
|
||||
dotenv_content=$(printf 'ANTHROPIC_API_KEY=%s\nANTHROPIC_BASE_URL=https://openrouter.ai/api\n' "${OPENROUTER_API_KEY}")
|
||||
upload_config_file cloud_upload cloud_run "${dotenv_content}" "\$HOME/nanoclaw/.env"
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ agent_configure() {
|
|||
}
|
||||
|
||||
agent_pre_launch() {
|
||||
cloud_run "source ~/.zshrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 </dev/null & disown"
|
||||
start_openclaw_gateway cloud_run
|
||||
wait_for_openclaw_gateway cloud_run
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,11 +14,10 @@ log_info "Amazon Q on DigitalOcean"
|
|||
echo ""
|
||||
|
||||
agent_install() { install_agent "Amazon Q CLI" "curl -fsSL https://desktop-release.q.us-east-1.amazonaws.com/latest/amazon-q-cli-install.sh | bash" cloud_run; }
|
||||
# Amazon Q uses AWS Builder ID auth — cannot route through OpenRouter.
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
agent_launch_cmd() { echo 'source ~/.zshrc && q chat'; }
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ echo ""
|
|||
agent_install() { install_agent "Cline" "npm install -g cline" cloud_run; }
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
agent_configure() {
|
||||
log_step "Authenticating Cline with OpenRouter..."
|
||||
cloud_run "source ~/.zshrc && cline auth -p openrouter -k \"${OPENROUTER_API_KEY}\""
|
||||
}
|
||||
agent_launch_cmd() { echo 'source ~/.zshrc && cline'; }
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,7 @@ agent_install() {
|
|||
}
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
agent_launch_cmd() { printf 'export PATH="$HOME/.local/bin:$PATH"; source ~/.zshrc && interpreter --model %s --api_key %s' "${MODEL_ID}" "${OPENROUTER_API_KEY}"; }
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ log_info "NanoClaw on DigitalOcean"
|
|||
echo ""
|
||||
|
||||
agent_install() {
|
||||
log_step "Installing Docker (required by NanoClaw on Linux)..."
|
||||
cloud_run "command -v docker >/dev/null || (curl -fsSL https://get.docker.com | sh)"
|
||||
log_step "Installing tsx..."
|
||||
cloud_run "source ~/.bashrc && bun install -g tsx"
|
||||
log_step "Cloning and building nanoclaw..."
|
||||
|
|
@ -27,13 +29,9 @@ agent_env_vars() {
|
|||
"ANTHROPIC_BASE_URL=https://openrouter.ai/api"
|
||||
}
|
||||
agent_configure() {
|
||||
log_step "Configuring nanoclaw..."
|
||||
local dotenv_temp
|
||||
dotenv_temp=$(mktemp)
|
||||
trap 'rm -f "${dotenv_temp}"' EXIT
|
||||
chmod 600 "${dotenv_temp}"
|
||||
printf 'ANTHROPIC_API_KEY=%s\n' "${OPENROUTER_API_KEY}" > "${dotenv_temp}"
|
||||
cloud_upload "${dotenv_temp}" "/root/nanoclaw/.env"
|
||||
local dotenv_content
|
||||
dotenv_content=$(printf 'ANTHROPIC_API_KEY=%s\nANTHROPIC_BASE_URL=https://openrouter.ai/api\n' "${OPENROUTER_API_KEY}")
|
||||
upload_config_file cloud_upload cloud_run "${dotenv_content}" "\$HOME/nanoclaw/.env"
|
||||
}
|
||||
agent_launch_cmd() { echo 'cd ~/nanoclaw && source ~/.zshrc && npm run dev'; }
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ agent_env_vars() {
|
|||
}
|
||||
agent_configure() { setup_openclaw_config "${OPENROUTER_API_KEY}" "${MODEL_ID}" cloud_upload cloud_run; }
|
||||
agent_pre_launch() {
|
||||
cloud_run "source ~/.zshrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 </dev/null & disown"
|
||||
start_openclaw_gateway cloud_run
|
||||
wait_for_openclaw_gateway cloud_run
|
||||
}
|
||||
agent_launch_cmd() { echo 'source ~/.zshrc && openclaw tui'; }
|
||||
|
|
|
|||
|
|
@ -16,11 +16,10 @@ agent_install() {
|
|||
install_agent "Amazon Q CLI" "curl -fsSL https://desktop-release.q.us-east-1.amazonaws.com/latest/amazon-q-cli-install.sh | bash" cloud_run
|
||||
}
|
||||
|
||||
# Amazon Q uses AWS Builder ID auth — cannot route through OpenRouter.
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,12 @@ agent_install() {
|
|||
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
|
||||
agent_configure() {
|
||||
log_step "Authenticating Cline with OpenRouter..."
|
||||
cloud_run "source ~/.zshrc && cline auth -p openrouter -k \"${OPENROUTER_API_KEY}\""
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -22,9 +22,7 @@ agent_install() {
|
|||
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ log_info "NanoClaw on Fly.io"
|
|||
echo ""
|
||||
|
||||
agent_install() {
|
||||
log_step "Installing Docker (required by NanoClaw on Linux)..."
|
||||
cloud_run "command -v docker >/dev/null || (curl -fsSL https://get.docker.com | sh)"
|
||||
log_step "Installing tsx..."
|
||||
cloud_run "source ~/.bashrc && bun install -g tsx"
|
||||
log_step "Cloning and building nanoclaw..."
|
||||
|
|
@ -28,13 +30,9 @@ agent_env_vars() {
|
|||
}
|
||||
|
||||
agent_configure() {
|
||||
log_step "Configuring nanoclaw..."
|
||||
local dotenv_temp
|
||||
dotenv_temp=$(mktemp)
|
||||
chmod 600 "${dotenv_temp}"
|
||||
track_temp_file "${dotenv_temp}"
|
||||
printf 'ANTHROPIC_API_KEY=%s\n' "${OPENROUTER_API_KEY}" > "${dotenv_temp}"
|
||||
cloud_upload "${dotenv_temp}" "/root/nanoclaw/.env"
|
||||
local dotenv_content
|
||||
dotenv_content=$(printf 'ANTHROPIC_API_KEY=%s\nANTHROPIC_BASE_URL=https://openrouter.ai/api\n' "${OPENROUTER_API_KEY}")
|
||||
upload_config_file cloud_upload cloud_run "${dotenv_content}" "\$HOME/nanoclaw/.env"
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ agent_configure() {
|
|||
}
|
||||
|
||||
agent_pre_launch() {
|
||||
cloud_run "source ~/.zshrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 </dev/null & disown"
|
||||
start_openclaw_gateway cloud_run
|
||||
wait_for_openclaw_gateway cloud_run
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,11 +14,10 @@ log_info "Amazon Q on GCP Compute Engine"
|
|||
echo ""
|
||||
|
||||
agent_install() { install_agent "Amazon Q CLI" "curl -fsSL https://desktop-release.q.us-east-1.amazonaws.com/latest/amazon-q-cli-install.sh | bash" cloud_run; }
|
||||
# Amazon Q uses AWS Builder ID auth — cannot route through OpenRouter.
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
agent_launch_cmd() { echo 'source ~/.zshrc && q chat'; }
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ echo ""
|
|||
agent_install() { install_agent "Cline" "npm install -g cline" cloud_run; }
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
agent_configure() {
|
||||
log_step "Authenticating Cline with OpenRouter..."
|
||||
cloud_run "source ~/.zshrc && cline auth -p openrouter -k \"${OPENROUTER_API_KEY}\""
|
||||
}
|
||||
agent_launch_cmd() { echo 'source ~/.zshrc && cline'; }
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,7 @@ agent_install() {
|
|||
}
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
agent_launch_cmd() { printf 'export PATH="$HOME/.local/bin:$PATH"; source ~/.zshrc && interpreter --model %s --api_key %s' "${MODEL_ID}" "${OPENROUTER_API_KEY}"; }
|
||||
|
||||
|
|
|
|||
|
|
@ -33,39 +33,47 @@ SPAWN_DASHBOARD_URL="https://console.cloud.google.com/compute/instances"
|
|||
|
||||
# Verify gcloud CLI is installed
|
||||
_gcp_check_cli_installed() {
|
||||
if command -v gcloud &>/dev/null; then return 0; fi
|
||||
|
||||
log_step "Installing Google Cloud SDK..."
|
||||
if [[ "$(uname)" == "Darwin" ]] && command -v brew &>/dev/null; then
|
||||
brew install --cask google-cloud-sdk \
|
||||
&& log_info "Google Cloud SDK installed via Homebrew" \
|
||||
|| {
|
||||
log_error "Auto-install failed. Install manually: brew install --cask google-cloud-sdk"
|
||||
return 1
|
||||
}
|
||||
# Homebrew cask puts gcloud in a non-standard location — source it
|
||||
local _gcloud_path
|
||||
for _gcloud_path in \
|
||||
"$(brew --prefix)/share/google-cloud-sdk/path.bash.inc" \
|
||||
"$(brew --prefix)/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/path.bash.inc"; do
|
||||
if [[ -f "${_gcloud_path}" ]]; then
|
||||
source "${_gcloud_path}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
else
|
||||
# Linux / macOS without brew: use Google's installer
|
||||
local _gcp_tmp
|
||||
_gcp_tmp=$(mktemp -d)
|
||||
curl -fsSL "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-x86_64.tar.gz" \
|
||||
-o "${_gcp_tmp}/gcloud.tar.gz" \
|
||||
&& tar -xzf "${_gcp_tmp}/gcloud.tar.gz" -C "${HOME}" \
|
||||
&& "${HOME}/google-cloud-sdk/install.sh" --quiet --path-update true \
|
||||
&& export PATH="${HOME}/google-cloud-sdk/bin:${PATH}" \
|
||||
&& rm -rf "${_gcp_tmp}" \
|
||||
&& log_info "Google Cloud SDK installed" \
|
||||
|| {
|
||||
rm -rf "${_gcp_tmp}"
|
||||
log_error "Auto-install failed. Install manually:"
|
||||
log_error " https://cloud.google.com/sdk/docs/install"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
|
||||
if ! command -v gcloud &>/dev/null; then
|
||||
log_error "Google Cloud SDK (gcloud) is required but not installed"
|
||||
log_error ""
|
||||
log_error "Possible causes:"
|
||||
log_error " - gcloud CLI has not been installed on this machine"
|
||||
log_error ""
|
||||
log_error "How to fix:"
|
||||
log_error " 1. Install gcloud CLI for your platform:"
|
||||
log_error ""
|
||||
log_error " ${CYAN}macOS (Homebrew)${NC}"
|
||||
log_error " brew install google-cloud-sdk"
|
||||
log_error ""
|
||||
log_error " ${CYAN}Ubuntu/Debian${NC}"
|
||||
log_error " curl https://sdk.cloud.google.com | bash"
|
||||
log_error " exec -l \$SHELL # Restart shell"
|
||||
log_error ""
|
||||
log_error " ${CYAN}Fedora/RHEL${NC}"
|
||||
log_error " sudo tee -a /etc/yum.repos.d/google-cloud-sdk.repo << EOM"
|
||||
log_error " [google-cloud-cli]"
|
||||
log_error " name=Google Cloud CLI"
|
||||
log_error " baseurl=https://packages.cloud.google.com/yum/repos/cloud-sdk-el9-x86_64"
|
||||
log_error " enabled=1"
|
||||
log_error " gpgcheck=1"
|
||||
log_error " repo_gpgcheck=0"
|
||||
log_error " gpgkey=https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg"
|
||||
log_error " EOM"
|
||||
log_error " sudo dnf install google-cloud-cli"
|
||||
log_error ""
|
||||
log_error " 2. Full installation guide: ${CYAN}https://cloud.google.com/sdk/docs/install${NC}"
|
||||
log_error ""
|
||||
log_error " 3. After installation, authenticate:"
|
||||
log_error " gcloud auth login"
|
||||
log_error " gcloud config set project YOUR_PROJECT_ID"
|
||||
log_error "gcloud not found after install. You may need to restart your shell."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
|
@ -249,8 +257,10 @@ if [[ ! "$GCP_USERNAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|||
fi
|
||||
su - "$GCP_USERNAME" -c 'curl -fsSL https://bun.sh/install | bash' || true
|
||||
su - "$GCP_USERNAME" -c 'curl -fsSL https://claude.ai/install.sh | bash' || true
|
||||
# Configure npm global prefix so non-root user can npm install -g without sudo
|
||||
su - "$GCP_USERNAME" -c 'mkdir -p ~/.npm-global/bin && npm config set prefix ~/.npm-global'
|
||||
# Configure PATH for all users
|
||||
echo 'export PATH="${HOME}/.claude/local/bin:${HOME}/.local/bin:${HOME}/.bun/bin:${PATH}"' >> /etc/profile.d/spawn.sh
|
||||
echo 'export PATH="${HOME}/.npm-global/bin:${HOME}/.claude/local/bin:${HOME}/.local/bin:${HOME}/.bun/bin:${PATH}"' >> /etc/profile.d/spawn.sh
|
||||
chmod +x /etc/profile.d/spawn.sh
|
||||
touch /tmp/.cloud-init-complete
|
||||
CLOUD_INIT_EOF
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ log_info "NanoClaw on GCP Compute Engine"
|
|||
echo ""
|
||||
|
||||
agent_install() {
|
||||
log_step "Installing Docker (required by NanoClaw on Linux)..."
|
||||
cloud_run "command -v docker >/dev/null || (curl -fsSL https://get.docker.com | sudo sh && sudo usermod -aG docker \$(whoami))"
|
||||
log_step "Installing tsx..."
|
||||
cloud_run "source ~/.bashrc && bun install -g tsx"
|
||||
log_step "Cloning and building nanoclaw..."
|
||||
|
|
@ -27,13 +29,9 @@ agent_env_vars() {
|
|||
"ANTHROPIC_BASE_URL=https://openrouter.ai/api"
|
||||
}
|
||||
agent_configure() {
|
||||
log_step "Configuring nanoclaw..."
|
||||
local dotenv_temp
|
||||
dotenv_temp=$(mktemp)
|
||||
trap 'rm -f "${dotenv_temp}"' EXIT
|
||||
chmod 600 "${dotenv_temp}"
|
||||
printf 'ANTHROPIC_API_KEY=%s\n' "${OPENROUTER_API_KEY}" > "${dotenv_temp}"
|
||||
cloud_upload "${dotenv_temp}" "/root/nanoclaw/.env"
|
||||
local dotenv_content
|
||||
dotenv_content=$(printf 'ANTHROPIC_API_KEY=%s\nANTHROPIC_BASE_URL=https://openrouter.ai/api\n' "${OPENROUTER_API_KEY}")
|
||||
upload_config_file cloud_upload cloud_run "${dotenv_content}" "\$HOME/nanoclaw/.env"
|
||||
}
|
||||
agent_launch_cmd() { echo 'cd ~/nanoclaw && source ~/.zshrc && npm run dev'; }
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ agent_env_vars() {
|
|||
}
|
||||
agent_configure() { setup_openclaw_config "${OPENROUTER_API_KEY}" "${MODEL_ID}" cloud_upload cloud_run; }
|
||||
agent_pre_launch() {
|
||||
cloud_run "source ~/.zshrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 </dev/null & disown"
|
||||
start_openclaw_gateway cloud_run
|
||||
wait_for_openclaw_gateway cloud_run
|
||||
}
|
||||
agent_launch_cmd() { echo 'source ~/.zshrc && openclaw tui'; }
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ log_info "Amazon Q on Hetzner Cloud"
|
|||
echo ""
|
||||
|
||||
agent_install() { install_agent "Amazon Q CLI" "curl -fsSL https://desktop-release.q.us-east-1.amazonaws.com/latest/amazon-q-cli-install.sh | bash" cloud_run; }
|
||||
# Amazon Q uses AWS Builder ID auth — cannot route through OpenRouter.
|
||||
# OPENROUTER_API_KEY is set for consistency but Q CLI ignores it.
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
agent_launch_cmd() { echo 'source ~/.zshrc && q chat'; }
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ echo ""
|
|||
agent_install() { install_agent "Cline" "npm install -g cline" cloud_run; }
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
agent_configure() {
|
||||
log_step "Authenticating Cline with OpenRouter..."
|
||||
cloud_run "source ~/.zshrc && cline auth -p openrouter -k \"${OPENROUTER_API_KEY}\""
|
||||
}
|
||||
agent_launch_cmd() { echo 'source ~/.zshrc && cline'; }
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,7 @@ agent_install() {
|
|||
}
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
agent_launch_cmd() { printf 'export PATH="$HOME/.local/bin:$PATH"; source ~/.zshrc && interpreter --model %s --api_key %s' "${MODEL_ID}" "${OPENROUTER_API_KEY}"; }
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ log_info "NanoClaw on Hetzner Cloud"
|
|||
echo ""
|
||||
|
||||
agent_install() {
|
||||
log_step "Installing Docker (required by NanoClaw on Linux)..."
|
||||
cloud_run "command -v docker >/dev/null || (curl -fsSL https://get.docker.com | sh)"
|
||||
log_step "Installing tsx..."
|
||||
cloud_run "source ~/.bashrc && bun install -g tsx"
|
||||
log_step "Cloning and building nanoclaw..."
|
||||
|
|
@ -27,13 +29,9 @@ agent_env_vars() {
|
|||
"ANTHROPIC_BASE_URL=https://openrouter.ai/api"
|
||||
}
|
||||
agent_configure() {
|
||||
log_step "Configuring nanoclaw..."
|
||||
local dotenv_temp
|
||||
dotenv_temp=$(mktemp)
|
||||
trap 'rm -f "${dotenv_temp}"' EXIT
|
||||
chmod 600 "${dotenv_temp}"
|
||||
printf 'ANTHROPIC_API_KEY=%s\n' "${OPENROUTER_API_KEY}" > "${dotenv_temp}"
|
||||
cloud_upload "${dotenv_temp}" "/root/nanoclaw/.env"
|
||||
local dotenv_content
|
||||
dotenv_content=$(printf 'ANTHROPIC_API_KEY=%s\nANTHROPIC_BASE_URL=https://openrouter.ai/api\n' "${OPENROUTER_API_KEY}")
|
||||
upload_config_file cloud_upload cloud_run "${dotenv_content}" "\$HOME/nanoclaw/.env"
|
||||
}
|
||||
agent_launch_cmd() { echo 'cd ~/nanoclaw && source ~/.zshrc && npm run dev'; }
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ agent_env_vars() {
|
|||
}
|
||||
agent_configure() { setup_openclaw_config "${OPENROUTER_API_KEY}" "${MODEL_ID}" cloud_upload cloud_run; }
|
||||
agent_pre_launch() {
|
||||
cloud_run "source ~/.zshrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 </dev/null & disown"
|
||||
start_openclaw_gateway cloud_run
|
||||
wait_for_openclaw_gateway cloud_run
|
||||
}
|
||||
agent_launch_cmd() { echo 'source ~/.zshrc && openclaw tui'; }
|
||||
|
|
|
|||
|
|
@ -16,11 +16,10 @@ agent_install() {
|
|||
install_agent "Amazon Q CLI" "curl -fsSL https://desktop-release.q.us-east-1.amazonaws.com/latest/amazon-q-cli-install.sh | bash" cloud_run
|
||||
}
|
||||
|
||||
# Amazon Q uses AWS Builder ID auth — cannot route through OpenRouter.
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,12 @@ agent_install() {
|
|||
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
|
||||
agent_configure() {
|
||||
log_step "Authenticating Cline with OpenRouter..."
|
||||
cloud_run "source ~/.zshrc 2>/dev/null; cline auth -p openrouter -k \"${OPENROUTER_API_KEY}\""
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -22,9 +22,7 @@ agent_install() {
|
|||
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -28,13 +28,9 @@ agent_env_vars() {
|
|||
}
|
||||
|
||||
agent_configure() {
|
||||
log_step "Configuring nanoclaw..."
|
||||
local dotenv_temp
|
||||
dotenv_temp=$(mktemp)
|
||||
chmod 600 "${dotenv_temp}"
|
||||
track_temp_file "${dotenv_temp}"
|
||||
printf 'ANTHROPIC_API_KEY=%s\n' "${OPENROUTER_API_KEY}" > "${dotenv_temp}"
|
||||
cloud_upload "${dotenv_temp}" "${HOME}/nanoclaw/.env"
|
||||
local dotenv_content
|
||||
dotenv_content=$(printf 'ANTHROPIC_API_KEY=%s\nANTHROPIC_BASE_URL=https://openrouter.ai/api\n' "${OPENROUTER_API_KEY}")
|
||||
upload_config_file cloud_upload cloud_run "${dotenv_content}" "\$HOME/nanoclaw/.env"
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ agent_configure() {
|
|||
}
|
||||
|
||||
agent_pre_launch() {
|
||||
cloud_run "source ~/.zshrc 2>/dev/null; nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 &"
|
||||
start_openclaw_gateway cloud_run
|
||||
wait_for_openclaw_gateway cloud_run
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,11 +14,10 @@ log_info "Amazon Q on OVHcloud"
|
|||
echo ""
|
||||
|
||||
agent_install() { install_agent "Amazon Q CLI" "curl -fsSL https://desktop-release.q.us-east-1.amazonaws.com/latest/amazon-q-cli-install.sh | bash" cloud_run; }
|
||||
# Amazon Q uses AWS Builder ID auth — cannot route through OpenRouter.
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
agent_launch_cmd() { echo 'source ~/.zshrc && q chat'; }
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ echo ""
|
|||
agent_install() { install_agent "Cline" "npm install -g cline" cloud_run; }
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
agent_configure() {
|
||||
log_step "Authenticating Cline with OpenRouter..."
|
||||
cloud_run "source ~/.zshrc && cline auth -p openrouter -k \"${OPENROUTER_API_KEY}\""
|
||||
}
|
||||
agent_launch_cmd() { echo 'source ~/.zshrc && cline'; }
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,7 @@ agent_install() {
|
|||
}
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
agent_launch_cmd() { printf 'export PATH="$HOME/.local/bin:$PATH"; source ~/.zshrc && interpreter --model %s --api_key %s' "${MODEL_ID}" "${OPENROUTER_API_KEY}"; }
|
||||
|
||||
|
|
|
|||
|
|
@ -383,9 +383,12 @@ install_base_deps() {
|
|||
# Install Claude Code
|
||||
run_ovh "$ip" "curl -fsSL https://claude.ai/install.sh | bash"
|
||||
|
||||
# Configure npm global prefix so non-root user can npm install -g without sudo
|
||||
run_ovh "$ip" "mkdir -p ~/.npm-global/bin && npm config set prefix ~/.npm-global"
|
||||
|
||||
# Configure PATH
|
||||
run_ovh "$ip" "printf '%s\n' 'export PATH=\"\${HOME}/.local/bin:\${HOME}/.bun/bin:\${PATH}\"' >> ~/.bashrc"
|
||||
run_ovh "$ip" "printf '%s\n' 'export PATH=\"\${HOME}/.local/bin:\${HOME}/.bun/bin:\${PATH}\"' >> ~/.zshrc"
|
||||
run_ovh "$ip" "printf '%s\n' 'export PATH=\"\${HOME}/.npm-global/bin:\${HOME}/.local/bin:\${HOME}/.bun/bin:\${PATH}\"' >> ~/.bashrc"
|
||||
run_ovh "$ip" "printf '%s\n' 'export PATH=\"\${HOME}/.npm-global/bin:\${HOME}/.local/bin:\${HOME}/.bun/bin:\${PATH}\"' >> ~/.zshrc"
|
||||
|
||||
log_info "Base dependencies installed"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ log_info "NanoClaw on OVHcloud"
|
|||
echo ""
|
||||
|
||||
agent_install() {
|
||||
log_step "Installing Docker (required by NanoClaw on Linux)..."
|
||||
cloud_run "command -v docker >/dev/null || (curl -fsSL https://get.docker.com | sudo sh && sudo usermod -aG docker \$(whoami))"
|
||||
log_step "Installing tsx..."
|
||||
cloud_run "source ~/.bashrc && bun install -g tsx"
|
||||
log_step "Cloning and building nanoclaw..."
|
||||
|
|
@ -27,13 +29,9 @@ agent_env_vars() {
|
|||
"ANTHROPIC_BASE_URL=https://openrouter.ai/api"
|
||||
}
|
||||
agent_configure() {
|
||||
log_step "Configuring nanoclaw..."
|
||||
local dotenv_temp
|
||||
dotenv_temp=$(mktemp)
|
||||
trap 'rm -f "${dotenv_temp}"' EXIT
|
||||
chmod 600 "${dotenv_temp}"
|
||||
printf 'ANTHROPIC_API_KEY=%s\n' "${OPENROUTER_API_KEY}" > "${dotenv_temp}"
|
||||
cloud_upload "${dotenv_temp}" "/root/nanoclaw/.env"
|
||||
local dotenv_content
|
||||
dotenv_content=$(printf 'ANTHROPIC_API_KEY=%s\nANTHROPIC_BASE_URL=https://openrouter.ai/api\n' "${OPENROUTER_API_KEY}")
|
||||
upload_config_file cloud_upload cloud_run "${dotenv_content}" "\$HOME/nanoclaw/.env"
|
||||
}
|
||||
agent_launch_cmd() { echo 'cd ~/nanoclaw && source ~/.zshrc && npm run dev'; }
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ agent_env_vars() {
|
|||
}
|
||||
agent_configure() { setup_openclaw_config "${OPENROUTER_API_KEY}" "${MODEL_ID}" cloud_upload cloud_run; }
|
||||
agent_pre_launch() {
|
||||
cloud_run "source ~/.zshrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 </dev/null & disown"
|
||||
start_openclaw_gateway cloud_run
|
||||
wait_for_openclaw_gateway cloud_run
|
||||
}
|
||||
agent_launch_cmd() { echo 'source ~/.zshrc && openclaw tui'; }
|
||||
|
|
|
|||
|
|
@ -319,8 +319,10 @@ verify_openrouter_key() {
|
|||
if ! command -v curl &>/dev/null; then return 0; fi # skip if no curl
|
||||
|
||||
local response http_code
|
||||
response=$(curl -s --connect-timeout 5 --max-time 10 -w "\n%{http_code}" \
|
||||
-H "Authorization: Bearer ${api_key}" \
|
||||
# Pass auth header via stdin (-K -) so the API key isn't visible in ps output
|
||||
response=$(printf 'header = "Authorization: Bearer %s"\n' "${api_key}" | \
|
||||
curl -s --connect-timeout 5 --max-time 10 -w "\n%{http_code}" \
|
||||
-K - \
|
||||
"https://openrouter.ai/api/v1/auth/key" 2>/dev/null) || return 0 # network error = skip
|
||||
http_code=$(printf '%s' "${response}" | tail -1)
|
||||
|
||||
|
|
@ -1342,7 +1344,7 @@ offer_github_auth() {
|
|||
# a merge to main. Base64-encode it for safe inline transport.
|
||||
local gh_cmd
|
||||
local _local_gh="${SCRIPT_DIR:-}/../../shared/github-auth.sh"
|
||||
if [[ -n "${SCRIPT_DIR:-}" && -f "${_local_gh}" ]]; then
|
||||
if [[ -n "${SCRIPT_DIR:-}" && -f "${_local_gh}" && ! -L "${_local_gh}" ]]; then
|
||||
local _gh_b64
|
||||
_gh_b64=$(base64 < "${_local_gh}" | tr -d '\n')
|
||||
gh_cmd="printf '%s' '${_gh_b64}' | base64 -d | bash"
|
||||
|
|
@ -1720,7 +1722,15 @@ spawn_agent() {
|
|||
server_name=$(get_server_name)
|
||||
cloud_provision "${server_name}"
|
||||
|
||||
# 6. Wait for readiness
|
||||
# 4. Get API key while server provisions (overlaps with cloud-init)
|
||||
get_or_prompt_api_key
|
||||
|
||||
# 5. Model selection while server provisions (if agent needs it)
|
||||
if [[ -n "${AGENT_MODEL_PROMPT:-}" ]]; then
|
||||
MODEL_ID=$(get_model_id_interactive "${AGENT_MODEL_DEFAULT:-openrouter/auto}" "${agent_name}") || exit 1
|
||||
fi
|
||||
|
||||
# 6. Wait for readiness (may already be done after OAuth)
|
||||
cloud_wait_ready
|
||||
|
||||
# 7. Install agent
|
||||
|
|
@ -2239,18 +2249,20 @@ execute_agent_non_interactive() {
|
|||
|
||||
log_step "Executing ${agent_name} with prompt in non-interactive mode..."
|
||||
|
||||
# Escape the prompt for safe shell execution
|
||||
# We use printf %q which properly escapes special characters for bash
|
||||
local escaped_prompt
|
||||
escaped_prompt=$(printf '%q' "${prompt}")
|
||||
# Do NOT use printf '%q' here — the run callback (run_server, sprite exec,
|
||||
# ssh) already handles escaping for remote transport. Double-escaping breaks
|
||||
# prompts containing quotes, spaces, or special characters on Fly.io.
|
||||
# Single-quote the prompt to protect it from shell expansion.
|
||||
local safe_prompt
|
||||
safe_prompt="'$(printf '%s' "${prompt}" | sed "s/'/'\\\\''/g")'"
|
||||
|
||||
# Build the command based on exec callback type
|
||||
if [[ "${exec_callback}" == *"sprite"* ]]; then
|
||||
# Sprite execution (no -tty flag for non-interactive)
|
||||
sprite exec -s "${sprite_name}" -- zsh -c "source ~/.zshrc && ${agent_name} ${agent_flags} ${escaped_prompt}"
|
||||
sprite exec -s "${sprite_name}" -- zsh -c "source ~/.zshrc && ${agent_name} ${agent_flags} ${safe_prompt}"
|
||||
else
|
||||
# Generic SSH execution
|
||||
${exec_callback} "${sprite_name}" "source ~/.zshrc && ${agent_name} ${agent_flags} ${escaped_prompt}"
|
||||
${exec_callback} "${sprite_name}" "source ~/.zshrc && ${agent_name} ${agent_flags} ${safe_prompt}"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -2371,7 +2383,8 @@ ssh_run_server() {
|
|||
local ip="${1}"
|
||||
local cmd="${2}"
|
||||
# Single-quoted so $HOME/$PATH expand on the remote side, not locally.
|
||||
local path_prefix='export PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH"'
|
||||
# .npm-global/bin: user-writable npm prefix (AWS Lightsail runs as ubuntu, not root)
|
||||
local path_prefix='export PATH="$HOME/.npm-global/bin:$HOME/.local/bin:$HOME/.bun/bin:$PATH"'
|
||||
if [[ -n "${SPAWN_DEBUG:-}" ]]; then
|
||||
cmd="set -x; ${cmd}"
|
||||
fi
|
||||
|
|
@ -3190,6 +3203,22 @@ setup_openclaw_config() {
|
|||
upload_config_file "${upload_callback}" "${run_callback}" "${openclaw_json}" "\$HOME/.openclaw/openclaw.json"
|
||||
}
|
||||
|
||||
# Start OpenClaw gateway as a fully detached daemon
|
||||
# Usage: start_openclaw_gateway RUN_CALLBACK
|
||||
#
|
||||
# Arguments:
|
||||
# RUN_CALLBACK - Function to run commands: func(command)
|
||||
#
|
||||
# SSH/exec channels hang if a backgrounded daemon inherits the session's file
|
||||
# descriptors. setsid creates a new session, fully detaching the gateway so
|
||||
# the channel can close. Falls back to nohup where setsid is unavailable
|
||||
# (e.g. macOS local — no SSH, so the hang doesn't apply).
|
||||
start_openclaw_gateway() {
|
||||
local run_callback="${1}"
|
||||
log_step "Starting OpenClaw gateway daemon..."
|
||||
${run_callback} "source ~/.zshrc 2>/dev/null; if command -v setsid >/dev/null 2>&1; then setsid openclaw gateway > /tmp/openclaw-gateway.log 2>&1 < /dev/null & else nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 < /dev/null & fi"
|
||||
}
|
||||
|
||||
# Wait for OpenClaw gateway to be ready
|
||||
# Usage: wait_for_openclaw_gateway RUN_CALLBACK
|
||||
#
|
||||
|
|
|
|||
|
|
@ -16,11 +16,10 @@ agent_install() {
|
|||
install_agent "Amazon Q CLI" "curl -fsSL https://desktop-release.q.us-east-1.amazonaws.com/latest/amazon-q-cli-install.sh | bash" cloud_run
|
||||
}
|
||||
|
||||
# Amazon Q uses AWS Builder ID auth — cannot route through OpenRouter.
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,12 @@ agent_install() {
|
|||
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
|
||||
agent_configure() {
|
||||
log_step "Authenticating Cline with OpenRouter..."
|
||||
cloud_run "source ~/.zshrc && cline auth -p openrouter -k \"${OPENROUTER_API_KEY}\""
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -22,9 +22,7 @@ agent_install() {
|
|||
|
||||
agent_env_vars() {
|
||||
generate_env_config \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"OPENAI_BASE_URL=https://openrouter.ai/api/v1"
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ log_info "NanoClaw on Sprite"
|
|||
echo ""
|
||||
|
||||
agent_install() {
|
||||
log_step "Installing Docker (required by NanoClaw on Linux)..."
|
||||
cloud_run "command -v docker >/dev/null || (curl -fsSL https://get.docker.com | sh)"
|
||||
log_step "Installing tsx..."
|
||||
cloud_run "source ~/.bashrc && bun install -g tsx"
|
||||
log_step "Cloning and building nanoclaw..."
|
||||
|
|
@ -28,13 +30,9 @@ agent_env_vars() {
|
|||
}
|
||||
|
||||
agent_configure() {
|
||||
log_step "Configuring nanoclaw..."
|
||||
local dotenv_temp
|
||||
dotenv_temp=$(mktemp)
|
||||
chmod 600 "${dotenv_temp}"
|
||||
track_temp_file "${dotenv_temp}"
|
||||
printf 'ANTHROPIC_API_KEY=%s\n' "${OPENROUTER_API_KEY}" > "${dotenv_temp}"
|
||||
cloud_upload "${dotenv_temp}" "/root/nanoclaw/.env"
|
||||
local dotenv_content
|
||||
dotenv_content=$(printf 'ANTHROPIC_API_KEY=%s\nANTHROPIC_BASE_URL=https://openrouter.ai/api\n' "${OPENROUTER_API_KEY}")
|
||||
upload_config_file cloud_upload cloud_run "${dotenv_content}" "\$HOME/nanoclaw/.env"
|
||||
}
|
||||
|
||||
agent_launch_cmd() {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ agent_configure() {
|
|||
}
|
||||
|
||||
agent_pre_launch() {
|
||||
cloud_run "source ~/.zshrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 </dev/null & disown"
|
||||
start_openclaw_gateway cloud_run
|
||||
wait_for_openclaw_gateway cloud_run
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue