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:
Ahmed Abushagur 2026-02-19 05:36:24 -08:00 committed by GitHub
parent b603e05043
commit 8ee54d01a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 253 additions and 208 deletions

View file

@ -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'; }

View file

@ -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'; }

View file

@ -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}"; }

View file

@ -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

View file

@ -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'; }

View file

@ -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'; }

View file

@ -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() {

View file

@ -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() {

View file

@ -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() {

View file

@ -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() {

View file

@ -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
}

View file

@ -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'; }

View file

@ -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'; }

View file

@ -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}"; }

View file

@ -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'; }

View file

@ -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'; }

View file

@ -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() {

View file

@ -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() {

View file

@ -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() {

View file

@ -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() {

View file

@ -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
}

View file

@ -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'; }

View file

@ -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'; }

View file

@ -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}"; }

View file

@ -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

View file

@ -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'; }

View file

@ -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'; }

View file

@ -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'; }

View file

@ -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'; }

View file

@ -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}"; }

View file

@ -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'; }

View file

@ -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'; }

View file

@ -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() {

View file

@ -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() {

View file

@ -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() {

View file

@ -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() {

View file

@ -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
}

View file

@ -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'; }

View file

@ -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'; }

View file

@ -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}"; }

View file

@ -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"
}

View file

@ -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'; }

View file

@ -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'; }

View file

@ -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
#

View file

@ -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() {

View file

@ -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() {

View file

@ -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() {

View file

@ -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() {

View file

@ -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
}