1800 lines
72 KiB
Bash
1800 lines
72 KiB
Bash
#!/bin/bash
|
|
|
|
# auto-sys-deploy.sh - Automated system deployment script for frpc
|
|
# Enhanced with .env file support and internal service container connection
|
|
# This script runs on the same computer as the service it connects to
|
|
|
|
set -e # Exit on any error
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Global variables
|
|
THIS_INSTANCE=""
|
|
SELECTED_NETWORK=""
|
|
INTERNAL_SERVICE_PORT=""
|
|
SERVER_ADDR=""
|
|
SERVER_PORT="16999"
|
|
RAD_REMOTE_PORT=""
|
|
SSH_REMOTE_PORT=""
|
|
WEB_REMOTE_PORT=""
|
|
DOMAIN_NAME=""
|
|
|
|
# Function to load environment variables from .env files
|
|
load_env() {
|
|
# Check for .env files in priority order: .env.production, .env.development, .env
|
|
# If no .env file is found in current directory, look for one in the script's directory
|
|
if [ -f ".env.development" ]; then
|
|
echo -e "${BLUE}[INFO]${NC} Loading development environment from .env.development" >&2
|
|
source .env.development
|
|
elif [ -f ".env.production" ]; then
|
|
echo -e "${BLUE}[INFO]${NC} Loading production environment from .env.production" >&2
|
|
source .env.production
|
|
elif [ -f ".env" ]; then
|
|
echo -e "${BLUE}[INFO]${NC} Loading default environment from .env" >&2
|
|
source .env
|
|
elif [ -f "$(dirname "$0")/.env.development" ]; then
|
|
echo -e "${BLUE}[INFO]${NC} Loading development environment from $(dirname "$0")/.env.development" >&2
|
|
source "$(dirname "$0")/.env.development"
|
|
elif [ -f "$(dirname "$0")/.env.production" ]; then
|
|
echo -e "${BLUE}[INFO]${NC} Loading production environment from $(dirname "$0")/.env.production" >&2
|
|
source "$(dirname "$0")/.env.production"
|
|
elif [ -f "$(dirname "$0")/.env" ]; then
|
|
echo -e "${BLUE}[INFO]${NC} Loading default environment from $(dirname "$0")/.env" >&2
|
|
source "$(dirname "$0")/.env"
|
|
else
|
|
echo -e "${RED}[ERROR]${NC} No .env file found in current directory or script directory." >&2
|
|
echo -e "${YELLOW}[WARN]${NC} Please create an .env.development file (or .env) with your configuration." >&2
|
|
echo -e "${YELLOW}Example .env file:${NC}" >&2
|
|
echo "INSTANCE_NAME=myapp-3000" >&2
|
|
echo "INTERNAL_SERVICE_PORT=3000" >&2
|
|
echo "DOCKER_NETWORK=app-network" >&2
|
|
echo "FRP_SERVER_ADDR=192.168.1.100" >&2
|
|
echo "DOMAIN_NAME=myapp.example.com" >&2
|
|
echo "PROXY_WEB_PORT=8080" >&2
|
|
echo "PROXY_API_PORT=8081" >&2
|
|
echo "PROXY_DB_PORT=5432" >&2
|
|
echo "PROXY_WEB_LOCAL_PORT=3000" >&2
|
|
echo "PROXY_API_LOCAL_PORT=4000" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Logging functions
|
|
log_info() {
|
|
echo -e "${GREEN}[INFO]${NC} $1" >&2
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1" >&2
|
|
}
|
|
|
|
log_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1" >&2
|
|
}
|
|
|
|
# Function to save answered values to .env file
|
|
save_to_env() {
|
|
local var_name="$1"
|
|
local var_value="$2"
|
|
|
|
# Create or update .env file with the new value
|
|
if [ -f ".env" ]; then
|
|
# Check if variable already exists
|
|
if grep -q "^${var_name}=" .env; then
|
|
# Update existing variable
|
|
sed -i "s/^${var_name}=.*/${var_name}=${var_value}/" .env
|
|
else
|
|
# Add new variable
|
|
echo "${var_name}=${var_value}" >> .env
|
|
fi
|
|
else
|
|
# Create new .env file
|
|
echo "${var_name}=${var_value}" >> .env
|
|
fi
|
|
}
|
|
|
|
# Function to read from terminal instead of pipe
|
|
read_terminal() {
|
|
local prompt="$1"
|
|
local default_value="$2"
|
|
|
|
# Read from terminal (/dev/tty) instead of stdin
|
|
read -p "$prompt" input_name </dev/tty
|
|
echo "${input_name:-$default_value}"
|
|
}
|
|
|
|
# Prompt user for instance name
|
|
prompt_instance_name() {
|
|
local system_name="$1"
|
|
local default_name="${system_name#setup-}-$(date +%Y%m%d-%H%M%S)"
|
|
|
|
# Check if INSTANCE_NAME is set from environment
|
|
if [ -n "$INSTANCE_NAME" ]; then
|
|
THIS_INSTANCE="$INSTANCE_NAME"
|
|
log_info "Using environment variable for instance name: ${THIS_INSTANCE}"
|
|
else
|
|
# Scan Docker volumes for rad-git-sshd-.*-frpc to find existing instances
|
|
local instances=()
|
|
local volume_list=$(docker volume ls --format '{{.Name}}' | grep '^rad-git-sshd-.*-frpc' || true)
|
|
if [ -n "$volume_list" ]; then
|
|
for volume in $volume_list; do
|
|
# Extract instance name from volume name (everything after first dash)
|
|
local volume_instance=$(echo "$volume" | sed 's/^rad-git-sshd-\(.*\)-frpc$/\1/')
|
|
# Validate the instance name
|
|
if [[ "$volume_instance" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
instances+=("$volume_instance")
|
|
fi
|
|
done
|
|
fi
|
|
|
|
echo -e "${BLUE}=== Instance Setup ===${NC}" >&2
|
|
echo -e "System: ${GREEN}${system_name}${NC}" >&2
|
|
|
|
if [ ${#instances[@]} -eq 1 ]; then
|
|
THIS_INSTANCE="${instances[0]}"
|
|
log_info "Found single existing instance: ${THIS_INSTANCE}"
|
|
elif [ ${#instances[@]} -gt 1 ]; then
|
|
echo -e "${YELLOW}Existing instances found:${NC}" >&2
|
|
for i in "${!instances[@]}"; do
|
|
echo -e "${BLUE}$((i+1)).${NC} ${instances[i]}" >&2
|
|
done
|
|
echo -e "Enter number to select existing instance, or press Enter for new default: ${YELLOW}${default_name}${NC}" >&2
|
|
local selection
|
|
selection=$(read_terminal "Selection: " "")
|
|
if [ -n "$selection" ] && [[ "$selection" =~ ^[0-9]+$ ]] && [ "$selection" -ge 1 ] && [ "$selection" -le ${#instances[@]} ]; then
|
|
THIS_INSTANCE="${instances[$((selection-1))]}"
|
|
else
|
|
THIS_INSTANCE="$default_name"
|
|
fi
|
|
else
|
|
THIS_INSTANCE="$default_name"
|
|
fi
|
|
|
|
# Allow user to override
|
|
input_name=$(read_terminal "Enter instance name [${THIS_INSTANCE}]: " "${THIS_INSTANCE}")
|
|
THIS_INSTANCE="${input_name:-${THIS_INSTANCE}}"
|
|
|
|
# Save to env file if not already set
|
|
if [ -z "$INSTANCE_NAME" ]; then
|
|
save_to_env "INSTANCE_NAME" "$THIS_INSTANCE"
|
|
fi
|
|
fi
|
|
|
|
# Validate instance name (alphanumeric, hyphens, underscores)
|
|
if [[ "$THIS_INSTANCE" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
log_info "Using instance name: ${THIS_INSTANCE}"
|
|
else
|
|
log_error "Invalid instance name. Only letters, numbers, hyphens, and underscores are allowed." >&2
|
|
exit 1
|
|
fi
|
|
echo "" >&2
|
|
}
|
|
|
|
# Function to list and select Docker networks
|
|
select_docker_network() {
|
|
# Check if DOCKER_NETWORK is set from environment
|
|
if [ -n "$DOCKER_NETWORK" ]; then
|
|
SELECTED_NETWORK="$DOCKER_NETWORK"
|
|
log_info "Using environment variable for Docker network: ${SELECTED_NETWORK}"
|
|
return 0
|
|
fi
|
|
|
|
echo -e "${BLUE}=== Docker Network Selection ===${NC}" >&2
|
|
|
|
# Load existing network selection if available
|
|
local existing_network
|
|
existing_network=$(load_parameters)
|
|
local default_option="1"
|
|
|
|
# Get list of available Docker networks
|
|
local networks
|
|
networks=$(docker network ls --format '{{.Name}}' 2>/dev/null || true)
|
|
|
|
if [ -z "$networks" ]; then
|
|
log_warn "No Docker networks found" >&2
|
|
return 1
|
|
fi
|
|
|
|
# Convert to array
|
|
local network_array=()
|
|
while IFS= read -r line; do
|
|
network_array+=("$line")
|
|
done <<< "$networks"
|
|
|
|
# Display networks as a list
|
|
echo -e "${YELLOW}Available Docker Networks:${NC}" >&2
|
|
for i in "${!network_array[@]}"; do
|
|
echo -e "${BLUE}$((i+1)).${NC} ${network_array[i]}" >&2
|
|
done
|
|
|
|
# Ask user to select or enter custom network
|
|
echo -e "" >&2
|
|
echo -e "${YELLOW}Options:${NC}" >&2
|
|
echo -e "${BLUE}1-${#network_array[@]}.${NC} Select from list above" >&2
|
|
echo -e "${BLUE}C.${NC} Custom network name" >&2
|
|
echo -e "${BLUE}N.${NC} No network (default bridge)${NC}" >&2
|
|
echo -e "" >&2
|
|
|
|
# Set default option based on existing network
|
|
if [ -n "$existing_network" ]; then
|
|
# Check if existing network is in the list
|
|
for i in "${!network_array[@]}"; do
|
|
if [ "${network_array[i]}" = "$existing_network" ]; then
|
|
default_option=$((i+1))
|
|
break
|
|
fi
|
|
done
|
|
# If not in list, check if it's a custom network or none
|
|
if [ "$default_option" = "1" ] && [ -n "$existing_network" ]; then
|
|
if [ "$existing_network" = "none" ]; then
|
|
default_option="n"
|
|
else
|
|
default_option="c"
|
|
fi
|
|
fi
|
|
log_info "Using existing network selection as default: $existing_network"
|
|
fi
|
|
|
|
while true; do
|
|
local selection
|
|
selection=$(read_terminal "Select option [${default_option}]: " "$default_option")
|
|
|
|
case "${selection,,}" in
|
|
c)
|
|
# Custom network name
|
|
local custom_network
|
|
if [ -n "$existing_network" ] && [ "$existing_network" != "none" ]; then
|
|
custom_network=$(read_terminal "Enter custom network name [${existing_network}]: " "$existing_network")
|
|
else
|
|
custom_network=$(read_terminal "Enter custom network name: " "")
|
|
fi
|
|
if [ -n "$custom_network" ]; then
|
|
SELECTED_NETWORK="$custom_network"
|
|
save_to_env "DOCKER_NETWORK" "$SELECTED_NETWORK"
|
|
return 0
|
|
else
|
|
log_error "Network name cannot be empty" >&2
|
|
fi
|
|
;;
|
|
n|"")
|
|
# No network (default)
|
|
SELECTED_NETWORK=""
|
|
save_to_env "DOCKER_NETWORK" "none"
|
|
return 0
|
|
;;
|
|
*)
|
|
# Numeric selection
|
|
if [[ "$selection" =~ ^[0-9]+$ ]] && [ "$selection" -ge 1 ] && [ "$selection" -le ${#network_array[@]} ]; then
|
|
SELECTED_NETWORK="${network_array[$((selection-1))]}"
|
|
save_to_env "DOCKER_NETWORK" "$SELECTED_NETWORK"
|
|
return 0
|
|
else
|
|
log_error "Invalid selection. Please choose a valid option." >&2
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Function to prompt for internal service port
|
|
prompt_internal_service_port() {
|
|
# Check if INTERNAL_SERVICE_PORT is set from environment
|
|
if [ -n "$INTERNAL_SERVICE_PORT" ]; then
|
|
log_info "Using environment variable for internal service port: ${INTERNAL_SERVICE_PORT}"
|
|
return
|
|
fi
|
|
|
|
echo -e "${BLUE}=== Internal Service Configuration ===${NC}" >&2
|
|
|
|
while [ -z "$INTERNAL_SERVICE_PORT" ]; do
|
|
INTERNAL_SERVICE_PORT=$(read_terminal "Enter internal service port (exposed by Docker container) [3000]: " "3000")
|
|
if [ -z "$INTERNAL_SERVICE_PORT" ]; then
|
|
log_error "Internal service port cannot be empty" >&2
|
|
elif ! [[ "$INTERNAL_SERVICE_PORT" =~ ^[0-9]+$ ]] || [ "$INTERNAL_SERVICE_PORT" -lt 1 ] || [ "$INTERNAL_SERVICE_PORT" -gt 65535 ]; then
|
|
log_error "Invalid port number. Please enter a number between 1 and 65535." >&2
|
|
INTERNAL_SERVICE_PORT=""
|
|
fi
|
|
done
|
|
|
|
save_to_env "INTERNAL_SERVICE_PORT" "$INTERNAL_SERVICE_PORT"
|
|
}
|
|
|
|
# Function to save parameters to .para file
|
|
save_parameters() {
|
|
local volume_name="${THIS_INSTANCE}-frpc"
|
|
local temp_container="temp-para-config-$(date +%s)"
|
|
local network_selection="$1"
|
|
|
|
# Create a temporary directory for the config file
|
|
local temp_dir="/tmp/para-config-${THIS_INSTANCE}"
|
|
mkdir -p "$temp_dir"
|
|
|
|
# Create JSON content for .para file
|
|
local para_content="{\"network\":\"$network_selection\"}"
|
|
|
|
# Write the parameters to a temporary file
|
|
echo "$para_content" > "${temp_dir}/.para"
|
|
|
|
# Copy the .para file to the volume
|
|
# Create a temporary container to copy the file
|
|
if docker run --rm -d --name "$temp_container" -v "$volume_name:/etc/frp" -v "${temp_dir}:/tmp/config" alpine:latest sleep 10 >/dev/null 2>&1; then
|
|
docker exec "$temp_container" cp /tmp/config/.para /etc/frp/.para 2>/dev/null || true
|
|
docker stop "$temp_container" >/dev/null 2>&1
|
|
fi
|
|
|
|
# Clean up temporary directory
|
|
rm -rf "$temp_dir"
|
|
}
|
|
|
|
# Function to load parameters from .para file
|
|
load_parameters() {
|
|
local volume_name="${THIS_INSTANCE}-frpc"
|
|
local temp_container="temp-para-config-$(date +%s)"
|
|
local existing_network=""
|
|
|
|
# Create a temporary container to check for existing config
|
|
if docker volume inspect "$volume_name" >/dev/null 2>&1; then
|
|
# Create temporary container to access volume
|
|
if docker run --rm -d --name "$temp_container" -v "$volume_name:/etc/frp" alpine:latest sleep 10 >/dev/null 2>&1; then
|
|
# Check if .para exists
|
|
if docker exec "$temp_container" test -f /etc/frp/.para >/dev/null 2>&1; then
|
|
# Try to extract existing network selection from JSON
|
|
existing_network=$(docker exec "$temp_container" grep '"network"' /etc/frp/.para | cut -d':' -f2 | tr -d ' "{}' 2>/dev/null || true)
|
|
if [ -n "$existing_network" ]; then
|
|
log_info "Found existing network selection: $existing_network"
|
|
fi
|
|
fi
|
|
# Stop the temporary container
|
|
docker stop "$temp_container" >/dev/null 2>&1
|
|
fi
|
|
fi
|
|
|
|
echo "$existing_network"
|
|
}
|
|
|
|
# Function to generate frpc.toml configuration
|
|
generate_frpc_config() {
|
|
echo -e "${BLUE}=== FRP Client Configuration ===${NC}" >&2
|
|
|
|
# Check if there's an existing frpc.toml in the volume
|
|
local volume_name="${THIS_INSTANCE}-frpc"
|
|
local temp_container="temp-frpc-config-$(date +%s)"
|
|
local existing_server_addr=""
|
|
local existing_server_port=""
|
|
local existing_proxy1_remote_port=""
|
|
local existing_proxy2_remote_port=""
|
|
local existing_proxy3_remote_port=""
|
|
|
|
# Create a temporary container to check for existing config
|
|
if docker volume inspect "$volume_name" >/dev/null 2>&1; then
|
|
log_info "Found existing volume: $volume_name"
|
|
# Create temporary container to access volume
|
|
if docker run --rm -d --name "$temp_container" -v "$volume_name:/etc/frp" alpine:latest sleep 10 >/dev/null 2>&1; then
|
|
# Check if frpc.toml exists
|
|
if docker exec "$temp_container" test -f /etc/frp/frpc.toml >/dev/null 2>&1; then
|
|
# Try to extract existing values
|
|
existing_server_addr=$(docker exec "$temp_container" grep "^serverAddr" /etc/frp/frpc.toml | cut -d'"' -f2 || true)
|
|
existing_server_port=$(docker exec "$temp_container" grep "^serverPort" /etc/frp/frpc.toml | cut -d'=' -f2 | tr -d ' ' || true)
|
|
existing_proxy1_remote_port=$(docker exec "$temp_container" grep -A 5 '\[\[proxies\]\]' /etc/frp/frpc.toml | grep "name = \"rad-${THIS_INSTANCE}\"" -A 5 | grep "remotePort" | cut -d'=' -f2 | tr -d ' ' | head -1 || true)
|
|
existing_proxy2_remote_port=$(docker exec "$temp_container" grep -A 5 '\[\[proxies\]\]' /etc/frp/frpc.toml | grep "name = \"ssh-${THIS_INSTANCE}\"" -A 5 | grep "remotePort" | cut -d'=' -f2 | tr -d ' ' | head -1 || true)
|
|
existing_proxy3_remote_port=$(docker exec "$temp_container" grep -A 5 '\[\[proxies\]\]' /etc/frp/frpc.toml | grep "name = \"web-${THIS_INSTANCE}\"" -A 5 | grep "remotePort" | cut -d'=' -f2 | tr -d ' ' | head -1 || true)
|
|
|
|
if [ -n "$existing_server_addr" ]; then
|
|
log_info "Found existing server address: $existing_server_addr"
|
|
fi
|
|
if [ -n "$existing_server_port" ]; then
|
|
log_info "Found existing server port: $existing_server_port"
|
|
fi
|
|
if [ -n "$existing_proxy1_remote_port" ]; then
|
|
log_info "Found existing rad proxy remote port: $existing_proxy1_remote_port"
|
|
fi
|
|
if [ -n "$existing_proxy2_remote_port" ]; then
|
|
log_info "Found existing ssh proxy remote port: $existing_proxy2_remote_port"
|
|
fi
|
|
if [ -n "$existing_proxy3_remote_port" ]; then
|
|
log_info "Found existing web proxy remote port: $existing_proxy3_remote_port"
|
|
fi
|
|
fi
|
|
# Stop the temporary container
|
|
docker stop "$temp_container" >/dev/null 2>&1
|
|
fi
|
|
fi
|
|
|
|
# Prompt for server address
|
|
if [ -n "$FRP_SERVER_ADDR" ]; then
|
|
SERVER_ADDR="$FRP_SERVER_ADDR"
|
|
log_info "Using environment variable for FRP server address: $SERVER_ADDR"
|
|
else
|
|
if [ -n "$existing_server_addr" ]; then
|
|
SERVER_ADDR=$(read_terminal "Enter FRP server address IP [${existing_server_addr}]: " "$existing_server_addr")
|
|
else
|
|
while [ -z "$SERVER_ADDR" ]; do
|
|
SERVER_ADDR=$(read_terminal "Enter FRP server address IP: " "")
|
|
if [ -z "$SERVER_ADDR" ]; then
|
|
log_error "Server address cannot be empty" >&2
|
|
fi
|
|
done
|
|
fi
|
|
save_to_env "FRP_SERVER_ADDR" "$SERVER_ADDR"
|
|
fi
|
|
|
|
# Use default server port (16999) without prompting, but use existing if available
|
|
if [ -n "$FRP_SERVER_PORT" ]; then
|
|
SERVER_PORT="$FRP_SERVER_PORT"
|
|
log_info "Using environment variable for FRP server port: ${SERVER_PORT}"
|
|
elif [ -n "$existing_server_port" ]; then
|
|
SERVER_PORT="$existing_server_port"
|
|
else
|
|
# SERVER_PORT remains default 16999
|
|
save_to_env "FRP_SERVER_PORT" "$SERVER_PORT"
|
|
fi
|
|
log_info "Using FRP server port: ${SERVER_PORT}"
|
|
|
|
# Generate the frpc.toml content
|
|
local config_content=""
|
|
config_content+="serverAddr = \"${SERVER_ADDR}\"\n"
|
|
config_content+="serverPort = ${SERVER_PORT}\n\n"
|
|
|
|
# Dynamic proxy configuration based on environment variables
|
|
# Look for environment variables in the format PROXY_*_PORT
|
|
local proxy_configs=()
|
|
|
|
# Process PROXY_*_PORT variables if they exist in environment
|
|
local env_vars=($(compgen -v | grep -E "^PROXY_[A-Z0-9_]*_PORT$" 2>/dev/null))
|
|
for var_name in "${env_vars[@]}"; do
|
|
local var_value="${!var_name}"
|
|
if [ -n "$var_value" ] && [[ "$var_value" =~ ^[0-9]+$ ]]; then
|
|
# Extract proxy name from variable (e.g., PROXY_CUSTOM_PORT -> custom)
|
|
local proxy_name=$(echo "$var_name" | sed 's/^PROXY_\([A-Z0-9_]*\)_PORT$/\1/' | tr '[:upper:]' '[:lower:]' | tr '_' '-')
|
|
local local_port_var="PROXY_${proxy_name^^}_LOCAL_PORT" # Convert back to uppercase with underscores
|
|
|
|
# Get local port - either from PROXY_*_LOCAL_PORT or use defaults
|
|
local local_port="$INTERNAL_SERVICE_PORT" # Default to the main service port
|
|
if [ -n "${!local_port_var}" ]; then
|
|
local_port="${!local_port_var}"
|
|
fi
|
|
|
|
# Validate that local_port is not empty
|
|
if [ -z "$local_port" ] || ! [[ "$local_port" =~ ^[0-9]+$ ]]; then
|
|
log_error "Invalid or empty local port for ${proxy_name} proxy: '$local_port'"
|
|
log_error "Make sure INTERNAL_SERVICE_PORT or ${local_port_var} is set correctly in your .env file"
|
|
exit 1
|
|
fi
|
|
|
|
# Create the proxy configuration string
|
|
proxy_configs+=("${proxy_name}-${local_port}:${var_value}")
|
|
log_info "Adding ${proxy_name} proxy: local port ${local_port} to remote port ${var_value}"
|
|
fi
|
|
done
|
|
|
|
# If no proxy variables were found, add a default proxy if needed
|
|
if [ ${#proxy_configs[@]} -eq 0 ]; then
|
|
# Add a default proxy using WEB_REMOTE_PORT if available, otherwise use a default
|
|
local default_remote_port="${WEB_REMOTE_PORT:-8080}"
|
|
# Validate that INTERNAL_SERVICE_PORT is not empty
|
|
if [ -z "$INTERNAL_SERVICE_PORT" ] || ! [[ "$INTERNAL_SERVICE_PORT" =~ ^[0-9]+$ ]]; then
|
|
log_error "Invalid or empty internal service port: '$INTERNAL_SERVICE_PORT'"
|
|
log_error "Make sure INTERNAL_SERVICE_PORT is set correctly in your .env file"
|
|
exit 1
|
|
fi
|
|
proxy_configs+=("default-${INTERNAL_SERVICE_PORT}:${default_remote_port}")
|
|
log_info "Adding default proxy: local port $INTERNAL_SERVICE_PORT to remote port $default_remote_port"
|
|
fi
|
|
|
|
# Add all proxy configurations to the config content
|
|
for proxy_config in "${proxy_configs[@]}"; do
|
|
# Parse the proxy config format: proxy_name-local_port:remote_port
|
|
# Example: web-3000:12077 -> proxy_name="web", local_port=3000, remote_port=12077
|
|
local proxy_name=""
|
|
local local_remote_port=""
|
|
local local_port=""
|
|
local remote_port=""
|
|
|
|
# First, split on the LAST occurrence of '-' to get proxy name and local:remote part
|
|
# This handles cases where the proxy name might contain hyphens after processing
|
|
if [[ "$proxy_config" =~ ^(.*)-([0-9]+:[0-9]+)$ ]]; then
|
|
proxy_name="${BASH_REMATCH[1]}"
|
|
local_remote_port="${BASH_REMATCH[2]}"
|
|
else
|
|
log_error "Invalid proxy configuration format: $proxy_config"
|
|
log_error "Expected format: proxy_name-local_port:remote_port (e.g., web-3000:12077)"
|
|
exit 1
|
|
fi
|
|
|
|
# Then, split the local_remote_port on ':' to separate local and remote ports
|
|
IFS=':' read -r local_port remote_port <<< "$local_remote_port"
|
|
|
|
# Validate that both local and remote ports are set and are numeric
|
|
if [ -z "$local_port" ] || [ -z "$remote_port" ] || ! [[ "$local_port" =~ ^[0-9]+$ ]] || ! [[ "$remote_port" =~ ^[0-9]+$ ]]; then
|
|
log_error "Invalid proxy configuration format: $proxy_config"
|
|
log_error "Local port: '$local_port', Remote port: '$remote_port'"
|
|
exit 1
|
|
fi
|
|
|
|
config_content+="[[proxies]]\n"
|
|
config_content+="name = \"${proxy_name}-${THIS_INSTANCE}\"\n"
|
|
config_content+="type = \"tcp\"\n"
|
|
config_content+="localPort = ${local_port}\n"
|
|
config_content+="localIP = \"${THIS_INSTANCE}-app\"\n"
|
|
config_content+="remotePort = ${remote_port}\n\n"
|
|
done
|
|
|
|
# Create or update the frpc.toml file in the volume
|
|
log_info "Creating/updating frpc.toml configuration"
|
|
|
|
# Create a temporary directory for the config file
|
|
local temp_dir="/tmp/frpc-config-${THIS_INSTANCE}"
|
|
mkdir -p "$temp_dir"
|
|
|
|
# Write the config to a temporary file
|
|
echo -e "$config_content" > "${temp_dir}/frpc.toml"
|
|
|
|
# Create the volume if it doesn't exist
|
|
if ! docker volume inspect "$volume_name" >/dev/null 2>&1; then
|
|
log_info "Creating Docker volume: $volume_name"
|
|
docker volume create "$volume_name" >/dev/null
|
|
fi
|
|
|
|
# Copy the config file to the volume
|
|
# Create a temporary container to copy the file
|
|
if docker run --rm -d --name "$temp_container" -v "$volume_name:/etc/frp" -v "${temp_dir}:/tmp/config" alpine:latest sleep 10 >/dev/null 2>&1; then
|
|
docker exec "$temp_container" cp /tmp/config/frpc.toml /etc/frp/frpc.toml
|
|
docker stop "$temp_container" >/dev/null 2>&1
|
|
fi
|
|
|
|
# Clean up temporary directory
|
|
rm -rf "$temp_dir"
|
|
|
|
log_info "FRP client configuration created successfully"
|
|
echo "" >&2
|
|
}
|
|
|
|
# Function to check if any proxy configurations are web proxies
|
|
has_web_proxy() {
|
|
# Check if any proxy config has "web" as the proxy name
|
|
# Accept proxy configs as parameters
|
|
local proxy_configs=("$@")
|
|
|
|
for proxy_config in "${proxy_configs[@]}"; do
|
|
# Split the proxy config into proxy_name-local_port:remote_port
|
|
IFS='-' read -r proxy_name local_remote_port <<< "$proxy_config"
|
|
if [ "$proxy_name" = "web" ]; then
|
|
echo "true"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
echo "false"
|
|
return 0
|
|
}
|
|
|
|
# Function to start the Docker container
|
|
start_docker_container() {
|
|
echo -e "${BLUE}=== Starting Docker Container ===${NC}" >&2
|
|
|
|
local container_name="${THIS_INSTANCE}-frpc"
|
|
local volume_name="${THIS_INSTANCE}-frpc"
|
|
|
|
# Check if container already exists
|
|
if docker ps -a -f name=^${container_name}$ --format '{{.ID}}' | grep -q .; then
|
|
log_info "Container ${container_name} already exists"
|
|
if docker ps -f name=^${container_name}$ --format '{{.ID}}' | grep -q .; then
|
|
log_info "Container is already running"
|
|
return 0
|
|
else
|
|
log_info "Starting existing container ${container_name}"
|
|
if ! docker start "${container_name}"; then
|
|
log_error "Failed to start existing container"
|
|
return 1
|
|
fi
|
|
fi
|
|
else
|
|
log_info "Starting new Docker container: ${container_name}"
|
|
|
|
# Build docker run command
|
|
local docker_run_cmd="docker run --restart=always -d"
|
|
|
|
# Add network option if selected
|
|
if [ -n "$SELECTED_NETWORK" ]; then
|
|
docker_run_cmd+=" --network ${SELECTED_NETWORK}"
|
|
fi
|
|
|
|
# Add volume mount
|
|
docker_run_cmd+=" -v ${volume_name}:/etc/frp"
|
|
|
|
# Add container name
|
|
docker_run_cmd+=" --name ${container_name}"
|
|
|
|
# Add image
|
|
docker_run_cmd+=" snowdreamtech/frpc:0.62.0-alpine3.21"
|
|
|
|
# Execute the command
|
|
log_info "Executing: $docker_run_cmd"
|
|
if ! eval $docker_run_cmd; then
|
|
log_error "Failed to start Docker container"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
log_info "Docker container started successfully"
|
|
echo "" >&2
|
|
}
|
|
|
|
# Function to display container operation commands
|
|
display_container_commands() {
|
|
local container_name="${THIS_INSTANCE}-frpc"
|
|
local volume_name="${THIS_INSTANCE}-frpc"
|
|
|
|
# Build the docker run command string with network option
|
|
local display_docker_run_cmd="docker run --restart=always -d"
|
|
if [ -n "$SELECTED_NETWORK" ]; then
|
|
display_docker_run_cmd+=" --network ${SELECTED_NETWORK}"
|
|
fi
|
|
display_docker_run_cmd+=" -v ${volume_name}:/etc/frp --name ${container_name} snowdreamtech/frpc:0.62.0-alpine3.21"
|
|
|
|
# Display container operation commands in a rectangle box without side borders
|
|
echo -e "${BLUE}=============================================${NC}" >&2
|
|
echo -e "${GREEN}Container Operation Commands${NC}" >&2
|
|
echo -e "" >&2
|
|
echo -e "Run Container: ${GREEN}${display_docker_run_cmd}${NC}" >&2
|
|
echo -e "------------------------------------------------------------" >&2
|
|
echo -e "Container Accessing: ${GREEN}docker exec -it ${container_name} sh${NC}" >&2
|
|
echo -e "------------------------------------------------------------" >&2
|
|
echo -e "View the Logs: ${GREEN}docker logs --tail 50 ${container_name}${NC}" >&2
|
|
echo -e "------------------------------------------------------------" >&2
|
|
echo -e "Inspect: ${GREEN}docker inspect ${container_name}${NC}" >&2
|
|
echo -e "------------------------------------------------------------" >&2
|
|
echo -e "Remove Container: ${GREEN}docker rm -f ${container_name}${NC}" >&2
|
|
echo -e "${BLUE}=============================================${NC}" >&2
|
|
echo -e "${YELLOW}[REMARK]${NC} Please copy down these operation commands for future use." >&2
|
|
echo "" >&2
|
|
}
|
|
|
|
# Function to extract FRP server address from frpc.toml
|
|
extract_frp_server_address() {
|
|
local volume_name="${THIS_INSTANCE}-frpc"
|
|
local temp_container="temp-extract-config-$(date +%s)"
|
|
local server_addr=""
|
|
|
|
# Create a temporary container to check for existing config
|
|
if docker volume inspect "$volume_name" >/dev/null 2>&1; then
|
|
# Create temporary container to access volume
|
|
if docker run --rm -d --name "$temp_container" -v "$volume_name:/etc/frp" alpine:latest sleep 10 >/dev/null 2>&1; then
|
|
# Check if frpc.toml exists
|
|
if docker exec "$temp_container" test -f /etc/frp/frpc.toml >/dev/null 2>&1; then
|
|
# Try to extract server address
|
|
server_addr=$(docker exec "$temp_container" grep "^serverAddr" /etc/frp/frpc.toml | cut -d'"' -f2 || true)
|
|
fi
|
|
# Stop the temporary container
|
|
docker stop "$temp_container" >/dev/null 2>&1
|
|
fi
|
|
fi
|
|
|
|
echo "$server_addr"
|
|
}
|
|
|
|
# Function to extract all remote ports from frpc.toml
|
|
extract_all_remote_ports() {
|
|
local volume_name="${THIS_INSTANCE}-frpc"
|
|
local temp_container="temp-extract-ports-$(date +%s)"
|
|
local ports=()
|
|
|
|
# Create a temporary container to check for existing config
|
|
if docker volume inspect "$volume_name" >/dev/null 2>&1; then
|
|
# Create temporary container to access volume
|
|
if docker run --rm -d --name "$temp_container" -v "$volume_name:/etc/frp" alpine:latest sleep 10 >/dev/null 2>&1; then
|
|
# Check if frpc.toml exists
|
|
if docker exec "$temp_container" test -f /etc/frp/frpc.toml >/dev/null 2>&1; then
|
|
# Get all remotePort values from the configuration
|
|
local remote_ports=$(docker exec "$temp_container" grep "remotePort" /etc/frp/frpc.toml | cut -d'=' -f2 | tr -d ' ')
|
|
|
|
# Convert to array
|
|
while IFS= read -r port; do
|
|
if [ -n "$port" ]; then
|
|
ports+=("$port")
|
|
fi
|
|
done <<< "$remote_ports"
|
|
fi
|
|
# Stop the temporary container
|
|
docker stop "$temp_container" >/dev/null 2>&1
|
|
fi
|
|
fi
|
|
|
|
# Output all ports as space-separated values
|
|
echo "${ports[@]}"
|
|
}
|
|
|
|
# Function to update docker-compose.app.yml on proxy server with dynamic ports
|
|
update_proxy_server_config() {
|
|
local proxy_server_addr="$1"
|
|
local has_web_proxy="$2"
|
|
shift 2 # Remove the first two arguments (proxy_server_addr and has_web_proxy), rest are ports
|
|
|
|
# The remaining arguments are all the ports
|
|
local all_ports=("$@")
|
|
|
|
# Make sure we have at least 1 port to work with
|
|
if [ ${#all_ports[@]} -lt 1 ]; then
|
|
log_error "Need at least 1 port for proxy configuration"
|
|
return 1
|
|
fi
|
|
|
|
# Use the first available port as the web port for domain routing
|
|
local web_port="${all_ports[0]}"
|
|
|
|
echo -e "${BLUE}=== Updating Proxy Server Configuration ===${NC}" >&2
|
|
|
|
# Check if SSH key exists
|
|
if [ ! -f ~/.ssh/itl-devs1 ]; then
|
|
log_error "SSH key ~/.ssh/itl-devs1 not found"
|
|
return 1
|
|
fi
|
|
|
|
# Prompt for domain name if not already set
|
|
if [ -z "$DOMAIN_NAME" ]; then
|
|
DOMAIN_NAME=$(prompt_domain_name)
|
|
fi
|
|
|
|
# Create temporary script to run on proxy server that contains the direct update functions
|
|
local temp_script="/tmp/update-compose-$THIS_INSTANCE.sh"
|
|
cat > "$temp_script" << 'EOF'
|
|
#!/bin/bash
|
|
|
|
# Function to check if the instance configuration is complete and up-to-date in ports section
|
|
# This function is a simplified version - in a real implementation, this would check dynamically
|
|
check_ports_config_status() {
|
|
local INSTANCE_NAME="$1"
|
|
local PORTS_CONFIG=("${@:2}") # All remaining arguments are ports
|
|
local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
|
|
|
|
# Set the instance marker for ports section
|
|
local INSTANCE_MARKER="# ${INSTANCE_NAME} Self-hosting ports ===="
|
|
|
|
# Check if the instance marker exists
|
|
if [ ! -f "${COMPOSE_FILE}" ] || ! grep -q "${INSTANCE_MARKER}" "${COMPOSE_FILE}"; then
|
|
echo "not_found"
|
|
return
|
|
fi
|
|
|
|
# Find the exact lines of the configuration block
|
|
local start_line=$(grep -n "${INSTANCE_MARKER}" "${COMPOSE_FILE}" | head -1 | cut -d: -f1)
|
|
local end_line=$(grep -n "# =========================" "${COMPOSE_FILE}" | awk -F: -v start="$start_line" '$1 > start {print $1; exit}')
|
|
|
|
# If we can't find the end marker, use a default range
|
|
if [ -z "$end_line" ]; then
|
|
end_line=$((start_line + ${#PORTS_CONFIG[@]} + 5))
|
|
fi
|
|
|
|
# Extract the configuration block
|
|
local config_block=$(sed -n "${start_line},${end_line}p" "${COMPOSE_FILE}")
|
|
|
|
# Count the port lines
|
|
local port_lines=$(echo "$config_block" | grep -c '^ - "[0-9]*:[0-9]*/tcp"')
|
|
|
|
# For simplicity in this implementation, we'll mark as 'outdated' to update
|
|
echo "outdated"
|
|
}
|
|
|
|
# Function to check if the instance configuration is complete and up-to-date in labels section
|
|
check_labels_config_status() {
|
|
local INSTANCE_NAME="$1"
|
|
local WEB_PORT="$2"
|
|
local DOMAIN_NAME="$3"
|
|
local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
|
|
|
|
# Set the instance marker for labels section (different from ports)
|
|
local INSTANCE_MARKER="# ${INSTANCE_NAME} Self-hosting labels ===="
|
|
|
|
# Check if the instance marker exists in labels section
|
|
if [ ! -f "${COMPOSE_FILE}" ] || ! grep -q "${INSTANCE_MARKER}" "${COMPOSE_FILE}"; then
|
|
echo "not_found"
|
|
return
|
|
fi
|
|
|
|
# Find the exact lines of the configuration block
|
|
local start_line=$(grep -n "${INSTANCE_MARKER}" "${COMPOSE_FILE}" | head -1 | cut -d: -f1)
|
|
local end_line=$(grep -n "# =========================" "${COMPOSE_FILE}" | awk -F: -v start="$start_line" '$1 > start {print $1; exit}')
|
|
|
|
# If we can't find the end marker, use a default range
|
|
if [ -z "$end_line" ]; then
|
|
end_line=$((start_line + 15))
|
|
fi
|
|
|
|
# Extract the configuration block
|
|
local config_block=$(sed -n "${start_line},${end_line}p" "${COMPOSE_FILE}")
|
|
|
|
# Count the label lines (should be 11 lines plus markers)
|
|
local label_lines=$(echo "$config_block" | grep -c '^ - "traefik\.')
|
|
|
|
if [ "$label_lines" -ge 11 ]; then
|
|
# Check if the domain and port match our current configuration
|
|
local current_domain=$(echo "$config_block" | grep 'rule=Host(' | head -1 | sed 's/.*Host(`\([^)]*\)`).*/\1/')
|
|
local current_port=$(echo "$config_block" | grep 'loadbalancer.server.port' | sed 's/.*port=\([0-9]*\).*/\1/')
|
|
|
|
if [ -n "$current_domain" ] && [ -n "$current_port" ] && \
|
|
[ "$current_domain" = "$DOMAIN_NAME" ] && [ "$current_port" = "$WEB_PORT" ]; then
|
|
echo "up_to_date"
|
|
else
|
|
echo "outdated"
|
|
fi
|
|
else
|
|
echo "incomplete"
|
|
fi
|
|
}
|
|
|
|
# Function to find the correct insertion point in a specific section
|
|
find_section_insertion_point() {
|
|
local section_name="$1"
|
|
local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
|
|
|
|
# Find the section start line
|
|
local section_start=$(grep -n "${section_name}:" "${COMPOSE_FILE}" | head -1 | cut -d: -f1)
|
|
if [ -z "$section_start" ]; then
|
|
echo "0"
|
|
return
|
|
fi
|
|
|
|
# Look for the next section after this one or end of section
|
|
local current_line=$((section_start + 1))
|
|
local total_lines=$(wc -l < "${COMPOSE_FILE}")
|
|
|
|
while [ "$current_line" -le "$total_lines" ]; do
|
|
local line_content=$(sed -n "${current_line}p" "${COMPOSE_FILE}")
|
|
|
|
# If we hit a line that starts a new section (not indented or comment)
|
|
if [[ "$line_content" =~ ^[[:space:]]*[a-zA-Z] ]] && [[ ! "$line_content" =~ ^[[:space:]]*# ]] && [[ ! "$line_content" =~ ^[[:space:]]*- ]]; then
|
|
echo "$current_line"
|
|
return
|
|
fi
|
|
|
|
current_line=$((current_line + 1))
|
|
done
|
|
|
|
# If we didn't find the end, insert at the end of the file
|
|
echo $((total_lines + 1))
|
|
}
|
|
|
|
# Function to update ports section with dynamic ports
|
|
update_ports_config() {
|
|
local INSTANCE_NAME="$1"
|
|
shift # Remove first argument, remaining are ports
|
|
local PORTS=("$@") # All remaining arguments are ports
|
|
local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
|
|
|
|
local INSTANCE_MARKER="# ${INSTANCE_NAME} Self-hosting ports ===="
|
|
local INSTANCE_END_MARKER="# ========================="
|
|
|
|
local config_status=$(check_ports_config_status "$INSTANCE_NAME" "${PORTS[@]}")
|
|
echo "Ports configuration status: $config_status"
|
|
|
|
case "$config_status" in
|
|
"not_found")
|
|
echo "Adding new instance to ports section"
|
|
local insertion_line=$(find_section_insertion_point "ports")
|
|
|
|
# Build the port configuration dynamically, excluding standard web ports (80, 443)
|
|
local port_config=""
|
|
local non_standard_ports=()
|
|
for port in "${PORTS[@]}"; do
|
|
# Skip standard web ports since they may be managed by the system
|
|
if [ "$port" != "80" ] && [ "$port" != "443" ]; then
|
|
port_config+=" - \"${port}:${port}/tcp\"\\n"
|
|
non_standard_ports+=("$port")
|
|
fi
|
|
done
|
|
|
|
# Only add ports section if there are non-standard ports to map
|
|
if [ -n "$port_config" ]; then
|
|
# Insert the configuration at the correct location
|
|
sed -i "${insertion_line}i\\
|
|
\\
|
|
${INSTANCE_MARKER}\\
|
|
${port_config} ${INSTANCE_END_MARKER}\\
|
|
" "${COMPOSE_FILE}"
|
|
echo "Ports configuration added successfully for ${#non_standard_ports[@]} non-standard port(s): ${non_standard_ports[*]}"
|
|
else
|
|
echo "No non-standard ports to add to ports section (ports 80 and 443 are standard)"
|
|
fi
|
|
;;
|
|
"up_to_date")
|
|
echo "Ports configuration is already up to date"
|
|
;;
|
|
"outdated"|"incomplete")
|
|
echo "Updating existing ports configuration"
|
|
# Find the exact lines of the existing configuration
|
|
local start_line=$(grep -n "${INSTANCE_MARKER}" "${COMPOSE_FILE}" | head -1 | cut -d: -f1)
|
|
local end_line=$(grep -n "${INSTANCE_END_MARKER}" "${COMPOSE_FILE}" | awk -F: -v start="$start_line" '$1 > start {print $1; exit}')
|
|
|
|
# If we can't find the end marker, use a default range
|
|
if [ -n "$start_line" ] && [ -z "$end_line" ]; then
|
|
end_line=$((start_line + ${#PORTS[@]} + 5))
|
|
fi
|
|
|
|
# Remove only the specific configuration block (from start marker to end marker)
|
|
if [ -n "$start_line" ] && [ -n "$end_line" ] && [ "$start_line" -gt 0 ] && [ "$end_line" -gt "$start_line" ]; then
|
|
sed -i "${start_line},${end_line}d" "${COMPOSE_FILE}"
|
|
|
|
# Build the updated port configuration dynamically, excluding standard web ports (80, 443)
|
|
local port_config=""
|
|
local non_standard_ports=()
|
|
for port in "${PORTS[@]}"; do
|
|
# Skip standard web ports since they may be managed by the system
|
|
if [ "$port" != "80" ] && [ "$port" != "443" ]; then
|
|
port_config+=" - \"${port}:${port}/tcp\"\\n"
|
|
non_standard_ports+=("$port")
|
|
fi
|
|
done
|
|
|
|
# Only update ports section if there are non-standard ports to map
|
|
if [ -n "$port_config" ]; then
|
|
# Insert the updated configuration at the same location
|
|
sed -i "${start_line}i\\
|
|
${INSTANCE_MARKER}\\
|
|
${port_config} ${INSTANCE_END_MARKER}" "${COMPOSE_FILE}"
|
|
echo "Ports configuration updated successfully for ${#non_standard_ports[@]} non-standard port(s): ${non_standard_ports[*]}"
|
|
else
|
|
echo "No non-standard ports to update in ports section (ports 80 and 443 are standard)"
|
|
fi
|
|
else
|
|
echo "Warning: Could not determine valid line range for ports update"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function to update labels section
|
|
update_labels_config() {
|
|
local INSTANCE_NAME="$1"
|
|
local WEB_PORT="$2"
|
|
local DOMAIN_NAME="$3"
|
|
local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
|
|
|
|
local INSTANCE_MARKER="# ${INSTANCE_NAME} Self-hosting labels ===="
|
|
local INSTANCE_END_MARKER="# ========================="
|
|
|
|
local config_status=$(check_labels_config_status "$INSTANCE_NAME" "$WEB_PORT" "$DOMAIN_NAME")
|
|
echo "Labels configuration status: $config_status"
|
|
|
|
case "$config_status" in
|
|
"not_found")
|
|
echo "Adding new instance to labels section"
|
|
local insertion_line=$(find_section_insertion_point "labels")
|
|
|
|
# Insert the configuration at the correct location
|
|
sed -i "${insertion_line}i\\
|
|
\\
|
|
${INSTANCE_MARKER}\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.entrypoints=web\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.rule=Host(\`${DOMAIN_NAME}\`)\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.service=frps-${INSTANCE_NAME}\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.middlewares=http2https@file\"\\
|
|
\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.entrypoints=websecure\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.rule=Host(\`${DOMAIN_NAME}\`)\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.service=frps-${INSTANCE_NAME}\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls=true\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls.certresolver=letsencrypt\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls.domains[0].main=${DOMAIN_NAME}\"\\
|
|
\\
|
|
- \"traefik.http.services.frps-${INSTANCE_NAME}.loadbalancer.server.port=${WEB_PORT}\"\\
|
|
${INSTANCE_END_MARKER}\\
|
|
" "${COMPOSE_FILE}"
|
|
echo "Labels configuration added successfully"
|
|
;;
|
|
"up_to_date")
|
|
echo "Labels configuration is already up to date"
|
|
;;
|
|
"outdated"|"incomplete")
|
|
echo "Updating existing labels configuration"
|
|
# Find the exact lines of the existing configuration
|
|
local start_line=$(grep -n "${INSTANCE_MARKER}" "${COMPOSE_FILE}" | head -1 | cut -d: -f1)
|
|
local end_line=$(grep -n "${INSTANCE_END_MARKER}" "${COMPOSE_FILE}" | awk -F: -v start="$start_line" '$1 > start {print $1; exit}')
|
|
|
|
# If we can't find the end marker, use a default range
|
|
if [ -z "$end_line" ]; then
|
|
end_line=$((start_line + 15))
|
|
fi
|
|
|
|
# Remove only the specific configuration block (from start marker to end marker)
|
|
if [ -n "$start_line" ] && [ -n "$end_line" ] && [ "$start_line" -gt 0 ] && [ "$end_line" -gt "$start_line" ]; then
|
|
sed -i "${start_line},${end_line}d" "${COMPOSE_FILE}"
|
|
|
|
# Insert the updated configuration at the same location
|
|
sed -i "${start_line}i\\
|
|
${INSTANCE_MARKER}\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.entrypoints=web\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.rule=Host(\`${DOMAIN_NAME}\`)\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.service=frps-${INSTANCE_NAME}\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.middlewares=http2https@file\"\\
|
|
\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.entrypoints=websecure\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.rule=Host(\`${DOMAIN_NAME}\`)\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.service=frps-${INSTANCE_NAME}\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls=true\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls.certresolver=letsencrypt\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls.domains[0].main=${DOMAIN_NAME}\"\\
|
|
\\
|
|
- \"traefik.http.services.frps-${INSTANCE_NAME}.loadbalancer.server.port=${WEB_PORT}\"\\
|
|
${INSTANCE_END_MARKER}" "${COMPOSE_FILE}"
|
|
echo "Labels configuration updated successfully"
|
|
else
|
|
echo "Warning: Could not determine valid line range for labels update"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function to restart Docker Compose service
|
|
restart_docker_compose() {
|
|
local COMPOSE_DIR="/data/ssd/rvs-prxy/frps/"
|
|
echo "Restarting Docker Compose service..."
|
|
|
|
# Change to the compose directory
|
|
cd "${COMPOSE_DIR}" || {
|
|
echo "Error: Could not change to directory ${COMPOSE_DIR}"
|
|
return 1
|
|
}
|
|
|
|
# Stop the service
|
|
echo "Stopping the service..."
|
|
if [ -f "./stop.sh" ]; then
|
|
./stop.sh
|
|
else
|
|
docker-compose -f docker-compose.app.yml down
|
|
fi
|
|
|
|
# Start the service
|
|
echo "Starting the service..."
|
|
if [ -f "./start.sh" ]; then
|
|
./start.sh
|
|
else
|
|
docker-compose -f docker-compose.app.yml up -d
|
|
fi
|
|
|
|
# Wait a moment for the service to start
|
|
sleep 3
|
|
|
|
# Show the logs
|
|
echo "Showing the last 50 lines of logs:"
|
|
docker logs --tail 50 frps_frps_1 2>/dev/null || docker logs --tail 50 "${COMPOSE_DIR##*/}_frps_1" 2>/dev/null || {
|
|
echo "Warning: Could not retrieve logs for frps_frps_1. Trying alternative container names..."
|
|
docker ps -a | grep frps
|
|
}
|
|
|
|
echo ""
|
|
echo "Docker Compose service has been restarted."
|
|
echo "You can view the logs with: docker logs --tail 50 frps_frps_1"
|
|
}
|
|
|
|
# Function to update the docker-compose configuration
|
|
update_compose_config() {
|
|
local INSTANCE_NAME="$1"
|
|
local HAS_WEB_PROXY="$2"
|
|
shift 2 # Remove first two arguments, remaining are ports
|
|
local PORTS=("$@") # Get all port arguments
|
|
local LAST_ARG="${PORTS[-1]}" # Last argument should be domain name
|
|
unset 'PORTS[-1]' # Remove domain from ports array
|
|
|
|
local DOMAIN_NAME="$LAST_ARG"
|
|
local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
|
|
|
|
# Use the first port as the web port for domain routing
|
|
local WEB_PORT="${PORTS[0]}"
|
|
|
|
# Create a backup of the original file with timestamp
|
|
if [ -f "${COMPOSE_FILE}" ]; then
|
|
local TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
local BACKUP_FILE="${COMPOSE_FILE}.backup_${TIMESTAMP}"
|
|
cp "${COMPOSE_FILE}" "${BACKUP_FILE}"
|
|
echo "Created backup of docker-compose.app.yml as ${BACKUP_FILE}"
|
|
else
|
|
echo "NOTE: docker-compose.app.yml does not exist yet, will create it"
|
|
fi
|
|
|
|
# Update ports section based on whether we have a web proxy
|
|
if [ "$HAS_WEB_PROXY" = "true" ]; then
|
|
echo "Skipping port mappings for configuration with web proxy (access via domain only)"
|
|
else
|
|
# Update ports section with all ports (don't pass domain name to ports config)
|
|
update_ports_config "$INSTANCE_NAME" "${PORTS[@]}"
|
|
fi
|
|
|
|
# Update labels section - only needs the web port for routing
|
|
update_labels_config "$INSTANCE_NAME" "$WEB_PORT" "$DOMAIN_NAME"
|
|
|
|
# Ask user if they want to restart the service
|
|
echo ""
|
|
read -p "Do you want to restart the Docker Compose service now? (y/N): " -n 1 -r
|
|
echo ""
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
restart_docker_compose
|
|
else
|
|
echo "You can restart the service later with:"
|
|
echo " cd /data/ssd/rvs-prxy/frps/ && ./stop.sh && ./start.sh"
|
|
echo "And view logs with:"
|
|
echo " docker logs --tail 50 frps_frps_1"
|
|
fi
|
|
}
|
|
|
|
# Execute the configuration update with passed parameters
|
|
# Pass all ports and domain name as arguments to update_compose_config
|
|
update_compose_config "$@"
|
|
#!/bin/bash
|
|
|
|
# Function to check if the instance configuration is complete and up-to-date in ports section
|
|
# This function is a simplified version - in a real implementation, this would check dynamically
|
|
check_ports_config_status() {
|
|
local INSTANCE_NAME="$1"
|
|
local PORTS_CONFIG=("${@:2}") # All remaining arguments are ports
|
|
local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
|
|
|
|
# Set the instance marker for ports section
|
|
local INSTANCE_MARKER="# ${INSTANCE_NAME} Self-hosting ports ===="
|
|
|
|
# Check if the instance marker exists
|
|
if [ ! -f "${COMPOSE_FILE}" ] || ! grep -q "${INSTANCE_MARKER}" "${COMPOSE_FILE}"; then
|
|
echo "not_found"
|
|
return
|
|
fi
|
|
|
|
# Find the exact lines of the configuration block
|
|
local start_line=$(grep -n "${INSTANCE_MARKER}" "${COMPOSE_FILE}" | head -1 | cut -d: -f1)
|
|
local end_line=$(grep -n "# =========================" "${COMPOSE_FILE}" | awk -F: -v start="$start_line" '$1 > start {print $1; exit}')
|
|
|
|
# If we can't find the end marker, use a default range
|
|
if [ -z "$end_line" ]; then
|
|
end_line=$((start_line + ${#PORTS_CONFIG[@]} + 5))
|
|
fi
|
|
|
|
# Extract the configuration block
|
|
local config_block=$(sed -n "${start_line},${end_line}p" "${COMPOSE_FILE}")
|
|
|
|
# Count the port lines
|
|
local port_lines=$(echo "$config_block" | grep -c '^ - "[0-9]*:[0-9]*/tcp"')
|
|
|
|
# For simplicity in this implementation, we'll mark as 'outdated' to update
|
|
echo "outdated"
|
|
}
|
|
|
|
# Function to check if the instance configuration is complete and up-to-date in labels section
|
|
check_labels_config_status() {
|
|
local INSTANCE_NAME="$1"
|
|
local WEB_PORT="$2"
|
|
local DOMAIN_NAME="$3"
|
|
local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
|
|
|
|
# Set the instance marker for labels section (different from ports)
|
|
local INSTANCE_MARKER="# ${INSTANCE_NAME} Self-hosting labels ===="
|
|
|
|
# Check if the instance marker exists in labels section
|
|
if [ ! -f "${COMPOSE_FILE}" ] || ! grep -q "${INSTANCE_MARKER}" "${COMPOSE_FILE}"; then
|
|
echo "not_found"
|
|
return
|
|
fi
|
|
|
|
# Find the exact lines of the configuration block
|
|
local start_line=$(grep -n "${INSTANCE_MARKER}" "${COMPOSE_FILE}" | head -1 | cut -d: -f1)
|
|
local end_line=$(grep -n "# =========================" "${COMPOSE_FILE}" | awk -F: -v start="$start_line" '$1 > start {print $1; exit}')
|
|
|
|
# If we can't find the end marker, use a default range
|
|
if [ -z "$end_line" ]; then
|
|
end_line=$((start_line + 15))
|
|
fi
|
|
|
|
# Extract the configuration block
|
|
local config_block=$(sed -n "${start_line},${end_line}p" "${COMPOSE_FILE}")
|
|
|
|
# Count the label lines (should be 11 lines plus markers)
|
|
local label_lines=$(echo "$config_block" | grep -c '^ - "traefik\.')
|
|
|
|
if [ "$label_lines" -ge 11 ]; then
|
|
# Check if the domain and port match our current configuration
|
|
local current_domain=$(echo "$config_block" | grep 'rule=Host(' | head -1 | sed 's/.*Host(`\([^)]*\)`).*/\1/')
|
|
local current_port=$(echo "$config_block" | grep 'loadbalancer.server.port' | sed 's/.*port=\([0-9]*\).*/\1/')
|
|
|
|
if [ -n "$current_domain" ] && [ -n "$current_port" ] && \
|
|
[ "$current_domain" = "$DOMAIN_NAME" ] && [ "$current_port" = "$WEB_PORT" ]; then
|
|
echo "up_to_date"
|
|
else
|
|
echo "outdated"
|
|
fi
|
|
else
|
|
echo "incomplete"
|
|
fi
|
|
}
|
|
|
|
# Function to find the correct insertion point in a specific section
|
|
find_section_insertion_point() {
|
|
local section_name="$1"
|
|
local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
|
|
|
|
# Find the section start line
|
|
local section_start=$(grep -n "${section_name}:" "${COMPOSE_FILE}" | head -1 | cut -d: -f1)
|
|
if [ -z "$section_start" ]; then
|
|
echo "0"
|
|
return
|
|
fi
|
|
|
|
# Look for the next section after this one or end of section
|
|
local current_line=$((section_start + 1))
|
|
local total_lines=$(wc -l < "${COMPOSE_FILE}")
|
|
|
|
while [ "$current_line" -le "$total_lines" ]; do
|
|
local line_content=$(sed -n "${current_line}p" "${COMPOSE_FILE}")
|
|
|
|
# If we hit a line that starts a new section (not indented or comment)
|
|
if [[ "$line_content" =~ ^[[:space:]]*[a-zA-Z] ]] && [[ ! "$line_content" =~ ^[[:space:]]*# ]] && [[ ! "$line_content" =~ ^[[:space:]]*- ]]; then
|
|
echo "$current_line"
|
|
return
|
|
fi
|
|
|
|
current_line=$((current_line + 1))
|
|
done
|
|
|
|
# If we didn't find the end, insert at the end of the file
|
|
echo $((total_lines + 1))
|
|
}
|
|
|
|
# Function to update ports section with dynamic ports
|
|
update_ports_config() {
|
|
local INSTANCE_NAME="$1"
|
|
shift # Remove first argument, remaining are ports
|
|
local PORTS=("$@") # All remaining arguments are ports
|
|
local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
|
|
|
|
local INSTANCE_MARKER="# ${INSTANCE_NAME} Self-hosting ports ===="
|
|
local INSTANCE_END_MARKER="# ========================="
|
|
|
|
local config_status=$(check_ports_config_status "$INSTANCE_NAME" "${PORTS[@]}")
|
|
echo "Ports configuration status: $config_status"
|
|
|
|
case "$config_status" in
|
|
"not_found")
|
|
echo "Adding new instance to ports section"
|
|
local insertion_line=$(find_section_insertion_point "ports")
|
|
|
|
# Build the port configuration dynamically, excluding standard web ports (80, 443)
|
|
local port_config=""
|
|
local non_standard_ports=()
|
|
for port in "${PORTS[@]}"; do
|
|
# Skip standard web ports since they may be managed by the system
|
|
if [ "$port" != "80" ] && [ "$port" != "443" ]; then
|
|
port_config+=" - \"${port}:${port}/tcp\"\\n"
|
|
non_standard_ports+=("$port")
|
|
fi
|
|
done
|
|
|
|
# Only add ports section if there are non-standard ports to map
|
|
if [ -n "$port_config" ]; then
|
|
# Insert the configuration at the correct location
|
|
sed -i "${insertion_line}i\\
|
|
\\
|
|
${INSTANCE_MARKER}\\
|
|
${port_config} ${INSTANCE_END_MARKER}\\
|
|
" "${COMPOSE_FILE}"
|
|
echo "Ports configuration added successfully for ${#non_standard_ports[@]} non-standard port(s): ${non_standard_ports[*]}"
|
|
else
|
|
echo "No non-standard ports to add to ports section (ports 80 and 443 are standard)"
|
|
fi
|
|
;;
|
|
"up_to_date")
|
|
echo "Ports configuration is already up to date"
|
|
;;
|
|
"outdated"|"incomplete")
|
|
echo "Updating existing ports configuration"
|
|
# Find the exact lines of the existing configuration
|
|
local start_line=$(grep -n "${INSTANCE_MARKER}" "${COMPOSE_FILE}" | head -1 | cut -d: -f1)
|
|
local end_line=$(grep -n "${INSTANCE_END_MARKER}" "${COMPOSE_FILE}" | awk -F: -v start="$start_line" '$1 > start {print $1; exit}')
|
|
|
|
# If we can't find the end marker, use a default range
|
|
if [ -n "$start_line" ] && [ -z "$end_line" ]; then
|
|
end_line=$((start_line + ${#PORTS[@]} + 5))
|
|
fi
|
|
|
|
# Remove only the specific configuration block (from start marker to end marker)
|
|
if [ -n "$start_line" ] && [ -n "$end_line" ] && [ "$start_line" -gt 0 ] && [ "$end_line" -gt "$start_line" ]; then
|
|
sed -i "${start_line},${end_line}d" "${COMPOSE_FILE}"
|
|
|
|
# Build the updated port configuration dynamically, excluding standard web ports (80, 443)
|
|
local port_config=""
|
|
local non_standard_ports=()
|
|
for port in "${PORTS[@]}"; do
|
|
# Skip standard web ports since they may be managed by the system
|
|
if [ "$port" != "80" ] && [ "$port" != "443" ]; then
|
|
port_config+=" - \"${port}:${port}/tcp\"\\n"
|
|
non_standard_ports+=("$port")
|
|
fi
|
|
done
|
|
|
|
# Only update ports section if there are non-standard ports to map
|
|
if [ -n "$port_config" ]; then
|
|
# Insert the updated configuration at the same location
|
|
sed -i "${start_line}i\\
|
|
${INSTANCE_MARKER}\\
|
|
${port_config} ${INSTANCE_END_MARKER}" "${COMPOSE_FILE}"
|
|
echo "Ports configuration updated successfully for ${#non_standard_ports[@]} non-standard port(s): ${non_standard_ports[*]}"
|
|
else
|
|
echo "No non-standard ports to update in ports section (ports 80 and 443 are standard)"
|
|
fi
|
|
else
|
|
echo "Warning: Could not determine valid line range for ports update"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function to update labels section
|
|
update_labels_config() {
|
|
local INSTANCE_NAME="$1"
|
|
local WEB_PORT="$2"
|
|
local DOMAIN_NAME="$3"
|
|
local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
|
|
|
|
local INSTANCE_MARKER="# ${INSTANCE_NAME} Self-hosting labels ===="
|
|
local INSTANCE_END_MARKER="# ========================="
|
|
|
|
local config_status=$(check_labels_config_status "$INSTANCE_NAME" "$WEB_PORT" "$DOMAIN_NAME")
|
|
echo "Labels configuration status: $config_status"
|
|
|
|
case "$config_status" in
|
|
"not_found")
|
|
echo "Adding new instance to labels section"
|
|
local insertion_line=$(find_section_insertion_point "labels")
|
|
|
|
# Insert the configuration at the correct location
|
|
sed -i "${insertion_line}i\\
|
|
\\
|
|
${INSTANCE_MARKER}\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.entrypoints=web\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.rule=Host(\`${DOMAIN_NAME}\`)\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.service=frps-${INSTANCE_NAME}\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.middlewares=http2https@file\"\\
|
|
\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.entrypoints=websecure\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.rule=Host(\`${DOMAIN_NAME}\`)\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.service=frps-${INSTANCE_NAME}\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls=true\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls.certresolver=letsencrypt\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls.domains[0].main=${DOMAIN_NAME}\"\\
|
|
\\
|
|
- \"traefik.http.services.frps-${INSTANCE_NAME}.loadbalancer.server.port=${WEB_PORT}\"\\
|
|
${INSTANCE_END_MARKER}\\
|
|
" "${COMPOSE_FILE}"
|
|
echo "Labels configuration added successfully"
|
|
;;
|
|
"up_to_date")
|
|
echo "Labels configuration is already up to date"
|
|
;;
|
|
"outdated"|"incomplete")
|
|
echo "Updating existing labels configuration"
|
|
# Find the exact lines of the existing configuration
|
|
local start_line=$(grep -n "${INSTANCE_MARKER}" "${COMPOSE_FILE}" | head -1 | cut -d: -f1)
|
|
local end_line=$(grep -n "${INSTANCE_END_MARKER}" "${COMPOSE_FILE}" | awk -F: -v start="$start_line" '$1 > start {print $1; exit}')
|
|
|
|
# If we can't find the end marker, use a default range
|
|
if [ -z "$end_line" ]; then
|
|
end_line=$((start_line + 15))
|
|
fi
|
|
|
|
# Remove only the specific configuration block (from start marker to end marker)
|
|
if [ -n "$start_line" ] && [ -n "$end_line" ] && [ "$start_line" -gt 0 ] && [ "$end_line" -gt "$start_line" ]; then
|
|
sed -i "${start_line},${end_line}d" "${COMPOSE_FILE}"
|
|
|
|
# Insert the updated configuration at the same location
|
|
sed -i "${start_line}i\\
|
|
${INSTANCE_MARKER}\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.entrypoints=web\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.rule=Host(\`${DOMAIN_NAME}\`)\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.service=frps-${INSTANCE_NAME}\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-http.middlewares=http2https@file\"\\
|
|
\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.entrypoints=websecure\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.rule=Host(\`${DOMAIN_NAME}\`)\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.service=frps-${INSTANCE_NAME}\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls=true\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls.certresolver=letsencrypt\"\\
|
|
- \"traefik.http.routers.frps-${INSTANCE_NAME}-https.tls.domains[0].main=${DOMAIN_NAME}\"\\
|
|
\\
|
|
- \"traefik.http.services.frps-${INSTANCE_NAME}.loadbalancer.server.port=${WEB_PORT}\"\\
|
|
${INSTANCE_END_MARKER}" "${COMPOSE_FILE}"
|
|
echo "Labels configuration updated successfully"
|
|
else
|
|
echo "Warning: Could not determine valid line range for labels update"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function to restart Docker Compose service
|
|
restart_docker_compose() {
|
|
local COMPOSE_DIR="/data/ssd/rvs-prxy/frps/"
|
|
echo "Restarting Docker Compose service..."
|
|
|
|
# Change to the compose directory
|
|
cd "${COMPOSE_DIR}" || {
|
|
echo "Error: Could not change to directory ${COMPOSE_DIR}"
|
|
return 1
|
|
}
|
|
|
|
# Stop the service
|
|
echo "Stopping the service..."
|
|
if [ -f "./stop.sh" ]; then
|
|
./stop.sh
|
|
else
|
|
docker-compose -f docker-compose.app.yml down
|
|
fi
|
|
|
|
# Start the service
|
|
echo "Starting the service..."
|
|
if [ -f "./start.sh" ]; then
|
|
./start.sh
|
|
else
|
|
docker-compose -f docker-compose.app.yml up -d
|
|
fi
|
|
|
|
# Wait a moment for the service to start
|
|
sleep 3
|
|
|
|
# Show the logs
|
|
echo "Showing the last 50 lines of logs:"
|
|
docker logs --tail 50 frps_frps_1 2>/dev/null || docker logs --tail 50 "${COMPOSE_DIR##*/}_frps_1" 2>/dev/null || {
|
|
echo "Warning: Could not retrieve logs for frps_frps_1. Trying alternative container names..."
|
|
docker ps -a | grep frps
|
|
}
|
|
|
|
echo ""
|
|
echo "Docker Compose service has been restarted."
|
|
echo "You can view the logs with: docker logs --tail 50 frps_frps_1"
|
|
}
|
|
|
|
# Function to update the docker-compose configuration
|
|
update_compose_config() {
|
|
local INSTANCE_NAME="$1"
|
|
local HAS_WEB_PROXY="$2"
|
|
shift 2 # Remove first two arguments, remaining are ports
|
|
local PORTS=("$@") # Get all port arguments
|
|
local LAST_ARG="${PORTS[-1]}" # Last argument should be domain name
|
|
unset 'PORTS[-1]' # Remove domain from ports array
|
|
|
|
local DOMAIN_NAME="$LAST_ARG"
|
|
local COMPOSE_FILE="/data/ssd/rvs-prxy/frps/apps/zzthis/docker-compose.app.yml"
|
|
|
|
# Use the first port as the web port for domain routing
|
|
local WEB_PORT="${PORTS[0]}"
|
|
|
|
# Create a backup of the original file with timestamp
|
|
if [ -f "${COMPOSE_FILE}" ]; then
|
|
local TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
local BACKUP_FILE="${COMPOSE_FILE}.backup_${TIMESTAMP}"
|
|
cp "${COMPOSE_FILE}" "${BACKUP_FILE}"
|
|
echo "Created backup of docker-compose.app.yml as ${BACKUP_FILE}"
|
|
else
|
|
echo "NOTE: docker-compose.app.yml does not exist yet, will create it"
|
|
fi
|
|
|
|
# Update ports section based on whether we have a web proxy
|
|
if [ "$HAS_WEB_PROXY" = "true" ]; then
|
|
echo "Skipping port mappings for configuration with web proxy (access via domain only)"
|
|
else
|
|
# Update ports section with all ports (don't pass domain name to ports config)
|
|
update_ports_config "$INSTANCE_NAME" "${PORTS[@]}"
|
|
fi
|
|
|
|
# Update labels section - only needs the web port for routing
|
|
update_labels_config "$INSTANCE_NAME" "$WEB_PORT" "$DOMAIN_NAME"
|
|
|
|
# Ask user if they want to restart the service
|
|
echo ""
|
|
read -p "Do you want to restart the Docker Compose service now? (y/N): " -n 1 -r
|
|
echo ""
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
restart_docker_compose
|
|
else
|
|
echo "You can restart the service later with:"
|
|
echo " cd /data/ssd/rvs-prxy/frps/ && ./stop.sh && ./start.sh"
|
|
echo "And view logs with:"
|
|
echo " docker logs --tail 50 frps_frps_1"
|
|
fi
|
|
}
|
|
|
|
# Execute the configuration update with passed parameters
|
|
# Pass all ports and domain name as arguments to update_compose_config
|
|
update_compose_config "$@"
|
|
|
|
EOF
|
|
|
|
# Make the script executable
|
|
chmod +x "$temp_script"
|
|
|
|
# Copy script to proxy server and execute it with all ports and domain
|
|
log_info "Connecting to proxy server at $proxy_server_addr"
|
|
if scp -i ~/.ssh/itl-devs1 "$temp_script" teenfuture@$proxy_server_addr:/tmp/; then
|
|
# Construct command with instance name, web proxy flag, all ports and domain name
|
|
local cmd_args=("$THIS_INSTANCE" "$has_web_proxy")
|
|
for port in "${all_ports[@]}"; do
|
|
cmd_args+=("$port")
|
|
done
|
|
cmd_args+=("$DOMAIN_NAME") # Add domain name as last argument
|
|
|
|
if ssh -i ~/.ssh/itl-devs1 teenfuture@$proxy_server_addr "bash /tmp/$(basename $temp_script) ${cmd_args[*]}"; then
|
|
log_info "Proxy server configuration updated successfully"
|
|
# Clean up remote script
|
|
ssh -i ~/.ssh/itl-devs1 teenfuture@$proxy_server_addr "rm /tmp/$(basename $temp_script)" >/dev/null 2>&1 || true
|
|
else
|
|
log_error "Failed to execute script on proxy server"
|
|
# Clean up remote script
|
|
ssh -i ~/.ssh/itl-devs1 teenfuture@$proxy_server_addr "rm /tmp/$(basename $temp_script)" >/dev/null 2>&1 || true
|
|
rm -f "$temp_script"
|
|
return 1
|
|
fi
|
|
else
|
|
log_error "Failed to copy script to proxy server"
|
|
rm -f "$temp_script"
|
|
return 1
|
|
fi
|
|
|
|
# Clean up local script
|
|
rm -f "$temp_script"
|
|
|
|
echo "" >&2
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Function to restart Docker Compose service
|
|
restart_docker_compose() {
|
|
local COMPOSE_DIR="/data/ssd/rvs-prxy/frps/"
|
|
echo "Restarting Docker Compose service..."
|
|
|
|
# Change to the compose directory
|
|
cd "${COMPOSE_DIR}" || {
|
|
echo "Error: Could not change to directory ${COMPOSE_DIR}"
|
|
return 1
|
|
}
|
|
|
|
# Stop the service
|
|
echo "Stopping the service..."
|
|
if [ -f "./stop.sh" ]; then
|
|
./stop.sh
|
|
else
|
|
docker-compose -f docker-compose.app.yml down
|
|
fi
|
|
|
|
# Start the service
|
|
echo "Starting the service..."
|
|
if [ -f "./start.sh" ]; then
|
|
./start.sh
|
|
else
|
|
docker-compose -f docker-compose.app.yml up -d
|
|
fi
|
|
|
|
# Wait a moment for the service to start
|
|
sleep 3
|
|
|
|
# Show the logs
|
|
echo "Showing the last 50 lines of logs:"
|
|
docker logs --tail 50 frps_frps_1 2>/dev/null || docker logs --tail 50 "${COMPOSE_DIR##*/}_frps_1" 2>/dev/null || {
|
|
echo "Warning: Could not retrieve logs for frps_frps_1. Trying alternative container names..."
|
|
docker ps -a | grep frps
|
|
}
|
|
|
|
echo ""
|
|
echo "Docker Compose service has been restarted."
|
|
echo "You can view the logs with: docker logs --tail 50 frps_frps_1"
|
|
}
|
|
|
|
|
|
|
|
# Setup rvs-prxy function
|
|
setup_rvs_prxy() {
|
|
log_info "Setting up FRP client for instance: ${THIS_INSTANCE}"
|
|
|
|
# Pre-request checks
|
|
echo -e "${YELLOW}[PRE-REQUEST]${NC} Make sure your current user has permission to execute docker command." >&2
|
|
echo -e "${YELLOW}[PRE-REQUEST]${NC} Make sure you have key pairs ~/.ssh/authorized_keys and ~/.ssh/devs1" >&2
|
|
echo -e "${YELLOW}[PRE-REQUEST]${NC} Make sure you have key pair ~/.ssh/itl-devs1 for proxy server access" >&2
|
|
echo "" >&2
|
|
|
|
# Prompt for OK/Cancel (DISABLED for smooth operation)
|
|
# local confirm
|
|
# confirm=$(read_terminal "Proceed with deployment? (OK/Cancel) [Cancel]: " "Cancel")
|
|
# if [[ ! "$confirm" =~ ^[Oo][Kk]$ ]]; then
|
|
# log_error "Deployment cancelled by user"
|
|
# exit 1
|
|
# fi
|
|
# echo "" >&2
|
|
|
|
# Auto-proceed with deployment (smooth operation)
|
|
echo "Auto-proceeding with deployment..." >&2
|
|
echo "" >&2
|
|
|
|
# Prompt for Docker network selection (DISABLED for smooth operation)
|
|
# SELECTED_NETWORK=$(select_docker_network)
|
|
# if [ $? -ne 0 ]; then
|
|
# log_error "Failed to select Docker network"
|
|
# exit 1
|
|
# fi
|
|
|
|
# Use Docker network from environment variable if available, otherwise use default
|
|
if [ -n "$DOCKER_NETWORK" ]; then
|
|
SELECTED_NETWORK="$DOCKER_NETWORK"
|
|
log_info "Using Docker network from environment variable: ${SELECTED_NETWORK}"
|
|
else
|
|
SELECTED_NETWORK=""
|
|
log_info "Using default Docker network"
|
|
fi
|
|
|
|
# Save the network selection
|
|
local network_to_save="$SELECTED_NETWORK"
|
|
if [ -z "$network_to_save" ]; then
|
|
network_to_save="none"
|
|
fi
|
|
save_parameters "$network_to_save"
|
|
|
|
if [ -n "$SELECTED_NETWORK" ]; then
|
|
log_info "Selected Docker network: ${SELECTED_NETWORK}"
|
|
else
|
|
log_info "Using default Docker network"
|
|
fi
|
|
|
|
# Generate FRP client configuration
|
|
generate_frpc_config
|
|
|
|
# Start Docker container
|
|
start_docker_container
|
|
|
|
# Extract FRP server address and remote ports
|
|
local server_addr
|
|
server_addr=$(extract_frp_server_address)
|
|
|
|
if [ -z "$server_addr" ]; then
|
|
log_error "Failed to extract FRP server address from configuration"
|
|
log_info "Skipping proxy server configuration update"
|
|
else
|
|
log_info "Extracted FRP server address: $server_addr"
|
|
|
|
# Extract all remote ports dynamically from configuration
|
|
local all_ports=()
|
|
local ports_string=$(extract_all_remote_ports)
|
|
if [ -n "$ports_string" ]; then
|
|
all_ports=($ports_string)
|
|
fi
|
|
|
|
# Check if we have at least one port to configure
|
|
if [ ${#all_ports[@]} -ge 1 ]; then
|
|
log_info "Extracted ${#all_ports[@]} remote port(s) for proxy server update: ${all_ports[*]}"
|
|
|
|
# Check if we have any web proxies
|
|
local has_web=$(has_web_proxy "${proxy_configs[@]}")
|
|
|
|
# Update proxy server configuration with all extracted ports
|
|
update_proxy_server_config "$server_addr" "$has_web" "${all_ports[@]}"
|
|
else
|
|
log_error "Failed to extract any remote ports from configuration"
|
|
log_info "Skipping proxy server configuration update"
|
|
fi
|
|
fi
|
|
|
|
# Display container operation commands
|
|
display_container_commands
|
|
|
|
log_info "FRP client instance ${THIS_INSTANCE} deployment completed"
|
|
}
|
|
|
|
# List available systems
|
|
list_systems() {
|
|
echo -e "${GREEN}Available deployment systems:${NC}" >&2
|
|
echo -e "${YELLOW}============================${NC}" >&2
|
|
echo -e "${BLUE}•${NC} setup-rvs-prxy" >&2
|
|
echo -e "${YELLOW}============================${NC}" >&2
|
|
echo -e "Use: ${GREEN}$0 <system-name>${NC} to deploy a system" >&2
|
|
}
|
|
|
|
# Usage function
|
|
usage() {
|
|
cat << EOF
|
|
Usage: $0 [COMMAND] [OPTIONS]
|
|
|
|
Available commands:
|
|
setup-rvs-prxy
|
|
|
|
Available options:
|
|
-l, --list List all available systems
|
|
-h, --help Show this help message
|
|
|
|
Examples:
|
|
$0 setup-rvs-prxy
|
|
$0 --list
|
|
$0 -l
|
|
|
|
Note: This script runs on the same computer as the service it connects to.
|
|
IMPORTANT: Configuration is primarily handled through .env files (.env, .env.development, .env.production)
|
|
located in the current directory or script directory. The .env file contains all parameters
|
|
for your setup and is essential for operation. Make sure to properly configure your .env file
|
|
before running the script.
|
|
EOF
|
|
}
|
|
|
|
# Main execution
|
|
main() {
|
|
# Load environment variables from .env files first
|
|
load_env
|
|
|
|
local command="${1:-help}"
|
|
|
|
# Handle options first
|
|
case "$command" in
|
|
-l|--list)
|
|
list_systems
|
|
exit 0
|
|
;;
|
|
-h|--help|help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
--)
|
|
# Skip the -- separator and use the next argument
|
|
shift
|
|
command="${1:-help}"
|
|
;;
|
|
esac
|
|
|
|
# Handle setup commands
|
|
if [[ "$command" == "setup-rvs-prxy" ]]; then
|
|
# Prompt user for instance name first
|
|
prompt_instance_name "$command"
|
|
|
|
# Prompt for internal service port
|
|
prompt_internal_service_port
|
|
|
|
# Prompt for domain name if not in environment
|
|
if [ -z "$DOMAIN_NAME" ]; then
|
|
DOMAIN_NAME=$(prompt_domain_name)
|
|
save_to_env "DOMAIN_NAME" "$DOMAIN_NAME"
|
|
fi
|
|
|
|
log_info "Starting deployment: $command"
|
|
setup_rvs_prxy
|
|
log_info "Completed deployment: $command for instance: ${THIS_INSTANCE}"
|
|
else
|
|
log_error "Unknown deployment system: $command"
|
|
echo ""
|
|
list_systems
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Function to prompt for domain name
|
|
prompt_domain_name() {
|
|
# Check if DOMAIN_NAME is set from environment
|
|
if [ -n "$DOMAIN_NAME" ]; then
|
|
log_info "Using environment variable for domain name: ${DOMAIN_NAME}"
|
|
echo "$DOMAIN_NAME"
|
|
return
|
|
fi
|
|
|
|
echo -e "${BLUE}=== Domain Name Configuration ===${NC}" >&2
|
|
echo -e "${YELLOW}Please enter the domain name for this instance${NC}" >&2
|
|
echo -e "${YELLOW}(e.g., cmty-repos2.ivantito.com)${NC}" >&2
|
|
echo "" >&2
|
|
|
|
local domain_name=""
|
|
while [ -z "$domain_name" ]; do
|
|
domain_name=$(read_terminal "Enter domain name: " "")
|
|
if [ -z "$domain_name" ]; then
|
|
log_error "Domain name cannot be empty" >&2
|
|
fi
|
|
done
|
|
|
|
echo "$domain_name"
|
|
}
|
|
|
|
# Execute main function with arguments
|
|
main "$@" |