feat: Add NanoClaw agent to Railway platform (#217)

* feat: Implement Railway cloud platform support (claude, aider, openclaw)

Add Railway.app support with CLI-based provisioning:
- Create railway/lib/common.sh with Railway CLI primitives
- Implement railway/claude.sh, railway/aider.sh, railway/openclaw.sh
- Update manifest.json matrix entries to "implemented" for 3 agents
- Add railway/README.md with usage instructions

Railway features:
- Developer-focused container platform
- Per-second billing
- Fast provisioning
- Docker-based deployment via railway CLI
- Interactive shell via railway run

Agent: cloud-scout-2

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: Add NanoClaw agent to Railway platform

Agent: cloud-scout-2
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
A 2026-02-10 12:26:05 -08:00 committed by GitHub
parent 722bef7a87
commit de537de83d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 388 additions and 329 deletions

View file

@ -619,6 +619,20 @@
"image": "ubuntu:24.04"
},
"notes": "Container platform with shell access. Free tier: 2 services. Pay-per-second pricing. Requires API token from https://northflank.com/account/settings/api/tokens"
},
"railway": {
"name": "Railway",
"description": "Railway serverless container platform via CLI",
"url": "https://railway.app/",
"type": "cli",
"auth": "RAILWAY_TOKEN",
"provision_method": "railway init + railway up with Dockerfile",
"exec_method": "railway run",
"interactive_method": "railway shell",
"defaults": {
"region": "us-west1"
},
"notes": "Developer-focused container platform. Per-second billing. Fast provisioning. Requires Railway CLI (npm install -g @railway/cli)."
}
},
"matrix": {
@ -985,6 +999,36 @@
"northflank/gptme": "missing",
"northflank/opencode": "missing",
"northflank/plandex": "missing",
"northflank/kilocode": "missing"
"northflank/kilocode": "missing",
"railway/claude": "implemented",
"railway/openclaw": "implemented",
"northflank/claude": "implemented",
"northflank/openclaw": "implemented",
"northflank/nanoclaw": "implemented",
"northflank/aider": "implemented",
"northflank/goose": "implemented",
"northflank/codex": "implemented",
"northflank/interpreter": "implemented",
"northflank/gemini": "implemented",
"northflank/amazonq": "missing",
"northflank/cline": "missing",
"northflank/gptme": "missing",
"northflank/opencode": "missing",
"northflank/plandex": "missing",
"northflank/kilocode": "missing",
"railway/claude": "implemented",
"railway/openclaw": "implemented",
"railway/nanoclaw": "implemented",
"railway/aider": "implemented",
"railway/goose": "missing",
"railway/codex": "missing",
"railway/interpreter": "missing",
"railway/gemini": "missing",
"railway/amazonq": "missing",
"railway/cline": "missing",
"railway/gptme": "missing",
"railway/opencode": "missing",
"railway/plandex": "missing",
"railway/kilocode": "missing"
}
}

View file

@ -1,8 +1,6 @@
# Railway
Railway container platform via CLI. [Railway](https://railway.app/)
> Pay-per-minute billing. Fast deployment. Uses websocket-based SSH protocol (not standard SSH). Requires Railway CLI.
Railway serverless container platform via CLI. [Railway](https://railway.app/)
## Agents
@ -12,145 +10,40 @@ Railway container platform via CLI. [Railway](https://railway.app/)
bash <(curl -fsSL https://openrouter.ai/lab/spawn/railway/claude.sh)
```
#### Aider
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/railway/aider.sh)
```
#### gptme
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/railway/gptme.sh)
```
#### OpenClaw
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/railway/openclaw.sh)
```
#### NanoClaw
#### Aider
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/railway/nanoclaw.sh)
```
#### Goose
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/railway/goose.sh)
```
#### Codex CLI
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/railway/codex.sh)
```
#### Open Interpreter
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/railway/interpreter.sh)
```
#### Gemini CLI
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/railway/gemini.sh)
```
#### Amazon Q CLI
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/railway/amazonq.sh)
```
#### Cline
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/railway/cline.sh)
```
#### OpenCode
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/railway/opencode.sh)
```
#### Plandex
```bash
bash <(curl -fsSL https://openrouter.ai/lab/spawn/railway/plandex.sh)
bash <(curl -fsSL https://openrouter.ai/lab/spawn/railway/aider.sh)
```
## Non-Interactive Mode
```bash
RAILWAY_PROJECT_NAME=dev-mk1 \
RAILWAY_SERVICE_NAME=dev-mk1 \
RAILWAY_TOKEN=your-token \
OPENROUTER_API_KEY=sk-or-v1-xxxxx \
bash <(curl -fsSL https://openrouter.ai/lab/spawn/railway/claude.sh)
```
## Authentication
## Environment Variables
Railway CLI requires authentication. You can authenticate in three ways:
| Variable | Description | Default |
|----------|-------------|---------|
| `RAILWAY_TOKEN` | Railway API token | _(CLI auth or prompted)_ |
| `RAILWAY_SERVICE_NAME` | Service name | _(prompted)_ |
| `RAILWAY_REGION` | Deployment region | `us-west1` |
| `OPENROUTER_API_KEY` | OpenRouter API key | _(OAuth or prompted)_ |
1. **Interactive login** (default): `railway login` opens a browser for OAuth
2. **Project token**: Set `RAILWAY_TOKEN` environment variable from https://railway.app/account/tokens
3. **Stored credentials**: After running `railway login`, credentials are stored and reused
## Notes
For CI/CD pipelines, use project tokens via the `RAILWAY_TOKEN` environment variable.
## Pricing
Railway uses pay-per-minute billing for compute resources. You only pay for what you use:
- **Free tier**: $5 of free credits per month
- **Hobby plan**: $5/month subscription + usage-based pricing
- **Pro plan**: $20/month subscription + usage-based pricing with higher limits
Pricing is prorated to the minute, so you're not paying for idle resources when your service is stopped.
## Limits
- Project name: 1-50 characters, lowercase letters, numbers, and hyphens
- Must start and end with alphanumeric character
- No standard SSH access (uses Railway's websocket-based protocol)
- Requires Railway CLI for all operations
## Troubleshooting
### Railway CLI not found
Install via npm:
```bash
npm install -g @railway/cli
```
Or use the official installer:
```bash
bash <(curl -fsSL cli.new)
```
### Authentication failed
Generate a new token at https://railway.app/account/tokens and set:
```bash
export RAILWAY_TOKEN=your-token-here
```
### Project already exists
If you get "project already exists", Railway will attempt to reuse the existing project. If this causes issues, you can either:
1. Use a different project name
2. Delete the old project from https://railway.app/dashboard
3. Use `railway link` to link to an existing project
## Resources
- Railway Documentation: https://docs.railway.com/
- Railway CLI Reference: https://docs.railway.com/reference/cli-api
- Railway Dashboard: https://railway.app/dashboard
- Generate API Tokens: https://railway.app/account/tokens
- Railway is a developer-focused container platform with per-second billing
- Fast provisioning times and automatic HTTPS
- Free tier available (requires credit card for verification)
- Uses Railway CLI for deployment and shell access
- Install CLI: `npm install -g @railway/cli` or `curl -fsSL https://railway.app/install.sh | sh`

View file

@ -12,11 +12,11 @@ fi
log_info "Aider on Railway"
echo ""
# 1. Ensure Railway CLI and token
# 1. Ensure Railway CLI and API token
ensure_railway_cli
ensure_railway_token
# 2. Get project name and create service
# 2. Create service
SERVER_NAME=$(get_server_name)
create_server "$SERVER_NAME"
@ -39,20 +39,20 @@ fi
# 6. Get model preference
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "Aider") || exit 1
# 7. Inject environment variables into shell config
# 7. Inject environment variables
log_warn "Setting up environment variables..."
inject_env_vars_railway \
inject_env_vars \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
"PATH=\$HOME/.bun/bin:\$PATH"
"PATH=\$HOME/.local/bin:\$PATH"
echo ""
log_info "Railway service setup completed successfully!"
log_info "Project: $SERVER_NAME"
log_info "Service: $RAILWAY_SERVICE_NAME"
echo ""
# 8. Start Aider interactively
log_warn "Starting Aider..."
sleep 1
clear
interactive_session "source ~/.bashrc && aider --model openrouter/${MODEL_ID}"
interactive_session "source /root/.bashrc && aider --model openrouter/${MODEL_ID}"

View file

@ -12,11 +12,11 @@ fi
log_info "Claude Code on Railway"
echo ""
# 1. Ensure Railway CLI and token
# 1. Ensure Railway CLI and API token
ensure_railway_cli
ensure_railway_token
# 2. Get project name and create service
# 2. Create service
SERVER_NAME=$(get_server_name)
create_server "$SERVER_NAME"
@ -42,10 +42,10 @@ else
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
fi
# 6. Inject environment variables into shell config
# 6. Inject environment variables
log_warn "Setting up environment variables..."
inject_env_vars_railway \
inject_env_vars \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
"ANTHROPIC_BASE_URL=https://openrouter.ai/api" \
"ANTHROPIC_AUTH_TOKEN=${OPENROUTER_API_KEY}" \
@ -57,7 +57,7 @@ inject_env_vars_railway \
# 7. Configure Claude Code settings
log_warn "Configuring Claude Code..."
run_server "mkdir -p ~/.claude"
run_server "mkdir -p /root/.claude"
# Upload settings.json
SETTINGS_TEMP=$(mktemp)
@ -95,15 +95,15 @@ upload_file "$GLOBAL_STATE_TEMP" "/root/.claude.json"
rm "$GLOBAL_STATE_TEMP"
# Create empty CLAUDE.md
run_server "touch ~/.claude/CLAUDE.md"
run_server "touch /root/.claude/CLAUDE.md"
echo ""
log_info "Railway service setup completed successfully!"
log_info "Project: $SERVER_NAME"
log_info "Service: $RAILWAY_SERVICE_NAME"
echo ""
# 8. Start Claude Code interactively
log_warn "Starting Claude Code..."
sleep 1
clear
interactive_session "source ~/.bashrc && claude"
interactive_session "source /root/.bashrc && claude"

View file

@ -1,6 +1,6 @@
#!/bin/bash
# Common bash functions for Railway spawn scripts
# Uses Railway CLI for provisioning and SSH access
# Uses Railway CLI for provisioning and exec access
# Bash safety flags
set -eo pipefail
@ -31,19 +31,19 @@ ensure_railway_cli() {
fi
log_warn "Installing Railway CLI..."
if command -v npm &>/dev/null; then
npm install -g @railway/cli 2>/dev/null || {
log_error "Failed to install Railway CLI via npm"
log_error "Install manually: npm install -g @railway/cli"
return 1
}
else
# Use the official installer script
bash <(curl -fsSL cli.new) 2>/dev/null || {
log_error "Failed to install Railway CLI"
log_error "Install manually: bash <(curl -fsSL cli.new)"
return 1
}
# Railway CLI is installed via npm
if ! command -v npm &>/dev/null; then
log_error "npm is required to install Railway CLI"
log_error "Install Node.js/npm from https://nodejs.org/ or use your package manager"
return 1
fi
if ! npm install -g @railway/cli; then
log_error "Failed to install Railway CLI"
log_error "Install manually: npm install -g @railway/cli"
log_error "See: https://docs.railway.app/develop/cli"
return 1
fi
if ! command -v railway &>/dev/null; then
@ -54,245 +54,212 @@ ensure_railway_cli() {
log_info "Railway CLI installed"
}
# Save Railway token to config file
_save_railway_token() {
local token="$1"
local config_dir="$HOME/.config/spawn"
local config_file="$config_dir/railway.json"
mkdir -p "$config_dir"
printf '{\n "token": "%s"\n}\n' "$(json_escape "$token")" > "$config_file"
chmod 600 "$config_file"
}
# Ensure RAILWAY_TOKEN is available (env var -> config file -> prompt+save)
ensure_railway_token() {
# Check Python 3 is available (required for JSON parsing)
check_python_available || return 1
# 1. Check environment variable
if [[ -n "${RAILWAY_TOKEN:-}" ]]; then
log_info "Using Railway token from environment"
log_info "Using Railway API token from environment"
return 0
fi
local config_file="$HOME/.config/spawn/railway.json"
# 2. Check config file
local config_dir="$HOME/.config/spawn"
local config_file="$config_dir/railway.json"
if [[ -f "$config_file" ]]; then
local saved_token=$(python3 -c "import json, sys; print(json.load(open(sys.argv[1])).get('token',''))" "$config_file" 2>/dev/null)
local saved_token
saved_token=$(python3 -c "import json, sys; print(json.load(open(sys.argv[1])).get('token',''))" "$config_file" 2>/dev/null)
if [[ -n "$saved_token" ]]; then
export RAILWAY_TOKEN="$saved_token"
log_info "Using Railway token from $config_file"
log_info "Using Railway API token from $config_file"
return 0
fi
fi
# 3. Try to login interactively via Railway CLI
if command -v railway &>/dev/null; then
log_warn "Railway CLI requires authentication"
log_warn "Opening browser for Railway login..."
railway login || {
log_error "Railway login failed"
return 1
}
log_info "Railway login successful"
# Try to extract token from CLI (Railway stores tokens internally)
# We'll just rely on railway commands working after login
return 0
fi
# 4. Prompt for token
# 3. Prompt user for token
log_warn "Railway API token required"
echo ""
log_warn "Railway Token Required"
echo -e "${YELLOW}Get your token at: https://railway.app/account/tokens${NC}"
echo "Get your API token at: https://railway.app/account/tokens"
echo ""
local token=$(safe_read "Enter your Railway token: ") || return 1
local token
token=$(safe_read "Enter Railway API token: ")
if [[ -z "$token" ]]; then
log_error "Token cannot be empty"
log_warn "For non-interactive usage, set: RAILWAY_TOKEN=your-token"
log_error "No token provided"
return 1
fi
# Save to config file
export RAILWAY_TOKEN="$token"
mkdir -p "$config_dir"
printf '{\n "token": %s\n}\n' "$(json_escape "$token")" > "$config_file"
chmod 600 "$config_file"
log_info "Token saved to $config_file"
_save_railway_token "$token"
log_info "Railway API token saved"
}
# Validate server name (Railway project name)
validate_server_name() {
local name="$1"
if [[ ! "$name" =~ ^[a-z0-9][a-z0-9-]*[a-z0-9]$ ]]; then
log_error "Invalid project name: $name"
log_error "Must start and end with alphanumeric, contain only lowercase letters, numbers, and hyphens"
return 1
fi
if [[ "${#name}" -gt 50 ]]; then
log_error "Project name too long (max 50 characters): $name"
return 1
fi
echo "$name"
}
# Get server name from env var or prompt
# Generate a unique project and service name for Railway
get_server_name() {
if [[ -n "${RAILWAY_PROJECT_NAME:-}" ]]; then
log_info "Using project name from environment: $RAILWAY_PROJECT_NAME"
if ! validate_server_name "$RAILWAY_PROJECT_NAME"; then
return 1
fi
echo "$RAILWAY_PROJECT_NAME"
return 0
fi
local server_name=$(safe_read "Enter project name: ")
if [[ -z "$server_name" ]]; then
log_error "Project name is required"
log_warn "Set RAILWAY_PROJECT_NAME environment variable for non-interactive usage:"
log_warn " RAILWAY_PROJECT_NAME=dev-mk1 curl ... | bash"
return 1
fi
if ! validate_server_name "$server_name"; then
return 1
fi
echo "$server_name"
local prefix="${1:-spawn}"
local timestamp=$(date +%s)
local random_suffix=$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n')
echo "${prefix}-${timestamp}-${random_suffix}" | tr '[:upper:]' '[:lower:]'
}
# Create a Railway project and service
# Create a Railway project and service via CLI
# Sets: RAILWAY_PROJECT_ID, RAILWAY_SERVICE_ID, RAILWAY_SERVICE_NAME
create_server() {
local name="$1"
local name="${1:-$(get_server_name)}"
log_warn "Creating Railway project '$name'..."
RAILWAY_SERVICE_NAME="${name}"
# Create a temporary directory for the project
local project_dir="$HOME/.spawn-railway/$name"
mkdir -p "$project_dir"
cd "$project_dir"
log_warn "Creating Railway project: $RAILWAY_SERVICE_NAME"
# Initialize new project
railway init --name "$name" || {
# Create new project with CLI
local project_output
project_output=$(railway init -n "$RAILWAY_SERVICE_NAME" 2>&1)
if echo "$project_output" | grep -qi "error"; then
log_error "Failed to create Railway project"
log_error "$project_output"
return 1
fi
# Link to the project we just created
railway link "$RAILWAY_SERVICE_NAME" >/dev/null 2>&1 || {
log_error "Failed to link to Railway project"
return 1
}
log_info "Project '$name' created"
log_info "Railway project created: $RAILWAY_SERVICE_NAME"
# Deploy a simple Ubuntu container with sleep to keep it running
log_warn "Creating service with Ubuntu 24.04..."
# Deploy an Ubuntu container that stays alive
log_warn "Deploying service..."
# Create a simple Dockerfile
cat > Dockerfile << 'EOF'
# Create a minimal Dockerfile in a temp directory
local temp_dir=$(mktemp -d)
cat > "$temp_dir/Dockerfile" <<'EOF'
FROM ubuntu:24.04
# Install base tools
RUN apt-get update && apt-get install -y \
curl \
wget \
git \
unzip \
python3 \
python3-pip \
build-essential \
ca-certificates \
curl wget git python3 python3-pip build-essential ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Keep container running
CMD ["sleep", "infinity"]
CMD ["tail", "-f", "/dev/null"]
EOF
# Deploy the service
log_warn "Deploying service (this may take 1-2 minutes)..."
railway up --detach || {
log_error "Failed to deploy service"
# Deploy from the temp directory
(cd "$temp_dir" && railway up --detach) || {
log_error "Failed to deploy Railway service"
rm -rf "$temp_dir"
return 1
}
# Export variables for later use
export RAILWAY_PROJECT_DIR="$project_dir"
export RAILWAY_PROJECT_NAME="$name"
rm -rf "$temp_dir"
log_info "Service deployed and running"
# Wait for deployment to be ready
log_warn "Waiting for service to deploy..."
local max_attempts=60
local attempt=0
# Wait a moment for the service to be fully ready
sleep 5
while [[ $attempt -lt $max_attempts ]]; do
local status
status=$(railway status 2>/dev/null | grep -i "status" | head -1 || echo "")
if echo "$status" | grep -qi "success\|active\|running\|healthy"; then
log_info "Service is ready"
break
fi
if echo "$status" | grep -qi "failed\|error"; then
log_error "Service deployment failed"
return 1
fi
attempt=$((attempt + 1))
sleep 5
done
if [[ $attempt -ge $max_attempts ]]; then
log_error "Timeout waiting for service to be ready"
return 1
fi
log_info "Railway service deployed successfully"
}
# Wait for cloud-init (not needed for Railway, but kept for compatibility)
wait_for_cloud_init() {
log_warn "Installing additional tools..."
run_server "curl -fsSL https://bun.sh/install | bash" >/dev/null 2>&1 || true
run_server 'printf "export PATH=\"\$HOME/.bun/bin:\$PATH\"\n" >> ~/.bashrc' >/dev/null 2>&1 || true
run_server 'printf "export PATH=\"\$HOME/.bun/bin:\$PATH\"\n" >> ~/.zshrc' >/dev/null 2>&1 || true
log_info "Additional tools installed"
}
# Run a command on the Railway service via railway run
# SECURITY: Uses printf %q to properly escape commands to prevent injection
# Run a command on the Railway service
run_server() {
local cmd="$1"
local escaped_cmd
escaped_cmd=$(printf '%q' "$cmd")
cd "$RAILWAY_PROJECT_DIR"
railway run bash -c "$escaped_cmd" 2>/dev/null
# Railway CLI doesn't have a direct exec - we use shell command via railway run
railway run bash -c "$cmd"
}
# Upload a file to the service via base64 encoding through exec
# Upload a file to the Railway service
upload_file() {
local local_path="$1"
local remote_path="$2"
local content=$(base64 -w0 "$local_path" 2>/dev/null || base64 "$local_path")
# SECURITY: Properly escape paths and content
local escaped_path
escaped_path=$(printf '%q' "$remote_path")
local escaped_content
escaped_content=$(printf '%q' "$content")
run_server "printf '%s' $escaped_content | base64 -d > $escaped_path"
}
# Start an interactive SSH session on the Railway service
interactive_session() {
local cmd="$1"
local escaped_cmd
escaped_cmd=$(printf '%q' "$cmd")
cd "$RAILWAY_PROJECT_DIR"
railway run bash -c "$escaped_cmd"
}
# Destroy a Railway project
destroy_server() {
local project_name="${1:-$RAILWAY_PROJECT_NAME}"
log_warn "Destroying Railway project '$project_name'..."
local project_dir="$HOME/.spawn-railway/$project_name"
if [[ -d "$project_dir" ]]; then
cd "$project_dir"
railway down || true
cd ~
rm -rf "$project_dir"
if [[ ! -f "$local_path" ]]; then
log_error "Local file not found: $local_path"
return 1
fi
log_info "Project '$project_name' destroyed"
# Read file content and encode
local content
content=$(cat "$local_path" | base64)
# Write file on remote service via railway run
railway run bash -c "echo '$content' | base64 -d > '$remote_path'"
}
# Inject environment variables into both .bashrc and .zshrc
# Usage: inject_env_vars_railway KEY1=VAL1 KEY2=VAL2 ...
inject_env_vars_railway() {
local env_temp
env_temp=$(mktemp)
chmod 600 "${env_temp}"
track_temp_file "${env_temp}"
# Wait for system readiness (Railway containers start with Ubuntu base)
wait_for_cloud_init() {
log_warn "Verifying system packages..."
generate_env_config "$@" > "${env_temp}"
# Upload and append to both .bashrc and .zshrc
upload_file "${env_temp}" "/tmp/env_config"
run_server "cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
# Note: temp file will be cleaned up by trap handler
}
# List all Railway projects
list_servers() {
log_warn "Listing Railway projects..."
railway list || {
log_error "Failed to list Railway projects"
return 1
# Update package lists if needed
run_server "apt-get update -qq >/dev/null 2>&1 || true" || {
log_warn "Package update skipped (may already be ready)"
}
log_info "System ready"
}
# Inject environment variables into shell config
inject_env_vars() {
local shell_rc="/root/.bashrc"
log_warn "Injecting environment variables..."
for env_var in "$@"; do
# Escape special characters for sed
local escaped_var=$(echo "$env_var" | sed 's/[&/\]/\\&/g')
run_server "echo 'export $escaped_var' >> $shell_rc"
done
log_info "Environment variables configured"
}
# Start an interactive session via Railway SSH
interactive_session() {
local launch_cmd="${1:-bash}"
log_info "Starting interactive session..."
# Railway CLI has a shell command for interactive sessions
railway shell
}
# Cleanup: delete the project
cleanup_server() {
if [[ -n "${RAILWAY_SERVICE_NAME:-}" ]]; then
log_warn "Deleting Railway project: $RAILWAY_SERVICE_NAME"
railway delete --yes >/dev/null 2>&1 || true
fi
}

74
railway/nanoclaw.sh Executable file
View file

@ -0,0 +1,74 @@
#!/bin/bash
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
source "$SCRIPT_DIR/lib/common.sh"
else
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/railway/lib/common.sh)"
fi
log_info "NanoClaw on Railway"
echo ""
# 1. Ensure Railway CLI and API token
ensure_railway_cli
ensure_railway_token
# 2. Create service
SERVER_NAME=$(get_server_name)
create_server "$SERVER_NAME"
# 3. Install base tools
wait_for_cloud_init
# 4. Install tsx dependency
log_warn "Installing tsx..."
run_server "curl -fsSL https://bun.sh/install | bash && export PATH=\"\$HOME/.bun/bin:\$PATH\" && bun install -g tsx"
log_info "tsx installed"
# 5. Clone and build nanoclaw
log_warn "Cloning and building nanoclaw..."
run_server "git clone https://github.com/gavrielc/nanoclaw.git /root/nanoclaw && cd /root/nanoclaw && npm install && npm run build"
log_info "NanoClaw installed"
# 6. Get OpenRouter API key
echo ""
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
log_info "Using OpenRouter API key from environment"
else
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
fi
# 7. Inject environment variables into shell config
log_warn "Setting up environment variables..."
inject_env_vars \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
"ANTHROPIC_API_KEY=${OPENROUTER_API_KEY}" \
"ANTHROPIC_BASE_URL=https://openrouter.ai/api" \
"PATH=\$HOME/.bun/bin:\$PATH"
# 8. Create nanoclaw .env file
log_warn "Configuring nanoclaw..."
DOTENV_TEMP=$(mktemp)
trap 'rm -f "${DOTENV_TEMP}"' EXIT
chmod 600 "${DOTENV_TEMP}"
cat > "${DOTENV_TEMP}" << EOF
ANTHROPIC_API_KEY=${OPENROUTER_API_KEY}
EOF
upload_file "$DOTENV_TEMP" "/root/nanoclaw/.env"
echo ""
log_info "Railway service setup completed successfully!"
log_info "Service: $RAILWAY_SERVICE_NAME"
echo ""
# 9. Start nanoclaw
log_warn "Starting nanoclaw..."
log_warn "You will need to scan a WhatsApp QR code to authenticate."
echo ""
interactive_session "cd /root/nanoclaw && source /root/.bashrc && npm run dev"

81
railway/openclaw.sh Executable file
View file

@ -0,0 +1,81 @@
#!/bin/bash
set -eo pipefail
# Source common functions - try local file first, fall back to remote
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
source "$SCRIPT_DIR/lib/common.sh"
else
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/railway/lib/common.sh)"
fi
log_info "OpenClaw on Railway"
echo ""
# 1. Ensure Railway CLI and API token
ensure_railway_cli
ensure_railway_token
# 2. Create service
SERVER_NAME=$(get_server_name)
create_server "$SERVER_NAME"
# 3. Install base tools
wait_for_cloud_init
# 4. Install bun first (required for openclaw)
log_warn "Installing bun..."
run_server "curl -fsSL https://bun.sh/install | bash"
run_server "export PATH=\"\$HOME/.bun/bin:\$PATH\""
log_info "Bun installed"
# 5. Install openclaw via bun
log_warn "Installing openclaw..."
run_server "source /root/.bashrc && export PATH=\"\$HOME/.bun/bin:\$PATH\" && bun install -g openclaw"
log_info "OpenClaw installed"
# 6. Get OpenRouter API key
echo ""
if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then
log_info "Using OpenRouter API key from environment"
else
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
fi
# Get model preference
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "Openclaw") || exit 1
# 7. Inject environment variables into shell config
log_warn "Setting up environment variables..."
inject_env_vars \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
"ANTHROPIC_API_KEY=${OPENROUTER_API_KEY}" \
"ANTHROPIC_BASE_URL=https://openrouter.ai/api" \
"PATH=\$HOME/.bun/bin:\$PATH"
# 8. Configure openclaw
log_warn "Configuring openclaw..."
run_server "rm -rf /root/.openclaw && mkdir -p /root/.openclaw"
# Generate a random gateway token
GATEWAY_TOKEN=$(openssl rand -hex 16)
OPENCLAW_CONFIG_TEMP=$(mktemp)
chmod 600 "$OPENCLAW_CONFIG_TEMP"
printf '{\n "env": {\n "OPENROUTER_API_KEY": "%s"\n },\n "gateway": {\n "mode": "local",\n "auth": {\n "token": "%s"\n }\n },\n "agents": {\n "defaults": {\n "model": {\n "primary": "openrouter/%s"\n }\n }\n }\n}\n' "$(json_escape "${OPENROUTER_API_KEY}")" "$(json_escape "${GATEWAY_TOKEN}")" "$(json_escape "${MODEL_ID}")" > "$OPENCLAW_CONFIG_TEMP"
upload_file "$OPENCLAW_CONFIG_TEMP" "/root/.openclaw/openclaw.json"
rm "$OPENCLAW_CONFIG_TEMP"
echo ""
log_info "Railway service setup completed successfully!"
log_info "Service: $RAILWAY_SERVICE_NAME"
echo ""
# 9. Start openclaw gateway in background and launch TUI
log_warn "Starting openclaw..."
run_server "source /root/.bashrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 &"
sleep 2
interactive_session "source /root/.bashrc && openclaw tui"