2095 lines
83 KiB
Bash
Executable File
2095 lines
83 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# auto-sys-deploy.sh - Unified system deployment script for both FRP and Traefik
|
|
# This script supports both FRP (Fast Reverse Proxy) and Traefik modes based on environment configuration
|
|
# 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=""
|
|
|
|
# FRP Mode variables
|
|
FRP=true
|
|
|
|
# Target server variables (for Traefik mode)
|
|
TARGET_SERVER=""
|
|
TARGET_USER=""
|
|
SSH_PORT="22"
|
|
SSH_KEY_FILE_NAME=""
|
|
SSH_KEY_PATH=""
|
|
LOCAL_FOLDER="../out/"
|
|
DEST_PATH="/"
|
|
DEBUG="false"
|
|
TARGET_SERVICE_NAME=""
|
|
TARGET_SERVICE_PORT=""
|
|
TARGET_DOCKER_NETWORK=""
|
|
|
|
# Function to load environment variables from .env files
|
|
load_env() {
|
|
# Check for .env files in priority order: .env.development, .env.production, .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 "# See .env.sample for a complete parameter reference" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Normalize boolean values
|
|
if [ "$FRP" = "false" ] || [ "$FRP" = "False" ] || [ "$FRP" = "FALSE" ]; then
|
|
FRP=false
|
|
else
|
|
FRP=true
|
|
fi
|
|
|
|
# Set SSH key path if in Traefik mode
|
|
if [ "$FRP" = false ]; then
|
|
if [ -n "$SSH_KEY_FILE_NAME" ]; then
|
|
SSH_KEY_PATH="$HOME/.ssh/$SSH_KEY_FILE_NAME"
|
|
if [ ! -f "$SSH_KEY_PATH" ]; then
|
|
log_error "SSH key file not found: $SSH_KEY_PATH"
|
|
exit 1
|
|
fi
|
|
else
|
|
log_error "SSH_KEY_FILE_NAME must be set when FRP=false"
|
|
exit 1
|
|
fi
|
|
|
|
# Validate required Traefik parameters
|
|
if [ -z "$TARGET_SERVER" ] || [ -z "$TARGET_USER" ] || [ -z "$TARGET_SERVICE_NAME" ] || [ -z "$TARGET_SERVICE_PORT" ]; then
|
|
log_error "Required Traefik parameters are missing when FRP=false"
|
|
exit 1
|
|
fi
|
|
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
|
|
}
|
|
|
|
log_debug() {
|
|
if [ "$DEBUG" = "true" ]; then
|
|
echo -e "${BLUE}[DEBUG]${NC} $1" >&2
|
|
fi
|
|
}
|
|
|
|
# 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 (FRP Mode)
|
|
generate_frpc_config() {
|
|
if [ "$FRP" = false ]; then
|
|
log_info "Skipping FRP client configuration as FRP=false"
|
|
return 0
|
|
fi
|
|
|
|
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 FRP server user from environment or default to 'teenfuture'
|
|
if [ -n "$FRP_SERVER_USER" ]; then
|
|
SERVER_USER="$FRP_SERVER_USER"
|
|
log_info "Using environment variable for FRP server user: $SERVER_USER"
|
|
else
|
|
# Use default 'teenfuture' user if not specified
|
|
SERVER_USER="teenfuture"
|
|
log_info "Using default FRP server user: $SERVER_USER"
|
|
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"
|
|
# Use SERVICE_DOCKER_CONTAINER_NAME from .env* files if available, otherwise fall back to ${THIS_INSTANCE}-app
|
|
if [ -n "$SERVICE_DOCKER_CONTAINER_NAME" ]; then
|
|
config_content+="localIP = \"${SERVICE_DOCKER_CONTAINER_NAME}\"\n"
|
|
else
|
|
config_content+="localIP = \"${THIS_INSTANCE}-app\"\n"
|
|
fi
|
|
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 (FRP Mode)
|
|
start_docker_container() {
|
|
if [ "$FRP" = false ]; then
|
|
log_info "Skipping Docker container start as FRP=false"
|
|
return 0
|
|
fi
|
|
|
|
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 (FRP Mode)
|
|
display_container_commands() {
|
|
if [ "$FRP" = false ]; then
|
|
log_info "Skipping container commands display as FRP=false"
|
|
return 0
|
|
fi
|
|
|
|
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() {
|
|
if [ "$FRP" = false ]; then
|
|
log_info "Skipping FRP server address extraction as FRP=false"
|
|
return 0
|
|
fi
|
|
|
|
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() {
|
|
if [ "$FRP" = false ]; then
|
|
log_info "Skipping remote port extraction as FRP=false"
|
|
return 0
|
|
fi
|
|
|
|
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 connect to target server via SSH and execute command (Traefik Mode)
|
|
# ssh_exec() {
|
|
# local command="$1"
|
|
# log_debug "Executing SSH command: $command"
|
|
# ssh -i "$SSH_KEY_PATH" -p "${SSH_PORT:-22}" "$TARGET_USER@$TARGET_SERVER" "$command"
|
|
# }
|
|
ssh_exec() {
|
|
local command="$1"
|
|
|
|
# Build the full SSH command for logging
|
|
local full_ssh_cmd=(ssh)
|
|
[[ -n "${SSH_KEY_PATH:-}" ]] && full_ssh_cmd+=(-i "$SSH_KEY_PATH")
|
|
[[ -n "${SSH_PORT:-}" ]] && full_ssh_cmd+=(-p "$SSH_PORT")
|
|
full_ssh_cmd+=("$TARGET_USER@$TARGET_SERVER" "$command")
|
|
|
|
# Log the complete command (quoted for clarity)
|
|
log_debug "Executing SSH command: ${full_ssh_cmd[*]}"
|
|
|
|
# Actually run it
|
|
ssh -i "$SSH_KEY_PATH" -p "${SSH_PORT:-22}" "$TARGET_USER@$TARGET_SERVER" "$command"
|
|
}
|
|
|
|
# Function to upload files to target server via SCP (Traefik Mode)
|
|
scp_upload() {
|
|
local source="$1"
|
|
local destination="$2"
|
|
log_debug "Uploading $source to $TARGET_USER@$TARGET_SERVER:$destination"
|
|
|
|
scp -i "$SSH_KEY_PATH" -P "${SSH_PORT:-22}" -r "$source" "$TARGET_USER@$TARGET_SERVER:$destination"
|
|
}
|
|
|
|
# Function to check if Traefik container exists on target server (Traefik Mode)
|
|
check_traefik_container() {
|
|
log_info "Checking if Traefik container exists on target server ($TARGET_SERVER)..."
|
|
|
|
local container_exists
|
|
container_exists=$(ssh_exec "docker ps -a --filter name=traefik --format '{{.Names}}' 2>/dev/null || true")
|
|
|
|
if [ -n "$container_exists" ] && [ "$container_exists" = "traefik" ]; then
|
|
log_info "Traefik container already exists on target server"
|
|
return 0
|
|
else
|
|
log_info "Traefik container does not exist on target server"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to create Traefik Docker container on target server if it doesn't exist (Traefik Mode)
|
|
create_traefik_container_on_target() {
|
|
log_info "Checking if Traefik container exists on target server ($TARGET_SERVER)..."
|
|
|
|
local container_exists
|
|
container_exists=$(ssh_exec "docker ps -a --filter name=traefik --format '{{.Names}}' 2>/dev/null || true")
|
|
|
|
# Use fixed container name "traefik" regardless of instance name
|
|
local TRAEFIK_CONTAINER_NAME="traefik"
|
|
|
|
# Use fixed volume name "traefik_data" regardless of instance name
|
|
local TRAEFIK_VOLUME_NAME="traefik_data"
|
|
|
|
if [ -n "$container_exists" ] && [ "$container_exists" = "$TRAEFIK_CONTAINER_NAME" ]; then
|
|
log_info "Traefik container '$TRAEFIK_CONTAINER_NAME' already exists on target server"
|
|
# Only update the config files in the volume, don't recreate the container
|
|
log_info "Updating configuration in existing Traefik container..."
|
|
|
|
# Use specified Traefik version or default to v3.5.4
|
|
local TRAEFIK_VER="${TRAEFIK_VER:-v3.5.4}"
|
|
local TRAEFIK_IMAGE="traefik:$TRAEFIK_VER"
|
|
|
|
# Use specified Let's Encrypt email or default
|
|
local LETSENCRYPT_EMAIL="${LETSENCRYPT_EMAIL:-admin@yourdomain.com}"
|
|
|
|
# Create a minimal dynamic configuration file for the dashboard and basic routing, plus HTTP to HTTPS redirect
|
|
local temp_config="/tmp/traefik-$$.yml"
|
|
cat > "$temp_config" << 'EOF_HEREDOC'
|
|
http:
|
|
routers:
|
|
api:
|
|
rule: "PathPrefix(`/api`)"
|
|
service: api@internal
|
|
dashboard:
|
|
rule: "PathPrefix(`/dashboard`)"
|
|
service: api@internal
|
|
http-catchall:
|
|
rule: "PathRegexp(`{any:.*}`)"
|
|
entryPoints:
|
|
- "web"
|
|
service: "noop@internal"
|
|
middlewares:
|
|
- "redirect-to-https"
|
|
middlewares:
|
|
api-headers:
|
|
headers:
|
|
customFrameOptionsValue: SAMEORIGIN
|
|
contentTypeNosniff: true
|
|
browserXssFilter: true
|
|
stsIncludeSubdomains: true
|
|
stsPreload: true
|
|
stsSeconds: 15552000
|
|
redirect-to-https:
|
|
redirectScheme:
|
|
scheme: "https"
|
|
permanent: true
|
|
EOF_HEREDOC
|
|
|
|
# Upload the configuration to target server
|
|
scp_upload "$temp_config" "/tmp/traefik.yml"
|
|
rm -f "$temp_config"
|
|
|
|
# Copy the configuration file and create necessary directories in the volume with proper permissions
|
|
ssh_exec "docker run --rm -v $TRAEFIK_VOLUME_NAME:/etc/traefik -v /tmp:/tmp alpine:latest sh -c 'mkdir -p /etc/traefik/config /etc/traefik/acme && cp /tmp/traefik.yml /etc/traefik/config/traefik.yml && touch /etc/traefik/acme/acme.json && chmod 600 /etc/traefik/acme/acme.json'"
|
|
|
|
log_info "Configuration updated in existing Traefik container"
|
|
|
|
# Exit early since container already exists and configuration was updated
|
|
log_info "Traefik container '$TRAEFIK_CONTAINER_NAME' is running on target server using image: $TRAEFIK_IMAGE with volume: $TRAEFIK_VOLUME_NAME"
|
|
|
|
# Wait a bit for the container to start/update
|
|
sleep 3
|
|
|
|
# Commands will be shown at the end of setup_traefik_only()
|
|
return 0
|
|
else
|
|
log_info "Creating Traefik container '$TRAEFIK_CONTAINER_NAME' on target server..."
|
|
|
|
# Create the Docker volume for persistent data using fixed name
|
|
ssh_exec "docker volume create $TRAEFIK_VOLUME_NAME"
|
|
fi
|
|
|
|
# Use specified Traefik version or default to v3.5.4
|
|
local TRAEFIK_VER="${TRAEFIK_VER:-v3.5.4}"
|
|
local TRAEFIK_IMAGE="traefik:$TRAEFIK_VER"
|
|
|
|
# Use specified Let's Encrypt email or default
|
|
local LETSENCRYPT_EMAIL="${LETSENCRYPT_EMAIL:-admin@yourdomain.com}"
|
|
|
|
# Create a minimal dynamic configuration file for the dashboard and basic routing, plus HTTP to HTTPS redirect
|
|
local temp_config="/tmp/traefik-$$.yml"
|
|
cat > "$temp_config" << 'EOF_HEREDOC'
|
|
http:
|
|
routers:
|
|
api:
|
|
rule: "PathPrefix(`/api`)"
|
|
service: api@internal
|
|
dashboard:
|
|
rule: "PathPrefix(`/dashboard`)"
|
|
service: api@internal
|
|
http-catchall:
|
|
rule: "PathRegexp(`{any:.*}`)"
|
|
entryPoints:
|
|
- "web"
|
|
service: "noop@internal"
|
|
middlewares:
|
|
- "redirect-to-https"
|
|
middlewares:
|
|
api-headers:
|
|
headers:
|
|
customFrameOptionsValue: SAMEORIGIN
|
|
contentTypeNosniff: true
|
|
browserXssFilter: true
|
|
stsIncludeSubdomains: true
|
|
stsPreload: true
|
|
stsSeconds: 15552000
|
|
redirect-to-https:
|
|
redirectScheme:
|
|
scheme: "https"
|
|
permanent: true
|
|
EOF_HEREDOC
|
|
|
|
# Upload the configuration to target server
|
|
scp_upload "$temp_config" "/tmp/traefik.yml"
|
|
rm -f "$temp_config"
|
|
|
|
# Determine Docker version to decide if we need the version field
|
|
local compose_version_field
|
|
compose_version_field=$(ssh_exec "
|
|
docker_version=\$(docker --version | sed 's/Docker version //' | sed 's/,.*//')
|
|
major_version=\$(echo \$docker_version | cut -d. -f1)
|
|
minor_version=\$(echo \$docker_version | cut -d. -f2)
|
|
|
|
# Docker Compose v2.0+ (around Docker 20.10.0+) deprecates the version field
|
|
# But keep it for older versions for compatibility
|
|
if [ \"\$major_version\" -lt 20 ] || ([ \"\$major_version\" -eq 20 ] && [ \"\$minor_version\" -lt 10 ]); then
|
|
echo \"version: '3.8'\"
|
|
else
|
|
echo \"\"
|
|
fi
|
|
")
|
|
|
|
# Create docker-compose.yml for Traefik using the specified version and volume
|
|
local temp_compose="/tmp/traefik-compose-$$.yml"
|
|
if [ -n "$compose_version_field" ]; then
|
|
# Include version field for older Docker versions
|
|
cat > "$temp_compose" << EOF
|
|
$compose_version_field
|
|
|
|
services:
|
|
traefik:
|
|
image: $TRAEFIK_IMAGE
|
|
container_name: $TRAEFIK_CONTAINER_NAME
|
|
restart: unless-stopped
|
|
command:
|
|
- '--api.insecure=true'
|
|
- '--providers.docker=true'
|
|
- '--providers.docker.exposedbydefault=false'
|
|
- '--entrypoints.web.address=:80'
|
|
- '--entrypoints.websecure.address=:443'
|
|
- '--certificatesresolvers.letsencrypt.acme.httpchallenge=true'
|
|
- '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web'
|
|
- '--certificatesresolvers.letsencrypt.acme.email=$LETSENCRYPT_EMAIL'
|
|
- '--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json'
|
|
- '--providers.file.directory=/etc/traefik/config'
|
|
- '--providers.file.watch=true'
|
|
ports:
|
|
- '80:80'
|
|
- '443:443'
|
|
- '127.0.0.1:8080:8080' # Bind dashboard to localhost only for security
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
- $TRAEFIK_VOLUME_NAME:/etc/traefik
|
|
labels:
|
|
- 'traefik.enable=true'
|
|
- 'traefik.http.routers.traefik.rule=Host(\`traefik.${DOMAIN_NAME:-traefik.localhost}\`)'
|
|
- 'traefik.http.routers.traefik.service=api@internal'
|
|
- 'traefik.http.routers.traefik.middlewares=auth'
|
|
- 'traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$H6usmud2$$YqJ2a7vLp9J9l5zK8Y4hD1'
|
|
|
|
volumes:
|
|
$TRAEFIK_VOLUME_NAME:
|
|
external: true
|
|
|
|
networks:
|
|
default:
|
|
name: traefik-public
|
|
external: true
|
|
EOF
|
|
else
|
|
# No version field for newer Docker versions
|
|
cat > "$temp_compose" << EOF
|
|
services:
|
|
traefik:
|
|
image: $TRAEFIK_IMAGE
|
|
container_name: $TRAEFIK_CONTAINER_NAME
|
|
restart: unless-stopped
|
|
command:
|
|
- '--api.insecure=true'
|
|
- '--providers.docker=true'
|
|
- '--providers.docker.exposedbydefault=false'
|
|
- '--entrypoints.web.address=:80'
|
|
- '--entrypoints.websecure.address=:443'
|
|
- '--certificatesresolvers.letsencrypt.acme.httpchallenge=true'
|
|
- '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web'
|
|
- '--certificatesresolvers.letsencrypt.acme.email=$LETSENCRYPT_EMAIL'
|
|
- '--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json'
|
|
- '--providers.file.directory=/etc/traefik/config'
|
|
- '--providers.file.watch=true'
|
|
ports:
|
|
- '80:80'
|
|
- '443:443'
|
|
- '127.0.0.1:8080:8080' # Bind dashboard to localhost only for security
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
- $TRAEFIK_VOLUME_NAME:/etc/traefik
|
|
labels:
|
|
- 'traefik.enable=true'
|
|
- 'traefik.http.routers.traefik.rule=Host(\`traefik.${DOMAIN_NAME:-traefik.localhost}\`)'
|
|
- 'traefik.http.routers.traefik.service=api@internal'
|
|
- 'traefik.http.routers.traefik.middlewares=auth'
|
|
- 'traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$H6usmud2$$YqJ2a7vLp9J9l5zK8Y4hD1'
|
|
|
|
volumes:
|
|
$TRAEFIK_VOLUME_NAME:
|
|
external: true
|
|
|
|
networks:
|
|
default:
|
|
name: traefik-public
|
|
external: true
|
|
EOF
|
|
fi
|
|
|
|
# Upload the docker-compose file to target server
|
|
scp_upload "$temp_compose" "/tmp/traefik-compose.yml"
|
|
rm -f "$temp_compose"
|
|
|
|
# Create the network if it doesn't exist
|
|
ssh_exec "docker network create traefik-public 2>/dev/null || true"
|
|
|
|
# Copy the configuration file and create necessary directories in the volume with proper permissions
|
|
ssh_exec "docker run --rm -v $TRAEFIK_VOLUME_NAME:/etc/traefik -v /tmp:/tmp alpine:latest sh -c 'mkdir -p /etc/traefik/config /etc/traefik/acme && cp /tmp/traefik.yml /etc/traefik/config/traefik.yml && touch /etc/traefik/acme/acme.json && chmod 600 /etc/traefik/acme/acme.json'"
|
|
|
|
# Determine which docker compose command to use based on Docker version
|
|
# Execute all version checking logic on the remote server to ensure proper variable assignment
|
|
local compose_cmd
|
|
compose_cmd=$(ssh_exec "
|
|
docker_version=\$(docker --version | sed 's/Docker version //' | sed 's/,.*//')
|
|
major_version=\$(echo \$docker_version | cut -d. -f1)
|
|
minor_version=\$(echo \$docker_version | cut -d. -f2)
|
|
|
|
if [ \"\$major_version\" -gt 20 ] || ([ \"\$major_version\" -eq 20 ] && [ \"\$minor_version\" -ge 10 ]); then
|
|
echo \"docker compose\"
|
|
else
|
|
echo \"docker-compose\"
|
|
fi
|
|
")
|
|
|
|
# Start Traefik container using appropriate command
|
|
ssh_exec "$compose_cmd -f /tmp/traefik-compose.yml up -d"
|
|
|
|
log_info "Traefik container '$TRAEFIK_CONTAINER_NAME' is running on target server using image: $TRAEFIK_IMAGE with volume: $TRAEFIK_VOLUME_NAME"
|
|
|
|
# Wait a bit for the container to start
|
|
sleep 3
|
|
|
|
# Commands will be shown at the end of setup_traefik_only()
|
|
}
|
|
|
|
# Function to upload files to target server via SCP (Traefik Mode)
|
|
scp_upload() {
|
|
local source="$1"
|
|
local destination="$2"
|
|
log_debug "Uploading $source to $TARGET_USER@$TARGET_SERVER:$destination"
|
|
|
|
scp -i "$SSH_KEY_PATH" -P "${SSH_PORT:-22}" "$source" "$TARGET_USER@$TARGET_SERVER:$destination"
|
|
}
|
|
|
|
# Function to configure Traefik on target server to connect to Application Service (Traefik Mode)
|
|
configure_traefik_on_target() {
|
|
log_info "Configuring Traefik on target server to connect to Application Service..."
|
|
|
|
# Check if the target service is running
|
|
local service_running
|
|
service_running=$(ssh_exec "docker ps --filter name=$TARGET_SERVICE_NAME --format '{{.Names}}' 2>/dev/null || true")
|
|
|
|
if [ -z "$service_running" ]; then
|
|
log_error "Target application service '$TARGET_SERVICE_NAME' is not running."
|
|
log_error "Please make sure your Application container is started."
|
|
exit 1
|
|
fi
|
|
|
|
# Check if the target service is connected to the traefik-public network
|
|
local service_in_network
|
|
service_in_network=$(ssh_exec "docker inspect $TARGET_SERVICE_NAME 2>/dev/null | grep -o '\"traefik-public\"' || true")
|
|
|
|
if [ -z "$service_in_network" ]; then
|
|
log_error "Target application service '$TARGET_SERVICE_NAME' is not connected to 'traefik-public' network."
|
|
log_error "Please ensure the target application container is connected to the 'traefik-public' network."
|
|
log_error "Application should connecting to traefik-public network, here is the docker compose yml example:"
|
|
log_error " services:"
|
|
log_error " you-app:"
|
|
log_error " networks:"
|
|
log_error " - traefik-public"
|
|
log_error " networks:"
|
|
log_error " traefik-public:"
|
|
log_error " external: true"
|
|
log_error ""
|
|
log_error "You can connect it with: docker network connect traefik-public $TARGET_SERVICE_NAME"
|
|
log_error "Make sure your Application container is started."
|
|
exit 1
|
|
fi
|
|
|
|
# Check if DOMAIN_NAME contains a sub-path (like event.cgu.org.hk/api)
|
|
local domain_part
|
|
local path_part
|
|
if [[ "$DOMAIN_NAME" == *"/"* ]]; then
|
|
# Extract domain and path parts
|
|
domain_part=$(echo "$DOMAIN_NAME" | cut -d'/' -f1)
|
|
path_part="/$(echo "$DOMAIN_NAME" | cut -d'/' -f2-)"
|
|
log_info "Detected sub-path configuration: domain=$domain_part, path=$path_part"
|
|
else
|
|
domain_part="$DOMAIN_NAME"
|
|
path_part=""
|
|
fi
|
|
|
|
# Create dynamic configuration file for the service inside the volume
|
|
local temp_config="/tmp/app-service-$$.yml"
|
|
|
|
if [ -n "$path_part" ]; then
|
|
# Configure for sub-path routing
|
|
cat > "$temp_config" << EOF
|
|
http:
|
|
routers:
|
|
${TARGET_SERVICE_NAME}-https:
|
|
rule: 'Host(\`${domain_part}\`) && PathPrefix(\`${path_part}\`)'
|
|
service: ${TARGET_SERVICE_NAME}-service
|
|
tls:
|
|
certResolver: letsencrypt
|
|
entryPoints:
|
|
- websecure
|
|
middlewares:
|
|
- ${TARGET_SERVICE_NAME}-strip-prefix
|
|
${TARGET_SERVICE_NAME}-http:
|
|
rule: 'Host(\`${domain_part}\`) && PathPrefix(\`${path_part}\`)'
|
|
service: ${TARGET_SERVICE_NAME}-service
|
|
entryPoints:
|
|
- web
|
|
middlewares:
|
|
- redirect-to-https
|
|
- ${TARGET_SERVICE_NAME}-strip-prefix
|
|
middlewares:
|
|
${TARGET_SERVICE_NAME}-strip-prefix:
|
|
stripPrefix:
|
|
prefixes:
|
|
- ${path_part}
|
|
services:
|
|
${TARGET_SERVICE_NAME}-service:
|
|
loadBalancer:
|
|
servers:
|
|
- url: 'http://${TARGET_SERVICE_NAME}:${TARGET_SERVICE_PORT}'
|
|
passHostHeader: true
|
|
EOF
|
|
else
|
|
# Configure for root domain routing
|
|
cat > "$temp_config" << EOF
|
|
http:
|
|
routers:
|
|
${TARGET_SERVICE_NAME}-https:
|
|
rule: 'Host(\`${DOMAIN_NAME}\`)'
|
|
service: ${TARGET_SERVICE_NAME}-service
|
|
tls:
|
|
certResolver: letsencrypt
|
|
entryPoints:
|
|
- websecure
|
|
${TARGET_SERVICE_NAME}-http:
|
|
rule: 'Host(\`${DOMAIN_NAME}\`)'
|
|
service: ${TARGET_SERVICE_NAME}-service
|
|
entryPoints:
|
|
- web
|
|
middlewares:
|
|
- redirect-to-https
|
|
services:
|
|
${TARGET_SERVICE_NAME}-service:
|
|
loadBalancer:
|
|
servers:
|
|
- url: 'http://${TARGET_SERVICE_NAME}:${TARGET_SERVICE_PORT}'
|
|
passHostHeader: true
|
|
EOF
|
|
fi
|
|
|
|
# Upload the configuration to target server
|
|
scp_upload "$temp_config" "/tmp/app-service.yml"
|
|
rm -f "$temp_config"
|
|
|
|
# Generate a unique configuration file name based on the domain and service name to avoid conflicts
|
|
# Use the domain (without path) and service name to create a unique identifier
|
|
local domain_base=$(echo "$DOMAIN_NAME" | cut -d'/' -f1)
|
|
local config_file_name="app-service-${domain_base}-${TARGET_SERVICE_NAME//[^a-zA-Z0-9]/-}.yml"
|
|
|
|
# Use fixed volume name for Traefik configuration
|
|
local TRAEFIK_VOLUME_NAME="traefik_data"
|
|
|
|
# Check if a config file already exists for this domain and service combination
|
|
local config_exists
|
|
config_exists=$(ssh_exec "docker run --rm -v ${TRAEFIK_VOLUME_NAME}:/etc/traefik alpine:latest ls -la /etc/traefik/config/ 2>/dev/null | grep '$config_file_name' || true")
|
|
|
|
if [ -n "$config_exists" ]; then
|
|
log_warn "Configuration file $config_file_name already exists, it will be overwritten"
|
|
fi
|
|
|
|
# Copy the configuration file into the volume using a temporary container
|
|
ssh_exec "docker run --rm -v ${TRAEFIK_VOLUME_NAME}:/etc/traefik -v /tmp:/tmp alpine:latest sh -c \"cp /tmp/app-service.yml /etc/traefik/config/$config_file_name\""
|
|
|
|
log_info "Added configuration for service ${TARGET_SERVICE_NAME} on domain $domain_base to Traefik (file: $config_file_name)"
|
|
|
|
# Connect the traefik container to the traefik-public network to ensure communication
|
|
ssh_exec "docker network connect traefik-public traefik 2>/dev/null || true"
|
|
|
|
# Check if restart is needed based on environment variable (default: false for dynamic loading)
|
|
if [ "${RESTART_TRAEFIK:-false}" = "true" ]; then
|
|
# Restart Traefik to apply the new configuration
|
|
ssh_exec "docker restart traefik"
|
|
log_info "Traefik restarted to apply new configuration"
|
|
else
|
|
log_info "Traefik dynamic configuration added (restart not performed, Traefik should auto-load the configuration)"
|
|
log_info "If needed, you can manually restart Traefik with: docker restart traefik (on target server)"
|
|
fi
|
|
|
|
log_info "Traefik configured to route traffic to Application Service"
|
|
|
|
# Show success message based on configuration type
|
|
if [ -n "$path_part" ]; then
|
|
log_info "Service will be accessible at: https://$domain_part$path_part"
|
|
else
|
|
log_info "Service will be accessible at: https://$DOMAIN_NAME"
|
|
fi
|
|
}
|
|
|
|
# Function to check if Application Service is running on target server (Traefik Mode)
|
|
check_app_service_on_target() {
|
|
log_info "Checking if Application Service is running on target server..."
|
|
|
|
local service_exists
|
|
service_exists=$(ssh_exec "docker ps --filter name=$TARGET_SERVICE_NAME --format '{{.Names}}' 2>/dev/null || true")
|
|
|
|
if [ -n "$service_exists" ] && [ "$service_exists" = "$TARGET_SERVICE_NAME" ]; then
|
|
log_info "Application Service is running on target server"
|
|
return 0
|
|
else
|
|
log_error "Application Service is not running on target server: $TARGET_SERVICE_NAME"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to deploy files to target server (Traefik Mode)
|
|
deploy_files_to_target() {
|
|
if [ -d "$LOCAL_FOLDER" ]; then
|
|
log_info "Deploying files from $LOCAL_FOLDER to $TARGET_SERVER:$DEST_PATH"
|
|
|
|
# Create destination directory
|
|
ssh_exec "mkdir -p $DEST_PATH"
|
|
|
|
# Sync files to target
|
|
ssh_exec "rsync -avz --delete $LOCAL_FOLDER $TARGET_USER@$TARGET_SERVER:$DEST_PATH"
|
|
|
|
log_info "Files deployed successfully"
|
|
else
|
|
log_warn "Local folder $LOCAL_FOLDER does not exist. Skipping file deployment."
|
|
fi
|
|
}
|
|
|
|
# Function to check status of Traefik and Application Service (Traefik Mode)
|
|
check_status_on_target() {
|
|
log_info "Checking status of Traefik and Application Service on $TARGET_SERVER..."
|
|
|
|
local traefik_status
|
|
local app_status
|
|
|
|
traefik_status=$(ssh_exec "docker ps --filter name=traefik --format '{{.Status}}' 2>/dev/null || echo 'Not running'")
|
|
app_status=$(ssh_exec "docker ps --filter name=$TARGET_SERVICE_NAME --format '{{.Status}}' 2>/dev/null || echo 'Not running'")
|
|
|
|
echo -e "Traefik status: ${GREEN}$traefik_status${NC}"
|
|
echo -e "Application Service ($TARGET_SERVICE_NAME) status: ${GREEN}$app_status${NC}"
|
|
}
|
|
|
|
# Function to view Traefik logs on target server (Traefik Mode)
|
|
view_traefik_logs() {
|
|
log_info "Viewing Traefik logs..."
|
|
ssh_exec "docker logs --tail 50 traefik"
|
|
}
|
|
|
|
# Function to update docker-compose.app.yml on proxy server with dynamic ports (FRP Mode)
|
|
update_proxy_server_config() {
|
|
if [ "$FRP" = false ]; then
|
|
log_info "Skipping proxy server configuration update as FRP=false"
|
|
return 0
|
|
fi
|
|
|
|
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 "$@"
|
|
|
|
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" ${SERVER_USER}@$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")
|
|
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 ${SERVER_USER}@$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 ${SERVER_USER}@$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 ${SERVER_USER}@$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 setup proxy in Traefik mode
|
|
setup_traefik_only() {
|
|
log_info "Setting up Traefik-only proxy 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 pair $SSH_KEY_PATH for target server access" >&2
|
|
echo "" >&2
|
|
|
|
# Auto-proceed with deployment (smooth operation)
|
|
echo "Auto-proceeding with Traefik setup..." >&2
|
|
echo "" >&2
|
|
|
|
# Check if Application Service exists on target server
|
|
if ! check_app_service_on_target; then
|
|
log_error "Application Service not found on target server. Please ensure the service '$TARGET_SERVICE_NAME' is running on the target server."
|
|
exit 1
|
|
fi
|
|
|
|
# Create Traefik container on target server if it doesn't exist
|
|
create_traefik_container_on_target
|
|
|
|
# Configure Traefik on target server for the Application Service
|
|
configure_traefik_on_target
|
|
|
|
log_info "Traefik-only proxy setup completed successfully!"
|
|
log_info "Your service should now be accessible via: https://$DOMAIN_NAME"
|
|
|
|
# Show helpful commands to the user using unified function
|
|
show_traefik_commands
|
|
}
|
|
|
|
# Setup rvs-prxy function (main function that decides FRP vs Traefik)
|
|
setup_rvs_prxy() {
|
|
if [ "$FRP" = false ]; then
|
|
# Run Traefik-only setup
|
|
log_info "Running Traefik-only setup as FRP=false"
|
|
setup_traefik_only
|
|
return 0
|
|
fi
|
|
|
|
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
|
|
|
|
# Auto-proceed with deployment (smooth operation)
|
|
echo "Auto-proceeding with deployment..." >&2
|
|
echo "" >&2
|
|
|
|
# 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 Setup reverse proxy (either FRP or Traefik based on environment)
|
|
deploy Deploy files to target server (Traefik mode only)
|
|
status Check status of Traefik and Application Service (Traefik mode only)
|
|
logs View Traefik logs (Traefik mode only)
|
|
|
|
Environment Variables:
|
|
FRP=true|false Set to 'false' for Traefik mode, 'true' for FRP mode (default: true)
|
|
SKIP_FRP=true Override FRP setting to skip FRP setup (default: false)
|
|
|
|
Examples:
|
|
$0 setup-rvs-prxy # Run based on .env configuration
|
|
FRP=false $0 setup-rvs-prxy # Force Traefik mode
|
|
FRP=true $0 setup-rvs-prxy # Force FRP mode
|
|
$0 deploy # Deploy files to target server
|
|
$0 status # Check status
|
|
$0 logs # View Traefik logs
|
|
|
|
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}"
|
|
;;
|
|
"deploy")
|
|
# Deploy command (Traefik mode only)
|
|
if [ "$FRP" = false ]; then
|
|
deploy_files_to_target
|
|
log_info "File deployment completed"
|
|
else
|
|
log_error "Deploy command is only available in Traefik mode (FRP=false)"
|
|
exit 1
|
|
fi
|
|
;;
|
|
"status")
|
|
# Status command (Traefik mode only)
|
|
if [ "$FRP" = false ]; then
|
|
check_status_on_target
|
|
else
|
|
log_error "Status command is only available in Traefik mode (FRP=false)"
|
|
exit 1
|
|
fi
|
|
;;
|
|
"logs")
|
|
# Logs command (Traefik mode only)
|
|
if [ "$FRP" = false ]; then
|
|
view_traefik_logs
|
|
else
|
|
log_error "Logs command is only available in Traefik mode (FRP=false)"
|
|
exit 1
|
|
fi
|
|
;;
|
|
"setup-rvs-prxy")
|
|
# 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}"
|
|
;;
|
|
*)
|
|
log_error "Unknown deployment system: $command"
|
|
echo ""
|
|
list_systems
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# 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"
|
|
}
|
|
|
|
# Function to display post-execution command reference for Traefik
|
|
show_traefik_commands() {
|
|
local domain_name="${DOMAIN_NAME:-your-domain.com}"
|
|
|
|
echo ""
|
|
echo "============================================="
|
|
echo "Container Operation Commands (Traefik Instance)"
|
|
echo ""
|
|
echo "Configuration Note:"
|
|
echo " By default, Traefik does not restart after configuration (RESTART_TRAEFIK=false)"
|
|
echo " Traefik will dynamically load new configurations automatically."
|
|
echo " Set RESTART_TRAEFIK=true in .env to force restart after configuration."
|
|
echo ""
|
|
echo "Run Container locally:"
|
|
echo " # Use the stored docker-compose.yml from volume"
|
|
echo " # First try to copy with 1000:1000 ownership, fallback to default if chown fails"
|
|
echo " docker run -d --name traefik --restart=unless-stopped \\"
|
|
echo " -p 80:80 -p 443:443 -p 127.0.0.1:8080:8080 \\"
|
|
echo " -v /var/run/docker.sock:/var/run/docker.sock:ro \\"
|
|
echo " -v traefik_data:/etc/traefik traefik:latest"
|
|
echo ""
|
|
echo "Restart Container: docker restart traefik"
|
|
echo "------------------------------------------------------------"
|
|
echo "Container Accessing by ubuntu: docker exec -it traefik /bin/sh"
|
|
echo "Container Accessing by root: docker exec -it -u root traefik /bin/sh"
|
|
echo "------------------------------------------------------------"
|
|
echo "View the Logs: docker logs --tail 50 traefik"
|
|
echo "------------------------------------------------------------"
|
|
echo "Inspect: docker inspect traefik"
|
|
echo "------------------------------------------------------------"
|
|
echo "Remove Container: docker rm -f traefik"
|
|
echo "------------------------------------------------------------"
|
|
echo "View Traefik Dashboard: http://localhost:8080 (only accessible locally for security)"
|
|
echo "------------------------------------------------------------"
|
|
echo "Your application is accessible via: https://$domain_name"
|
|
echo "------------------------------------------------------------"
|
|
echo "Totally Remove (Pay Attention):"
|
|
echo " docker rm -f traefik"
|
|
echo " docker volume rm traefik_data"
|
|
echo "============================================="
|
|
}
|
|
|
|
# Execute main function with arguments
|
|
main "$@" |