mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-06 16:31:08 +00:00
feat: Add Northflank cloud provider (#210)
Add Northflank container platform with CLI exec access: - northflank/lib/common.sh: Provider primitives (auth, create, exec, upload) - northflank/claude.sh: Claude Code deployment - northflank/aider.sh: Aider deployment - northflank/openclaw.sh: OpenClaw deployment - manifest.json: Add Northflank cloud + 14 matrix entries (3 implemented, 11 missing) - northflank/README.md: Usage instructions and pricing info Free tier: 2 services. Pay-per-second pricing after free tier. Agent: cloud-scout 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:
parent
75416a8fd9
commit
822413bcbf
6 changed files with 578 additions and 1 deletions
|
|
@ -603,6 +603,22 @@
|
|||
"region": "was"
|
||||
},
|
||||
"notes": "Serverless container platform. Free tier available (no credit card). Per-second billing. Requires koyeb CLI."
|
||||
},
|
||||
"northflank": {
|
||||
"name": "Northflank",
|
||||
"description": "Northflank container platform via CLI with exec access",
|
||||
"url": "https://northflank.com/",
|
||||
"type": "cli",
|
||||
"auth": "NORTHFLANK_TOKEN",
|
||||
"provision_method": "northflank create service deployment with Docker image",
|
||||
"exec_method": "northflank exec --command",
|
||||
"interactive_method": "northflank exec (PTY)",
|
||||
"defaults": {
|
||||
"cpu": "0.5",
|
||||
"memory": 1024,
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"matrix": {
|
||||
|
|
@ -955,6 +971,20 @@
|
|||
"koyeb/gptme": "implemented",
|
||||
"koyeb/opencode": "implemented",
|
||||
"koyeb/plandex": "implemented",
|
||||
"koyeb/kilocode": "implemented"
|
||||
"koyeb/kilocode": "implemented",
|
||||
"northflank/claude": "implemented",
|
||||
"northflank/openclaw": "implemented",
|
||||
"northflank/nanoclaw": "missing",
|
||||
"northflank/aider": "implemented",
|
||||
"northflank/goose": "missing",
|
||||
"northflank/codex": "missing",
|
||||
"northflank/interpreter": "missing",
|
||||
"northflank/gemini": "missing",
|
||||
"northflank/amazonq": "missing",
|
||||
"northflank/cline": "missing",
|
||||
"northflank/gptme": "missing",
|
||||
"northflank/opencode": "missing",
|
||||
"northflank/plandex": "missing",
|
||||
"northflank/kilocode": "missing"
|
||||
}
|
||||
}
|
||||
61
northflank/README.md
Normal file
61
northflank/README.md
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# Northflank
|
||||
|
||||
Northflank container platform via CLI with exec access. [Northflank](https://northflank.com/)
|
||||
|
||||
> Uses Northflank CLI for container exec. Free tier: 2 services. Pay-per-second pricing.
|
||||
|
||||
## Agents
|
||||
|
||||
#### Claude Code
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/northflank/claude.sh)
|
||||
```
|
||||
|
||||
#### OpenClaw
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/northflank/openclaw.sh)
|
||||
```
|
||||
|
||||
#### Aider
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/northflank/aider.sh)
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
1. Create a Northflank account at https://northflank.com
|
||||
2. Generate an API token at https://northflank.com/account/settings/api/tokens
|
||||
3. Install the Northflank CLI:
|
||||
|
||||
```bash
|
||||
npm install -g @northflank/cli
|
||||
```
|
||||
|
||||
## Non-Interactive Mode
|
||||
|
||||
```bash
|
||||
NORTHFLANK_SERVICE_NAME=spawn-dev \
|
||||
NORTHFLANK_PROJECT_NAME=spawn-project \
|
||||
NORTHFLANK_TOKEN=your-token \
|
||||
OPENROUTER_API_KEY=sk-or-v1-xxxxx \
|
||||
bash <(curl -fsSL https://openrouter.ai/lab/spawn/northflank/claude.sh)
|
||||
```
|
||||
|
||||
## Free Tier
|
||||
|
||||
Northflank offers a Developer Sandbox with:
|
||||
- 2 free services
|
||||
- 2 free cron jobs
|
||||
- 1 free database/add-on
|
||||
|
||||
Perfect for testing and hobby projects. Production apps should use pay-as-you-go pricing.
|
||||
|
||||
## Pricing
|
||||
|
||||
Pay-per-second usage-based pricing after free tier:
|
||||
- Compute: $0.01667 per vCPU/hour, $0.00833 per GB memory/hour
|
||||
- Disk: $0.30/GB per month
|
||||
- Network egress: $0.15/GB
|
||||
63
northflank/aider.sh
Executable file
63
northflank/aider.sh
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
#!/bin/bash
|
||||
# shellcheck disable=SC2154
|
||||
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)"
|
||||
# shellcheck source=northflank/lib/common.sh
|
||||
if [[ -f "${SCRIPT_DIR}/lib/common.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/northflank/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Aider on Northflank"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure Northflank CLI and API token
|
||||
ensure_northflank_cli
|
||||
ensure_northflank_token
|
||||
|
||||
# 2. Get service and project names, then create service
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
|
||||
# 3. Wait for base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install Aider
|
||||
log_warn "Installing Aider..."
|
||||
run_server "pip install aider-chat 2>/dev/null || pip3 install aider-chat"
|
||||
log_info "Aider installed"
|
||||
|
||||
# 5. 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
|
||||
|
||||
# 6. Get model preference
|
||||
echo ""
|
||||
log_warn "Browse models at: https://openrouter.ai/models"
|
||||
log_warn "Which model would you like to use with Aider?"
|
||||
MODEL_ID=$(safe_read "Enter model ID [openrouter/auto]: ") || MODEL_ID=""
|
||||
MODEL_ID="${MODEL_ID:-openrouter/auto}"
|
||||
|
||||
# 7. Inject environment variables into shell configs
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
inject_env_vars_local upload_file run_server \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
|
||||
|
||||
echo ""
|
||||
log_info "Northflank service setup completed successfully!"
|
||||
log_info "Service: ${SERVER_NAME} (Project: ${NORTHFLANK_PROJECT_NAME})"
|
||||
echo ""
|
||||
|
||||
# 8. Start Aider interactively
|
||||
log_warn "Starting Aider..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "source ~/.bashrc && aider --model openrouter/${MODEL_ID}"
|
||||
72
northflank/claude.sh
Executable file
72
northflank/claude.sh
Executable file
|
|
@ -0,0 +1,72 @@
|
|||
#!/bin/bash
|
||||
# shellcheck disable=SC2154
|
||||
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)"
|
||||
# shellcheck source=northflank/lib/common.sh
|
||||
if [[ -f "${SCRIPT_DIR}/lib/common.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/northflank/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "Claude Code on Northflank"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure Northflank CLI and API token
|
||||
ensure_northflank_cli
|
||||
ensure_northflank_token
|
||||
|
||||
# 2. Get service and project names, then create service
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
|
||||
# 3. Wait for base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install Claude Code
|
||||
log_warn "Installing Claude Code..."
|
||||
run_server "curl -fsSL https://claude.ai/install.sh | bash"
|
||||
|
||||
# Verify installation
|
||||
if ! run_server "command -v claude" >/dev/null 2>&1; then
|
||||
log_error "Claude Code installation failed"
|
||||
exit 1
|
||||
fi
|
||||
log_info "Claude Code is installed"
|
||||
|
||||
# 5. 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
|
||||
|
||||
# 6. Inject environment variables into shell configs
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
inject_env_vars_local upload_file run_server \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"ANTHROPIC_BASE_URL=https://openrouter.ai/api" \
|
||||
"ANTHROPIC_AUTH_TOKEN=${OPENROUTER_API_KEY}" \
|
||||
"ANTHROPIC_API_KEY=" \
|
||||
"CLAUDE_CODE_SKIP_ONBOARDING=1" \
|
||||
"CLAUDE_CODE_ENABLE_TELEMETRY=0"
|
||||
|
||||
# 7. Configure Claude Code settings
|
||||
setup_claude_code_config "${OPENROUTER_API_KEY}" \
|
||||
"upload_file" \
|
||||
"run_server"
|
||||
|
||||
echo ""
|
||||
log_info "Northflank service setup completed successfully!"
|
||||
log_info "Service: ${SERVER_NAME} (Project: ${NORTHFLANK_PROJECT_NAME})"
|
||||
echo ""
|
||||
|
||||
# 8. Start Claude Code interactively
|
||||
log_warn "Starting Claude Code..."
|
||||
sleep 1
|
||||
clear
|
||||
interactive_session "source ~/.bashrc && claude"
|
||||
285
northflank/lib/common.sh
Normal file
285
northflank/lib/common.sh
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
#!/bin/bash
|
||||
# Common bash functions for Northflank spawn scripts
|
||||
# Uses Northflank CLI (northflank) — https://northflank.com
|
||||
# Containers with exec/shell access via CLI
|
||||
# Free tier: 2 services, pay-per-second pricing after
|
||||
|
||||
# Bash safety flags
|
||||
set -eo pipefail
|
||||
|
||||
# ============================================================
|
||||
# Provider-agnostic functions
|
||||
# ============================================================
|
||||
|
||||
# Source shared provider-agnostic functions (local or remote fallback)
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
||||
if [[ -n "${SCRIPT_DIR}" && -f "${SCRIPT_DIR}/../../shared/common.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../../shared/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/shared/common.sh)"
|
||||
fi
|
||||
|
||||
# Note: Provider-agnostic functions (logging, OAuth, browser, nc_listen) are now in shared/common.sh
|
||||
|
||||
# ============================================================
|
||||
# Northflank specific functions
|
||||
# ============================================================
|
||||
|
||||
ensure_northflank_cli() {
|
||||
if ! command -v northflank &>/dev/null; then
|
||||
log_warn "Installing Northflank CLI..."
|
||||
npm install -g @northflank/cli 2>/dev/null || {
|
||||
log_error "Failed to install Northflank CLI. Install manually: npm install -g @northflank/cli"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
log_info "Northflank CLI available"
|
||||
}
|
||||
|
||||
test_northflank_token() {
|
||||
local test_response
|
||||
# Test token by listing projects (lightweight API call)
|
||||
test_response=$(northflank list projects 2>&1)
|
||||
local exit_code=$?
|
||||
|
||||
if [[ ${exit_code} -ne 0 ]]; then
|
||||
if echo "${test_response}" | grep -qi "unauthorized\|invalid.*token\|authentication"; then
|
||||
log_error "Invalid API token"
|
||||
log_warn "Remediation steps:"
|
||||
log_warn " 1. Verify API token at: https://northflank.com/account/settings/api/tokens"
|
||||
log_warn " 2. Ensure the token has appropriate permissions"
|
||||
log_warn " 3. Check token hasn't expired (90 day limit)"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
ensure_northflank_token() {
|
||||
check_python_available || return 1
|
||||
|
||||
# 1. Check environment variable
|
||||
if [[ -n "${NORTHFLANK_TOKEN:-}" ]]; then
|
||||
log_info "Using Northflank token from environment"
|
||||
# Login with token
|
||||
if ! northflank login -t "${NORTHFLANK_TOKEN}" &>/dev/null; then
|
||||
log_error "Northflank token in environment is invalid"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
local config_file="${HOME}/.config/spawn/northflank.json"
|
||||
|
||||
# 2. Check config file
|
||||
if [[ -f "${config_file}" ]]; then
|
||||
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 NORTHFLANK_TOKEN="${saved_token}"
|
||||
log_info "Using Northflank token from ${config_file}"
|
||||
if ! northflank login -t "${saved_token}" &>/dev/null; then
|
||||
log_warn "Saved Northflank token is invalid, prompting for new one"
|
||||
unset NORTHFLANK_TOKEN
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. Prompt and validate
|
||||
echo ""
|
||||
log_warn "Northflank API Token Required"
|
||||
printf '%b\n' "${YELLOW}Get your token at: https://northflank.com/account/settings/api/tokens${NC}"
|
||||
echo ""
|
||||
|
||||
local token
|
||||
token=$(safe_read "Enter your Northflank token: ") || return 1
|
||||
if [[ -z "${token}" ]]; then
|
||||
log_error "Northflank token cannot be empty"
|
||||
log_warn "For non-interactive usage, set: NORTHFLANK_TOKEN=your-token"
|
||||
return 1
|
||||
fi
|
||||
|
||||
export NORTHFLANK_TOKEN="${token}"
|
||||
if ! northflank login -t "${token}" &>/dev/null; then
|
||||
log_error "Invalid Northflank token"
|
||||
unset NORTHFLANK_TOKEN
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Save token
|
||||
local config_dir="${HOME}/.config/spawn"
|
||||
mkdir -p "${config_dir}"
|
||||
printf '{\n "token": "%s"\n}\n' "$(json_escape "${token}")" > "${config_file}"
|
||||
chmod 600 "${config_file}"
|
||||
log_info "Northflank token saved to ${config_file}"
|
||||
}
|
||||
|
||||
get_server_name() {
|
||||
get_resource_name "NORTHFLANK_SERVICE_NAME" "Enter service name: "
|
||||
}
|
||||
|
||||
get_project_name() {
|
||||
get_resource_name "NORTHFLANK_PROJECT_NAME" "Enter project name: "
|
||||
}
|
||||
|
||||
create_server() {
|
||||
local name="${1}"
|
||||
local image="${NORTHFLANK_IMAGE:-ubuntu:24.04}"
|
||||
local project_name="${NORTHFLANK_PROJECT_NAME:-spawn-project}"
|
||||
|
||||
log_warn "Creating Northflank project '${project_name}'..."
|
||||
|
||||
# Create project (idempotent - won't fail if exists)
|
||||
northflank create project \
|
||||
--name "${project_name}" \
|
||||
--description "Spawn AI agent deployment" 2>/dev/null || true
|
||||
|
||||
log_info "Project '${project_name}' ready"
|
||||
export NORTHFLANK_PROJECT_NAME="${project_name}"
|
||||
|
||||
log_warn "Creating service '${name}' with image: ${image}"
|
||||
|
||||
# Create deployment service with Docker image
|
||||
local service_output
|
||||
service_output=$(northflank create service deployment \
|
||||
--name "${name}" \
|
||||
--project "${project_name}" \
|
||||
--image "${image}" \
|
||||
--cpu 0.5 \
|
||||
--memory 1024 \
|
||||
--replicas 1 2>&1)
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
log_error "Failed to create service: ${service_output}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
export NORTHFLANK_SERVICE_NAME="${name}"
|
||||
log_info "Service '${name}' created"
|
||||
|
||||
# Wait for service to be running
|
||||
log_warn "Waiting for service to start..."
|
||||
local max_attempts=60
|
||||
local attempt=1
|
||||
while [[ "${attempt}" -le "${max_attempts}" ]]; do
|
||||
local status
|
||||
status=$(northflank get service --name "${name}" --project "${project_name}" 2>/dev/null | grep -i "status" || true)
|
||||
if echo "${status}" | grep -qi "running\|active"; then
|
||||
log_info "Service is running"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_warn "Waiting for service to start (${attempt}/${max_attempts})..."
|
||||
sleep 3
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
log_error "Service did not start in time"
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_for_cloud_init() {
|
||||
log_warn "Installing base tools in container..."
|
||||
|
||||
# Update package lists and install essentials
|
||||
run_server "apt-get update -y && apt-get install -y curl git unzip python3 pip" >/dev/null 2>&1 || true
|
||||
|
||||
# Install bun for agent CLI tools
|
||||
run_server "curl -fsSL https://bun.sh/install | bash" >/dev/null 2>&1 || true
|
||||
run_server 'echo "export PATH=\"\$HOME/.bun/bin:\$PATH\"" >> ~/.bashrc' >/dev/null 2>&1 || true
|
||||
run_server 'echo "export PATH=\"\$HOME/.bun/bin:\$PATH\"" >> ~/.zshrc' >/dev/null 2>&1 || true
|
||||
|
||||
log_info "Base tools installed"
|
||||
}
|
||||
|
||||
# Run a command on the Northflank service via northflank exec
|
||||
run_server() {
|
||||
local cmd="${1}"
|
||||
local project="${NORTHFLANK_PROJECT_NAME}"
|
||||
local service="${NORTHFLANK_SERVICE_NAME}"
|
||||
|
||||
# SECURITY: Properly escape command to prevent injection
|
||||
local escaped_cmd
|
||||
escaped_cmd=$(printf '%q' "${cmd}")
|
||||
|
||||
# Use northflank exec with non-interactive mode
|
||||
northflank exec \
|
||||
--project "${project}" \
|
||||
--service "${service}" \
|
||||
--command "bash -c ${escaped_cmd}" 2>/dev/null
|
||||
}
|
||||
|
||||
# Upload a file to the service via base64 encoding through exec
|
||||
upload_file() {
|
||||
local local_path="${1}"
|
||||
local remote_path="${2}"
|
||||
local content
|
||||
content=$(base64 -w0 "${local_path}" 2>/dev/null || base64 "${local_path}")
|
||||
|
||||
# SECURITY: Properly escape paths and content to prevent injection
|
||||
local escaped_path
|
||||
escaped_path=$(printf '%q' "${remote_path}")
|
||||
local escaped_content
|
||||
escaped_content=$(printf '%q' "${content}")
|
||||
|
||||
run_server "echo ${escaped_content} | base64 -d > ${escaped_path}"
|
||||
}
|
||||
|
||||
# Start an interactive shell session on the Northflank service
|
||||
interactive_session() {
|
||||
local cmd="${1}"
|
||||
local project="${NORTHFLANK_PROJECT_NAME}"
|
||||
local service="${NORTHFLANK_SERVICE_NAME}"
|
||||
|
||||
# SECURITY: Properly escape command to prevent injection
|
||||
local escaped_cmd
|
||||
escaped_cmd=$(printf '%q' "${cmd}")
|
||||
|
||||
# Use northflank exec for interactive shell
|
||||
northflank exec \
|
||||
--project "${project}" \
|
||||
--service "${service}" \
|
||||
--command "bash -c ${escaped_cmd}"
|
||||
}
|
||||
|
||||
# Destroy a Northflank service
|
||||
destroy_server() {
|
||||
local service_name="${1:-${NORTHFLANK_SERVICE_NAME}}"
|
||||
local project_name="${NORTHFLANK_PROJECT_NAME}"
|
||||
|
||||
log_warn "Destroying service '${service_name}'..."
|
||||
|
||||
northflank delete service \
|
||||
--name "${service_name}" \
|
||||
--project "${project_name}" \
|
||||
--yes 2>/dev/null || true
|
||||
|
||||
log_info "Service destroyed"
|
||||
}
|
||||
|
||||
# Inject environment variables into .bashrc and .zshrc
|
||||
inject_env_vars_northflank() {
|
||||
local env_temp
|
||||
env_temp=$(mktemp)
|
||||
chmod 600 "${env_temp}"
|
||||
track_temp_file "${env_temp}"
|
||||
|
||||
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 Northflank services
|
||||
list_servers() {
|
||||
log_info "Northflank services:"
|
||||
northflank list services 2>/dev/null || {
|
||||
log_error "Failed to list Northflank services"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
66
northflank/openclaw.sh
Executable file
66
northflank/openclaw.sh
Executable file
|
|
@ -0,0 +1,66 @@
|
|||
#!/bin/bash
|
||||
# shellcheck disable=SC2154
|
||||
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)"
|
||||
# shellcheck source=northflank/lib/common.sh
|
||||
if [[ -f "${SCRIPT_DIR}/lib/common.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
else
|
||||
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/northflank/lib/common.sh)"
|
||||
fi
|
||||
|
||||
log_info "OpenClaw on Northflank"
|
||||
echo ""
|
||||
|
||||
# 1. Ensure Northflank CLI and API token
|
||||
ensure_northflank_cli
|
||||
ensure_northflank_token
|
||||
|
||||
# 2. Get service and project names, then create service
|
||||
SERVER_NAME=$(get_server_name)
|
||||
create_server "${SERVER_NAME}"
|
||||
|
||||
# 3. Wait for base tools
|
||||
wait_for_cloud_init
|
||||
|
||||
# 4. Install openclaw via bun
|
||||
log_warn "Installing openclaw..."
|
||||
run_server "source ~/.bashrc && bun install -g openclaw"
|
||||
log_info "OpenClaw installed"
|
||||
|
||||
# 5. 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
|
||||
|
||||
# 6. Get model preference
|
||||
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "Openclaw") || exit 1
|
||||
|
||||
# 7. Inject environment variables into shell configs
|
||||
log_warn "Setting up environment variables..."
|
||||
|
||||
inject_env_vars_local upload_file run_server \
|
||||
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"ANTHROPIC_API_KEY=${OPENROUTER_API_KEY}" \
|
||||
"ANTHROPIC_BASE_URL=https://openrouter.ai/api"
|
||||
|
||||
# 8. Configure openclaw
|
||||
setup_openclaw_config "${OPENROUTER_API_KEY}" "${MODEL_ID}" \
|
||||
upload_file \
|
||||
run_server
|
||||
|
||||
echo ""
|
||||
log_info "Northflank service setup completed successfully!"
|
||||
log_info "Service: ${SERVER_NAME} (Project: ${NORTHFLANK_PROJECT_NAME})"
|
||||
echo ""
|
||||
|
||||
# 9. Start openclaw gateway in background and launch TUI
|
||||
log_warn "Starting openclaw..."
|
||||
run_server "source ~/.bashrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 &"
|
||||
sleep 2
|
||||
interactive_session "source ~/.bashrc && openclaw tui"
|
||||
Loading…
Add table
Add a link
Reference in a new issue