Skip to content

Instantly share code, notes, and snippets.

@secemp9
Created March 20, 2026 19:52
Show Gist options
  • Select an option

  • Save secemp9/3ef68c1539e979611d1bb44490ff1513 to your computer and use it in GitHub Desktop.

Select an option

Save secemp9/3ef68c1539e979611d1bb44490ff1513 to your computer and use it in GitHub Desktop.
script for clipproxyapi
#!/usr/bin/env bash
#
# setup_proxy.sh - CLIProxyAPI Setup and Management Script
#
# This script provides comprehensive setup and management capabilities for CLIProxyAPI,
# a proxy server that provides OpenAI/Gemini/Claude compatible API interfaces.
#
# Features:
# - Automatic Go 1.26+ installation check
# - CLIProxyAPI build and setup
# - OAuth-based account login (Claude, Codex)
# - API key management
# - Proxy server lifecycle management
#
# Usage: ./setup_proxy.sh [OPTIONS]
#
# Author: Generated for round_robin_subscription project
# License: MIT
#
set -euo pipefail
# =============================================================================
# CONFIGURATION
# =============================================================================
# Paths
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLIPROXYAPI_DIR="${SCRIPT_DIR}/CLIProxyAPI"
CONFIG_FILE="${CLIPROXYAPI_DIR}/config.yaml"
CONFIG_EXAMPLE="${CLIPROXYAPI_DIR}/config.example.yaml"
AUTH_DIR="${HOME}/.cli-proxy-api"
BINARY_NAME="cli-proxy-api"
BINARY_PATH="${CLIPROXYAPI_DIR}/${BINARY_NAME}"
PID_FILE="${CLIPROXYAPI_DIR}/.proxy.pid"
LOG_FILE="${CLIPROXYAPI_DIR}/proxy.log"
# Required Go version
REQUIRED_GO_VERSION="1.26"
# Default proxy port
DEFAULT_PORT=8317
# =============================================================================
# COLOR DEFINITIONS
# =============================================================================
# Check if terminal supports colors
if [[ -t 1 ]] && command -v tput &>/dev/null && [[ $(tput colors 2>/dev/null || echo 0) -ge 8 ]]; then
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
YELLOW=$(tput setaf 3)
BLUE=$(tput setaf 4)
MAGENTA=$(tput setaf 5)
CYAN=$(tput setaf 6)
WHITE=$(tput setaf 7)
BOLD=$(tput bold)
RESET=$(tput sgr0)
else
RED=""
GREEN=""
YELLOW=""
BLUE=""
MAGENTA=""
CYAN=""
WHITE=""
BOLD=""
RESET=""
fi
# =============================================================================
# LOGGING FUNCTIONS
# =============================================================================
log_info() {
echo "${BLUE}[INFO]${RESET} $*"
}
log_success() {
echo "${GREEN}[SUCCESS]${RESET} $*"
}
log_warning() {
echo "${YELLOW}[WARNING]${RESET} $*"
}
log_error() {
echo "${RED}[ERROR]${RESET} $*" >&2
}
log_header() {
echo ""
echo "${BOLD}${CYAN}=== $* ===${RESET}"
echo ""
}
# =============================================================================
# UTILITY FUNCTIONS
# =============================================================================
# Check if a command exists
command_exists() {
command -v "$1" &>/dev/null
}
# Compare semantic versions (returns 0 if $1 >= $2)
version_ge() {
local ver1="$1"
local ver2="$2"
# Remove 'go' prefix if present
ver1="${ver1#go}"
ver2="${ver2#go}"
# Compare using sort -V
printf '%s\n%s\n' "$ver2" "$ver1" | sort -V -C
}
# Generate a random API key (32 hex characters)
generate_api_key() {
if command_exists openssl; then
openssl rand -hex 16
elif [[ -r /dev/urandom ]]; then
head -c 16 /dev/urandom | od -An -tx1 | tr -d ' \n'
else
# Fallback: use date and process info
echo "$(date +%s%N)$$" | sha256sum | head -c 32
fi
}
# Mask an API key showing only last 4 characters
mask_api_key() {
local key="$1"
local len=${#key}
if [[ $len -le 4 ]]; then
echo "****"
else
local masked_len=$((len - 4))
printf '%*s' "$masked_len" '' | tr ' ' '*'
echo -n "${key: -4}"
echo ""
fi
}
# Check if config.yaml exists
config_exists() {
[[ -f "$CONFIG_FILE" ]]
}
# Backup config file
backup_config() {
if config_exists; then
local backup_file="${CONFIG_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
cp "$CONFIG_FILE" "$backup_file"
log_info "Config backed up to: $backup_file"
fi
}
# Validate API key format (basic validation)
validate_api_key() {
local key="$1"
local provider="$2"
if [[ -z "$key" ]]; then
log_error "API key cannot be empty"
return 1
fi
case "$provider" in
claude)
# Claude API keys typically start with sk-ant-
if [[ ! "$key" =~ ^sk-ant- ]] && [[ ! "$key" =~ ^sk- ]]; then
log_warning "Claude API key may not be in the expected format (should start with sk-ant- or sk-)"
fi
;;
codex)
# Codex/OpenAI keys start with sk-
if [[ ! "$key" =~ ^sk- ]]; then
log_warning "Codex API key may not be in the expected format (should start with sk-)"
fi
;;
esac
return 0
}
# =============================================================================
# GO INSTALLATION FUNCTIONS
# =============================================================================
# Get installed Go version
get_go_version() {
if command_exists go; then
go version | grep -oP 'go\d+\.\d+(\.\d+)?' | head -1
else
echo ""
fi
}
# Check if Go version meets requirements
check_go_version() {
local current_version
current_version=$(get_go_version)
if [[ -z "$current_version" ]]; then
return 1
fi
version_ge "$current_version" "go${REQUIRED_GO_VERSION}"
}
# Install Go
install_go() {
log_header "Installing Go ${REQUIRED_GO_VERSION}"
local arch
local os_type="linux"
# Detect architecture
case "$(uname -m)" in
x86_64)
arch="amd64"
;;
aarch64|arm64)
arch="arm64"
;;
armv7l|armv6l)
arch="armv6l"
;;
*)
log_error "Unsupported architecture: $(uname -m)"
return 1
;;
esac
local go_version="${REQUIRED_GO_VERSION}.0"
local go_tarball="go${go_version}.${os_type}-${arch}.tar.gz"
local download_url="https://go.dev/dl/${go_tarball}"
local install_dir="$HOME/.local"
local tmp_dir
tmp_dir=$(mktemp -d)
# Create install directory if it doesn't exist
if [[ ! -d "$install_dir" ]]; then
mkdir -p "$install_dir"
fi
log_info "Downloading Go ${go_version} for ${os_type}/${arch}..."
if command_exists curl; then
curl -fsSL -o "${tmp_dir}/${go_tarball}" "$download_url"
elif command_exists wget; then
wget -q -O "${tmp_dir}/${go_tarball}" "$download_url"
else
log_error "Neither curl nor wget found. Please install one of them."
rm -rf "$tmp_dir"
return 1
fi
log_info "Installing Go to ${install_dir}..."
# Remove existing Go installation
if [[ -d "${install_dir}/go" ]]; then
log_warning "Removing existing Go installation..."
rm -rf "${install_dir}/go"
fi
# Extract new Go installation
tar -C "$install_dir" -xzf "${tmp_dir}/${go_tarball}"
# Clean up
rm -rf "$tmp_dir"
# Update PATH if needed
if [[ ":$PATH:" != *":$HOME/.local/go/bin:"* ]]; then
export PATH="$PATH:$HOME/.local/go/bin"
# Add to shell profile
local profile_file=""
if [[ -f "${HOME}/.bashrc" ]]; then
profile_file="${HOME}/.bashrc"
elif [[ -f "${HOME}/.zshrc" ]]; then
profile_file="${HOME}/.zshrc"
elif [[ -f "${HOME}/.profile" ]]; then
profile_file="${HOME}/.profile"
fi
if [[ -n "$profile_file" ]]; then
if ! grep -q '\$HOME/.local/go/bin' "$profile_file" 2>/dev/null; then
echo 'export PATH="$PATH:$HOME/.local/go/bin"' >> "$profile_file"
log_info "Added Go to PATH in $profile_file"
fi
fi
fi
# Verify installation
if check_go_version; then
log_success "Go $(get_go_version) installed successfully"
else
log_error "Go installation verification failed"
return 1
fi
}
# =============================================================================
# BUILD FUNCTIONS
# =============================================================================
# Build CLIProxyAPI binary
build_binary() {
log_header "Building CLIProxyAPI"
if [[ ! -d "$CLIPROXYAPI_DIR" ]]; then
log_error "CLIProxyAPI directory not found at: $CLIPROXYAPI_DIR"
return 1
fi
cd "$CLIPROXYAPI_DIR"
log_info "Downloading dependencies..."
go mod download
log_info "Building binary..."
# Build with version info
local version commit build_date
version="dev"
commit="local"
build_date=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Check if we're in a git repo and get version info
if command_exists git && [[ -d .git ]]; then
commit=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
version=$(git describe --tags --always 2>/dev/null || echo "dev")
fi
go build -ldflags "-X main.Version=${version} -X main.Commit=${commit} -X main.BuildDate=${build_date}" \
-o "$BINARY_NAME" ./cmd/server
chmod +x "$BINARY_NAME"
log_success "Binary built successfully: ${BINARY_PATH}"
}
# =============================================================================
# CONFIG MANAGEMENT FUNCTIONS
# =============================================================================
# Create default config.yaml if it doesn't exist
create_default_config() {
log_header "Creating Default Configuration"
if config_exists; then
log_info "Config file already exists at: $CONFIG_FILE"
return 0
fi
if [[ ! -f "$CONFIG_EXAMPLE" ]]; then
log_error "Example config not found at: $CONFIG_EXAMPLE"
return 1
fi
# Generate a random API key for client authentication
local local_api_key
local_api_key=$(generate_api_key)
log_info "Creating config.yaml with round-robin routing enabled..."
# Create a minimal config based on the example
cat > "$CONFIG_FILE" << EOF
# CLIProxyAPI Configuration
# Generated by setup_proxy.sh on $(date)
# Server host/interface to bind to
host: ""
# Server port
port: ${DEFAULT_PORT}
# Authentication directory
auth-dir: "${AUTH_DIR}"
# API keys for client authentication
api-keys:
- "${local_api_key}"
# Enable debug logging
debug: false
# Routing strategy for selecting credentials when multiple match
routing:
strategy: "round-robin"
# Number of times to retry a request
request-retry: 3
# Quota exceeded behavior
quota-exceeded:
switch-project: true
switch-preview-model: true
# Management API settings (enable via --panel)
remote-management:
allow-remote: false
secret-key: ""
disable-control-panel: false
# Claude API keys (add via --add-claude-key)
# claude-api-key:
# - api-key: "sk-ant-..."
# Codex API keys (add via --add-codex-key)
# codex-api-key:
# - api-key: "sk-..."
EOF
log_success "Config created at: $CONFIG_FILE"
log_info "Your local API key for client authentication: ${local_api_key}"
log_warning "Save this key! It's required for connecting clients to the proxy."
}
# Setup auth directory
setup_auth_dir() {
log_header "Setting up Authentication Directory"
if [[ ! -d "$AUTH_DIR" ]]; then
mkdir -p "$AUTH_DIR"
chmod 700 "$AUTH_DIR"
log_success "Auth directory created: $AUTH_DIR"
else
log_info "Auth directory already exists: $AUTH_DIR"
fi
}
# Add Claude API key to config
add_claude_api_key() {
local api_key="$1"
log_header "Adding Claude API Key"
if ! validate_api_key "$api_key" "claude"; then
return 1
fi
if ! config_exists; then
log_error "Config file not found. Run --install first."
return 1
fi
backup_config
# Check if claude-api-key section exists (not commented)
if grep -q "^claude-api-key:" "$CONFIG_FILE"; then
# Section exists, append to it
# Find the line number of claude-api-key and add after the last entry
local insert_line
insert_line=$(grep -n "^claude-api-key:" "$CONFIG_FILE" | cut -d: -f1)
# Find the next section or end of file
local next_section_line
next_section_line=$(tail -n +$((insert_line + 1)) "$CONFIG_FILE" | grep -n "^[a-z]" | head -1 | cut -d: -f1)
if [[ -n "$next_section_line" ]]; then
insert_line=$((insert_line + next_section_line - 1))
else
insert_line=$(wc -l < "$CONFIG_FILE")
fi
# Insert the new key
sed -i "${insert_line}a\\ - api-key: \"${api_key}\"" "$CONFIG_FILE"
else
# Section doesn't exist, create it
# Remove commented claude-api-key section if present
sed -i '/^# claude-api-key:/,/^[^#]/{ /^# claude-api-key:/d; /^#.*api-key:/d; }' "$CONFIG_FILE" 2>/dev/null || true
# Append new section
cat >> "$CONFIG_FILE" << EOF
# Claude API keys
claude-api-key:
- api-key: "${api_key}"
EOF
fi
log_success "Claude API key added (masked): $(mask_api_key "$api_key")"
}
# Add Codex API key to config
add_codex_api_key() {
local api_key="$1"
log_header "Adding Codex API Key"
if ! validate_api_key "$api_key" "codex"; then
return 1
fi
if ! config_exists; then
log_error "Config file not found. Run --install first."
return 1
fi
backup_config
# Check if codex-api-key section exists (not commented)
if grep -q "^codex-api-key:" "$CONFIG_FILE"; then
# Section exists, append to it
local insert_line
insert_line=$(grep -n "^codex-api-key:" "$CONFIG_FILE" | cut -d: -f1)
# Find the next section or end of file
local next_section_line
next_section_line=$(tail -n +$((insert_line + 1)) "$CONFIG_FILE" | grep -n "^[a-z]" | head -1 | cut -d: -f1)
if [[ -n "$next_section_line" ]]; then
insert_line=$((insert_line + next_section_line - 1))
else
insert_line=$(wc -l < "$CONFIG_FILE")
fi
# Insert the new key
sed -i "${insert_line}a\\ - api-key: \"${api_key}\"" "$CONFIG_FILE"
else
# Section doesn't exist, create it
# Remove commented codex-api-key section if present
sed -i '/^# codex-api-key:/,/^[^#]/{ /^# codex-api-key:/d; /^#.*api-key:/d; }' "$CONFIG_FILE" 2>/dev/null || true
# Append new section
cat >> "$CONFIG_FILE" << EOF
# Codex API keys
codex-api-key:
- api-key: "${api_key}"
EOF
fi
log_success "Codex API key added (masked): $(mask_api_key "$api_key")"
}
# =============================================================================
# MANAGEMENT PANEL FUNCTIONS
# =============================================================================
# Enable and configure the Management Center panel
setup_management_panel() {
local allow_remote="${1:-false}"
log_header "Management Center Setup"
if ! config_exists; then
log_error "Config file not found. Run --install first."
return 1
fi
backup_config
# Generate a management secret key
local mgmt_key
mgmt_key="mgmt-$(generate_api_key)"
log_info "Configuring Management Center..."
# Check if remote-management section already has a non-empty secret-key
local existing_key
existing_key=$(sed -n '/^remote-management:/,/^[a-z]/{
/secret-key:/{ s/.*secret-key:[[:space:]]*"\?\([^"]*\)"\?/\1/; /^$/!p; }
}' "$CONFIG_FILE" 2>/dev/null || true)
if [[ -n "$existing_key" ]]; then
log_info "Management panel is already configured with an existing key."
mgmt_key="$existing_key"
else
# Update or create the remote-management section
if grep -q "^remote-management:" "$CONFIG_FILE"; then
# Section exists, update the secret-key
sed -i "/^remote-management:/,/^[a-z]/{
s|^\([[:space:]]*\)secret-key:.*|\1secret-key: \"${mgmt_key}\"|
}" "$CONFIG_FILE"
else
# Section doesn't exist, append it
cat >> "$CONFIG_FILE" << EOF
# Management API settings
remote-management:
allow-remote: ${allow_remote}
secret-key: "${mgmt_key}"
disable-control-panel: false
EOF
fi
fi
# Update allow-remote if requested
if [[ "$allow_remote" == "true" ]]; then
sed -i "/^remote-management:/,/^[a-z]/{
s|^\([[:space:]]*\)allow-remote:.*|\1allow-remote: true|
}" "$CONFIG_FILE"
log_warning "Remote management access is ENABLED. Only enable this on trusted networks."
fi
# Read port from config
local port
port=$(grep "^port:" "$CONFIG_FILE" | head -1 | awk '{print $2}' || echo "$DEFAULT_PORT")
log_success "Management Center configured!"
echo ""
echo "${BOLD}Access the Management Center:${RESET}"
echo " URL: ${CYAN}http://localhost:${port}/management.html${RESET}"
echo ""
echo "${BOLD}Management Key (NOT the same as proxy API keys):${RESET}"
echo " ${CYAN}${mgmt_key}${RESET}"
echo ""
log_warning "Save this management key! You need it to log into the panel."
echo ""
echo "${BOLD}How to use:${RESET}"
echo " 1. Start the proxy: ${CYAN}$0 --start${RESET}"
echo " 2. Open the URL above in your browser"
echo " 3. Enter the management key to connect"
echo ""
if [[ "$allow_remote" != "true" ]]; then
echo "${BOLD}Note:${RESET} Remote access is disabled (localhost only)."
echo " To enable: ${CYAN}$0 --panel --remote${RESET}"
fi
}
# =============================================================================
# OAUTH LOGIN FUNCTIONS
# =============================================================================
# Login to Claude via OAuth
login_claude() {
log_header "Claude OAuth Login"
if [[ ! -x "$BINARY_PATH" ]]; then
log_error "Binary not found. Run --install first."
return 1
fi
cd "$CLIPROXYAPI_DIR"
log_info "Starting Claude OAuth login flow..."
log_info "A browser window will open for authentication."
echo ""
./"$BINARY_NAME" -claude-login -config "$CONFIG_FILE"
log_success "Claude OAuth login completed"
}
# Login to Codex via OAuth
login_codex() {
log_header "Codex OAuth Login"
if [[ ! -x "$BINARY_PATH" ]]; then
log_error "Binary not found. Run --install first."
return 1
fi
cd "$CLIPROXYAPI_DIR"
log_info "Starting Codex OAuth login flow..."
log_info "A browser window will open for authentication."
echo ""
./"$BINARY_NAME" -codex-login -config "$CONFIG_FILE"
log_success "Codex OAuth login completed"
}
# =============================================================================
# ACCOUNT LISTING FUNCTIONS
# =============================================================================
# List all configured accounts
list_accounts() {
log_header "Configured Accounts"
local claude_api_count=0
local codex_api_count=0
local claude_oauth_count=0
local codex_oauth_count=0
local gemini_oauth_count=0
local other_oauth_count=0
# Count and display API keys from config
if config_exists; then
echo "${BOLD}API Keys from config.yaml:${RESET}"
echo ""
# Claude API keys
if grep -q "^claude-api-key:" "$CONFIG_FILE"; then
echo " ${CYAN}Claude API Keys:${RESET}"
while IFS= read -r line; do
if [[ "$line" =~ api-key:\ *[\"\']?([^\"\']+)[\"\']? ]]; then
local key="${BASH_REMATCH[1]}"
echo " - $(mask_api_key "$key")"
((claude_api_count++)) || true
fi
done < <(sed -n '/^claude-api-key:/,/^[a-z]/p' "$CONFIG_FILE" | grep "api-key:")
if [[ $claude_api_count -eq 0 ]]; then
echo " (none configured)"
fi
echo ""
fi
# Codex API keys
if grep -q "^codex-api-key:" "$CONFIG_FILE"; then
echo " ${CYAN}Codex API Keys:${RESET}"
while IFS= read -r line; do
if [[ "$line" =~ api-key:\ *[\"\']?([^\"\']+)[\"\']? ]]; then
local key="${BASH_REMATCH[1]}"
echo " - $(mask_api_key "$key")"
((codex_api_count++)) || true
fi
done < <(sed -n '/^codex-api-key:/,/^[a-z]/p' "$CONFIG_FILE" | grep "api-key:")
if [[ $codex_api_count -eq 0 ]]; then
echo " (none configured)"
fi
echo ""
fi
# Local API keys (for client auth)
if grep -q "^api-keys:" "$CONFIG_FILE"; then
echo " ${CYAN}Local Client API Keys:${RESET}"
local local_key_count=0
while IFS= read -r line; do
line=$(echo "$line" | sed 's/^[[:space:]]*-[[:space:]]*//' | tr -d '"' | tr -d "'")
if [[ -n "$line" && ! "$line" =~ ^# ]]; then
echo " - $(mask_api_key "$line")"
((local_key_count++)) || true
fi
done < <(sed -n '/^api-keys:/,/^[a-z]/{ /^[[:space:]]*-/p }' "$CONFIG_FILE")
if [[ $local_key_count -eq 0 ]]; then
echo " (none configured)"
fi
echo ""
fi
else
echo " ${YELLOW}Config file not found${RESET}"
echo ""
fi
# List OAuth token files
echo "${BOLD}OAuth Tokens in ${AUTH_DIR}:${RESET}"
echo ""
if [[ -d "$AUTH_DIR" ]]; then
# Find all token files
local found_tokens=false
while IFS= read -r -d '' token_file; do
found_tokens=true
local filename
filename=$(basename "$token_file")
local provider="unknown"
local account_info=""
# Determine provider from filename
case "$filename" in
*claude* | *anthropic*)
provider="Claude"
((claude_oauth_count++)) || true
;;
*codex* | *openai*)
provider="Codex/OpenAI"
((codex_oauth_count++)) || true
;;
*gemini* | *google*)
provider="Gemini/Google"
((gemini_oauth_count++)) || true
;;
*)
((other_oauth_count++)) || true
;;
esac
# Try to extract account info (email) from token file if it's JSON
if command_exists jq && [[ -f "$token_file" ]]; then
account_info=$(jq -r '.email // .account // .user // empty' "$token_file" 2>/dev/null || true)
fi
echo " ${CYAN}${provider}:${RESET}"
echo " File: $filename"
if [[ -n "$account_info" ]]; then
echo " Account: $account_info"
fi
local mtime
mtime=$(stat -c %y "$token_file" 2>/dev/null | cut -d' ' -f1 || stat -f %Sm -t %Y-%m-%d "$token_file" 2>/dev/null || echo "unknown")
echo " Last modified: $mtime"
echo ""
done < <(find "$AUTH_DIR" -type f \( -name "*.json" -o -name "*.token" -o -name "token*" \) -print0 2>/dev/null)
if [[ "$found_tokens" == "false" ]]; then
echo " ${YELLOW}No OAuth tokens found${RESET}"
echo ""
fi
else
echo " ${YELLOW}Auth directory not found${RESET}"
echo ""
fi
# Summary
echo "${BOLD}Summary:${RESET}"
echo " Claude: ${claude_api_count} API key(s), ${claude_oauth_count} OAuth account(s)"
echo " Codex: ${codex_api_count} API key(s), ${codex_oauth_count} OAuth account(s)"
echo " Gemini: ${gemini_oauth_count} OAuth account(s)"
if [[ $other_oauth_count -gt 0 ]]; then
echo " Other: ${other_oauth_count} OAuth account(s)"
fi
}
# =============================================================================
# SERVER MANAGEMENT FUNCTIONS
# =============================================================================
# Get proxy server PID
get_proxy_pid() {
if [[ -f "$PID_FILE" ]]; then
local pid
pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
echo "$pid"
return 0
fi
fi
# Try to find by process name
pgrep -f "${BINARY_NAME}.*-config" 2>/dev/null | head -1 || true
}
# Check if proxy is running
is_proxy_running() {
local pid
pid=$(get_proxy_pid)
[[ -n "$pid" ]]
}
# Start the proxy server
start_proxy() {
log_header "Starting Proxy Server"
if is_proxy_running; then
local pid
pid=$(get_proxy_pid)
log_warning "Proxy server is already running (PID: $pid)"
return 0
fi
if [[ ! -x "$BINARY_PATH" ]]; then
log_error "Binary not found. Run --install first."
return 1
fi
if ! config_exists; then
log_error "Config file not found. Run --install first."
return 1
fi
cd "$CLIPROXYAPI_DIR"
log_info "Starting proxy server..."
# Start the server in the background
nohup ./"$BINARY_NAME" -config "$CONFIG_FILE" > "$LOG_FILE" 2>&1 &
local pid=$!
# Save PID
echo "$pid" > "$PID_FILE"
# Wait a moment and check if it started successfully
sleep 2
if kill -0 "$pid" 2>/dev/null; then
log_success "Proxy server started (PID: $pid)"
log_info "Log file: $LOG_FILE"
log_info "Listening on port: ${DEFAULT_PORT}"
# Show the API key for convenience
if config_exists; then
local api_key
api_key=$(sed -n '/^api-keys:/,/^[a-z]/{ /^[[:space:]]*-/{ s/^[[:space:]]*-[[:space:]]*//; s/["\x27]//g; p; q; }}' "$CONFIG_FILE" 2>/dev/null || true)
if [[ -n "$api_key" ]]; then
echo ""
log_info "Use this API key for client connections:"
echo " ${CYAN}${api_key}${RESET}"
fi
fi
else
rm -f "$PID_FILE"
log_error "Failed to start proxy server. Check logs at: $LOG_FILE"
tail -20 "$LOG_FILE" 2>/dev/null || true
return 1
fi
}
# Stop the proxy server
stop_proxy() {
log_header "Stopping Proxy Server"
local pid
pid=$(get_proxy_pid)
if [[ -z "$pid" ]]; then
log_warning "Proxy server is not running"
rm -f "$PID_FILE"
return 0
fi
log_info "Stopping proxy server (PID: $pid)..."
# Send SIGTERM first
kill "$pid" 2>/dev/null || true
# Wait for graceful shutdown (up to 10 seconds)
local count=0
while kill -0 "$pid" 2>/dev/null && [[ $count -lt 10 ]]; do
sleep 1
((count++)) || true
done
# Force kill if still running
if kill -0 "$pid" 2>/dev/null; then
log_warning "Graceful shutdown failed, forcing termination..."
kill -9 "$pid" 2>/dev/null || true
sleep 1
fi
rm -f "$PID_FILE"
if kill -0 "$pid" 2>/dev/null; then
log_error "Failed to stop proxy server"
return 1
else
log_success "Proxy server stopped"
fi
}
# Show proxy status
show_status() {
log_header "Proxy Server Status"
local pid
pid=$(get_proxy_pid)
if [[ -n "$pid" ]]; then
echo "${GREEN}Status: Running${RESET}"
echo "PID: $pid"
# Get process info
if command_exists ps; then
echo ""
echo "Process Info:"
ps -p "$pid" -o pid,ppid,user,%cpu,%mem,etime,cmd --no-headers 2>/dev/null || true
fi
# Check if port is listening
if command_exists ss; then
echo ""
echo "Listening Ports:"
ss -tlnp 2>/dev/null | grep "$pid" || echo " (unable to determine)"
elif command_exists netstat; then
echo ""
echo "Listening Ports:"
netstat -tlnp 2>/dev/null | grep "$pid" || echo " (unable to determine)"
fi
# Show recent log entries
if [[ -f "$LOG_FILE" ]]; then
echo ""
echo "Recent Log Entries:"
tail -5 "$LOG_FILE" 2>/dev/null | sed 's/^/ /'
fi
else
echo "${RED}Status: Not Running${RESET}"
fi
echo ""
echo "Configuration:"
echo " Config file: $CONFIG_FILE"
echo " Auth directory: $AUTH_DIR"
echo " Binary: $BINARY_PATH"
echo " Log file: $LOG_FILE"
}
# =============================================================================
# FULL INSTALLATION
# =============================================================================
do_install() {
log_header "CLIProxyAPI Installation"
# Check dependencies
log_info "Checking dependencies..."
local missing_deps=()
if ! command_exists git; then
missing_deps+=("git")
fi
if [[ ${#missing_deps[@]} -gt 0 ]]; then
log_error "Missing required dependencies: ${missing_deps[*]}"
log_info "Please install them and try again."
return 1
fi
# Check Go version
log_info "Checking Go installation..."
if ! command_exists go; then
log_warning "Go is not installed"
install_go
elif ! check_go_version; then
local current_version
current_version=$(get_go_version)
log_warning "Go ${current_version} is installed but Go ${REQUIRED_GO_VERSION}+ is required"
install_go
else
log_success "Go $(get_go_version) is already installed"
fi
# Check if CLIProxyAPI directory exists
if [[ ! -d "$CLIPROXYAPI_DIR" ]]; then
log_error "CLIProxyAPI directory not found at: $CLIPROXYAPI_DIR"
log_info "Please clone the repository first."
return 1
fi
# Build the binary
build_binary
# Setup auth directory
setup_auth_dir
# Create default config
create_default_config
log_header "Installation Complete"
echo "Next steps:"
echo " 1. Add accounts:"
echo " ${CYAN}$0 --add-claude${RESET} # OAuth login for Claude"
echo " ${CYAN}$0 --add-codex${RESET} # OAuth login for Codex"
echo " ${CYAN}$0 --add-claude-key <key>${RESET} # Add Claude API key"
echo " ${CYAN}$0 --add-codex-key <key>${RESET} # Add Codex API key"
echo ""
echo " 2. Enable the web Management Center (optional):"
echo " ${CYAN}$0 --panel${RESET}"
echo ""
echo " 3. Start the proxy:"
echo " ${CYAN}$0 --start${RESET}"
echo ""
echo " 4. Configure your client to use:"
echo " URL: http://localhost:${DEFAULT_PORT}"
echo ""
}
# =============================================================================
# HELP MESSAGE
# =============================================================================
show_help() {
cat << EOF
${BOLD}CLIProxyAPI Setup and Management Script${RESET}
${BOLD}USAGE:${RESET}
$0 [OPTIONS]
${BOLD}OPTIONS:${RESET}
${CYAN}Installation & Setup:${RESET}
-i, --install Full installation and setup
(Go check/install, build, config creation)
${CYAN}Account Management:${RESET}
-c, --add-claude Login to Claude/Anthropic via OAuth
-x, --add-codex Login to OpenAI Codex via OAuth
--add-claude-key <key> Add a Claude API key directly to config
--add-codex-key <key> Add a Codex API key directly to config
-l, --list List all configured accounts
${CYAN}Management Panel:${RESET}
-p, --panel Enable the web Management Center
(generates key, shows access URL)
--panel --remote Enable with remote access allowed
${CYAN}Server Management:${RESET}
-s, --start Start the proxy server
--stop Stop the proxy server
--status Show proxy server status
${CYAN}Help:${RESET}
-h, --help Show this help message
${BOLD}EXAMPLES:${RESET}
# Full installation
$0 --install
# Add Claude account via OAuth
$0 --add-claude
# Add API key directly
$0 --add-claude-key "sk-ant-api03-..."
# Enable Management Center web panel
$0 --panel
# Enable Management Center with remote access
$0 --panel --remote
# Start proxy server
$0 --start
# Check status
$0 --status
${BOLD}CONFIGURATION:${RESET}
Config file: ${CONFIG_FILE}
Auth directory: ${AUTH_DIR}
Default port: ${DEFAULT_PORT}
${BOLD}NOTES:${RESET}
- The proxy uses round-robin routing by default for load balancing
- Multiple accounts can be added for the same provider
- OAuth tokens are stored in ${AUTH_DIR}
- API keys are stored in config.yaml (masked when listed)
EOF
}
# =============================================================================
# MAIN ENTRY POINT
# =============================================================================
main() {
# No arguments provided
if [[ $# -eq 0 ]]; then
show_help
exit 0
fi
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-i|--install)
do_install
exit $?
;;
-c|--add-claude)
login_claude
exit $?
;;
-x|--add-codex)
login_codex
exit $?
;;
--add-claude-key)
if [[ -z "${2:-}" ]]; then
log_error "Missing API key argument"
echo "Usage: $0 --add-claude-key <api-key>"
exit 1
fi
add_claude_api_key "$2"
exit $?
;;
--add-codex-key)
if [[ -z "${2:-}" ]]; then
log_error "Missing API key argument"
echo "Usage: $0 --add-codex-key <api-key>"
exit 1
fi
add_codex_api_key "$2"
exit $?
;;
-p|--panel)
local allow_remote="false"
if [[ "${2:-}" == "--remote" ]]; then
allow_remote="true"
shift
fi
setup_management_panel "$allow_remote"
exit $?
;;
-l|--list)
list_accounts
exit $?
;;
-s|--start)
start_proxy
exit $?
;;
--stop)
stop_proxy
exit $?
;;
--status)
show_status
exit $?
;;
-h|--help)
show_help
exit 0
;;
*)
log_error "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
shift
done
}
# Run main function
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment