#!/usr/bin/env bash set -euo pipefail # =========================================================== # OpenClaw Agent Deployment for Targon # Usage: curl -fsSL https://targon.com/openclaw.sh | bash # =========================================================== BOLD='' INFO='' SUCCESS='' WARN='' ERROR='' MUTED='' ACCENT='' NC='' init_colors() { # Respect no-color and non-interactive environments. if [ -n "${NO_COLOR:-}" ] || { [ ! -t 1 ] && [ ! -t 2 ]; } || [ "${TERM:-}" = "dumb" ]; then return 0 fi BOLD='\033[1m' NC='\033[0m' # Apple Terminal has inconsistent support for 24-bit colors on some setups. # Use 256-color palette there; keep truecolor for iTerm/modern terminals. if [ "${TERM_PROGRAM:-}" = "Apple_Terminal" ]; then INFO='\033[38;5;110m' SUCCESS='\033[38;5;42m' WARN='\033[38;5;214m' ERROR='\033[38;5;203m' MUTED='\033[38;5;245m' ACCENT='\033[38;5;203m' else INFO='\033[38;2;136;146;176m' SUCCESS='\033[38;2;0;229;204m' WARN='\033[38;2;255;176;32m' ERROR='\033[38;2;230;57;70m' MUTED='\033[38;2;90;100;128m' ACCENT='\033[38;2;255;77;77m' fi } print_banner() { echo -e "${ACCENT}" cat <<'BANNER' ┌──────────────────────────────────────────┐ │░▀█▀░█▀█░█▀▄░█▀▀░█▀█░█▀█░░░█▀▀░█░░░█▀█░█░█│ │░░█░░█▀█░█▀▄░█░█░█░█░█░█░░░█░░░█░░░█▀█░█▄█│ │░░▀░░▀░▀░▀░▀░▀▀▀░▀▀▀░▀░▀░░░▀▀▀░▀▀▀░▀░▀░▀░▀│ └──────────────────────────────────────────┘ BANNER echo -e "${NC}" } log_info() { echo -e "${INFO} →${NC} $*"; } log_success() { echo -e "${SUCCESS} ✓${NC} $*"; } log_warn() { echo -e "${WARN} ⚠${NC} $*" >&2; } log_error() { echo -e "${ERROR} ✗${NC} $*" >&2; } log_section() { echo -e "\n${BOLD}${INFO}$*${NC}\n"; } log_muted() { echo -e "${MUTED}$*${NC}"; } TARGON_DEPLOY_URL="${TARGON_DEPLOY_URL:-https://api.targon.com/v1/deployments}" die() { log_error "$*" exit 1 } check_deps() { log_section "Checking dependencies" local missing=() for cmd in curl jq; do if command -v "$cmd" &>/dev/null; then log_success "$cmd found" else log_error "$cmd not found" missing+=("$cmd") fi done if command -v openssl &>/dev/null; then log_success "openssl found (used for token generation)" else log_warn "openssl not found — will fall back to /dev/urandom for token generation" fi if [ "${#missing[@]}" -gt 0 ]; then die "Missing required tools: ${missing[*]}. Please install them and re-run." fi } generate_token() { if command -v openssl &>/dev/null; then openssl rand -hex 32 elif [ -r /dev/urandom ]; then head -c 32 /dev/urandom | od -A n -t x1 | tr -d ' \n' else date +%s%N | sha256sum | head -c 64 fi } # Read interactive user input from terminal when script stdin is piped. # Usage: read_user_input VAR_NAME [secret] read_user_input() { local var_name="$1" local secret="${2:-}" if [ -r /dev/tty ] && [ -w /dev/tty ]; then if [ "$secret" = "secret" ]; then IFS= read -rs "$var_name" /dev/tty || true else IFS= read -r "$var_name" 0))) | map(if (test("^https?://")) then . else ("https://" + .) end) | unique | .[] ' 2>/dev/null || true)" if [ -n "$dns_urls" ]; then echo log_success "ClawBot URL(s):" while IFS= read -r url; do [ -n "$url" ] && echo -e " ${BOLD}${url}${NC}" done <<< "$dns_urls" else log_muted "Deployment created, but URL is not ready yet (PortToDNSMapping empty)." fi fi echo log_warn "Save your Gateway Token — you will need it to connect agents:" echo -e " ${BOLD}${ACCENT}$gateway_token${NC}" if [ -n "$capacity_warning" ] && [ "$capacity_warning" != "null" ]; then echo log_warn "Capacity notice: $capacity_warning" fi echo log_muted "It may take a minute for the gateway to become reachable." } main() { init_colors print_banner echo -e "${BOLD}Welcome to the OpenClaw installer for Targon.${NC}" log_muted "This will deploy OpenClaw to Targon's cloud and return your dashboard URL." echo check_deps if ! [ -r /dev/tty ] || ! [ -w /dev/tty ]; then die "Interactive setup requires a TTY. Re-run in a terminal (not CI) and avoid redirecting /dev/tty." fi # ----- Targon credentials ----------------------------------------------- log_section "Targon Account" prompt_required TARGON_API_KEY "Targon API Key" secret # ----- Deployment name --------------------------------------------------- log_section "Deployment" local DEPLOY_NAME="" while true; do prompt_required DEPLOY_NAME "Deployment name (lowercase, hyphens allowed)" if validate_deploy_name "$DEPLOY_NAME"; then break else log_error "Invalid name. Use lowercase letters, numbers and hyphens (e.g. my-openclaw). Must start/end with alphanumeric." DEPLOY_NAME="" fi done # ----- Resource Size ------------------------------------------------------ log_section "Resource Size" echo -e " ${MUTED}1)${NC} cpu-small" echo -e " ${MUTED}2)${NC} cpu-medium" echo -e " ${MUTED}3)${NC} cpu-large" echo -e " ${MUTED}4)${NC} cpu-xlarge" echo local RESOURCE_CHOICE="" RESOURCE_NAME="" while true; do echo -en "${BOLD}Choose resource size [1/2/3/4]:${NC} " read_user_input RESOURCE_CHOICE case "$RESOURCE_CHOICE" in 1|"") RESOURCE_NAME="cpu-small"; break ;; 2) RESOURCE_NAME="cpu-medium"; break ;; 3) RESOURCE_NAME="cpu-large"; break ;; 4) RESOURCE_NAME="cpu-xlarge"; break ;; *) log_error "Please enter 1, 2, 3, or 4." ;; esac done log_info "Resource: ${BOLD}$RESOURCE_NAME${NC}" # ----- AI Provider ------------------------------------------------------- log_section "Model / Auth Provider" echo -e " ${MUTED}1)${NC} OpenAI" echo -e " ${MUTED}2)${NC} Anthropic" echo -e " ${MUTED}3)${NC} Google" echo -e " ${MUTED}4)${NC} Sybill" echo -e " ${MUTED}5)${NC} Skip for now" echo local PROVIDER_CHOICE="" PROVIDER="" local PROVIDER_KEY_NAME="" PROVIDER_API_KEY="" DEFAULT_MODEL="" while true; do echo -en "${BOLD}Choose provider [1/2/3/4/5]:${NC} " read_user_input PROVIDER_CHOICE case "$PROVIDER_CHOICE" in 1) PROVIDER="openai"; break ;; 2) PROVIDER="anthropic"; break ;; 3) PROVIDER="google"; break ;; 4) PROVIDER="sybill"; break ;; 5) PROVIDER="skip"; break ;; *) log_error "Please enter 1, 2, 3, 4, or 5." ;; esac done local DISPLAY_NAME local MODEL_DEFAULT if [ "$PROVIDER" = "skip" ]; then DISPLAY_NAME="skip" DEFAULT_MODEL="skip" log_warn "Skipping provider setup for now — configure API provider later." else DISPLAY_NAME="$(provider_display_name "$PROVIDER")" log_info "Selected: ${BOLD}$DISPLAY_NAME${NC}" # API key PROVIDER_KEY_NAME="$(provider_env_key "$PROVIDER")" prompt_required PROVIDER_API_KEY "Enter ${DISPLAY_NAME} API key" secret # Default model (editable, pre-filled with sensible default) MODEL_DEFAULT="$(default_model_for_provider "$PROVIDER")" prompt_optional DEFAULT_MODEL "Default model name" "$MODEL_DEFAULT" log_info "Model: ${BOLD}$DEFAULT_MODEL${NC}" fi # Sybill-specific: API base URL for the custom OpenAI-compatible endpoint local SYBILL_BASE_URL="" if [ "$PROVIDER" = "sybill" ]; then prompt_optional SYBILL_BASE_URL "Sybill API base URL" "https://api.sybil.com/v1" log_info "Base URL: ${BOLD}$SYBILL_BASE_URL${NC}" fi # ----- Channel (QuickStart) ----------------------------------------------- log_section "Select Channel (QuickStart)" echo -e " ${MUTED}1)${NC} Telegram (Bot API)" echo -e " ${MUTED}2)${NC} WhatsApp (QR link)" echo -e " ${MUTED}3)${NC} Discord (Bot API)" echo -e " ${MUTED}4)${NC} Skip for now" echo local CHANNEL_CHOICE="" CHANNEL="" local CHANNEL_TOKEN="" CHANNEL_ALLOW_FROM="" while true; do echo -en "${BOLD}Choose channel [1/2/3/4]:${NC} " read_user_input CHANNEL_CHOICE case "$CHANNEL_CHOICE" in 1) CHANNEL="telegram"; break ;; 2) CHANNEL="whatsapp"; break ;; 3) CHANNEL="discord"; break ;; 4) CHANNEL="skip"; break ;; *) log_error "Please enter 1, 2, 3, or 4." ;; esac done case "$CHANNEL" in telegram) log_info "Selected: ${BOLD}Telegram${NC}" prompt_required CHANNEL_TOKEN "Telegram Bot Token" secret prompt_optional CHANNEL_ALLOW_FROM "Telegram allowFrom (comma-separated, e.g. tg:123,tg:456)" "" ;; whatsapp) log_info "Selected: ${BOLD}WhatsApp${NC}" log_muted "WhatsApp uses QR-based linking. The QR code will appear in the gateway logs after deploy." prompt_optional CHANNEL_ALLOW_FROM "WhatsApp allowFrom (comma-separated, e.g. +15550001,+44770002)" "" ;; discord) log_info "Selected: ${BOLD}Discord${NC}" prompt_required CHANNEL_TOKEN "Discord Bot Token" secret prompt_optional CHANNEL_ALLOW_FROM "Discord allowFrom (comma-separated IDs/usernames)" "" ;; skip) log_warn "Skipping channel setup — you can configure channels later via the dashboard." ;; esac # ----- Gateway Port ------------------------------------------------------ log_section "Gateway Port" local GATEWAY_PORT="" prompt_optional GATEWAY_PORT "Gateway port" "18789" if ! [[ "$GATEWAY_PORT" =~ ^[0-9]+$ ]] || [ "$GATEWAY_PORT" -lt 1 ] || [ "$GATEWAY_PORT" -gt 65535 ]; then die "Invalid port number: $GATEWAY_PORT (must be 1-65535)" fi log_info "Gateway port: ${BOLD}$GATEWAY_PORT${NC}" # ----- Gateway Token ----------------------------------------------------- log_section "Gateway Token" log_muted "The gateway token secures communication between your agents and OpenClaw." local GATEWAY_TOKEN="" prompt_optional GATEWAY_TOKEN "Gateway token" "" secret if [ -z "$GATEWAY_TOKEN" ]; then GATEWAY_TOKEN="$(generate_token)" log_warn "Auto-generated gateway token (save this, required for login):" echo -e " ${BOLD}${ACCENT}${GATEWAY_TOKEN}${NC}" else log_info "Using provided gateway token." fi # ----- Device Auth ------------------------------------------------------- log_section "Device Pairing / Auth" log_muted "If disabled, users can access without manual SSH pairing approval." local DISABLE_DEVICE_AUTH_CHOICE="" local DISABLE_DEVICE_AUTH="true" while true; do echo -en "${BOLD}Disable device auth and skip pairing requests?${NC} ${MUTED}[Y/n]${NC}: " read_user_input DISABLE_DEVICE_AUTH_CHOICE case "$(printf '%s' "$DISABLE_DEVICE_AUTH_CHOICE" | tr '[:upper:]' '[:lower:]')" in ""|y|yes) DISABLE_DEVICE_AUTH="true" log_warn "Device auth disabled." break ;; n|no) DISABLE_DEVICE_AUTH="false" log_info "Device auth enabled (manual approval required)." break ;; *) log_error "Please answer y or n." ;; esac done # ----- Review ------------------------------------------------------------ log_section "Review" log_muted "Deployment name: $DEPLOY_NAME" log_muted "Resource size : $RESOURCE_NAME" log_muted "Provider : $DISPLAY_NAME" log_muted "Model : $DEFAULT_MODEL" log_muted "Channel : ${CHANNEL:-skip}" log_muted "Gateway port : $GATEWAY_PORT" log_muted "Device auth : $([ "$DISABLE_DEVICE_AUTH" = "true" ] && echo "disabled" || echo "enabled (pairing required)")" echo local CONFIRM_DEPLOY="" prompt_confirm CONFIRM_DEPLOY "Proceed with deployment?" if [ "$CONFIRM_DEPLOY" != "yes" ]; then die "Deployment cancelled by user." fi # ----- Build & Send ------------------------------------------------------ log_section "Deploying" local ENV_JSON ENV_JSON="$(build_env_json \ "$PROVIDER" \ "$PROVIDER_KEY_NAME" \ "$PROVIDER_API_KEY" \ "$DEFAULT_MODEL" \ "$GATEWAY_TOKEN" \ "$GATEWAY_PORT" \ "${CHANNEL:-skip}" \ "${CHANNEL_TOKEN:-}" \ "$DISABLE_DEVICE_AUTH")" # Sybill-specific: pass the custom base URL so entrypoint.sh can build # the full models.providers section in the config if [ "$PROVIDER" = "sybill" ] && [ -n "${SYBILL_BASE_URL:-}" ]; then ENV_JSON=$(echo "$ENV_JSON" | jq \ --arg url "$SYBILL_BASE_URL" \ '. + [{"name": "SYBILL_BASE_URL", "value": $url}]') fi # Channel-specific env vars consumed by entrypoint.sh for config patching case "${CHANNEL:-skip}" in telegram) ENV_JSON=$(echo "$ENV_JSON" | jq \ --arg token "${CHANNEL_TOKEN:-}" \ '. + [{"name": "TELEGRAM_BOT_TOKEN", "value": $token}]') if [ -n "${CHANNEL_ALLOW_FROM:-}" ]; then ENV_JSON=$(echo "$ENV_JSON" | jq \ --arg allow "${CHANNEL_ALLOW_FROM}" \ '. + [{"name": "OPENCLAW_TELEGRAM_ALLOW_FROM", "value": $allow}]') fi ;; discord) ENV_JSON=$(echo "$ENV_JSON" | jq \ --arg token "${CHANNEL_TOKEN:-}" \ '. + [{"name": "DISCORD_BOT_TOKEN", "value": $token}]') if [ -n "${CHANNEL_ALLOW_FROM:-}" ]; then ENV_JSON=$(echo "$ENV_JSON" | jq \ --arg allow "${CHANNEL_ALLOW_FROM}" \ '. + [{"name": "OPENCLAW_DISCORD_ALLOW_FROM", "value": $allow}]') fi ;; whatsapp) ENV_JSON=$(echo "$ENV_JSON" | jq \ '. + [{"name": "OPENCLAW_WHATSAPP_ENABLED", "value": "true"}]') if [ -n "${CHANNEL_ALLOW_FROM:-}" ]; then ENV_JSON=$(echo "$ENV_JSON" | jq \ --arg allow "${CHANNEL_ALLOW_FROM}" \ '. + [{"name": "OPENCLAW_WHATSAPP_ALLOW_FROM", "value": $allow}]') fi ;; esac local PAYLOAD PAYLOAD="$(build_payload "$DEPLOY_NAME" "$ENV_JSON" "$GATEWAY_PORT" "$RESOURCE_NAME")" echo deploy "$TARGON_API_KEY" "$PAYLOAD" local HTTP_CODE="$DEPLOY_HTTP_CODE" local BODY="$DEPLOY_BODY" # Treat any non-2xx as an error if [[ "$HTTP_CODE" != 2* ]]; then log_error "Targon API returned HTTP $HTTP_CODE:" echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY" exit 1 fi print_result "$BODY" "$GATEWAY_TOKEN" "$TARGON_API_KEY" } main "$@"