#!/bin/bash # Nginx Proxy Manager CLI Script # Github [ https://github.com/Erreur32/nginx-proxy-manager-Bash-API ] # By Erreur32 - July 2024 # NPM api https://github.com/NginxProxyManager/nginx-proxy-manager/tree/develop/backend/schema # https://github.com/NginxProxyManager/nginx-proxy-manager/tree/develop/backend/schema/components VERSION="3.6.9" ################################# # This script allows you to manage Nginx Proxy Manager via the API. It provides # functionalities such as creating proxy hosts, managing users, listing hosts, # backing up configurations, and more. # ################################# # PERSISTENT Config ################################# # Create config file $SCRIPT_DIR/npm-api.conf and Edit Variables (required) # NGINX_IP="127.0.0.1" # NGINX_PORT="81" # API_USER="admin@example.com" # API_PASS="changeme" # # Optional (only if you want in other placer than script directory) # DATA_DIR="/path/nginx_backup/dir" # ################################ # Optional (for a future version, not use) # NGINX_PATH_DOCKER="/home/docker/nginx_proxy/nginx" ################################# # Common Examples # # 1. Create a new proxy host: # ./npm-api.sh --host-create example.com -i 192.168.1.10 -p 8080 # # 2. Enable SSL for a host: # ./npm-api.sh --host-ssl-enable 1 # # 3. Create a new user: # ./npm-api.sh --user-create admin admin@example.com password123 # # 4. List all proxy hosts: # ./npm-api.sh --host-list # # 5. Generate SSL certificate: # ./npm-api.sh --cert-generate example.com admin@example.com # ./npm-api.sh --cert-generate "*.example.com" \ # --dns-provider cloudflare --dns-credentials '{"dns_cloudflare_api_token":"your_token"}' # # 6. Show host details: # ./npm-api.sh --host-show 1 # # debug version set -eu -o pipefail #set -x # Active dbog #set -eu -o pipefail # Check if config file npm-api.conf exist SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONFIG_FILE="$SCRIPT_DIR/npm-api.conf" ################################ # Variables to Edit (required) # # or create a config file # ################################ # set default variables DEFAULT_NGINX_IP="127.0.0.1" DEFAULT_NGINX_PORT="81" DEFAULT_API_USER="admin@example.com" DEFAULT_API_PASS="changeme" DEFAULT_NGINX_PATH_DOCKER="/path/docker/npm" # default backup directoy (You can override with your path) DEFAULT_DATA_DIR="$SCRIPT_DIR/data" ################################ # DON'T TOUCH BELOW !!! ;) ################################ # Colors Custom COLOR_GREEN="\033[32m" COLOR_GREEN_b="\e[92m" COLOR_RED="\033[91m" COLOR_RED_FULL="\033[41;1m" COLOR_ORANGE="\033[38;5;202m" COLOR_YELLOW="\033[93m" COLOR_CYAN="\033[36m" COLOR_GREY="\e[90m" COLOR_GRAY=" \e[100m" WHITE_ON_GREEN="\033[30;48;5;83m" CoR="\033[0m" # load config file if exists if [ -f "$CONFIG_FILE" ]; then # First set default values NGINX_IP="$DEFAULT_NGINX_IP" NGINX_PORT="$DEFAULT_NGINX_PORT" API_USER="$DEFAULT_API_USER" API_PASS="$DEFAULT_API_PASS" DATA_DIR="$DEFAULT_DATA_DIR" NGINX_PATH_DOCKER="$DEFAULT_NGINX_PATH_DOCKER" # Then load config file which will override defaults source "$CONFIG_FILE" # Finally set variables as read only declare -r NGINX_IP declare -r NGINX_PORT declare -r API_USER declare -r API_PASS #declare -r DATA_DIR # NGINX_PATH_DOCKER is optional, only make it readonly if it was set in config if [ -n "${NGINX_PATH_DOCKER+x}" ]; then declare -r NGINX_PATH_DOCKER fi else # Use default values NGINX_IP="$DEFAULT_NGINX_IP" NGINX_PORT="$DEFAULT_NGINX_PORT" API_USER="$DEFAULT_API_USER" API_PASS="$DEFAULT_API_PASS" DATA_DIR="$DEFAULT_DATA_DIR" NGINX_PATH_DOCKER="$DEFAULT_NGINX_PATH_DOCKER" # Check if using default API user if [ "$API_USER" = "$DEFAULT_API_USER" ]; then echo -e "\nāš ļø ${COLOR_RED}Using default API credentials - Please configure the script!${CoR}" echo -e "\nšŸ“ Create configuration file: $CONFIG_FILE with content:" echo -e "${COLOR_GREY}NGINX_IP=\"$NGINX_IP\"${CoR} ${COLOR_YELLOW}(current default)${CoR}" echo -e "${COLOR_GREY}NGINX_PORT=\"$NGINX_PORT\"${CoR} ${COLOR_YELLOW}(current default)${CoR}" echo -e "${COLOR_RED}API_USER=\"admin@example.com\"${CoR} ${COLOR_RED}(required)${CoR}" echo -e "${COLOR_RED}API_PASS=\"your_password\"${CoR} ${COLOR_RED}(required)${CoR}" echo -e "${COLOR_GREY}DATA_DIR=\"$DATA_DIR\"${CoR} ${COLOR_YELLOW}(current default)${CoR}" echo -e "\nāŒ ${COLOR_RED}Cannot continue with default API credentials${CoR}\n" exit 1 fi fi # API Endpoints BASE_URL="http://$NGINX_IP:$NGINX_PORT/api" # Set Token duration validity. #TOKEN_EXPIRY="365d" #TOKEN_EXPIRY="31536000s" TOKEN_EXPIRY="1y" # Default variables for creating a new proxy host (adapt to your needs) # Note: These variables must match the names used in argument parsing and JSON payload generation CACHING_ENABLED=false BLOCK_EXPLOITS=true ALLOW_WEBSOCKET_UPGRADE=false # Fixed: was '1' instead of 'false' (Issue #23) HTTP2_SUPPORT=false # consistent true/false convention (Issue #23 class) ADVANCED_CONFIG="" LETS_ENCRYPT_AGREE=false LETS_ENCRYPT_EMAIL="" FORWARD_SCHEME="http" FORCE_CERT_CREATION=false SSL_FORCED=false HSTS_ENABLED=false HSTS_SUBDOMAINS=false #DNS_PROVIDER="" #DNS_API_KEY="" # Don't touch below that line (or you know ...) DEFAULT_EMAIL="$API_USER" DOMAIN="" DOMAIN_NAMES="" FORWARD_HOST="" FORWARD_PORT="" CUSTOM_LOCATIONS="" USERNAME="" PASSWORD="" EMAIL="" HOST_ID="" FIELD="" VALUE="" FIELD_VALUE="" # Control variables AUTO_YES=false JSON_OUTPUT=false LIST_JSON_COMMANDS=false CHECK_TOKEN=false CHECK_UPDATE=false EXAMPLES=false INFO=false SHOW_HELP=false SHOW_DEFAULT=false USER_CREATE=false USER_DELETE=false USER_IS_ADMIN=false USER_LIST=false HOST_SHOW=false HOST_LIST=false HOST_LIST_FULL=false HOST_SEARCH=false HOST_UPDATE=false HOST_ENABLE=false HOST_SEARCHNAME="" HOST_DISABLE=false HOST_DELETE=false HOST_ACL_ENABLE=false HOST_ACL_DISABLE=false HOST_CREATE=false LIST_CERT_ALL=false CERT_SHOW=false CERT_GENERATE=false CERT_DELETE=false CERT_PURGE=false CERT_DOWNLOAD=false CERT_DOMAIN="" CERT_EMAIL="" # IDs / search term resolved by command parsers; initialized for set -u safety CERT_ID="" GENERATED_CERT_ID="" USER_ID="" search_term="" # Cached API token (populated by check_token_notverbose) to avoid re-reading # the token file on every curl. Curl headers use ${TOKEN:-$(cat "$TOKEN_FILE")} # so an empty cache safely falls back to reading the file. TOKEN="" DNS_PROVIDER="" CERT_DNS_PROVIDER="" CERT_DNS_CREDENTIALS="" DOMAIN_EXISTS="" HOST_SSL_ENABLE=false HOST_SSL_DISABLE=false SSL_RESTORE=false ACCESS_LIST=false ACCESS_LIST_CREATE=false ACCESS_LIST_UPDATE=false ACCESS_LIST_DELETE=false ACCESS_LIST_SHOW=false ACCESS_LIST_ID="" ACCESS_LIST_UPDATE_ARGS=() ACCESS_LIST_CREATE_ARGS=() BACKUP=false BACKUP_LIST=false ### in progress BACKUP_HOST=false CLEAN_HOSTS=false REDIRECT_HOST_LIST=false REDIRECT_HOST_CREATE=false REDIRECT_HOST_DELETE=false REDIRECT_HOST_ENABLE=false REDIRECT_HOST_DISABLE=false REDIRECT_HOST_ID="" FORWARD_DOMAIN="" FORWARD_HTTP_CODE="301" PRESERVE_PATH=false STREAM_HOST_LIST=false STREAM_HOST_CREATE=false STREAM_HOST_DELETE=false STREAM_HOST_ENABLE=false STREAM_HOST_DISABLE=false STREAM_HOST_ID="" INCOMING_PORT="" TCP_FORWARDING=false UDP_FORWARDING=false DEAD_HOST_LIST=false DEAD_HOST_CREATE=false DEAD_HOST_DELETE=false DEAD_HOST_ENABLE=false DEAD_HOST_DISABLE=false DEAD_HOST_ID="" if [ $# -eq 0 ]; then INFO=true #SHOW_HELP=true fi ############################################### # Check if necessary dependencies are installed ################################ # Function to check and create required directories # Usage: check_dependencies # Returns: 0 if successful, exits with 1 if error ################################ check_dependencies() { #echo -e "\n šŸ” ${COLOR_CYAN}Checking system dependencies and directories...${CoR}" ################################ # 1. Check System Dependencies # ################################ local dependencies=("curl" "jq") local missing=() for dep in "${dependencies[@]}"; do if ! command -v "$dep" &>/dev/null; then missing+=("$dep") fi done if [ ${#missing[@]} -gt 0 ]; then echo -e " ā›” ${COLOR_RED}Missing dependencies. Please install:${CoR}" printf " - %s\n" "${missing[@]}" exit 1 fi #return 0 } #check_dependencies # Check if the Nginx Proxy Manager API is accessible check_nginx_access() { local max_retries=3 local retry_count=0 local timeout=5 #echo -e "\nšŸ” Checking NPM availability..." #echo -e " • Attempting to connect to: ${COLOR_YELLOW}$BASE_URL${CoR}" #echo -e "\n āœ… Loading variables from file $CONFIG_FILE" while [ $retry_count -lt $max_retries ]; do # Try to connect with timeout if curl --output /dev/null --silent --head --fail --connect-timeout $timeout --max-redirs 0 "$BASE_URL"; then #echo -e " āœ… NPM is accessible at: ${COLOR_GREEN}$BASE_URL${CoR}" local version_info=$(curl -s --max-redirs 0 "${BASE_URL%/api}/version") return 0 fi retry_count=$((retry_count + 1)) # Show retry message if not last attempt if [ $retry_count -lt $max_retries ]; then echo -e " ā³ Attempt $retry_count/$max_retries - Retrying in ${timeout}s..." sleep $timeout fi done # If we get here, all attempts failed echo -e "\nāŒ ${COLOR_RED}ERROR: Cannot connect to NPM${CoR}" echo -e "šŸ” Details:" echo -e " • URL: ${COLOR_YELLOW}$BASE_URL${CoR}" echo -e " • Host: ${COLOR_YELLOW}$NGINX_IP${CoR}" echo -e " • Port: ${COLOR_YELLOW}${NGINX_PORT}${CoR}" echo -e "\nšŸ“‹ Troubleshooting:" echo -e " 1. Check if NPM is running" echo -e " 2. Verify network connectivity" echo -e " 3. Confirm NPM IP and port settings" echo -e " 4. Check NPM logs for errors" echo -e "\nšŸ’” Command to check NPM status:" echo -e " $ docker ps | grep nginx-proxy-manager" echo -e " $ docker logs nginx-proxy-manager\n" exit 1 } ################################ # Generate and/or validate token # $1: boolean - true to display messages, false for silent mode check_token() { local verbose=${1:-false} # PATH ############################### [ "$verbose" = true ] && echo -e "\n šŸ” ${COLOR_CYAN}Checking system dependencies and directories...${CoR}" check_dependencies local ip_port_dir="${NGINX_IP//[.:]/_}_${NGINX_PORT}" DATA_DIR_ID="$DATA_DIR/$ip_port_dir" BACKUP_DIR="$DATA_DIR_ID/backups" TOKEN_DIR="$DATA_DIR_ID/token" TOKEN_FILE="$TOKEN_DIR/token.txt" EXPIRY_FILE="$TOKEN_DIR/expiry.txt" ################################ # Create directories if they don't exist ################################ for dir in "$DATA_DIR_ID" "$TOKEN_DIR" "$BACKUP_DIR"; do if [ ! -d "$dir" ]; then [ "$verbose" = true ] && echo -e " šŸ“¢ ${COLOR_YELLOW}Creating directory: $dir${CoR}" if ! mkdir -p "$dir" 2>/dev/null; then echo -e "\n ${COLOR_RED}Error: Failed to create directory $dir${CoR}" exit 1 fi # Set proper permissions chmod 755 "$dir" 2>/dev/null fi done [ "$verbose" = true ] && echo -e " āœ… ${COLOR_GREEN}All dependencies and directories are properly set up${CoR}" [ "$verbose" = true ] && echo -e " ${COLOR_GREY}ā”œā”€ā”€ System tools: OK${CoR}" [ "$verbose" = true ] && echo -e " ${COLOR_GREY}ā”œā”€ā”€ Directories : OK${CoR}" [ "$verbose" = true ] && echo -e " ${COLOR_GREY}└── Permissions : OK${CoR}" [ "$verbose" = true ] && echo -e "\n šŸ”‘ Checking token validity..." # Check if token files exist and are readable if [ ! -f "$TOKEN_FILE" ] || [ ! -f "$EXPIRY_FILE" ] || [ ! -r "$TOKEN_FILE" ] || [ ! -r "$EXPIRY_FILE" ] || [ ! -s "$TOKEN_FILE" ] || [ ! -s "$EXPIRY_FILE" ]; then [ "$verbose" = true ] && echo -e " ā›” ${COLOR_RED}Token files missing or unreadable. Generating new token...${CoR}" generate_new_token # Ensure files were created and are readable if [ ! -f "$TOKEN_FILE" ] || [ ! -f "$EXPIRY_FILE" ] || [ ! -r "$TOKEN_FILE" ] || [ ! -r "$EXPIRY_FILE" ]; then echo -e "\n ${COLOR_RED}Error: Failed to create or read token files${CoR}" exit 1 fi return fi # Read token and expiry token=$(cat "$TOKEN_FILE" 2>/dev/null) expires=$(cat "$EXPIRY_FILE" 2>/dev/null) current_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ") # Validate expiry date format if ! date -d "$expires" >/dev/null 2>&1; then [ "$verbose" = true ] && echo -e " ā›” ${COLOR_RED}Invalid expiry date. Generating new token...${CoR}" generate_new_token return fi # Check if token is expired or will expire soon (1 hour) local expiry_timestamp=$(date -d "$expires" +%s) local current_timestamp=$(date -d "$current_time" +%s) local time_diff=$((expiry_timestamp - current_timestamp)) if [ $time_diff -lt 3600 ]; then [ "$verbose" = true ] && echo -e " āš ļø ${COLOR_YELLOW}Token expires soon. Generating new token...${CoR}" generate_new_token return fi # Test token with API call local test_response test_response=$(curl -s -w "HTTPSTATUS:%{http_code}" \ -H "Authorization: Bearer $token" \ --connect-timeout 5 \ "$BASE_URL/tokens") local http_status=$(echo "$test_response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') if [ "${http_status:-0}" -eq 200 ]; then [ "$verbose" = true ] && echo -e " āœ… ${COLOR_GREEN}Token is valid${CoR}" [ "$verbose" = true ] && echo -e " šŸ“… Expires: ${COLOR_YELLOW}$expires${CoR}\n" else [ "$verbose" = true ] && echo -e " ā›” ${COLOR_RED}Token is invalid. Generating new token...${CoR}\n" generate_new_token fi return 0 } # validate_token not verbose check_token_notverbose() { check_token false # Cache the validated token once so subsequent curls reuse it (see $TOKEN) TOKEN=$(cat "$TOKEN_FILE" 2>/dev/null) } ################################ # Generate a new API token generate_new_token() { echo -e "\n šŸ”„ ${COLOR_YELLOW}Generating a new API token...${CoR}" # check if API credentials are missing if [ -z "$API_USER" ] || [ -z "$API_PASS" ]; then echo -e " āŒ ${COLOR_RED}Error: API credentials are missing.${CoR}" exit 1 fi # check if NPM is accessible if ! curl --output /dev/null --silent --head --fail --connect-timeout 5 "$BASE_URL"; then echo -e "\nāŒ ${COLOR_RED}ERROR: Cannot connect to NPM to generate token${CoR}" echo -e "šŸ” Please check if NPM is running and accessible at ${COLOR_YELLOW}$BASE_URL${CoR}\n" exit 1 fi # First get a temporary token local temp_response=$(curl -s -w "\nHTTPSTATUS:%{http_code}" -X POST "$BASE_URL/tokens" \ -H "Content-Type: application/json" \ --data-raw "{\"identity\":\"$API_USER\",\"secret\":\"$API_PASS\"}") local temp_body=$(echo "$temp_response" | sed -e 's/HTTPSTATUS\:.*//g') local temp_status=$(echo "$temp_response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') if [ "${temp_status:-0}" -ne 200 ]; then echo -e " āŒ ${COLOR_RED}Failed to generate temporary token. Status: $temp_status${CoR}" echo -e " šŸ“ Response: $temp_body" exit 1 fi # Extract the temporary token local temp_token=$(echo "$temp_body" | jq -r '.token') # Now get a long-term token using the temporary one local response=$(curl -s -w "\nHTTPSTATUS:%{http_code}" -X GET "$BASE_URL/tokens?expiry=$TOKEN_EXPIRY" \ -H "Authorization: Bearer $temp_token" \ -H "Accept: application/json") local body=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g') local status=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') if [ "${status:-0}" -ne 200 ]; then echo -e " āŒ ${COLOR_RED}Failed to generate long-term token. Status: $status${CoR}" echo -e " šŸ“ Response: $body" exit 1 fi # Extract token and expiry local token=$(echo "$body" | jq -r '.token') local expiry=$(echo "$body" | jq -r '.expires') # Ensure token directory exists with proper permissions if [ ! -d "$TOKEN_DIR" ]; then mkdir -p "$TOKEN_DIR" 2>/dev/null chmod 755 "$TOKEN_DIR" 2>/dev/null fi # Save token and expiry with proper permissions echo "$token" >"$TOKEN_FILE" echo "$expiry" >"$EXPIRY_FILE" chmod 644 "$TOKEN_FILE" "$EXPIRY_FILE" 2>/dev/null # Verify files were created successfully if [ ! -f "$TOKEN_FILE" ] || [ ! -f "$EXPIRY_FILE" ] || [ ! -r "$TOKEN_FILE" ] || [ ! -r "$EXPIRY_FILE" ]; then echo -e " āŒ ${COLOR_RED}Failed to save token files${CoR}" exit 1 fi echo -e " āœ… ${COLOR_GREEN}New token successfully generated and stored.${CoR}" echo -e " šŸ“… Token expiry date: ${COLOR_YELLOW}$expiry${CoR}" } # Function to pad strings to a certain length pad() { local str="$1" local len="$2" local str_len=${#str} local pad_len=$((len - str_len)) local padding=$(printf '%*s' "$pad_len" "") echo "$str$padding" } ################################ # Like pad() but truncates with an ellipsis when the string is longer than # the requested width, so columns stay aligned even with long names. # Always returns a string of exactly $len visible characters. truncate_pad() { local str="$1" local len="$2" if [ "${#str}" -gt "$len" ]; then str="${str:0:$((len - 1))}…" fi local pad_len=$((len - ${#str})) [ "$pad_len" -lt 0 ] && pad_len=0 printf '%s%*s' "$str" "$pad_len" "" } ################################ # Colorize boolean values for display colorize_boolean() { local value="$1" if [ "$value" = "true" ]; then echo "${COLOR_GREEN}true${CoR}" elif [ "$value" = "false" ]; then echo "${COLOR_RED}false${CoR}" else # If value is neither true nor false, convert to boolean if [ "$value" = "1" ] || [ "$value" = "yes" ] || [ "$value" = "on" ]; then echo "${COLOR_GREEN}true${CoR}" else echo "${COLOR_RED}false${CoR}" fi fi } ################################ # Colorize other boolean values for display colorize_booleanh() { local value=$1 if [ "$value" = https ]; then echo -e "${COLOR_GREEN}https${CoR}" else echo -e "${COLOR_YELLOW}http${CoR}" fi } # Function to sanitize names for directory sanitize_name() { local name=$1 echo "${name//[^a-zA-Z0-9]/_}" } ################################ # Function to validate JSON files validate_json() { local file=$1 if ! jq empty "$file" 2>/dev/null; then echo -e "\n ā›” Invalid JSON detected in file: $file" cat "$file" # Show file content for debug return 1 fi return 0 } ################################ # Display help # Print one help line with its description aligned to a fixed column. # Visible width ignores literal ANSI color sequences (\033[..m / \e[..m) and # counts wide emoji (1 code point but 2 terminal cells) as 2. HELP_DESC_COL=42 help_row() { local left="$1" desc="$2" local plain width pad g tmp plain=$(printf '%s' "$left" | sed -E 's/\\(033|e)\[[0-9;]*m//g') width=${#plain} for g in šŸ†” šŸ’¾ šŸ”– šŸŒ šŸ”— šŸ”’ šŸ”„ ✨ šŸ“„ šŸ“¦ šŸ“œ; do tmp=${plain//"$g"/} width=$((width + ${#plain} - ${#tmp})) done pad=$((HELP_DESC_COL - width)) if [ "$pad" -lt 1 ]; then # Left part is wider than the description column: wrap the description onto # the next line, aligned under the same column so everything stays tidy. echo -e "${left}" echo -e "$(printf '%*s' "$HELP_DESC_COL" '')${desc}" else echo -e "${left}$(printf '%*s' "$pad" '')${desc}" fi } show_help() { echo -e "\n Options available: ${COLOR_GREY}(see --examples for more details)${CoR}" help_row " -y" "Automatic ${COLOR_YELLOW}yes${CoR} prompts!" help_row " --json" "${COLOR_GREEN}šŸ¤– ${CoR}Modifier: force ${COLOR_GREY}pure JSON output${CoR} on read commands (list/show), for scripts" help_row " --json --list" "${COLOR_GREEN}šŸ¤– ${CoR}List ${COLOR_GREY}the commands compatible with --json${CoR}" help_row " --info" "Display ${COLOR_GREY}Script Variables Information${CoR}" help_row " --show-default" "Show ${COLOR_GREY}Default settings for host creation${CoR}" help_row " --check-token" "Check ${COLOR_GREY}current token info${CoR}" help_row " --check-update" "${COLOR_GREEN}šŸ†• ${CoR}Check ${COLOR_GREY}GitHub for a newer script version${CoR}" help_row " --backup" "${COLOR_GREEN}šŸ’¾ ${CoR}Backup ${COLOR_GREY}All configurations to a different files in \$DATA_DIR${CoR}" #echo -e " --clean-hosts ${COLOR_GREEN}šŸ“„ ${CoR}Reimport${CoR} ${COLOR_GREY}Clean Proxy ID and SSL ID in sqlite database ;)${CoR}" help_row " --backup-host ${COLOR_CYAN}[id]${CoR}" "${COLOR_GREEN}šŸ“¦ ${CoR}Backup ${COLOR_GREY}One proxy host (with SSL cert), or all if no id${CoR}" help_row " --backup-host-list" "${COLOR_GREEN}šŸ“‹ ${CoR}List ${COLOR_GREY}Available per-host proxy backups${CoR}" #echo -e " --host-list-full > backup.txt šŸ’¾ ${COLOR_YELLOW}Export${CoR} Full host configuration to file" echo "" echo -e " Proxy Host Management:" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" help_row " --host-search ${COLOR_CYAN}domain${CoR}" "Search ${COLOR_GREY}Proxy host by ${COLOR_YELLOW}domain name${CoR}" help_row " --host-list" "List ${COLOR_GREY}All Proxy hosts (to find ID)${CoR}" help_row " --host-list-full" "List ${COLOR_GREY}All Proxy hosts with full details (${COLOR_YELLOW}JSON${COLOR_GREY})${CoR}" help_row " --host-show ${COLOR_CYAN}šŸ†”${CoR}" "Show ${COLOR_GREY}Full details for a specific host by ${COLOR_YELLOW}ID${CoR}" echo "" echo -e " --host-create ${COLOR_ORANGE}domain${CoR} ${COLOR_CYAN}-i ${COLOR_ORANGE}forward_host${CoR} ${COLOR_CYAN}-p ${COLOR_ORANGE}forward_port${CoR} [options]\n" echo -e " ${COLOR_RED}Required:${CoR}" help_row " domain" "Domain name (${COLOR_RED}required${CoR})" help_row " ${COLOR_CYAN}-i${CoR} forward-host" "IP address or domain name of the target server (${COLOR_RED}required${CoR})" help_row " ${COLOR_CYAN}-p${CoR} forward-port" "Port of the target server (${COLOR_RED}required${CoR})" echo "" echo -e " optional: ${COLOR_GREY}(Check default settings,no argument needed if already set!)${CoR}" help_row " ${COLOR_CYAN}-f ${COLOR_GREY}FORWARD_SCHEME${CoR}" "Scheme for forwarding (http/https, default: $(colorize_booleanh "$FORWARD_SCHEME"))" help_row " ${COLOR_CYAN}-c ${COLOR_GREY}CACHING_ENABLED${CoR}" "Enable caching (true/false, default: $(colorize_boolean "$CACHING_ENABLED"))" help_row " ${COLOR_CYAN}-b ${COLOR_GREY}BLOCK_EXPLOITS${CoR}" "Block exploits (true/false, default: $(colorize_boolean "$BLOCK_EXPLOITS"))" help_row " ${COLOR_CYAN}-w ${COLOR_GREY}ALLOW_WEBSOCKET_UPGRADE${CoR}" "Allow WebSocket upgrade (true/false, default: $(colorize_boolean "$ALLOW_WEBSOCKET_UPGRADE"))" help_row " ${COLOR_CYAN}-l ${COLOR_GREY}CUSTOM_LOCATIONS${CoR}" "Custom locations (${COLOR_YELLOW}JSON array${CoR} of location objects)" help_row " ${COLOR_CYAN}-a ${COLOR_GREY}ADVANCED_CONFIG${CoR}" "Advanced configuration (${COLOR_YELLOW}string${CoR})" #echo -e " ${COLOR_CYAN}-h ${COLOR_GREY}HTTP2_SUPPORT${CoR} HTTP2 (true/false, default: $(colorize_boolean "$HTTP2_SUPPORT"))" echo "" help_row " --host-enable ${COLOR_CYAN}šŸ†”${CoR}" "Enable Proxy host by ${COLOR_YELLOW}ID${CoR}" help_row " --host-disable ${COLOR_CYAN}šŸ†”${CoR}" "Disable Proxy host by ${COLOR_YELLOW}ID${CoR}" help_row " --host-delete ${COLOR_CYAN}šŸ†”${CoR}" "Delete Proxy host by ${COLOR_YELLOW}ID${CoR}" help_row " --host-update ${COLOR_CYAN}šŸ†”${CoR} ${COLOR_CYAN}[field]=value${CoR}" "Update One specific field of an existing proxy host by ${COLOR_YELLOW}ID${CoR}" echo -e " (eg., --host-update 42 forward_host=foobar.local)${CoR}" echo "" help_row " --host-acl-enable ${COLOR_CYAN}šŸ†”${CoR} ${COLOR_CYAN}access_list_id${CoR}" "Enable ACL for Proxy host by ${COLOR_YELLOW}ID${CoR} with Access List ID" help_row " --host-acl-disable ${COLOR_CYAN}šŸ†”${CoR}" "Disable ACL for Proxy host by ${COLOR_YELLOW}ID${CoR}" help_row " --host-ssl-enable ${COLOR_CYAN}šŸ†”${CoR} ${COLOR_CYAN}[cert_id]${CoR}" "Enable SSL for host ID optionally using specific certificate ID" help_row " --host-ssl-disable ${COLOR_CYAN}šŸ†”${CoR}" "Disable SSL, HTTP/2, and HSTS for a proxy host" echo "" help_row " --cert-list" "List ALL SSL certificates" help_row " --cert-show ${COLOR_CYAN}domain${CoR} Or ${COLOR_CYAN}šŸ†”${CoR}" "List SSL certificates filtered by [domain name] (add ${COLOR_YELLOW}--json${CoR} for raw JSON)" help_row " --cert-delete ${COLOR_CYAN}domain${CoR} Or ${COLOR_CYAN}šŸ†”${CoR} ${COLOR_CYAN}[--purge]${CoR}" "Delete Certificate for the given '${COLOR_YELLOW}domain${CoR}' (${COLOR_GREY}--purge${CoR} also removes on-disk files)" help_row " --cert-download ${COLOR_CYAN}šŸ†”${CoR} ${COLOR_CYAN}[output_dir]${CoR} ${COLOR_CYAN}[cert_name]${CoR}" "Download certificate as ZIP with fallback support" help_row " --cert-generate ${COLOR_CYAN}domain${CoR} ${COLOR_CYAN}[email]${CoR}" "Generate Let's Encrypt Certificate or others Providers." echo -e " • ${COLOR_YELLOW}Email:${CoR} positional [email] or ${COLOR_CYAN}--cert-email${CoR} email (default: \$DEFAULT_EMAIL), NPM < 2.13 only" echo -e " - NPM >= 2.13 uses the email of the NPM user account" echo -e " • ${COLOR_YELLOW}Standard domains:${CoR} example.com, sub.example.com" echo -e " • ${COLOR_YELLOW}Wildcard domains:${CoR} *.example.com (requires DNS challenge)${CoR}" echo -e " • DNS Challenge:${CoR} Required for wildcard certificates" echo -e " - ${COLOR_YELLOW}Format:${CoR} --dns-provider PROVIDER --dns-credentials 'JSON' (or certbot INI text)" echo -e " - ${COLOR_YELLOW}Providers:${CoR} dynu, cloudflare, digitalocean, godaddy, namecheap, route53, ovh, gcloud, hostinger, rcodezero, hoster.by, lws, tencentcloud-edgeone, ..." echo -e " - ${COLOR_YELLOW}Cloudflare:${CoR} '{\"dns_cloudflare_api_token\":\"TOKEN\"}' (recommended, Zone:DNS:Edit)" echo -e " or '{\"dns_cloudflare_email\":\"EMAIL\",\"dns_cloudflare_api_key\":\"GLOBAL_KEY\"}'" echo -e " • ${COLOR_YELLOW}Reuse:${CoR} one wildcard cert for many hosts: --host-ssl-enable HOST_ID CERT_ID" echo "" echo "" echo -e " Redirection Host Management:" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" help_row " --redirect-host-list" "List all Redirection Hosts" echo -e " --redirect-host-create ${COLOR_ORANGE}domain${CoR} --forward-domain ${COLOR_ORANGE}target${CoR} [options]" echo -e " Required:" help_row " domain" "Source domain name" help_row " --forward-domain ${COLOR_ORANGE}target${CoR}" "Target domain to redirect to" echo -e " Optional:" help_row " --forward-scheme ${COLOR_GREY}http|https${CoR}" "Scheme (default: http)" help_row " --http-code ${COLOR_GREY}301|302|307...${CoR}" "HTTP redirect code (default: 301)" help_row " --preserve-path ${COLOR_GREY}true|false${CoR}" "Keep URI path (default: false)" help_row " --redirect-host-enable ${COLOR_CYAN}šŸ†”${CoR}" "Enable Redirection Host by ${COLOR_YELLOW}ID${CoR}" help_row " --redirect-host-disable ${COLOR_CYAN}šŸ†”${CoR}" "Disable Redirection Host by ${COLOR_YELLOW}ID${CoR}" help_row " --redirect-host-delete ${COLOR_CYAN}šŸ†”${CoR}" "Delete Redirection Host by ${COLOR_YELLOW}ID${CoR}" echo "" echo "" echo -e " Stream Host Management (TCP/UDP forwarding):" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" help_row " --stream-host-list" "List all Stream Hosts" echo -e " --stream-host-create ${COLOR_ORANGE}incoming_port${CoR} --forward-host ${COLOR_ORANGE}host${CoR} --forward-port ${COLOR_ORANGE}port${CoR} [options]" echo -e " Required:" help_row " incoming_port" "Port to listen on" help_row " --forward-host ${COLOR_ORANGE}host${CoR}" "Target host or IP" help_row " --forward-port ${COLOR_ORANGE}port${CoR}" "Target port" echo -e " Optional:" help_row " --tcp" "Enable TCP forwarding" help_row " --udp" "Enable UDP forwarding" help_row " --cert-id ${COLOR_GREY}id${CoR}" "Certificate ID (default: 0)" help_row " --stream-host-enable ${COLOR_CYAN}šŸ†”${CoR}" "Enable Stream Host by ${COLOR_YELLOW}ID${CoR}" help_row " --stream-host-disable ${COLOR_CYAN}šŸ†”${CoR}" "Disable Stream Host by ${COLOR_YELLOW}ID${CoR}" help_row " --stream-host-delete ${COLOR_CYAN}šŸ†”${CoR}" "Delete Stream Host by ${COLOR_YELLOW}ID${CoR}" echo "" echo "" echo -e " Dead Host (404) Management:" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" help_row " --dead-host-list" "List all Dead Hosts (404)" echo -e " --dead-host-create ${COLOR_ORANGE}domain${CoR} [options]" echo -e " Required:" help_row " domain" "Domain name to serve 404 for" echo -e " Optional:" help_row " --cert-id ${COLOR_GREY}id${CoR}" "Certificate ID (default: 0)" help_row " --ssl-forced ${COLOR_GREY}true|false${CoR}" "Force SSL (default: false)" help_row " --http2 ${COLOR_GREY}true|false${CoR}" "Enable HTTP/2 (default: false)" help_row " --hsts ${COLOR_GREY}true|false${CoR}" "Enable HSTS (default: false)" help_row " --hsts-subdomains ${COLOR_GREY}true|false${CoR}" "HSTS subdomains (default: false)" help_row " --advanced-config ${COLOR_GREY}string${CoR}" "Custom nginx config" help_row " --dead-host-enable ${COLOR_CYAN}šŸ†”${CoR}" "Enable Dead Host by ${COLOR_YELLOW}ID${CoR}" help_row " --dead-host-disable ${COLOR_CYAN}šŸ†”${CoR}" "Disable Dead Host by ${COLOR_YELLOW}ID${CoR}" help_row " --dead-host-delete ${COLOR_CYAN}šŸ†”${CoR}" "Delete Dead Host by ${COLOR_YELLOW}ID${CoR}" echo "" help_row " --user-list" "List All Users" help_row " --user-create ${COLOR_CYAN}username${CoR} ${COLOR_CYAN}password${CoR} ${COLOR_CYAN}email${CoR} ${COLOR_CYAN}[--admin]${CoR}" "Create User (${COLOR_GREY}--admin${CoR} grants admin role; default standard)" help_row " --user-delete ${COLOR_CYAN}šŸ†”${CoR}" "Delete User by ${COLOR_YELLOW}username${CoR}" echo "" help_row " --access-list" "List All available Access Lists (ID and Name)" help_row " --access-list-show ${COLOR_CYAN}šŸ†”${CoR}" "Show detailed information for specific access list" help_row " --access-list-create" "Create Access Lists with options:" echo -e " • ${COLOR_YELLOW}--satisfy [any|all]${CoR} Set access list satisfaction mode" echo -e " • ${COLOR_YELLOW}--pass-auth [true|false]${CoR} Enable/disable password authentication" echo -e " • ${COLOR_YELLOW}--users \"user1,user2\"${CoR} List of users (comma-separated)" echo -e " • ${COLOR_YELLOW}--allow \"ip1,ip2\"${CoR} List of allowed IPs/ranges" echo -e " • ${COLOR_YELLOW}--deny \"ip1,ip2\"${CoR} List of denied IPs/ranges" help_row " --access-list-delete ${COLOR_CYAN}šŸ†”${CoR}" "Delete Access List by access ID" help_row " --access-list-update ${COLOR_CYAN}šŸ†”${CoR}" "Update Access List by access ID with options:" echo -e " • ${COLOR_YELLOW}--name \"new_name\"${CoR} New name for the access list" echo -e " • ${COLOR_YELLOW}--satisfy [any|all]${CoR} Update satisfaction mode" echo -e " • ${COLOR_YELLOW}--pass-auth [true|false]${CoR} Update password authentication" echo -e " • ${COLOR_YELLOW}--users \"user1,user2\"${CoR} Update list of users" echo -e " • ${COLOR_YELLOW}--allow \"ip1,ip2\"${CoR} Update allowed IPs/ranges" echo -e " • ${COLOR_YELLOW}--deny \"ip1,ip2\"${CoR} Update denied IPs/ranges\n" #echo -e "\n ${COLOR_CYAN}šŸ†”${CoR} = ID Host Proxy" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" help_row " --examples" "${COLOR_ORANGE}šŸ”– ${CoR}Examples ${COLOR_GREY}commands, more explicits${CoR}" echo -e " --help ${COLOR_YELLOW} šŸ‘‰ ${COLOR_GREY}It's me${CoR}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" exit 0 } # List read commands compatible with the global --json modifier. # Combine with --json (e.g. "--json --list") to get this list as raw JSON. list_json_commands() { local cmds=( "--host-list|List all proxy hosts|./npm-api.sh --host-list --json" "--host-list-full|List all proxy hosts with full details (always pure JSON)|./npm-api.sh --host-list-full" "--host-show |Show one proxy host|./npm-api.sh --host-show 42 --json" "--cert-list|List all SSL certificates|./npm-api.sh --cert-list --json" "--cert-show |Show certificate(s) filtered by domain or id|./npm-api.sh --cert-show example.com --json" "--redirect-host-list|List all redirection hosts|./npm-api.sh --redirect-host-list --json" "--stream-host-list|List all stream hosts|./npm-api.sh --stream-host-list --json" "--dead-host-list|List all dead hosts|./npm-api.sh --dead-host-list --json" "--access-list|List all access lists|./npm-api.sh --access-list --json" "--access-list-show |Show one access list|./npm-api.sh --access-list-show 5 --json" ) if [ "$JSON_OUTPUT" = true ]; then printf '%s\n' "${cmds[@]}" | jq -R -c 'split("|") | {command: .[0], description: .[1], example: .[2]}' | jq -s '.' else echo -e "\n${COLOR_ORANGE} šŸ¤– Commands compatible with --json:${CoR}\n" local c flag desc example for c in "${cmds[@]}"; do IFS='|' read -r flag desc example <<<"$c" help_row " $flag" "$desc ${COLOR_GREY}, e.g. $example${CoR}" done echo "" fi exit 0 } ################################ # Examples CLI Commands examples_cli() { echo -e "\n${COLOR_YELLOW}šŸ’” Tips:${CoR}" echo -e " • Use -y flag to skip confirmation prompts" echo -e " • Check --help for complete command list" echo -e " • Always backup before making major changes" echo -e " • Use --host-list OR --host-search to find host IDs\n" echo -e "\n${COLOR_YELLOW}šŸ”° Common Usage Examples:${CoR}" # Basic commands echo -e "\n${COLOR_GREEN}šŸ“‹ Basic Commands:${CoR}" echo -e "${COLOR_GREY} # List all hosts in table format${CoR}" echo -e " $0 --host-list" echo -e "${COLOR_GREY} # Show detailed information about a specific host${CoR}" echo -e " $0 --host-show 42" echo -e "${COLOR_GREY} # Display default settings${CoR}" echo -e " $0 --show-default" # Host Management echo -e "\n${COLOR_GREEN}🌐 Host Management:${CoR}" echo -e "${COLOR_GREY} # Create new proxy host (basic)${CoR}" echo -e " $0 --host-create example.com -i 192.168.1.10 -p 8080" echo -e "${COLOR_GREY} # Create host with SSL and advanced config${CoR}" echo -e " $0 --host-create example.com -i 127.0.0.1 -p 6666 -f https -b true -c true --cert-generate example.com --host-ssl-enable -y" echo -e "${COLOR_GREY} # Create host with custom locations${CoR}" echo -e " $0 --host-create example.com -i 192.168.1.10 -p 8080 -l '[{\"path\":\"/api\",\"forward_host\":\"192.168.1.11\",\"forward_port\":8081}]'" # SSL Management echo -e "\n${COLOR_GREEN}šŸ”’ SSL Certificate Management:${CoR}" echo -e "${COLOR_GREY} # Generate new SSL certificate${CoR}" echo -e " $0 --cert-generate example.com admin@example.com" echo -e "${COLOR_GREY} # Enable SSL for existing host${CoR}" echo -e " $0 --host-ssl-enable 42" echo -e "${COLOR_GREY} # List all SSL certificates${CoR}" echo -e " $0 --cert-show" echo -e "${COLOR_GREY} # List all certificates${CoR}" echo -e " $0 --cert-list" echo -e "${COLOR_GREY} # Download certificate as ZIP${CoR}" echo -e " $0 --cert-download 123" echo -e "${COLOR_GREY} # Generate SSL certificate${CoR}" echo -e " $0 --cert-generate example.com --cert-email admin@example.com" # Add new section for Wildcard certificates echo -e "\n ${COLOR_GREEN}🌟 Wildcard SSL Certificates with DNS Providers:${CoR}" echo -e "${COLOR_GREY} # Generate wildcard certificate with ${COLOR_CYAN}Cloudflare${COLOR_GREY} API token (recommended, permission Zone:DNS:Edit)${CoR}" echo -e " $0 --cert-generate \"*.example.com\" \\" echo -e " --dns-provider cloudflare \\" echo -e " --dns-credentials '{\"dns_cloudflare_api_token\":\"your_token\"}'" echo -e "${COLOR_GREY} # Same with the Cloudflare Global API Key (37 hex chars)${CoR}" echo -e " $0 --cert-generate \"*.example.com\" \\" echo -e " --dns-provider cloudflare \\" echo -e " --dns-credentials '{\"dns_cloudflare_email\":\"your@email.com\",\"dns_cloudflare_api_key\":\"your_global_api_key\"}'" echo -e "\n${COLOR_GREY} # Generate wildcard certificate with ${COLOR_CYAN}DigitalOcean${CoR}" echo -e " $0 --cert-generate \"*.example.com\" \\" echo -e " --dns-provider digitalocean \\" echo -e " --dns-credentials '{\"dns_digitalocean_token\":\"your_token\"}'" echo -e "${COLOR_GREY} # Generate wildcard certificate with ${COLOR_CYAN}GoDaddy${CoR}" echo -e " $0 --cert-generate \"*.example.com\" \\" echo -e " --dns-provider godaddy \\" echo -e " --dns-credentials '{\"dns_godaddy_key\":\"your_key\",\"dns_godaddy_secret\":\"your_secret\"}'" echo -e "${COLOR_GREY} # Generate wildcard certificate with ${COLOR_CYAN}OVH${CoR}" echo -e " $0 --cert-generate \"*.example.com\" \\" echo -e " --dns-provider ovh \\" echo -e " --dns-credentials '{\"dns_ovh_endpoint\":\"ovh-eu\",\"dns_ovh_application_key\":\"key\",\"dns_ovh_application_secret\":\"secret\",\"dns_ovh_consumer_key\":\"consumer_key\"}'" echo -e "${COLOR_GREY} # Generate wildcard certificate with ${COLOR_CYAN}Dynu${CoR}" echo -e " $0 --cert-generate \"*.example.com\" \\" echo -e " --dns-provider dynu \\" echo -e " --dns-credentials '{\"dns_dynu_auth_token\":\"your_token\"}'" echo -e "\n${COLOR_GREY} # Reuse an existing wildcard certificate for a new host (no DNS credentials needed)${CoR}" echo -e " $0 --cert-show example.com ${COLOR_GREY}# get the certificate ID${CoR}" echo -e " $0 --host-create app.example.com -i 192.168.1.10 -p 8080 -y" echo -e " $0 --host-ssl-enable " echo -e "${COLOR_GREY} # Note: NPM >= 2.13 registers Let's Encrypt with the email of the NPM user account (--cert-email is ignored)${CoR}" # Redirection Host Management echo -e "\n${COLOR_GREEN}šŸ”€ Redirection Host Management:${CoR}" echo -e "${COLOR_GREY} # List all redirection hosts${CoR}" echo -e " $0 --redirect-host-list" echo -e "${COLOR_GREY} # Create a simple 301 redirect${CoR}" echo -e " $0 --redirect-host-create old.example.com --forward-domain new.example.com" echo -e "${COLOR_GREY} # Create a 302 redirect preserving path${CoR}" echo -e " $0 --redirect-host-create old.example.com --forward-domain new.example.com --http-code 302 --preserve-path true" echo -e "${COLOR_GREY} # Delete a redirection host (with auto-confirm)${CoR}" echo -e " $0 --redirect-host-delete 5 -y" echo -e "${COLOR_GREY} # Enable / disable a redirection host${CoR}" echo -e " $0 --redirect-host-enable 5" echo -e " $0 --redirect-host-disable 5" echo # Stream Host Management echo -e "\n${COLOR_GREEN}šŸ”Œ Stream Hosts:${CoR}" echo -e "${COLOR_GREY} # List all stream hosts${CoR}" echo -e " $0 --stream-host-list" echo -e "${COLOR_GREY} # Forward DNS (TCP+UDP) port 53 to a Pi-hole container${CoR}" echo -e " $0 --stream-host-create 53 --forward-host pihole --forward-port 53 --tcp --udp" echo -e "${COLOR_GREY} # TCP-only forward${CoR}" echo -e " $0 --stream-host-create 2222 --forward-host 192.168.1.10 --forward-port 22 --tcp" echo -e "${COLOR_GREY} # Delete a stream host (with auto-confirm)${CoR}" echo -e " $0 --stream-host-delete 5 -y" echo -e "${COLOR_GREY} # Enable / disable a stream host${CoR}" echo -e " $0 --stream-host-enable 5" echo -e " $0 --stream-host-disable 5" echo # Dead Host (404) Management echo -e "\n${COLOR_GREEN}🚫 Dead Hosts (404):${CoR}" echo -e "${COLOR_GREY} # List all dead hosts${CoR}" echo -e " $0 --dead-host-list" echo -e "${COLOR_GREY} # Create a 404 host for a decommissioned domain${CoR}" echo -e " $0 --dead-host-create old.example.com" echo -e "${COLOR_GREY} # Create with SSL forced and a certificate${CoR}" echo -e " $0 --dead-host-create old.example.com --cert-id 3 --ssl-forced true --http2 true" echo -e "${COLOR_GREY} # Delete a dead host (with auto-confirm)${CoR}" echo -e " $0 --dead-host-delete 5 -y" echo -e "${COLOR_GREY} # Enable / disable a dead host${CoR}" echo -e " $0 --dead-host-enable 5" echo -e " $0 --dead-host-disable 5" echo # User Management echo -e "\n${COLOR_GREEN}šŸ‘¤ User Management:${CoR}" echo -e "${COLOR_GREY} # Create new user${CoR}" echo -e " $0 --user-create john.doe secretpass john.doe@example.com" echo -e "${COLOR_GREY} # List all users${CoR}" echo -e " $0 --user-list" # Access Control echo -e "\n${COLOR_GREEN}šŸ›”ļø Access List Management Examples:${CoR}" echo -e "${COLOR_GREY} # List all access lists${CoR}" echo -e " $0 --access-list" echo echo -e "${COLOR_GREY} # Show detailed information for specific access list${CoR}" echo -e " $0 --access-list-show 123" echo echo -e "${COLOR_GREY} # Create a basic access list${CoR}" echo -e " $0 --access-list-create \"office\" --satisfy any" echo echo -e "${COLOR_GREY} # Create access list with authentication${CoR}" echo -e " $0 --access-list-create \"secure_area\" --satisfy all --pass-auth true" echo echo -e "${COLOR_GREY} # Create access list with users${CoR}" echo -e " $0 --access-list-create \"dev_team\" --users \"john,jane,bob\" --pass-auth true" echo echo -e "${COLOR_GREY} # Create access list with IP rules${CoR}" echo -e " $0 --access-list-create \"internal\" --allow \"192.168.1.0/24\" --deny \"192.168.1.100\"" echo echo -e "${COLOR_GREY} # Create comprehensive access list${CoR}" echo -e " $0 --access-list-create \"full_config\" \\" echo -e " --satisfy all \\" echo -e " --pass-auth true \\" echo -e " --users \"admin1,admin2\" \\" echo -e " --allow \"10.0.0.0/8,172.16.0.0/12\" \\" echo -e " --deny \"10.0.0.50,172.16.1.100\"" echo echo -e "${COLOR_GREY} # Update existing access list${CoR}" echo -e " $0 --access-list-update 123 --name \"new_name\" --satisfy any" echo echo -e "${COLOR_GREY} # Update access list with new allow rules${CoR}" echo -e " $0 --access-list-update 123 --allow \"172.0.0.0/8,10.0.0.0/8\"" echo echo -e "${COLOR_GREY} # Delete access list${CoR}" echo -e " $0 --access-list-delete 123" echo -e "${COLOR_GREY} # Enable ACL for a host${CoR}" echo -e " $0 --host-acl-enable 42 2" # Advanced Configuration echo -e "${COLOR_GREY} # Create host with custom headers${CoR}" echo -e " $0 --host-create example.com -i 192.168.1.10 -p 8080 -a 'proxy_set_header X-Real-IP \$remote_addr;'" echo -e "${COLOR_GREY} # Update specific field of existing host${CoR}" echo -e " $0 --host-update 42 forward_host=new.example.com" echo -e "\n${COLOR_GREEN}āš™ļø Advanced Configuration:${CoR}" echo -e "${COLOR_GREY} # Create host with all options${CoR}" echo -e " $0 --host-create example.com -i 192.168.1.10 -p 8080 \\" echo -e " -f https \\" echo -e " -c true \\" echo -e " -b true \\" echo -e " -w true \\" echo -e " -h true \\" echo -e " -s true \\" echo -e " -a 'proxy_set_header X-Real-IP \$remote_addr;' \\" echo -e " -l '[{\"path\":\"/api\",\"forward_host\":\"api.local\",\"forward_port\":3000}]'" echo -e "\n${COLOR_YELLOW}šŸ“ Command Parameters:${CoR}" echo -e " domain : Domain name for the proxy host" echo -e " -i, --forward-host : Target server (IP/hostname)" echo -e " -p, --forward-port : Target port" echo -e " -f, --forward-scheme : http/https" echo -e " -c, --cache : Enable cache" echo -e " -b, --block-exploits : Protection against exploits" echo -e " -w, --websocket : WebSocket support" echo -e " -h, --http2 : HTTP/2 support" echo -e " -s, --ssl-force : Force SSL" echo -e " -a, --advanced-config : Custom Nginx configuration" echo -e " -l, --locations : Custom location rules (JSON)\n" exit 0 } ################################ # Display script variables info display_info() { check_token true echo -e "${COLOR_YELLOW}\n Script Info: ${COLOR_GREEN}${VERSION}${CoR}" echo -e " ${COLOR_YELLOW}Script Variables Information:${CoR}" #echo -e "\n ${COLOR_GREEN}DATA_DIR${CoR} ${DATA_DIR}" echo -e " ${COLOR_GREEN}Config${CoR} : ${SCRIPT_DIR}/npm-api.conf" echo -e " ${COLOR_GREEN}BASE URL${CoR} : ${BASE_URL}" echo -e " ${COLOR_GREEN}NGINX IP${CoR} : ${NGINX_IP}" echo -e " ${COLOR_GREEN}USER NPM${CoR} : ${API_USER}" echo -e " ${COLOR_GREEN}BACKUP DIR ${CoR} : ${DATA_DIR_ID}" #echo -e " ${COLOR_GREEN}DOCKER Path${CoR} : ${NGINX_PATH_DOCKER}" display_dashboard DATE=$(date +"_%Y_%m_%d__%H_%M_%S") BACKUP_PATH="$BACKUP_DIR" # Check if directory exists and contains files if [ -d "$BACKUP_PATH" ] && [ -n "$(find "$BACKUP_PATH" -mindepth 1 -print -quit 2>/dev/null)" ]; then # Count backup files by type local total_files=$(find "$BACKUP_PATH" -type f \( \ -name "*.json" -o \ -name "*.conf" -o \ -name "*.log" -o \ -name "*.pem" -o \ -name "*.key" \ \) 2>/dev/null | wc -l) if [ "$total_files" -gt 0 ]; then echo -e "\n ${COLOR_CYAN}šŸ’¾ Backup Statistics šŸ“¦${CoR}" echo -e " ${COLOR_GREY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${CoR}" local config_files=$(find "$BACKUP_PATH" -maxdepth 1 -type f -name "full_config*.json" 2>/dev/null | wc -l) local proxy_files=$(find "$BACKUP_PATH/.Proxy_Hosts" -type f -name "proxy_config.json" 2>/dev/null | wc -l) local redirect_files=$(find "$BACKUP_PATH/.Redirection_Hosts" -type f -name "*.json" 2>/dev/null | wc -l) local stream_files=$(find "$BACKUP_PATH/.Stream_Hosts" -type f -name "*.json" 2>/dev/null | wc -l) local dead_files=$(find "$BACKUP_PATH/.Dead_Hosts" -type f -name "*.json" 2>/dev/null | wc -l) local ssl_files=$(find "$BACKUP_PATH" -type f \( \ -name "certificate*.json" -o \ -name "*.pem" -o \ -name "*.key" \ \) 2>/dev/null | wc -l) local access_files=$(find "$BACKUP_PATH/.access_lists" -type f -name "*.json" 2>/dev/null | wc -l) local settings_files=$(find "$BACKUP_PATH/.settings" -type f -name "*.json" 2>/dev/null | wc -l) local user_files=$(find "$BACKUP_PATH/.user" -type f -name "*.json" 2>/dev/null | wc -l) # Show statistics only if there are files [ "$config_files" -gt 0 ] && echo -e " • Full Config Files : ${COLOR_CYAN}$config_files${CoR}" [ "$proxy_files" -gt 0 ] && echo -e " • Proxy Host Files : ${COLOR_CYAN}$proxy_files${CoR}" [ "$redirect_files" -gt 0 ] && echo -e " • Redirection Files : ${COLOR_CYAN}$redirect_files${CoR}" [ "$stream_files" -gt 0 ] && echo -e " • Stream Host Files : ${COLOR_CYAN}$stream_files${CoR}" [ "$dead_files" -gt 0 ] && echo -e " • 404 Host Files : ${COLOR_CYAN}$dead_files${CoR}" [ "$ssl_files" -gt 0 ] && echo -e " • SSL Files : ${COLOR_CYAN}$ssl_files${CoR}" [ "$access_files" -gt 0 ] && echo -e " • Access Lists : ${COLOR_CYAN}$access_files${CoR}" [ "$settings_files" -gt 0 ] && echo -e " • Settings Files : ${COLOR_CYAN}$settings_files${CoR}" [ "$user_files" -gt 0 ] && echo -e " • User Files : ${COLOR_CYAN}$user_files${CoR}" echo -e " • Total Files : ${COLOR_CYAN}$total_files${CoR}" fi fi # Display backup locations echo -e "\n šŸ“‚ ${COLOR_YELLOW}Backup Locations:${CoR}" echo -e " • Backup: ${COLOR_GREY}$BACKUP_PATH${CoR}" echo -e " • Token: ${COLOR_GREY}$BACKUP_PATH/token/${CoR}" echo -e "\n ${COLOR_YELLOW}šŸ’” Use --help to see available commands${CoR}" echo -e " ${COLOR_GREY} Check --examples for more help examples${CoR}\n" } ################################ # Check GitHub for a newer release of this script # Usage: check_update ################################ check_update() { check_dependencies local repo="Erreur32/nginx-proxy-manager-Bash-API" echo -e "\n šŸ” ${COLOR_CYAN}Checking for script updates...${CoR}" local latest_json latest_json=$(curl -s --max-time 5 "https://api.github.com/repos/$repo/releases/latest" 2>/dev/null || true) local latest_version latest_version=$(echo "$latest_json" | jq -r '.tag_name // empty' 2>/dev/null) latest_version="${latest_version#v}" if [ -z "$latest_version" ]; then echo -e " ā›” ${COLOR_RED}Unable to check for updates (no network or GitHub API unavailable).${CoR}\n" return 1 fi echo -e " • Current version : ${COLOR_YELLOW}${VERSION}${CoR}" echo -e " • Latest version : ${COLOR_YELLOW}${latest_version}${CoR}" if [ "$latest_version" = "$VERSION" ]; then echo -e "\n āœ… ${COLOR_GREEN}You are running the latest version!${CoR}\n" else local html_url html_url=$(echo "$latest_json" | jq -r '.html_url // empty' 2>/dev/null) echo -e "\n šŸ†• ${COLOR_ORANGE}A new version is available: ${COLOR_GREEN}${latest_version}${CoR}" [ -n "$html_url" ] && echo -e " šŸ”— ${COLOR_CYAN}${html_url}${CoR}" echo -e " šŸ’” ${COLOR_GREY}Update: git -C \"$SCRIPT_DIR\" pull${CoR} ${COLOR_GREY}(or download the release manually)${CoR}\n" fi } # Function to display dashboard display_dashboard() { #check_token_notverbose echo -e "\n ${COLOR_CYAN}šŸ“Š NGINX - Proxy Manager - Dashboard šŸ”§ ${CoR}" echo -e " ${COLOR_GREY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${CoR}" # Get all data first local proxy_hosts=$(curl -s --max-redirs 0 -X GET "$BASE_URL/nginx/proxy-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") local redirection_hosts=$(curl -s --max-redirs 0 -X GET "$BASE_URL/nginx/redirection-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") local stream_hosts=$(curl -s --max-redirs 0 -X GET "$BASE_URL/nginx/streams" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") local dead_hosts=$(curl -s --max-redirs 0 -X GET "$BASE_URL/nginx/dead-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") local access_lists=$(curl -s --max-redirs 0 -X GET "$BASE_URL/nginx/access-lists" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") local certificates=$(curl -s --max-redirs 0 -X GET "$BASE_URL/nginx/certificates" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") local users=$(curl -s --max-redirs 0 -X GET "$BASE_URL/users" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") # Calculate counts with error checking local proxy_count=0 local enabled_proxy_count=0 local redirect_count=0 local enabled_redirect_count=0 local stream_count=0 local enabled_stream_count=0 local dead_count=0 local enabled_dead_count=0 local cert_count=0 local expired_cert_count=0 local user_count=0 local access_list_count=0 # Check and calculate proxy hosts if [ "$(echo "$proxy_hosts" | jq -r 'type')" == "array" ]; then proxy_count=$(echo "$proxy_hosts" | jq '. | length') enabled_proxy_count=$(echo "$proxy_hosts" | jq '[.[] | select(.enabled == true)] | length') fi local disabled_proxy_count=$((proxy_count - enabled_proxy_count)) # Check and calculate redirection hosts if [ "$(echo "$redirection_hosts" | jq -r 'type')" == "array" ]; then redirect_count=$(echo "$redirection_hosts" | jq '. | length') enabled_redirect_count=$(echo "$redirection_hosts" | jq '[.[] | select(.enabled == true)] | length') fi local disabled_redirect_count=$((redirect_count - enabled_redirect_count)) # Check and calculate stream hosts if [ "$(echo "$stream_hosts" | jq -r 'type')" == "array" ]; then stream_count=$(echo "$stream_hosts" | jq '. | length') enabled_stream_count=$(echo "$stream_hosts" | jq '[.[] | select(.enabled == true)] | length') fi local disabled_stream_count=$((stream_count - enabled_stream_count)) # Check and calculate 404 hosts (dead hosts) if [ "$(echo "$dead_hosts" | jq -r 'type')" == "array" ]; then dead_count=$(echo "$dead_hosts" | jq '. | length') enabled_dead_count=$(echo "$dead_hosts" | jq '[.[] | select(.enabled == true)] | length') fi local disabled_dead_count=$((dead_count - enabled_dead_count)) # Check and calculate access lists if [ "$(echo "$access_lists" | jq -r 'type')" == "array" ]; then access_list_count=$(echo "$access_lists" | jq '. | length') fi # Check and calculate certificates if [ "$(echo "$certificates" | jq -r 'type')" == "array" ]; then cert_count=$(echo "$certificates" | jq '. | length') expired_cert_count=$(echo "$certificates" | jq "$CERT_JQ_EXPIRED"' [.[] | select(is_expired)] | length') fi local valid_cert_count=$((cert_count - expired_cert_count)) # Check and calculate users if [ "$(echo "$users" | jq -r 'type')" == "array" ]; then user_count=$(echo "$users" | jq '. | length') fi # Get version and uptime local uptime=$(uptime | sed 's/.*up \([^,]*\),.*/\1/') local version_info=$(curl -s --max-redirs 0 -X GET "$BASE_URL/") local npm_version="Unknown" if echo "$version_info" | jq -e '.version' >/dev/null 2>&1; then npm_version=$(echo "$version_info" | jq -r '"\(.version.major).\(.version.minor).\(.version.revision)"') fi print_row() { local component="$1" local status="$2" local force_color="${3:-}" # Initialize with empty value by default local status_color="" # If a color is forced, use it if [ -n "$force_color" ]; then status_color="$force_color" # Otherwise, apply automatic coloring logic else # Components that should not be colored green case "$component" in *"Disabled"* | *"Expired"* | *"Uptime"* | *"Version"*) status_color="" ;; *) # For others, color green if > 0 if [[ "$status" =~ ^[0-9]+$ ]] && [ "$status" -gt 0 ]; then status_color="$COLOR_GREEN" fi ;; esac fi # Calculate padding needed (20 is the max width of status column) local status_length=${#status} local padding=$((8 - status_length)) local spaces=$(printf '%*s' "$padding" '') echo -e " ${COLOR_GREY}│${CoR} $component ${COLOR_GREY}│${CoR} ${status_color}$status${spaces}${CoR}${COLOR_GREY}│${CoR}" } echo -e " ${COLOR_GREY}ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”${CoR}" echo -e " ${COLOR_GREY}│${CoR} COMPONENT ${COLOR_GREY}│${CoR} STATUS ${COLOR_GREY}│${CoR}" echo -e " ${COLOR_GREY}ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤${CoR}" # --- Hosts (same order as the NPM web UI: Proxy / Redirection / Streams / 404) --- # Proxy Hosts print_row "🌐 Proxy Hosts " "$proxy_count" "$COLOR_YELLOW" print_row "ā”œā”€ Enabled " "$enabled_proxy_count" print_row "└─ Disabled " "$disabled_proxy_count" "$COLOR_RED" echo -e " ${COLOR_GREY}ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤${CoR}" # Redirection Hosts print_row "šŸ”„ Redirections" "$redirect_count" "$COLOR_YELLOW" print_row "ā”œā”€ Enabled " "$enabled_redirect_count" print_row "└─ Disabled " "$disabled_redirect_count" "$COLOR_RED" echo -e " ${COLOR_GREY}ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤${CoR}" # Stream Hosts print_row "šŸ”Œ Stream Hosts" "$stream_count" "$COLOR_YELLOW" print_row "ā”œā”€ Enabled " "$enabled_stream_count" print_row "└─ Disabled " "$disabled_stream_count" "$COLOR_RED" echo -e " ${COLOR_GREY}ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤${CoR}" # 404 Hosts (Dead Hosts) print_row "🚫 404 Hosts " "$dead_count" "$COLOR_YELLOW" print_row "ā”œā”€ Enabled " "$enabled_dead_count" print_row "└─ Disabled " "$disabled_dead_count" "$COLOR_RED" echo -e " ${COLOR_GREY}ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤${CoR}" # Access Lists print_row "šŸ”’ Access Lists" "$access_list_count" echo -e " ${COLOR_GREY}ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤${CoR}" # SSL Certificates print_row "šŸ”’ Certificates" "$cert_count" "$COLOR_YELLOW" print_row "ā”œā”€ Valid " "$valid_cert_count" print_row "└─ Expired " "$expired_cert_count" "$COLOR_RED" echo -e " ${COLOR_GREY}ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤${CoR}" # Users print_row "šŸ‘„ Users " "$user_count" echo -e " ${COLOR_GREY}ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤${CoR}" # System print_row "ā±ļø Uptime " "$uptime" "$COLOR_YELLOW" print_row "šŸ“¦ NPM Version " "$npm_version" "$COLOR_YELLOW" echo -e " ${COLOR_GREY}ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜${CoR}" } ################################ # show_default # Display default settings for creating hosts show_default() { check_token_notverbose echo -e "\nšŸ“ ${COLOR_YELLOW}Default Settings for Creating Hosts:${CoR}" echo -e "\n ${COLOR_GREEN}Basic Settings:${CoR}" echo -e " Forward Scheme: $(colorize_booleanh "$FORWARD_SCHEME")" echo -e " Caching Enabled: $(colorize_boolean "$CACHING_ENABLED")" echo -e " Block Exploits: $(colorize_boolean "$BLOCK_EXPLOITS")" echo -e " Allow Websocket Upgrade: $(colorize_boolean "$ALLOW_WEBSOCKET_UPGRADE")" echo -e "\n ${COLOR_GREEN}SSL Settings:${CoR}" echo -e " HTTP/2 Support: $(colorize_boolean "$HTTP2_SUPPORT")" echo -e " SSL Forced: $(colorize_boolean "$SSL_FORCED")" echo -e " HSTS Enabled: $(colorize_boolean "$HSTS_ENABLED")" echo -e " HSTS Subdomains: $(colorize_boolean "$HSTS_SUBDOMAINS")" echo -e "\n ${COLOR_GREEN}Advanced Settings:${CoR}" if [ -n "$ADVANCED_CONFIG" ]; then echo -e " Advanced Config: ${COLOR_YELLOW}$ADVANCED_CONFIG${CoR}" else echo -e " Advanced Config: ${COLOR_GREY}Not configured${CoR}" fi if [ -n "$CUSTOM_LOCATIONS" ]; then echo -e " Custom Locations: ${COLOR_YELLOW}$CUSTOM_LOCATIONS${CoR}" else echo -e " Custom Locations: ${COLOR_GREY}Not configured${CoR}" fi echo -e "\n ${COLOR_GREEN}System Settings:${CoR}" echo -e " Base URL: $BASE_URL" echo -e " Base Directory: $DATA_DIR" echo -e " Token Directory: $TOKEN_DIR" echo -e " Backup Directory: $BACKUP_DIR" echo -e " Token Expiry: $TOKEN_EXPIRY\n" exit 0 } ####################################################################################### ################################ # List all users user_list() { check_token_notverbose echo -e "\n šŸ‘‰ List of users..." RESPONSE=$(curl -s -X GET "$BASE_URL/users" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") echo -e "\n $RESPONSE" | jq exit 0 } ################################ # Create a new user user_create() { check_token_notverbose if [ -z "$USERNAME" ] || [ -z "$PASSWORD" ] || [ -z "$EMAIL" ]; then echo -e "\n šŸ‘¤ ${COLOR_RED}The --user-create option requires username, password, and email.${CoR}" echo -e " Usage: ${COLOR_ORANGE}$0 --user-create username password email [--admin]${CoR}" echo -e " Example:" echo -e " ${COLOR_GREEN}$0 --user-create john secretpass john@domain.com${CoR}\n" return 1 fi # check if user already exists EXISTING_USERS=$(curl -s -X GET "$BASE_URL/users" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") # check if email already exists if echo "$EXISTING_USERS" | jq -e --arg email "$EMAIL" '.[] | select(.email == $email)' >/dev/null; then echo -e "\n ā›” ${COLOR_RED}Error: A user with email $EMAIL already exists.${CoR}" return 1 fi # check if username already exists if echo "$EXISTING_USERS" | jq -e --arg name "$USERNAME" '.[] | select(.name == $name)' >/dev/null; then echo -e "\n ā›” ${COLOR_RED}Error: A user with name $USERNAME already exists.${CoR}" return 1 fi # create user # Standard user by default; admin only when --admin was passed if [ "$USER_IS_ADMIN" = true ]; then ROLES_JSON='["admin"]' echo -e "\n šŸ‘¤ Creating ${COLOR_YELLOW}admin${CoR} user ${COLOR_GREEN}$USERNAME${CoR}..." else ROLES_JSON='[]' echo -e "\n šŸ‘¤ Creating user ${COLOR_GREEN}$USERNAME${CoR}..." fi DATA=$(jq -n \ --arg username "$USERNAME" \ --arg password "$PASSWORD" \ --arg email "$EMAIL" \ --arg name "$USERNAME" \ --arg nickname "$USERNAME" \ --arg secret "$PASSWORD" \ --argjson roles "$ROLES_JSON" \ '{ name: $name, nickname: $nickname, email: $email, roles: $roles, is_disabled: false, auth: { type: "password", secret: $secret } }') # send data to API RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" -X POST "$BASE_URL/users" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8" \ --data-raw "$DATA") HTTP_BODY=${RESPONSE//HTTPSTATUS:*/} HTTP_STATUS=${RESPONSE##*HTTPSTATUS:} if [ "${HTTP_STATUS:-0}" -eq 201 ]; then # get user id USERS_RESPONSE=$(curl -s -X GET "$BASE_URL/users" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") USER_ID=$(echo "$USERS_RESPONSE" | jq -r --arg email "$EMAIL" --arg name "$USERNAME" \ '.[] | select(.email == $email and .name == $name) | .id') if [ -n "$USER_ID" ]; then echo -e " āœ… ${COLOR_GREEN}User $USERNAME created successfully!${CoR}" echo -e " šŸ“§ Email: ${COLOR_YELLOW}$EMAIL${CoR}" echo -e " šŸ†” User ID: ${COLOR_YELLOW}$USER_ID${CoR}\n" else echo -e " āš ļø ${COLOR_GREEN}User created but couldn't fetch ID${CoR}" echo -e " šŸ“§ Email: ${COLOR_YELLOW}$EMAIL${CoR}\n" fi return 0 else echo -e " ā›” ${COLOR_RED}Failed to create user. Status: $HTTP_STATUS${CoR}" echo -e " Error: ${COLOR_RED}$HTTP_BODY${CoR}\n" return 1 fi } ################################ # Delete a user # $1: user_id - ID of the user to delete user_delete() { local USER_ID="$1" if [ -z "$USER_ID" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: User ID is required${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --user-delete [-y]${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --user-delete 42${CoR}" exit 1 fi # Check if USER_ID is a number if ! [[ "$USER_ID" =~ ^[0-9]+$ ]]; then echo -e " ā›” ${COLOR_RED}ERROR: Invalid user ID '$USER_ID' - must be a number${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --user-delete [-y]${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --user-delete 42${CoR}" exit 1 fi check_token_notverbose # Get user details first local RESPONSE=$(curl -s -X GET "$BASE_URL/users/$USER_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ "$(echo "$RESPONSE" | jq -r '.error.code // empty')" = "404" ]; then echo -e " ā›” ${COLOR_RED}User ID $USER_ID not found${CoR}" exit 1 fi local USERNAME=$(echo "$RESPONSE" | jq -r '.name') local EMAIL=$(echo "$RESPONSE" | jq -r '.email') if [ "$AUTO_YES" = true ]; then echo -e " šŸ”” Auto-confirming deletion of user '$USERNAME' (ID: $USER_ID)..." else echo -e " ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€" echo -e " │ ID: ${COLOR_YELLOW}$USER_ID${CoR}" echo -e " │ Name: ${COLOR_GREEN}$USERNAME${CoR}" echo -e " │ Email: ${COLOR_CYAN}$EMAIL${CoR}" echo -e " └───────────────────────────────────────────" echo -e " āš ļø ${COLOR_RED}WARNING: This action cannot be undone!${CoR}" read -n 1 -r -p " šŸ”” Confirm deletion? (y/n): " CONFIRM echo if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then echo -e " āŒ ${COLOR_RED}Operation cancelled${CoR}" exit 1 fi fi # Delete user RESPONSE=$(curl -s -X DELETE "$BASE_URL/users/$USER_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ "$RESPONSE" = "true" ]; then echo -e " āœ… ${COLOR_GREEN}User '$USERNAME' (ID: $USER_ID) deleted successfully!${CoR}" else echo -e " ā›” ${COLOR_RED}Failed to delete user.${CoR}" if [ -n "$RESPONSE" ]; then echo -e " Error: $RESPONSE" fi exit 1 fi } ################################ # Check if a certificate exists and is valid check_certificate_exists() { local DOMAIN="$1" local RESPONSE local CERT_ID RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/certificates" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [[ "$DOMAIN" == \** ]]; then CERT_ID=$(echo "$RESPONSE" | jq -r --arg domain "$DOMAIN" \ '.[] | select(.domain_names[] == $domain) | .id' | sort -n | tail -n1) else local BASE_DOMAIN="${DOMAIN#\*\.}" CERT_ID=$(echo "$RESPONSE" | jq -r --arg domain "$BASE_DOMAIN" \ '.[] | select( (.domain_names[] == $domain) or (.domain_names[] | startswith("*.") and ($domain | endswith(.[2:]))) or ($domain | startswith("*.") and (.domain_names[] | endswith(.[2:]))) ) | .id' | sort -n | tail -n1) fi if [ -n "$CERT_ID" ]; then return 0 else return 1 fi } ############################################################## # Function to delete all existing proxy hosts # DEBUG ############################################################## # Delete all existing proxy hosts delete_all_proxy_hosts() { check_token_notverbose echo -e "\n šŸ—‘ļø ${COLOR_ORANGE}Deleting all existing proxy hosts...${CoR}" # Get all host IDs local existing_hosts=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" | jq -r '.[].id') local count=0 for host_id in $existing_hosts; do echo -e " • Deleting host ID ${COLOR_CYAN}$host_id${CoR}...${COLOR_GREEN}āœ“${CoR}" local response=$(curl -s -w "HTTPSTATUS:%{http_code}" -X DELETE "$BASE_URL/nginx/proxy-hosts/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") local http_body=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g') local http_status=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') if [ "${http_status:-0}" -ne 200 ]; then echo -e " ā›” ${COLOR_RED}Failed to delete host ID $host_id. HTTP status: $http_status. Response: $http_body${CoR}" return 1 fi count=$((count + 1)) done echo -e " āœ… ${COLOR_GREEN}Successfully deleted $count proxy hosts!${CoR}" return 0 } ################################ # --clean_hosts from_backup_file # Function to reimport hosts from backup file # Create a safety backup before major operations create_safety_backup() { check_token_notverbose TIMESTAMP=$(date +%Y%m%d_%H%M%S) SAFETY_BACKUP="$BACKUP_DIR/pre_reimport_SAFETY_BACKUP_${TIMESTAMP}.json" echo -e "šŸ“¦ Creating safety backup..." # Get the list of IDs local host_ids=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" | jq -r '.[].id') # Create a table to store the complete configurations echo "[" >"$SAFETY_BACKUP" local first=true # Get the detailed configuration of each host for id in $host_ids; do if [ "$first" = true ]; then first=false else echo "," >>"$SAFETY_BACKUP" fi curl -s -X GET "$BASE_URL/nginx/proxy-hosts/$id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" >>"$SAFETY_BACKUP" done echo "]" >>"$SAFETY_BACKUP" # Verify that the backup is valid if ! jq empty "$SAFETY_BACKUP" 2>/dev/null; then echo -e " āŒ ${COLOR_RED}Failed to create valid safety backup${CoR}" exit 1 fi echo -e " āœ… ${COLOR_GREEN}Safety backup created: ${COLOR_CYAN}$SAFETY_BACKUP${CoR}" return 0 } ################################ # Function to display import summary display_import_summary() { echo -e "\nšŸ“Š ${COLOR_YELLOW}Import Summary:${CoR}" echo -e " • Total hosts processed: ${COLOR_CYAN}$total${CoR}" echo -e " • Successfully imported: ${COLOR_GREEN}$success${CoR}" echo -e " • Failed imports: ${COLOR_RED}$failed${CoR}" echo -e "\nšŸ”’ SSL Certificates:" echo -e " • Successfully configured: ${COLOR_GREEN}$ssl_success${CoR}" echo -e " • Failed configurations: ${COLOR_RED}$ssl_failed${CoR}" if [ ${#failed_ssl_domains[@]} -gt 0 ]; then echo -e "\nāš ļø ${COLOR_YELLOW}Domains with failed SSL setup:${CoR}" for domain in "${failed_ssl_domains[@]}"; do echo -e " • $domain" done fi echo -e "\n✨ ${COLOR_GREEN}Import process completed${CoR}" } ################################ # jq helper: the NPM API has no `expired` field, derive it from expires_on # ("YYYY-MM-DD HH:MM:SS" on SQLite, ISO 8601 on MySQL/Postgres) CERT_JQ_EXPIRED='def is_expired: if has("expired") then .expired == true else ((.expires_on // "")[0:19] | sub(" "; "T") + "Z" | try fromdateiso8601 catch null) as $t | ($t != null and $t < now) end;' ################################ # Shared jq format + colorizer for certificate listings (used by cert_show and # list_cert_all). CERT_JQ_FMT formats one certificate object; cert_colorize # reads that output on stdin and colors the Status line. CERT_JQ_FMT='" šŸ”’ ID: \(.id)\n • Domain(s): \(.domain_names | join(", "))\n • Provider: \(.provider)\n • Created on: \(.created_on // "N/A")\n • Expires on: \(.expires_on // "N/A")\n • Status: \(if is_expired then "āŒ EXPIRED" else if .expires_on then "āœ… VALID" else "āš ļø PENDING" end end)"' cert_colorize() { while IFS= read -r line; do if [[ $line == *"āŒ EXPIRED"* ]]; then echo -e "${line/āŒ EXPIRED/${COLOR_RED}āŒ EXPIRED${CoR}}" elif [[ $line == *"āœ… VALID"* ]]; then echo -e "${line/āœ… VALID/${COLOR_GREEN}āœ… VALID${CoR}}" elif [[ $line == *"āš ļø PENDING"* ]]; then echo -e "${line/āš ļø PENDING/${COLOR_YELLOW}āš ļø PENDING${CoR}}" else echo -e "$line" fi done } ################################ # Function to list SSL certificates by ID or domain cert_show() { local search_term="$1" check_token_notverbose # If no search term is provided, show usage if [ -z "$search_term" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: Missing argument${CoR}" echo -e " Usage: " echo -e " ${COLOR_ORANGE}$0 --cert-show ${CoR} šŸ” Search by domain name" echo -e " ${COLOR_ORANGE}$0 --cert-show ${CoR} šŸ”¢ Search by ID" echo -e " ${COLOR_ORANGE}$0 --cert-show-all${CoR} šŸ“œ List all certificates\n" exit 1 fi # Get all certificates RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/certificates" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ -z "$RESPONSE" ] || [ "$RESPONSE" == "null" ]; then echo -e " ā›” ${COLOR_RED}Error: Unable to retrieve certificates${CoR}" exit 1 fi # Search by ID if numeric if [[ "$search_term" =~ ^[0-9]+$ ]]; then [ "$JSON_OUTPUT" = true ] || echo -e "\n šŸ” Searching for certificate with ID: ${COLOR_YELLOW}$search_term${CoR}" # Get specific certificate by ID CERT_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/certificates/$search_term" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if echo "$CERT_RESPONSE" | jq -e '.error' >/dev/null; then if [ "$JSON_OUTPUT" = true ]; then echo '{"error": "Certificate not found with ID: '"$search_term"'"}' exit 1 fi echo -e " ā›” ${COLOR_RED}Certificate not found with ID: $search_term${CoR}" else if [ "$JSON_OUTPUT" = true ]; then echo "$CERT_RESPONSE" | jq . exit 0 fi echo "$CERT_RESPONSE" | jq -r "$CERT_JQ_EXPIRED $CERT_JQ_FMT" | cert_colorize fi echo "" return 0 fi # Search by domain name (partial match) if [ "$JSON_OUTPUT" = true ]; then echo "$RESPONSE" | jq --arg domain "$search_term" '[.[] | select(.domain_names[] | contains($domain))]' exit 0 fi echo -e "\n šŸ” Searching certificates for domain: ${COLOR_YELLOW}$search_term${CoR}" DOMAIN_CERTS=$(echo "$RESPONSE" | jq -r --arg domain "$search_term" \ '.[] | select(.domain_names[] | contains($domain))') if [ -z "$DOMAIN_CERTS" ]; then echo -e " ā„¹ļø ${COLOR_YELLOW}No certificates found for domain: $search_term${CoR}" else echo "$DOMAIN_CERTS" | jq -r "$CERT_JQ_EXPIRED $CERT_JQ_FMT" | cert_colorize echo "" fi } ################################ # Function to list all SSL certificates list_cert_all() { check_token_notverbose # Get all certificates RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/certificates" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ -z "$RESPONSE" ] || [ "$RESPONSE" == "null" ]; then echo -e " ā›” ${COLOR_RED}Error: Unable to retrieve certificates${CoR}" exit 1 fi if [ "$JSON_OUTPUT" = true ]; then echo "$RESPONSE" | jq . exit 0 fi echo -e "\n šŸ“œ SSL Certificates List:" # Check if there are any certificates if [ "$RESPONSE" = "[]" ]; then echo -e " ā„¹ļø ${COLOR_YELLOW}No certificates found${CoR}" return 0 fi # Process and display all certificates echo "$RESPONSE" | jq -r "$CERT_JQ_EXPIRED .[] | $CERT_JQ_FMT" | cert_colorize # Display statistics TOTAL_CERTS=$(echo "$RESPONSE" | jq '. | length') EXPIRED_CERTS=$(echo "$RESPONSE" | jq "$CERT_JQ_EXPIRED"' [.[] | select(is_expired)] | length') VALID_CERTS=$((TOTAL_CERTS - EXPIRED_CERTS)) echo -e "\n šŸ“Š Statistics" echo -e " Total certs: ${COLOR_YELLOW}$TOTAL_CERTS${CoR}" echo -e " • ${COLOR_GREEN}Valid${CoR} : ${COLOR_GREEN}$VALID_CERTS${CoR}" echo -e " • ${COLOR_RED}Expired${CoR} : ${COLOR_RED}$EXPIRED_CERTS${CoR}\n" } ################################ # Function to download certificate as ZIP with fallback support cert_download() { local cert_id="$1" local output_dir="${2:-./certificates}" local cert_name="${3:-certificate_$cert_id}" check_token_notverbose if [ -z "$cert_id" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: Certificate ID is required${CoR}" echo -e " Usage: " echo -e " ${COLOR_ORANGE}$0 --cert-download [output_dir] [cert_name]${CoR}" echo -e " ${COLOR_ORANGE}$0 --cert-download 123 ./certs mydomain${CoR}\n" exit 1 fi # Create output directory mkdir -p "$output_dir" echo -e "\n šŸ”’ Downloading certificate (ID: ${COLOR_YELLOW}$cert_id${CoR})" echo -e " Output directory: ${COLOR_CYAN}$output_dir${CoR}" echo -e " Certificate name: ${COLOR_CYAN}$cert_name${CoR}" # Get certificate metadata first local CERT_META CERT_META=$(curl -s -X GET "$BASE_URL/nginx/certificates/$cert_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ -z "$CERT_META" ] || ! echo "$CERT_META" | jq empty 2>/dev/null; then echo -e " ā›” ${COLOR_RED}Error: Unable to retrieve certificate metadata${CoR}" exit 1 fi # Check if certificate exists if echo "$CERT_META" | jq -e '.error' >/dev/null; then echo -e " ā›” ${COLOR_RED}Certificate not found with ID: $cert_id${CoR}" exit 1 fi # Extract certificate information local domain_names=$(echo "$CERT_META" | jq -r '.domain_names | join(", ")') local provider=$(echo "$CERT_META" | jq -r '.provider // "Unknown"') local expires_on=$(echo "$CERT_META" | jq -r '.expires_on // "N/A"') echo -e " Domain(s): ${COLOR_YELLOW}$domain_names${CoR}" echo -e " Provider: ${COLOR_YELLOW}$provider${CoR}" echo -e " Expires: ${COLOR_YELLOW}$expires_on${CoR}" # Try new API first (JSON format) echo -e "\n šŸ”„ Trying new API format..." local CERT_CONTENT CERT_CONTENT=$(curl -s -X GET "$BASE_URL/nginx/certificates/$cert_id/certificates" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Accept: application/json") if [ -n "$CERT_CONTENT" ] && echo "$CERT_CONTENT" | jq empty 2>/dev/null; then # Check if we got valid certificate data local cert_data=$(echo "$CERT_CONTENT" | jq -r '.certificate // empty') local private_data=$(echo "$CERT_CONTENT" | jq -r '.private // empty') if [ -n "$cert_data" ] && [ -n "$private_data" ]; then echo -e " āœ… ${COLOR_GREEN}New API format successful${CoR}" # Save certificate files echo "$cert_data" >"$output_dir/${cert_name}.crt" echo "$private_data" >"$output_dir/${cert_name}.key" chmod 600 "$output_dir/${cert_name}.key" # Save intermediate certificate if available local intermediate_data=$(echo "$CERT_CONTENT" | jq -r '.intermediate // empty') if [ -n "$intermediate_data" ]; then echo "$intermediate_data" >"$output_dir/${cert_name}.chain.crt" fi # Save metadata echo "$CERT_META" | jq '.' >"$output_dir/${cert_name}_metadata.json" # Create ZIP file local zip_file="$output_dir/${cert_name}_certificate.zip" cd "$output_dir" || exit 1 zip -q "$zip_file" "${cert_name}.crt" "${cert_name}.key" "${cert_name}_metadata.json" [ -f "${cert_name}.chain.crt" ] && zip -q "$zip_file" "${cert_name}.chain.crt" cd - >/dev/null || exit 1 echo -e " āœ… ${COLOR_GREEN}Certificate downloaded successfully${CoR}" echo -e " Files created:" echo -e " • ${COLOR_CYAN}${cert_name}.crt${CoR} (Certificate)" echo -e " • ${COLOR_CYAN}${cert_name}.key${CoR} (Private Key)" [ -f "$output_dir/${cert_name}.chain.crt" ] && echo -e " • ${COLOR_CYAN}${cert_name}.chain.crt${CoR} (Chain)" echo -e " • ${COLOR_CYAN}${cert_name}_metadata.json${CoR} (Metadata)" echo -e " • ${COLOR_CYAN}${cert_name}_certificate.zip${CoR} (ZIP Archive)" return 0 fi fi # Fallback to old API (ZIP download) echo -e " āš ļø ${COLOR_YELLOW}New API failed, trying legacy ZIP download...${CoR}" local TMP_DIR=$(mktemp -d) local zip_file="$TMP_DIR/certificate.zip" # Download ZIP file local download_response download_response=$(curl -s -w "%{http_code}" -X GET "$BASE_URL/nginx/certificates/$cert_id/download" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -o "$zip_file") local http_code="${download_response: -3}" if [ "$http_code" = "200" ] && [ -f "$zip_file" ] && [ -s "$zip_file" ]; then echo -e " āœ… ${COLOR_GREEN}Legacy ZIP download successful${CoR}" # Extract ZIP file if command -v unzip >/dev/null 2>&1; then unzip -q "$zip_file" -d "$TMP_DIR" # Find certificate files with wildcards (supporting cert8.pem, cert9.pem, etc.) local cert_file=$(find "$TMP_DIR" -name "cert*.pem" | head -1) local key_file=$(find "$TMP_DIR" -name "privkey*.pem" | head -1) local chain_file=$(find "$TMP_DIR" -name "chain*.pem" | head -1) local fullchain_file=$(find "$TMP_DIR" -name "fullchain*.pem" | head -1) if [ -n "$cert_file" ] && [ -n "$key_file" ]; then # Copy files to output directory cp "$cert_file" "$output_dir/${cert_name}.crt" cp "$key_file" "$output_dir/${cert_name}.key" chmod 600 "$output_dir/${cert_name}.key" # Copy chain file if available if [ -n "$chain_file" ]; then cp "$chain_file" "$output_dir/${cert_name}.chain.crt" fi # Copy fullchain if available if [ -n "$fullchain_file" ]; then cp "$fullchain_file" "$output_dir/${cert_name}.fullchain.crt" fi # Save metadata echo "$CERT_META" | jq '.' >"$output_dir/${cert_name}_metadata.json" # Create new ZIP file with standardized names local final_zip="$output_dir/${cert_name}_certificate.zip" cd "$output_dir" || exit 1 zip -q "$final_zip" "${cert_name}.crt" "${cert_name}.key" "${cert_name}_metadata.json" [ -f "${cert_name}.chain.crt" ] && zip -q "$final_zip" "${cert_name}.chain.crt" [ -f "${cert_name}.fullchain.crt" ] && zip -q "$final_zip" "${cert_name}.fullchain.crt" cd - >/dev/null || exit 1 echo -e " āœ… ${COLOR_GREEN}Certificate downloaded successfully (legacy method)${CoR}" echo -e " Files created:" echo -e " • ${COLOR_CYAN}${cert_name}.crt${CoR} (Certificate)" echo -e " • ${COLOR_CYAN}${cert_name}.key${CoR} (Private Key)" [ -f "$output_dir/${cert_name}.chain.crt" ] && echo -e " • ${COLOR_CYAN}${cert_name}.chain.crt${CoR} (Chain)" [ -f "$output_dir/${cert_name}.fullchain.crt" ] && echo -e " • ${COLOR_CYAN}${cert_name}.fullchain.crt${CoR} (Full Chain)" echo -e " • ${COLOR_CYAN}${cert_name}_metadata.json${CoR} (Metadata)" echo -e " • ${COLOR_CYAN}${cert_name}_certificate.zip${CoR} (ZIP Archive)" # Cleanup rm -rf "$TMP_DIR" return 0 else echo -e " ā›” ${COLOR_RED}Error: Could not find certificate files in ZIP${CoR}" fi else echo -e " ā›” ${COLOR_RED}Error: unzip command not found${CoR}" fi else echo -e " ā›” ${COLOR_RED}Error: Failed to download certificate ZIP (HTTP $http_code)${CoR}" fi # Cleanup on failure rm -rf "$TMP_DIR" echo -e " ā›” ${COLOR_RED}Certificate download failed${CoR}" exit 1 } # Verify Cloudflare API Key validity verify_cloudflare_api_key() { local api_key="$1" local email="$2" echo -e " šŸ” Verifying Cloudflare API Key..." # Test API call to Cloudflare local response=$(curl -s -X GET "https://api.cloudflare.com/client/v4/user" \ -H "X-Auth-Email: $email" \ -H "X-Auth-Key: $api_key" \ -H "Content-Type: application/json") # Check if the API call was successful if [ "$(echo "$response" | jq -r '.success')" = "true" ]; then local username=$(echo "$response" | jq -r '.result.username') echo -e " āœ… ${COLOR_GREEN}Cloudflare API Key is valid${CoR}" echo -e " šŸ‘¤ Connected as: ${COLOR_CYAN}$username${CoR}" return 0 else local error_msg=$(echo "$response" | jq -r '.errors[0].message') echo -e " āŒ ${COLOR_RED}Invalid Cloudflare API Key${CoR}" echo -e " ā›” Error: $error_msg" return 1 fi } ############################### # Create or update a proxy host based on the existence of the domain create_or_update_proxy_host() { # Add static variable to track execution if [ "${FUNCTION_CALLED:-0}" -eq 1 ]; then return 0 fi FUNCTION_CALLED=1 # Check for wildcard domains in host creation if [[ "$DOMAIN_NAMES" == \** ]]; then echo -e "\n ā›” ${COLOR_RED}ERROR: Wildcard domains (*.domain.com) are not allowed for host creation${CoR}" echo -e " Wildcards are only supported for SSL certificates" exit 1 fi check_token_notverbose # Check if the host already exists #echo -e "\n šŸ”Ž Checking if the host ${COLOR_RED}$DOMAIN_NAMES${CoR} already exists..." RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") EXISTING_HOST=$(echo "$RESPONSE" | jq -r --arg DOMAIN "$DOMAIN_NAMES" '.[] | select(.domain_names[] == $DOMAIN)') HOST_ID=$(echo "$EXISTING_HOST" | jq -r '.id // empty') # Prepare JSON data for API if [[ -z "$CUSTOM_LOCATIONS" || "$CUSTOM_LOCATIONS" == "null" ]]; then CUSTOM_LOCATIONS_ESCAPED="[]" else CUSTOM_LOCATIONS_ESCAPED=$(echo "$CUSTOM_LOCATIONS" | jq -c . 2>/dev/null || echo '[]') fi # Convert boolean to JSON CACHING_ENABLED_JSON=$([ "$CACHING_ENABLED" == "true" ] && echo true || echo false) BLOCK_EXPLOITS_JSON=$([ "$BLOCK_EXPLOITS" == "true" ] && echo true || echo false) ALLOW_WEBSOCKET_UPGRADE_JSON=$([ "$ALLOW_WEBSOCKET_UPGRADE" == "true" ] && echo true || echo false) HTTP2_SUPPORT_JSON=$([ "$HTTP2_SUPPORT" == "true" ] && echo true || echo false) # Generate JSON DATA=$(jq -n \ --arg domain "$DOMAIN_NAMES" \ --arg host "$FORWARD_HOST" \ --arg port "$FORWARD_PORT" \ --arg scheme "$FORWARD_SCHEME" \ --argjson caching "$CACHING_ENABLED_JSON" \ --argjson block_exploits "$BLOCK_EXPLOITS_JSON" \ --arg advanced_config "$ADVANCED_CONFIG" \ --argjson websocket_upgrade "$ALLOW_WEBSOCKET_UPGRADE_JSON" \ --argjson http2_support "$HTTP2_SUPPORT_JSON" \ --argjson enabled true \ --argjson locations "$CUSTOM_LOCATIONS_ESCAPED" \ '{ domain_names: [$domain], forward_host: $host, forward_port: ($port | tonumber), access_list_id: null, certificate_id: null, ssl_forced: false, caching_enabled: $caching, block_exploits: $block_exploits, advanced_config: $advanced_config, meta: { dns_challenge: null }, allow_websocket_upgrade: $websocket_upgrade, http2_support: $http2_support, forward_scheme: $scheme, enabled: $enabled, locations: $locations }') # Check if the JSON is valid if ! echo "$DATA" | jq empty >/dev/null 2>&1; then echo -e " ${COLOR_RED}ā›” ERROR: Invalid JSON generated:\n$DATA${CoR}" exit 1 fi if [ -n "$HOST_ID" ]; then # Update existing HOST #echo -e "\n ${COLOR_CYAN}šŸ”„${CoR} Updating the proxy host for ${COLOR_GREEN}$DOMAIN_NAMES${CoR}" if [ "$AUTO_YES" != "true" ]; then echo -e " ${COLOR_YELLOW}šŸ‘‰ Do you want to update this host ${CoR} $DOMAIN_NAMES ${COLOR_YELLOW}?${CoR}" read -n 1 -r -p " (y/n): " answer echo if [[ ! $answer =~ ^[Yy]$ ]]; then echo -e " ${COLOR_YELLOW}🚫 No changes made.${CoR}\n" return 0 fi fi echo -e "${CoR}" METHOD="PUT" URL="$BASE_URL/nginx/proxy-hosts/$HOST_ID" else # Create NEW HOST echo -e "\n ${COLOR_CYAN}šŸŒ${CoR} Creating a new proxy host: ${COLOR_GREEN}$DOMAIN_NAMES${CoR}" METHOD="POST" URL="$BASE_URL/nginx/proxy-hosts" fi # Send API request RESPONSE=$(curl -s -X "$METHOD" "$URL" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8" \ --data-raw "$DATA") # Check API response ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // empty') if [ -z "$ERROR_MSG" ]; then PROXY_ID=$(echo "$RESPONSE" | jq -r '.id // "unknown"') # If certificate generation is requested if [ "$CERT_GENERATE" = true ]; then #echo -e " ${COLOR_YELLOW}šŸ” Generate SSL certificate CREATE_OR_${CoR}" # Check if it's a wildcard certificate (the host itself can't be a wildcard) if [[ "$CERT_DOMAIN" == \** ]]; then # Use --dns-provider / --dns-credentials, prompt only when missing if [ -z "$CERT_DNS_PROVIDER" ] || [ -z "$CERT_DNS_CREDENTIALS" ]; then echo -e " āš ļø ${COLOR_YELLOW}Wildcard certificate requires DNS challenge${CoR}" if [ "$AUTO_YES" = true ]; then echo -e " ā›” ${COLOR_RED}Missing --dns-provider / --dns-credentials${CoR}\n" exit 1 fi echo -e " šŸ‘‰ Please provide DNS provider and credentials:" [ -z "$CERT_DNS_PROVIDER" ] && read -r -p " DNS Provider (cloudflare, dynu, etc.): " CERT_DNS_PROVIDER [ -z "$CERT_DNS_CREDENTIALS" ] && read -r -p " DNS Credentials (JSON): " CERT_DNS_CREDENTIALS fi # Verify Cloudflare Global API key if provided (API tokens are not checked) local cf_key cf_email cf_key=$(echo "$CERT_DNS_CREDENTIALS" | jq -r '.dns_cloudflare_api_key // empty' 2>/dev/null || true) cf_email=$(echo "$CERT_DNS_CREDENTIALS" | jq -r '.dns_cloudflare_email // empty' 2>/dev/null || true) cf_email=${cf_email:-$CERT_EMAIL} if [[ "${CERT_DNS_PROVIDER,,}" == "cloudflare" ]] && [[ "$cf_key" =~ ^[0-9a-fA-F]{37}$ ]]; then if ! verify_cloudflare_api_key "$cf_key" "$cf_email"; then echo -e " ā›” ${COLOR_RED}Cannot proceed with invalid Cloudflare API Key${CoR}" return 1 fi # Verify domain is managed by Cloudflare local domain=${CERT_DOMAIN#\*.} local zone_check=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$domain" \ -H "X-Auth-Email: $cf_email" \ -H "X-Auth-Key: $cf_key" \ -H "Content-Type: application/json") if [ "$(echo "$zone_check" | jq -r '.result | length')" -eq 0 ]; then echo -e " ā›” ${COLOR_RED}Domain $domain is not managed by Cloudflare${CoR}" echo -e " šŸ‘‰ Please make sure your domain is added to your Cloudflare account" return 1 fi echo -e " āœ… ${COLOR_GREEN}Domain verification successful${CoR}" fi fi # Generate the certificate cert_generate "$CERT_DOMAIN" "$CERT_EMAIL" "$CERT_DNS_PROVIDER" "$CERT_DNS_CREDENTIALS" # Check SSL creation CERT_CHECK=$(curl -s -X GET "$BASE_URL/nginx/certificates" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") CERT_ID=$(echo "$CERT_CHECK" | jq -r --arg domain "$CERT_DOMAIN" \ '.[] | select(.domain_names[] == $domain) | .id' | sort -n | tail -n1) if [ -n "$CERT_ID" ]; then #echo -e "\n ${COLOR_YELLOW}✨ Automatic SSL Activation${CoR} $CERT_DOMAIN " # update certificat with HOST ID UPDATE_DATA=$(jq -n \ --arg cert_id "$CERT_ID" \ '{ certificate_id: $cert_id, ssl_forced: true, http2_support: true, hsts_enabled: false, hsts_subdomains: false, enabled: true }') UPDATE_RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" -X PUT \ "$BASE_URL/nginx/proxy-hosts/$PROXY_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json" \ --data "$UPDATE_DATA") UPDATE_STATUS=${UPDATE_RESPONSE##*HTTPSTATUS:} # Check if successfull if [ "$UPDATE_STATUS" -eq 200 ]; then echo -e "\n āœ… ${COLOR_GREEN}SSL Configuration Complete ${CoR}šŸŽ‰" echo -e " šŸ“‹ CHECK SSL Status for ${COLOR_GREEN}$DOMAIN_NAMES${CoR}:" echo -e " ā”œā”€ šŸ”’ SSL: ${COLOR_GREEN}Enabled${CoR}" echo -e " ā”œā”€ šŸ“œ Certificate ID: $CERT_ID" echo -e " ā”œā”€ šŸš€ HTTP/2: ${COLOR_GREEN}Active${CoR}" echo -e " ā”œā”€ šŸ›”ļø HSTS: ${COLOR_RED}Disabled${CoR}" echo -e " └─ 🌐 HSTS Subdomains: ${COLOR_RED}Disabled${CoR}\n" exit 0 else echo -e " ā›” ${COLOR_RED}Failed to enable SSL. Status code: $UPDATE_STATUS${CoR}\n" exit 1 fi else echo -e " ā›” ${COLOR_RED}Certificate not found after generation${CoR}\n" exit 1 fi fi if [ "$METHOD" = "PUT" ]; then echo -e " āœ… ${COLOR_GREEN}Proxy host šŸ”—$DOMAIN_NAMES (ID: ${COLOR_YELLOW}$PROXY_ID${COLOR_GREEN}) OK${CoR}\n" else echo -e " āœ… ${COLOR_GREEN}Proxy host šŸ”—$DOMAIN_NAMES (ID: ${COLOR_YELLOW}$PROXY_ID${COLOR_GREEN}) created successfully! šŸŽ‰${CoR}\n" fi else echo -e " ā›” ${COLOR_RED}Operation failed. Error: $ERROR_MSG${CoR}\n" exit 1 fi } # List all proxy hosts with basic details, including SSL certificate status and associated domain host_list() { check_token_notverbose RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ "$JSON_OUTPUT" = true ]; then echo "$RESPONSE" | jq . exit 0 fi echo -e "\n${COLOR_ORANGE} šŸ‘‰ List of proxy hosts ${CoR}\n" printf " %4s %-36s %-9s %-6s %-30s %-36s\n" "ID" " DOMAIN" " STATUS" " SSL" " TARGET" " CERT DOMAIN" # Clean the response to remove control characters CLEANED_RESPONSE=$(echo "$RESPONSE" | tr -d '\000-\031') # Fetch all certificates once and build an "id -> domain1, domain2" map, so we # don't make one API call per proxy host to resolve the CERT DOMAIN column. ALL_CERTS=$(curl -s -X GET "$BASE_URL/nginx/certificates" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") declare -A CERT_DOMAINS=() while IFS=$'\t' read -r _cid _cdoms; do [ -n "$_cid" ] && CERT_DOMAINS["$_cid"]="$_cdoms" done < <(echo "$ALL_CERTS" | jq -r '.[]? | select(.domain_names != null) | "\(.id)\t\(.domain_names | join(", "))"') # Fix PR #28: Use tab delimiter to safely handle multiple domain names # Fix PR #29: Extract forward_host, forward_port, forward_scheme for TARGET column echo "$CLEANED_RESPONSE" | jq -r '.[] | "\(.id)\t\(.domain_names | join(", "))\t\(.enabled)\t\(.certificate_id)\t\(.forward_scheme)\t\(.forward_host)\t\(.forward_port)"' | while IFS=$'\t' read -r id domain enabled certificate_id forward_scheme forward_host forward_port; do if [ "$enabled" = "true" ]; then status="$(echo -e "${WHITE_ON_GREEN} enabled ${CoR}")" else status="$(echo -e "${COLOR_RED} disable ${CoR}")" fi # Default SSL status ssl_status="$(pad "✘" 6)" ssl_color="${COLOR_RED}" cert_domain="" # Resolve the certificate domain from the prefetched map (no per-host call) if [ "$certificate_id" != "null" ] && [ -n "$certificate_id" ]; then cert_domain="${CERT_DOMAINS[$certificate_id]:-}" if [ -n "$cert_domain" ]; then ssl_status="$(pad "$certificate_id" 6)" ssl_color="${COLOR_CYAN}" fi fi # Build TARGET column (PR #29): scheme://host:port if [ -n "$forward_host" ] && [ "$forward_host" != "null" ] && [ -n "$forward_port" ] && [ "$forward_port" != "null" ]; then target="${forward_scheme:-http}://${forward_host}:${forward_port}" else target="N/A" fi # Split the domain list: the first domain goes on the main row, any extra # domains are printed below, indented under the DOMAIN column. This keeps # all columns aligned even with long or multiple domain names. IFS=', ' read -ra domain_arr <<<"$domain" primary_domain="${domain_arr[0]}" # Print the row with colors, TARGET column, and certificate domain (if available) printf " ${COLOR_YELLOW}%4s${CoR} ${COLOR_GREEN}%s${CoR} %-9s ${ssl_color}%-6s${CoR} ${COLOR_CYAN}%s${CoR} ${COLOR_CYAN}%s${CoR}\n" \ "$id" "$(truncate_pad "$primary_domain" 36)" "$status" "$ssl_status" "$(truncate_pad "$target" 30)" "$cert_domain" # Continuation lines for the remaining domains (indented under DOMAIN, col 8) for ((i = 1; i < ${#domain_arr[@]}; i++)); do printf " ${COLOR_GREEN}%s${CoR}\n" "$(truncate_pad "${domain_arr[i]}" 36)" done done echo "" exit 0 } ################################ # List all proxy hosts with full details host_list_full() { check_token_notverbose RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") echo "$RESPONSE" | jq -c '.[]' | while read -r proxy; do echo "$proxy" | jq . done echo "" exit 0 } ################################ # List all redirection hosts redirect_host_list() { check_token_notverbose RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/redirection-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ "$JSON_OUTPUT" = true ]; then echo "$RESPONSE" | jq . exit 0 fi echo -e "\n${COLOR_ORANGE} šŸ‘‰ List of Redirection hosts ${CoR}\n" printf " %4s %-36s %-9s %-7s %-7s %-36s\n" "ID" " DOMAIN" " STATUS" " CODE" " SSL" " FORWARD DOMAIN" CLEANED_RESPONSE=$(echo "$RESPONSE" | tr -d '\000-\031') echo "$CLEANED_RESPONSE" | jq -r '.[] | "\(.id)\t\(.domain_names | join(", "))\t\(.enabled)\t\(.forward_http_code)\t\(.certificate_id)\t\(.forward_domain_name)"' | while IFS=$'\t' read -r id domain enabled http_code certificate_id forward_domain; do if [ "$enabled" = "true" ]; then status="$(echo -e "${WHITE_ON_GREEN} enabled ${CoR}")" else status="$(echo -e "${COLOR_RED} disable ${CoR}")" fi ssl_status="$(pad "✘" 7)" ssl_color="${COLOR_RED}" if [ "$certificate_id" != "null" ] && [ -n "$certificate_id" ]; then ssl_status="$(pad "$certificate_id" 7)" ssl_color="${COLOR_CYAN}" fi # Split the domain list: the first domain goes on the main row, any extra # domains are printed below, indented under the DOMAIN column. Keeps all # columns aligned even with long or multiple domains (same as host_list). IFS=', ' read -ra domain_arr <<<"$domain" primary_domain="${domain_arr[0]}" printf " ${COLOR_YELLOW}%4s${CoR} ${COLOR_GREEN}%s${CoR} %-9s ${COLOR_CYAN}%-7s${CoR} ${ssl_color}%-7s${CoR} ${COLOR_CYAN}%s${CoR}\n" \ "$id" "$(truncate_pad "$primary_domain" 36)" "$status" "$http_code" "$ssl_status" "$(truncate_pad "$forward_domain" 36)" # Continuation lines for the remaining domains (indented under DOMAIN, col 8) for ((i = 1; i < ${#domain_arr[@]}; i++)); do printf " ${COLOR_GREEN}%s${CoR}\n" "$(truncate_pad "${domain_arr[i]}" 36)" done done echo "" exit 0 } ################################ # Create or Update a redirection host # Reads globals: DOMAIN_NAMES, FORWARD_DOMAIN, FORWARD_SCHEME, FORWARD_HTTP_CODE, # PRESERVE_PATH, BLOCK_EXPLOITS, ADVANCED_CONFIG, AUTO_YES create_or_update_redirect_host() { if [ "${REDIRECT_FUNCTION_CALLED:-0}" -eq 1 ]; then return 0 fi REDIRECT_FUNCTION_CALLED=1 if [ -z "$DOMAIN_NAMES" ] || [ -z "$FORWARD_DOMAIN" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: Missing required parameters (domain, forward domain).${CoR}" echo -e " Usage: ${COLOR_ORANGE}$0 --redirect-host-create domain.com --forward-domain target.com${CoR}" exit 1 fi # Validate HTTP code if ! [[ "$FORWARD_HTTP_CODE" =~ ^(301|302|303|307|308)$ ]]; then echo -e "\n ā›” ${COLOR_RED}ERROR: Invalid HTTP code '$FORWARD_HTTP_CODE'. Must be 301, 302, 303, 307, or 308.${CoR}" exit 1 fi # Validate forward scheme if ! [[ "$FORWARD_SCHEME" =~ ^(http|https)$ ]]; then echo -e "\n ā›” ${COLOR_RED}ERROR: Invalid forward scheme '$FORWARD_SCHEME'. Must be 'http' or 'https'.${CoR}" exit 1 fi check_token_notverbose # Check if a redirect host for this domain already exists RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/redirection-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") EXISTING=$(echo "$RESPONSE" | jq -r --arg DOMAIN "$DOMAIN_NAMES" '.[] | select(.domain_names[] == $DOMAIN)') REDIRECT_ID=$(echo "$EXISTING" | jq -r '.id // empty') # Convert booleans to JSON PRESERVE_PATH_JSON=$([ "$PRESERVE_PATH" == "true" ] && echo true || echo false) BLOCK_EXPLOITS_JSON=$([ "$BLOCK_EXPLOITS" == "true" ] && echo true || echo false) DATA=$(jq -n \ --arg domain "$DOMAIN_NAMES" \ --arg forward_scheme "$FORWARD_SCHEME" \ --argjson forward_http_code "$FORWARD_HTTP_CODE" \ --arg forward_domain_name "$FORWARD_DOMAIN" \ --argjson preserve_path "$PRESERVE_PATH_JSON" \ --argjson block_exploits "$BLOCK_EXPLOITS_JSON" \ --arg advanced_config "$ADVANCED_CONFIG" \ '{ domain_names: [$domain], forward_scheme: $forward_scheme, forward_http_code: $forward_http_code, forward_domain_name: $forward_domain_name, preserve_path: $preserve_path, block_exploits: $block_exploits, advanced_config: $advanced_config, meta: {} }') if ! echo "$DATA" | jq empty >/dev/null 2>&1; then echo -e " ${COLOR_RED}ā›” ERROR: Invalid JSON generated:\n$DATA${CoR}" exit 1 fi if [ -n "$REDIRECT_ID" ]; then if [ "$AUTO_YES" != "true" ]; then echo -e " ${COLOR_YELLOW}šŸ‘‰ Do you want to update this redirect host ${CoR} $DOMAIN_NAMES ${COLOR_YELLOW}?${CoR}" read -n 1 -r -p " (y/n): " answer echo if [[ ! $answer =~ ^[Yy]$ ]]; then echo -e " ${COLOR_YELLOW}🚫 No changes made.${CoR}\n" return 0 fi fi echo -e "\n ${COLOR_CYAN}šŸ”„${CoR} Updating redirect host: ${COLOR_GREEN}$DOMAIN_NAMES${CoR}" METHOD="PUT" URL="$BASE_URL/nginx/redirection-hosts/$REDIRECT_ID" else echo -e "\n ${COLOR_CYAN}šŸ”€${CoR} Creating redirect host: ${COLOR_GREEN}$DOMAIN_NAMES${CoR} → ${COLOR_YELLOW}$FORWARD_DOMAIN${CoR}" METHOD="POST" URL="$BASE_URL/nginx/redirection-hosts" fi RESPONSE=$(curl -s -X "$METHOD" "$URL" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8" \ --data-raw "$DATA") ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // empty') if [ -z "$ERROR_MSG" ]; then RID=$(echo "$RESPONSE" | jq -r '.id // "unknown"') if [ "$METHOD" = "PUT" ]; then echo -e " āœ… ${COLOR_GREEN}Redirect host šŸ”—$DOMAIN_NAMES (ID: ${COLOR_YELLOW}$RID${COLOR_GREEN}) updated successfully! šŸŽ‰${CoR}\n" else echo -e " āœ… ${COLOR_GREEN}Redirect host šŸ”—$DOMAIN_NAMES (ID: ${COLOR_YELLOW}$RID${COLOR_GREEN}) created successfully! šŸŽ‰${CoR}\n" fi else echo -e " ā›” ${COLOR_RED}Operation failed. Error: $ERROR_MSG${CoR}\n" exit 1 fi } ################################ # Delete a redirection host by ID redirect_host_delete() { local host_id="$1" if [ -z "$host_id" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: The --redirect-host-delete option requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --redirect-host-delete ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --redirect-host-delete 5${CoR}" exit 1 fi check_token_notverbose CHECK_RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" \ "$BASE_URL/nginx/redirection-hosts/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" 2>/dev/null) CHECK_BODY=${CHECK_RESPONSE//HTTPSTATUS:*/} CHECK_STATUS=${CHECK_RESPONSE##*HTTPSTATUS:} if [ "$CHECK_STATUS" -eq 404 ]; then echo -e " ā›” ${COLOR_RED}ERROR: Redirect host ID ${COLOR_YELLOW}$host_id${COLOR_RED} not found!${CoR}" exit 1 elif [ "$CHECK_STATUS" -ne 200 ]; then echo -e " ā›” ${COLOR_RED}ERROR: Failed to check redirect host. Status: $CHECK_STATUS${CoR}" exit 1 fi DOMAIN_NAME=$(echo "$CHECK_BODY" | jq -r '.domain_names[0] // "unknown"') if [ "$AUTO_YES" != true ]; then echo -e " ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€" echo -e " │ ID: ${COLOR_YELLOW}$host_id${CoR}" echo -e " │ Domain: ${COLOR_GREEN}$DOMAIN_NAME${CoR}" echo -e " └───────────────────────────────────────────" echo -e " āš ļø ${COLOR_RED}WARNING: This action cannot be undone!${CoR}" read -n 1 -r -p " šŸ”” Confirm deletion? (y/n): " CONFIRM echo if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then echo -e " āŒ ${COLOR_RED}Operation cancelled${CoR}" exit 1 fi else echo -e "\n ${COLOR_YELLOW}šŸ”” -y Auto-confirming deletion${CoR}" fi echo -e " šŸ—‘ļø Deleting redirect host ${COLOR_GREEN}$DOMAIN_NAME${CoR} (ID: ${COLOR_GREEN}$host_id${CoR})..." RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" -X DELETE \ "$BASE_URL/nginx/redirection-hosts/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" 2>/dev/null) HTTP_STATUS=${RESPONSE##*HTTPSTATUS:} if [ "${HTTP_STATUS:-0}" -eq 200 ]; then echo -e " āœ… ${COLOR_GREEN}Redirect host ${COLOR_YELLOW}$DOMAIN_NAME${CoR} (ID: $host_id) deleted successfully!${CoR}\n" exit 0 else echo -e " ā›” ${COLOR_RED}Failed to delete redirect host. Status: $HTTP_STATUS${CoR}" exit 1 fi } ################################ # Enable a redirection host by ID redirect_host_enable() { local host_id="$1" if [ -z "$host_id" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: The --redirect-host-enable option requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --redirect-host-enable ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --redirect-host-enable 5${CoR}" return 1 fi check_token_notverbose CHECK_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/redirection-hosts/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if echo "$CHECK_RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " ā›” ${COLOR_RED}Redirect host with ID $host_id does not exist${CoR}" return 1 fi DOMAIN_NAME=$(echo "$CHECK_RESPONSE" | jq -r '.domain_names[0]') echo -e "\n ${COLOR_YELLOW}šŸ”„ Enabling redirect host ${CoR} šŸ†”${COLOR_CYAN}$host_id${CoR} 🌐Domain: ${COLOR_CYAN}$DOMAIN_NAME${CoR}" RESPONSE=$(curl -s -X POST "$BASE_URL/nginx/redirection-hosts/$host_id/enable" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8") if ! echo "$RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " āœ… ${COLOR_GREEN}Successfully enabled ${CoR}šŸ†”${COLOR_CYAN}$host_id${CoR} 🌐Domain: ${COLOR_CYAN}$DOMAIN_NAME${CoR}" else ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // "Unknown error"') echo -e " ā›” ${COLOR_RED}Failed to enable redirect host: $ERROR_MSG${CoR}" return 1 fi } ################################ # Disable a redirection host by ID redirect_host_disable() { local host_id="$1" if [ -z "$host_id" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: The --redirect-host-disable option requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --redirect-host-disable ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --redirect-host-disable 5${CoR}" return 1 fi check_token_notverbose CHECK_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/redirection-hosts/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if echo "$CHECK_RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " ā›” ${COLOR_RED}Redirect host with ID $host_id does not exist${CoR}" return 1 fi DOMAIN_NAME=$(echo "$CHECK_RESPONSE" | jq -r '.domain_names[0]') echo -e "\n ${COLOR_YELLOW}šŸ”„ Disabling redirect host ${CoR} šŸ†”${COLOR_CYAN}$host_id${CoR} 🌐Domain: ${COLOR_CYAN}$DOMAIN_NAME${CoR}" RESPONSE=$(curl -s -X POST "$BASE_URL/nginx/redirection-hosts/$host_id/disable" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8") if ! echo "$RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " āœ… ${COLOR_GREEN}Successfully disabled ${CoR}šŸ†”${COLOR_CYAN}$host_id${CoR} 🌐Domain: ${COLOR_CYAN}$DOMAIN_NAME${CoR}" else ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // "Unknown error"') echo -e " ā›” ${COLOR_RED}Failed to disable redirect host: $ERROR_MSG${CoR}" return 1 fi } ################################ # List all stream hosts (TCP/UDP forwarding) stream_host_list() { check_token_notverbose RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/streams" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ "$JSON_OUTPUT" = true ]; then echo "$RESPONSE" | jq . exit 0 fi echo -e "\n${COLOR_ORANGE} šŸ‘‰ List of Stream hosts ${CoR}\n" printf " %4s %-8s %-9s %-4s %-4s %-36s\n" "ID" " PORT" " STATUS" " TCP" " UDP" " FORWARD HOST:PORT" CLEANED_RESPONSE=$(echo "$RESPONSE" | tr -d '\000-\031') echo "$CLEANED_RESPONSE" | jq -r '.[] | "\(.id)\t\(.incoming_port)\t\(.enabled)\t\(.tcp_forwarding)\t\(.udp_forwarding)\t\(.forwarding_host):\(.forwarding_port)"' | while IFS=$'\t' read -r id port enabled tcp udp forward; do if [ "$enabled" = "true" ]; then status="$(echo -e "${WHITE_ON_GREEN} enabled ${CoR}")" else status="$(echo -e "${COLOR_RED} disable ${CoR}")" fi tcp_flag="✘" [ "$tcp" = "true" ] && tcp_flag="āœ”" udp_flag="✘" [ "$udp" = "true" ] && udp_flag="āœ”" printf " ${COLOR_YELLOW}%4s${CoR} ${COLOR_GREEN}%-7s${CoR} %-9s ${COLOR_CYAN}%-4s${CoR} ${COLOR_CYAN}%-4s${CoR} ${COLOR_CYAN}%s${CoR}\n" \ "$id" "$port" "$status" "$tcp_flag" "$udp_flag" "$(truncate_pad "$forward" 36)" done echo "" exit 0 } ################################ # Create or Update a stream host # Reads globals: INCOMING_PORT, FORWARD_HOST, FORWARD_PORT, TCP_FORWARDING, # UDP_FORWARDING, CERT_ID, AUTO_YES create_or_update_stream_host() { if [ -z "$INCOMING_PORT" ] || [ -z "$FORWARD_HOST" ] || [ -z "$FORWARD_PORT" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: Missing required parameters (incoming port, forward host, forward port).${CoR}" echo -e " Usage: ${COLOR_ORANGE}$0 --stream-host-create --forward-host --forward-port [--tcp] [--udp]${CoR}" exit 1 fi if [ "$TCP_FORWARDING" != "true" ] && [ "$UDP_FORWARDING" != "true" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: At least one of --tcp or --udp must be enabled.${CoR}" exit 1 fi check_token_notverbose # Check if a stream host for this incoming port already exists RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/streams" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") EXISTING=$(echo "$RESPONSE" | jq -r --argjson PORT "$INCOMING_PORT" '.[] | select(.incoming_port == $PORT)') STREAM_ID=$(echo "$EXISTING" | jq -r '.id // empty') DATA=$(jq -n \ --argjson incoming_port "$INCOMING_PORT" \ --arg forwarding_host "$FORWARD_HOST" \ --argjson forwarding_port "$FORWARD_PORT" \ --argjson tcp_forwarding "$TCP_FORWARDING" \ --argjson udp_forwarding "$UDP_FORWARDING" \ --argjson certificate_id "${CERT_ID:-0}" \ '{ incoming_port: $incoming_port, forwarding_host: $forwarding_host, forwarding_port: $forwarding_port, tcp_forwarding: $tcp_forwarding, udp_forwarding: $udp_forwarding, certificate_id: $certificate_id, meta: {} }') if ! echo "$DATA" | jq empty >/dev/null 2>&1; then echo -e " ${COLOR_RED}ā›” ERROR: Invalid JSON generated:\n$DATA${CoR}" exit 1 fi if [ -n "$STREAM_ID" ]; then if [ "$AUTO_YES" != "true" ]; then echo -e " ${COLOR_YELLOW}šŸ‘‰ Do you want to update this stream host ${CoR} port $INCOMING_PORT ${COLOR_YELLOW}?${CoR}" read -n 1 -r -p " (y/n): " answer echo if [[ ! $answer =~ ^[Yy]$ ]]; then echo -e " ${COLOR_YELLOW}🚫 No changes made.${CoR}\n" return 0 fi fi echo -e "\n ${COLOR_CYAN}šŸ”„${CoR} Updating stream host: ${COLOR_GREEN}port $INCOMING_PORT${CoR}" METHOD="PUT" URL="$BASE_URL/nginx/streams/$STREAM_ID" else echo -e "\n ${COLOR_CYAN}šŸ”Œ${CoR} Creating stream host: ${COLOR_GREEN}port $INCOMING_PORT${CoR} → ${COLOR_YELLOW}$FORWARD_HOST:$FORWARD_PORT${CoR}" METHOD="POST" URL="$BASE_URL/nginx/streams" fi RESPONSE=$(curl -s -X "$METHOD" "$URL" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8" \ --data-raw "$DATA") ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // empty') if [ -z "$ERROR_MSG" ]; then RID=$(echo "$RESPONSE" | jq -r '.id // "unknown"') if [ "$METHOD" = "PUT" ]; then echo -e " āœ… ${COLOR_GREEN}Stream host šŸ”Œport $INCOMING_PORT (ID: ${COLOR_YELLOW}$RID${COLOR_GREEN}) updated successfully! šŸŽ‰${CoR}\n" else echo -e " āœ… ${COLOR_GREEN}Stream host šŸ”Œport $INCOMING_PORT (ID: ${COLOR_YELLOW}$RID${COLOR_GREEN}) created successfully! šŸŽ‰${CoR}\n" fi else echo -e " ā›” ${COLOR_RED}Operation failed. Error: $ERROR_MSG${CoR}\n" exit 1 fi } ################################ # Delete a stream host by ID stream_host_delete() { local host_id="$1" if [ -z "$host_id" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: The --stream-host-delete option requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --stream-host-delete ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --stream-host-delete 5${CoR}" exit 1 fi check_token_notverbose CHECK_RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" \ "$BASE_URL/nginx/streams/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" 2>/dev/null) CHECK_BODY=${CHECK_RESPONSE//HTTPSTATUS:*/} CHECK_STATUS=${CHECK_RESPONSE##*HTTPSTATUS:} if [ "$CHECK_STATUS" -eq 404 ]; then echo -e " ā›” ${COLOR_RED}ERROR: Stream host ID ${COLOR_YELLOW}$host_id${COLOR_RED} not found!${CoR}" exit 1 elif [ "$CHECK_STATUS" -ne 200 ]; then echo -e " ā›” ${COLOR_RED}ERROR: Failed to check stream host. Status: $CHECK_STATUS${CoR}" exit 1 fi STREAM_PORT=$(echo "$CHECK_BODY" | jq -r '.incoming_port // "unknown"') if [ "$AUTO_YES" != true ]; then echo -e " ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€" echo -e " │ ID: ${COLOR_YELLOW}$host_id${CoR}" echo -e " │ Port: ${COLOR_GREEN}$STREAM_PORT${CoR}" echo -e " └───────────────────────────────────────────" echo -e " āš ļø ${COLOR_RED}WARNING: This action cannot be undone!${CoR}" read -n 1 -r -p " šŸ”” Confirm deletion? (y/n): " CONFIRM echo if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then echo -e " āŒ ${COLOR_RED}Operation cancelled${CoR}" exit 1 fi else echo -e "\n ${COLOR_YELLOW}šŸ”” -y Auto-confirming deletion${CoR}" fi echo -e " šŸ—‘ļø Deleting stream host ${COLOR_GREEN}port $STREAM_PORT${CoR} (ID: ${COLOR_GREEN}$host_id${CoR})..." RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" -X DELETE \ "$BASE_URL/nginx/streams/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" 2>/dev/null) HTTP_STATUS=${RESPONSE##*HTTPSTATUS:} if [ "${HTTP_STATUS:-0}" -eq 200 ]; then echo -e " āœ… ${COLOR_GREEN}Stream host ${COLOR_YELLOW}port $STREAM_PORT${CoR} (ID: $host_id) deleted successfully!${CoR}\n" exit 0 else echo -e " ā›” ${COLOR_RED}Failed to delete stream host. Status: $HTTP_STATUS${CoR}" exit 1 fi } ################################ # Enable a stream host by ID stream_host_enable() { local host_id="$1" if [ -z "$host_id" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: The --stream-host-enable option requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --stream-host-enable ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --stream-host-enable 5${CoR}" return 1 fi check_token_notverbose CHECK_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/streams/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if echo "$CHECK_RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " ā›” ${COLOR_RED}Stream host with ID $host_id does not exist${CoR}" return 1 fi STREAM_PORT=$(echo "$CHECK_RESPONSE" | jq -r '.incoming_port') echo -e "\n ${COLOR_YELLOW}šŸ”„ Enabling stream host ${CoR} šŸ†”${COLOR_CYAN}$host_id${CoR} šŸ”ŒPort: ${COLOR_CYAN}$STREAM_PORT${CoR}" RESPONSE=$(curl -s -X POST "$BASE_URL/nginx/streams/$host_id/enable" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8") if ! echo "$RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " āœ… ${COLOR_GREEN}Successfully enabled ${CoR}šŸ†”${COLOR_CYAN}$host_id${CoR} šŸ”ŒPort: ${COLOR_CYAN}$STREAM_PORT${CoR}" else ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // "Unknown error"') echo -e " ā›” ${COLOR_RED}Failed to enable stream host: $ERROR_MSG${CoR}" return 1 fi } ################################ # Disable a stream host by ID stream_host_disable() { local host_id="$1" if [ -z "$host_id" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: The --stream-host-disable option requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --stream-host-disable ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --stream-host-disable 5${CoR}" return 1 fi check_token_notverbose CHECK_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/streams/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if echo "$CHECK_RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " ā›” ${COLOR_RED}Stream host with ID $host_id does not exist${CoR}" return 1 fi STREAM_PORT=$(echo "$CHECK_RESPONSE" | jq -r '.incoming_port') echo -e "\n ${COLOR_YELLOW}šŸ”„ Disabling stream host ${CoR} šŸ†”${COLOR_CYAN}$host_id${CoR} šŸ”ŒPort: ${COLOR_CYAN}$STREAM_PORT${CoR}" RESPONSE=$(curl -s -X POST "$BASE_URL/nginx/streams/$host_id/disable" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8") if ! echo "$RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " āœ… ${COLOR_GREEN}Successfully disabled ${CoR}šŸ†”${COLOR_CYAN}$host_id${CoR} šŸ”ŒPort: ${COLOR_CYAN}$STREAM_PORT${CoR}" else ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // "Unknown error"') echo -e " ā›” ${COLOR_RED}Failed to disable stream host: $ERROR_MSG${CoR}" return 1 fi } ################################ # List all dead hosts (404 Hosts) dead_host_list() { check_token_notverbose RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/dead-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ "$JSON_OUTPUT" = true ]; then echo "$RESPONSE" | jq . exit 0 fi echo -e "\n${COLOR_ORANGE} šŸ‘‰ List of Dead hosts (404) ${CoR}\n" printf " %4s %-36s %-9s %-7s\n" "ID" " DOMAIN" " STATUS" " SSL" CLEANED_RESPONSE=$(echo "$RESPONSE" | tr -d '\000-\031') echo "$CLEANED_RESPONSE" | jq -r '.[] | "\(.id)\t\(.domain_names | join(", "))\t\(.enabled)\t\(.certificate_id)"' | while IFS=$'\t' read -r id domain enabled certificate_id; do if [ "$enabled" = "true" ]; then status="$(echo -e "${WHITE_ON_GREEN} enabled ${CoR}")" else status="$(echo -e "${COLOR_RED} disable ${CoR}")" fi ssl_status="$(pad "✘" 7)" ssl_color="${COLOR_RED}" if [ "$certificate_id" != "null" ] && [ -n "$certificate_id" ] && [ "$certificate_id" != "0" ]; then ssl_status="$(pad "$certificate_id" 7)" ssl_color="${COLOR_CYAN}" fi IFS=', ' read -ra domain_arr <<<"$domain" primary_domain="${domain_arr[0]}" printf " ${COLOR_YELLOW}%4s${CoR} ${COLOR_GREEN}%s${CoR} %-9s ${ssl_color}%-7s${CoR}\n" \ "$id" "$(truncate_pad "$primary_domain" 36)" "$status" "$ssl_status" for ((i = 1; i < ${#domain_arr[@]}; i++)); do printf " ${COLOR_GREEN}%s${CoR}\n" "$(truncate_pad "${domain_arr[i]}" 36)" done done echo "" exit 0 } ################################ # Create or Update a dead host (404 host) # Reads globals: DOMAIN_NAMES, CERT_ID, SSL_FORCED, HSTS_ENABLED, HSTS_SUBDOMAINS, # HTTP2_SUPPORT, ADVANCED_CONFIG, AUTO_YES create_or_update_dead_host() { if [ -z "$DOMAIN_NAMES" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: Missing required parameter (domain).${CoR}" echo -e " Usage: ${COLOR_ORANGE}$0 --dead-host-create domain.com [options]${CoR}" exit 1 fi check_token_notverbose # Check if a dead host for this domain already exists RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/dead-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") EXISTING=$(echo "$RESPONSE" | jq -r --arg DOMAIN "$DOMAIN_NAMES" '.[] | select(.domain_names[] == $DOMAIN)') DEAD_ID=$(echo "$EXISTING" | jq -r '.id // empty') SSL_FORCED_JSON=$([ "$SSL_FORCED" == "true" ] && echo true || echo false) HSTS_ENABLED_JSON=$([ "$HSTS_ENABLED" == "true" ] && echo true || echo false) HSTS_SUBDOMAINS_JSON=$([ "$HSTS_SUBDOMAINS" == "true" ] && echo true || echo false) HTTP2_SUPPORT_JSON=$([ "$HTTP2_SUPPORT" == "true" ] && echo true || echo false) DATA=$(jq -n \ --arg domain "$DOMAIN_NAMES" \ --argjson certificate_id "${CERT_ID:-0}" \ --argjson ssl_forced "$SSL_FORCED_JSON" \ --argjson hsts_enabled "$HSTS_ENABLED_JSON" \ --argjson hsts_subdomains "$HSTS_SUBDOMAINS_JSON" \ --argjson http2_support "$HTTP2_SUPPORT_JSON" \ --arg advanced_config "$ADVANCED_CONFIG" \ '{ domain_names: [$domain], certificate_id: $certificate_id, ssl_forced: $ssl_forced, hsts_enabled: $hsts_enabled, hsts_subdomains: $hsts_subdomains, http2_support: $http2_support, advanced_config: $advanced_config, meta: {} }') if ! echo "$DATA" | jq empty >/dev/null 2>&1; then echo -e " ${COLOR_RED}ā›” ERROR: Invalid JSON generated:\n$DATA${CoR}" exit 1 fi if [ -n "$DEAD_ID" ]; then if [ "$AUTO_YES" != "true" ]; then echo -e " ${COLOR_YELLOW}šŸ‘‰ Do you want to update this dead host ${CoR} $DOMAIN_NAMES ${COLOR_YELLOW}?${CoR}" read -n 1 -r -p " (y/n): " answer echo if [[ ! $answer =~ ^[Yy]$ ]]; then echo -e " ${COLOR_YELLOW}🚫 No changes made.${CoR}\n" return 0 fi fi echo -e "\n ${COLOR_CYAN}šŸ”„${CoR} Updating dead host: ${COLOR_GREEN}$DOMAIN_NAMES${CoR}" METHOD="PUT" URL="$BASE_URL/nginx/dead-hosts/$DEAD_ID" else echo -e "\n ${COLOR_CYAN}🚫${CoR} Creating dead host (404): ${COLOR_GREEN}$DOMAIN_NAMES${CoR}" METHOD="POST" URL="$BASE_URL/nginx/dead-hosts" fi RESPONSE=$(curl -s -X "$METHOD" "$URL" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8" \ --data-raw "$DATA") ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // empty') if [ -z "$ERROR_MSG" ]; then RID=$(echo "$RESPONSE" | jq -r '.id // "unknown"') if [ "$METHOD" = "PUT" ]; then echo -e " āœ… ${COLOR_GREEN}Dead host 🚫$DOMAIN_NAMES (ID: ${COLOR_YELLOW}$RID${COLOR_GREEN}) updated successfully! šŸŽ‰${CoR}\n" else echo -e " āœ… ${COLOR_GREEN}Dead host 🚫$DOMAIN_NAMES (ID: ${COLOR_YELLOW}$RID${COLOR_GREEN}) created successfully! šŸŽ‰${CoR}\n" fi else echo -e " ā›” ${COLOR_RED}Operation failed. Error: $ERROR_MSG${CoR}\n" exit 1 fi } ################################ # Delete a dead host by ID dead_host_delete() { local host_id="$1" if [ -z "$host_id" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: The --dead-host-delete option requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --dead-host-delete ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --dead-host-delete 5${CoR}" exit 1 fi check_token_notverbose CHECK_RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" \ "$BASE_URL/nginx/dead-hosts/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" 2>/dev/null) CHECK_BODY=${CHECK_RESPONSE//HTTPSTATUS:*/} CHECK_STATUS=${CHECK_RESPONSE##*HTTPSTATUS:} if [ "$CHECK_STATUS" -eq 404 ]; then echo -e " ā›” ${COLOR_RED}ERROR: Dead host ID ${COLOR_YELLOW}$host_id${COLOR_RED} not found!${CoR}" exit 1 elif [ "$CHECK_STATUS" -ne 200 ]; then echo -e " ā›” ${COLOR_RED}ERROR: Failed to check dead host. Status: $CHECK_STATUS${CoR}" exit 1 fi DOMAIN_NAME=$(echo "$CHECK_BODY" | jq -r '.domain_names[0] // "unknown"') if [ "$AUTO_YES" != true ]; then echo -e " ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€" echo -e " │ ID: ${COLOR_YELLOW}$host_id${CoR}" echo -e " │ Domain: ${COLOR_GREEN}$DOMAIN_NAME${CoR}" echo -e " └───────────────────────────────────────────" echo -e " āš ļø ${COLOR_RED}WARNING: This action cannot be undone!${CoR}" read -n 1 -r -p " šŸ”” Confirm deletion? (y/n): " CONFIRM echo if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then echo -e " āŒ ${COLOR_RED}Operation cancelled${CoR}" exit 1 fi else echo -e "\n ${COLOR_YELLOW}šŸ”” -y Auto-confirming deletion${CoR}" fi echo -e " šŸ—‘ļø Deleting dead host ${COLOR_GREEN}$DOMAIN_NAME${CoR} (ID: ${COLOR_GREEN}$host_id${CoR})..." RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" -X DELETE \ "$BASE_URL/nginx/dead-hosts/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" 2>/dev/null) HTTP_STATUS=${RESPONSE##*HTTPSTATUS:} if [ "${HTTP_STATUS:-0}" -eq 200 ]; then echo -e " āœ… ${COLOR_GREEN}Dead host ${COLOR_YELLOW}$DOMAIN_NAME${CoR} (ID: $host_id) deleted successfully!${CoR}\n" exit 0 else echo -e " ā›” ${COLOR_RED}Failed to delete dead host. Status: $HTTP_STATUS${CoR}" exit 1 fi } ################################ # Enable a dead host by ID dead_host_enable() { local host_id="$1" if [ -z "$host_id" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: The --dead-host-enable option requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --dead-host-enable ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --dead-host-enable 5${CoR}" return 1 fi check_token_notverbose CHECK_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/dead-hosts/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if echo "$CHECK_RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " ā›” ${COLOR_RED}Dead host with ID $host_id does not exist${CoR}" return 1 fi DOMAIN_NAME=$(echo "$CHECK_RESPONSE" | jq -r '.domain_names[0]') echo -e "\n ${COLOR_YELLOW}šŸ”„ Enabling dead host ${CoR} šŸ†”${COLOR_CYAN}$host_id${CoR} 🌐Domain: ${COLOR_CYAN}$DOMAIN_NAME${CoR}" RESPONSE=$(curl -s -X POST "$BASE_URL/nginx/dead-hosts/$host_id/enable" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8") if ! echo "$RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " āœ… ${COLOR_GREEN}Successfully enabled ${CoR}šŸ†”${COLOR_CYAN}$host_id${CoR} 🌐Domain: ${COLOR_CYAN}$DOMAIN_NAME${CoR}" else ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // "Unknown error"') echo -e " ā›” ${COLOR_RED}Failed to enable dead host: $ERROR_MSG${CoR}" return 1 fi } ################################ # Disable a dead host by ID dead_host_disable() { local host_id="$1" if [ -z "$host_id" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: The --dead-host-disable option requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --dead-host-disable ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --dead-host-disable 5${CoR}" return 1 fi check_token_notverbose CHECK_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/dead-hosts/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if echo "$CHECK_RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " ā›” ${COLOR_RED}Dead host with ID $host_id does not exist${CoR}" return 1 fi DOMAIN_NAME=$(echo "$CHECK_RESPONSE" | jq -r '.domain_names[0]') echo -e "\n ${COLOR_YELLOW}šŸ”„ Disabling dead host ${CoR} šŸ†”${COLOR_CYAN}$host_id${CoR} 🌐Domain: ${COLOR_CYAN}$DOMAIN_NAME${CoR}" RESPONSE=$(curl -s -X POST "$BASE_URL/nginx/dead-hosts/$host_id/disable" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8") if ! echo "$RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " āœ… ${COLOR_GREEN}Successfully disabled ${CoR}šŸ†”${COLOR_CYAN}$host_id${CoR} 🌐Domain: ${COLOR_CYAN}$DOMAIN_NAME${CoR}" else ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // "Unknown error"') echo -e " ā›” ${COLOR_RED}Failed to disable dead host: $ERROR_MSG${CoR}" return 1 fi } ################################ # Update an existing proxy host update_proxy_host() { check_token_notverbose HOST_ID=$1 echo -e "\n šŸ”„ Updating proxy host for $DOMAIN_NAMES..." # šŸ”„ check if the required parameters are set if [ -z "$DOMAIN_NAMES" ] || [ -z "$FORWARD_HOST" ] || [ -z "$FORWARD_PORT" ] || [ -z "$FORWARD_SCHEME" ]; then echo -e " ā›”${COLOR_RED} ERROR: Missing required parameters (domain, forward host, forward port, forward scheme).${CoR}" exit 1 fi # šŸ”„ check if the FORWARD_PORT is a number if ! [[ "$FORWARD_PORT" =~ ^[0-9]+$ ]]; then echo -e " ā›”${COLOR_RED} ERROR: FORWARD_PORT is not a number! Value: '$FORWARD_PORT'${CoR}" exit 1 fi # šŸ”„ Correct the CUSTOM_LOCATIONS if [[ -z "$CUSTOM_LOCATIONS" || "$CUSTOM_LOCATIONS" == "null" ]]; then CUSTOM_LOCATIONS_ESCAPED="[]" else CUSTOM_LOCATIONS_ESCAPED=$(echo "$CUSTOM_LOCATIONS" | jq -c . 2>/dev/null || echo '[]') fi # correct the boolean (true / false in JSON) CACHING_ENABLED_JSON=$([ "$CACHING_ENABLED" == "true" ] && echo true || echo false) BLOCK_EXPLOITS_JSON=$([ "$BLOCK_EXPLOITS" == "true" ] && echo true || echo false) ALLOW_WEBSOCKET_UPGRADE_JSON=$([ "$ALLOW_WEBSOCKET_UPGRADE" == "true" ] && echo true || echo false) HTTP2_SUPPORT_JSON=$([ "$HTTP2_SUPPORT" == "true" ] && echo true || echo false) # šŸ” Debugging variables before JSON update: debug_var # šŸ”„ generate the JSON properly DATA=$( jq -n \ --arg domain "$DOMAIN_NAMES" \ --arg host "$FORWARD_HOST" \ --arg port "$FORWARD_PORT" \ --arg scheme "$FORWARD_SCHEME" \ --argjson caching "$CACHING_ENABLED_JSON" \ --argjson block_exploits "$BLOCK_EXPLOITS_JSON" \ --arg advanced_config "$ADVANCED_CONFIG" \ --argjson websocket_upgrade "$ALLOW_WEBSOCKET_UPGRADE_JSON" \ --argjson http2_support "$HTTP2_SUPPORT_JSON" \ --argjson enabled true \ --argjson locations "$CUSTOM_LOCATIONS_ESCAPED" \ '{ domain_names: [$domain], forward_host: $host, forward_port: ($port | tonumber), access_list_id: null, certificate_id: null, ssl_forced: false, caching_enabled: $caching, block_exploits: $block_exploits, advanced_config: $advanced_config, meta: { dns_challenge: null }, allow_websocket_upgrade: $websocket_upgrade, http2_support: $http2_support, forward_scheme: $scheme, enabled: $enabled, locations: $locations }' ) # šŸ” check if the JSON is valid if ! echo "$DATA" | jq empty >/dev/null 2>&1; then echo -e " ā›”${COLOR_RED} ERROR: Invalid JSON generated:\n$DATA ${CoR}" exit 1 fi # šŸš€ send the API request for update RESPONSE=$(curl -s -X PUT "$BASE_URL/nginx/proxy-hosts/$HOST_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8" \ --data-raw "$DATA") # šŸ“¢ check if the response is valid ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // empty') if [ -z "$ERROR_MSG" ]; then PROXY_ID=$(echo "$RESPONSE" | jq -r '.id // "unknown"') echo -e "\n āœ… ${COLOR_GREEN}SUCCESS: Proxy host šŸ”—$DOMAIN_NAMES (ID: ${COLOR_YELLOW}$PROXY_ID${COLOR_GREEN}) was created successfully! šŸŽ‰${CoR}\n" else echo -e " ā›” ${COLOR_RED}Failed to create proxy host. Error: $ERROR_MSG ${CoR}" exit 1 fi } ################################ # Update field of existing proxy host host_update() { check_token_notverbose HOST_ID="$1" FIELD="$2" NEW_VALUE="$3" #echo -e "\n šŸ”„ DEBUG: host-update() function has started" # echo -e " šŸ†” HOST_ID: $HOST_ID" # echo -e " šŸ· FIELD: $FIELD" # echo -e " āœļø VALUE: $NEW_VALUE" # 1) Check that all required parameters are provided if [ -z "$HOST_ID" ] || [ -z "$FIELD" ] || [ -z "$NEW_VALUE" ]; then echo -e "\n ā›” ${COLOR_RED}INVALID command: Missing required parameters.${CoR}" echo -e " Usage: ${COLOR_ORANGE}$0 --host-update ${CoR}" exit 1 fi # 2) Fetch current configuration CURRENT_DATA=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts/$HOST_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") #echo -e "\n šŸ”„ DEBUG: API response (CURRENT_DATA):\n$CURRENT_DATA\n" if ! echo "$CURRENT_DATA" | jq empty >/dev/null 2>&1; then echo -e " ā›” ${COLOR_RED}ERROR:${CoR} Failed to fetch current proxy configuration." exit 1 fi # 3) Check if the requested field is editable FILTERED_DATA=$(echo "$CURRENT_DATA" | jq '{ domain_names, forward_host, forward_port, access_list_id, certificate_id, ssl_forced, caching_enabled, block_exploits, advanced_config, meta, allow_websocket_upgrade, http2_support, forward_scheme, enabled, locations, hsts_enabled, hsts_subdomains }') #echo -e "\n šŸ”„ DEBUG: Filtered JSON before update:\n$FILTERED_DATA\n" if ! echo "$FILTERED_DATA" | jq -e --arg field "$FIELD" 'has($field)' >/dev/null; then echo -e " ā›” ${COLOR_RED}ERROR:${CoR} The field '$FIELD' is not a valid field for update." exit 1 fi # 4) Modifier la configuration if [ "$FIELD" = "forward_port" ]; then UPDATED_DATA=$(echo "$FILTERED_DATA" | jq --argjson newVal "$(echo "$NEW_VALUE" | jq -R 'tonumber? // 0')" \ '.forward_port = $newVal') else UPDATED_DATA=$(echo "$FILTERED_DATA" | jq --arg newVal "$NEW_VALUE" \ ".$FIELD = \$newVal") fi #echo -e "\n šŸ”„ DEBUG: JSON Sent to API:\n$UPDATED_DATA\n" if ! echo "$UPDATED_DATA" | jq empty >/dev/null 2>&1; then echo -e " ā›” ${COLOR_RED}ERROR: Invalid JSON generated.${CoR}\n$UPDATED_DATA" exit 1 fi # Sending update request to API..." RESPONSE=$(curl -s -X PUT "$BASE_URL/nginx/proxy-hosts/$HOST_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8" \ --data-raw "$UPDATED_DATA") #echo -e "\n šŸ”„ DEBUG: API Response:\n$RESPONSE\n" ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // empty') if [ -z "$ERROR_MSG" ]; then echo -e "\n āœ… ${COLOR_GREEN}SUCCESS:${CoR} Proxy host šŸ†” $HOST_ID updated successfully! šŸŽ‰" SUMMARY=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts/$HOST_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if echo "$SUMMARY" | jq empty >/dev/null 2>&1; then echo -e " New Value Proxy host šŸ†” $HOST_ID $FIELD : $(echo "$SUMMARY" | jq -r --arg field "$FIELD" '.[$field]')\n" echo -e " šŸ”„ ${COLOR_CYAN}Summary Proxy Host:${CoR}" echo -e " Domaine(s) : $(echo "$SUMMARY" | jq -r '.domain_names | join(", ")')" echo -e " Forward Host : $(echo "$SUMMARY" | jq -r '.forward_host')" echo -e " Forward Port : $(echo "$SUMMARY" | jq -r '.forward_port')" echo -e " Forward Scheme : $(echo "$SUMMARY" | jq -r '.forward_scheme')" echo -e " SSL Forced : $(echo "$SUMMARY" | jq -r '.ssl_forced')\n" else echo -e "\n āš ļø ${COLOR_YELLOW}WARNING:${CoR} Error to get proxy host data." fi else echo -e "\n ā›” ${COLOR_RED}Failed to update proxy host. Error:${CoR} $ERROR_MSG" exit 1 fi } ################################ # Search for a proxy host by domain name host_search() { #check_token false check_token_notverbose if [ -z "$HOST_SEARCHNAME" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: The --host-search option requires a .${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --host-search domain_name${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --host-search example.com${CoR}\n" exit 1 fi echo -e "\n ${COLOR_CYAN}šŸ”${CoR} Searching proxy hosts for: ${COLOR_YELLOW}$HOST_SEARCHNAME${CoR}" RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ -z "$RESPONSE" ] || [ "$RESPONSE" == "null" ]; then echo -e " ā›” ${COLOR_RED}Error: Unable to retrieve proxy host data.${CoR}" exit 1 fi MATCHES=$(echo "$RESPONSE" | jq -c --arg search "$HOST_SEARCHNAME" '.[] | select(.domain_names[] | contains($search))') if [ -z "$MATCHES" ]; then echo -e " āŒ No proxy host found for: ${COLOR_YELLOW}$HOST_SEARCHNAME${CoR}" else echo "$MATCHES" | while IFS= read -r line; do id=$(echo "$line" | jq -r '.id') domain_names=$(echo "$line" | jq -r '.domain_names[]') printf " ${COLOR_GREY}šŸ†”${COLOR_YELLOW}%4s${CoR} ${COLOR_GREEN}%s${CoR}\n" "$id" "$domain_names" done fi echo "" exit 0 } ################################ ################################ # Enable a proxy host by ID # # šŸ’” NPM-specific endpoint discovery (2025-05-30): # ================================================ # Like host_disable(), this function uses NPM's dedicated endpoint: # # āœ… CORRECT approach: # POST /api/nginx/proxy-hosts/{id}/enable with empty body # → Actually enables the site and makes it accessible # # āŒ The generic PUT approach with JSON modification doesn't work reliably. # Reference: Same discovery as host_disable() function above. ################################ # Enable a proxy host by ID host_enable() { local host_id="$1" if [ -z "$host_id" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: The --host-enable option requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --host-enable ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --host-enable 42${CoR}" return 1 fi check_token_notverbose # Check if the proxy host exists first CHECK_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ $? -eq 0 ] && [ -n "$CHECK_RESPONSE" ]; then # Check if the host was found (not a 404 error) if echo "$CHECK_RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " ā›” ${COLOR_RED}Proxy host with ID $host_id does not exist${CoR}" return 1 fi # Get domain name for display DOMAIN_NAME=$(echo "$CHECK_RESPONSE" | jq -r '.domain_names[0]') echo -e "\n ${COLOR_YELLOW}šŸ”„ Enabling proxy host ${CoR} šŸ†”${COLOR_CYAN}$host_id${CoR} 🌐Domain: ${COLOR_CYAN}$DOMAIN_NAME${CoR}" # Use the CORRECT NPM endpoint: POST to /enable (like the web interface does) RESPONSE=$(curl -s -X POST "$BASE_URL/nginx/proxy-hosts/$host_id/enable" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8") if [ $? -eq 0 ] && ! echo "$RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " āœ… ${COLOR_GREEN}Successfully enabled ${CoR}šŸ†”${COLOR_CYAN}$host_id${CoR} 🌐Domain: ${COLOR_CYAN}$DOMAIN_NAME${CoR}" # echo -e " šŸŽÆ ${COLOR_GREEN}Using NPM's native enable endpoint${CoR}\n" else ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // "Unknown error"') echo -e " ā›” ${COLOR_RED}Failed to enable proxy host: $ERROR_MSG${CoR}" return 1 fi else echo -e " ā›” ${COLOR_RED}Proxy host with ID $host_id does not exist${CoR}" return 1 fi } ################################ # Disable a proxy host by ID # # šŸ› IMPORTANT BUG DISCOVERY (2025-05-30): # ========================================== # The NPM web interface uses a DIFFERENT endpoint than expected! # # āŒ WRONG approach (what we tried before): # PUT /api/nginx/proxy-hosts/{id} with modified JSON (enabled: false) # → Shows "disabled" in interface but site remains ACCESSIBLE! # # āœ… CORRECT approach (what NPM web interface actually uses): # POST /api/nginx/proxy-hosts/{id}/disable with empty body # → Actually disables the site and makes it INACCESSIBLE! # # This was discovered by inspecting NPM web interface network traffic. # NPM has dedicated /enable and /disable endpoints that work properly. # Reference: GitHub issue #3 (nginx-proxy-manager-Bash-API) ################################ host_disable() { local host_id="$1" if [ -z "$host_id" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: The --host-disable option requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --host-disable ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --host-disable 42${CoR}" return 1 fi check_token_notverbose # Check if the proxy host exists first CHECK_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ $? -eq 0 ] && [ -n "$CHECK_RESPONSE" ]; then # Check if the host was found (not a 404 error) if echo "$CHECK_RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " ā›” ${COLOR_RED}Proxy host with ID $host_id does not exist${CoR}" return 1 fi # Get domain name for display DOMAIN_NAME=$(echo "$CHECK_RESPONSE" | jq -r '.domain_names[0]') echo -e "\n ${COLOR_YELLOW}šŸ”„ Disabling proxy host ${CoR} šŸ†”${COLOR_CYAN}$host_id${CoR} 🌐Domain: ${COLOR_CYAN}$DOMAIN_NAME${CoR}" # Use the CORRECT NPM endpoint: POST to /disable (like the web interface does) RESPONSE=$(curl -s -X POST "$BASE_URL/nginx/proxy-hosts/$host_id/disable" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8") if [ $? -eq 0 ] && ! echo "$RESPONSE" | jq -e '.error' >/dev/null 2>&1; then echo -e " āœ… ${COLOR_GREEN}Successfully disabled ${CoR}šŸ†”${COLOR_CYAN}$host_id${CoR} 🌐Domain: ${COLOR_CYAN}$DOMAIN_NAME${CoR}" # echo -e " šŸŽÆ ${COLOR_GREEN}Using NPM's native disable endpoint${CoR}\n" else ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // "Unknown error"') echo -e " ā›” ${COLOR_RED}Failed to disable proxy host: $ERROR_MSG${CoR}" return 1 fi else echo -e " ā›” ${COLOR_RED}Proxy host with ID $host_id does not exist${CoR}" return 1 fi } ################################ # Delete a proxy host # $1: host_id - ID of the host to delete host_delete() { local host_id="$1" # Check if ID is provided if [ -z "$HOST_ID" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: The --host-delete option requires a host šŸ†”${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --host-delete ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --host-delete 42${CoR}" exit 1 fi check_token_notverbose # Check if host exists before attempting deletion #echo -e "\n šŸ”Ž Checking if host ID ${COLOR_GREEN}$HOST_ID${CoR} exists..." CHECK_RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" \ "$BASE_URL/nginx/proxy-hosts/$HOST_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" 2>/dev/null) CHECK_BODY=${CHECK_RESPONSE//HTTPSTATUS:*/} CHECK_STATUS=${CHECK_RESPONSE##*HTTPSTATUS:} # Verify host existence if [ "$CHECK_STATUS" -eq 404 ]; then echo -e " ā›” ${COLOR_RED}ERROR: Host ID ${COLOR_GREEN}$HOST_ID${COLOR_RED} not found!${CoR}" echo -e "\n ${COLOR_YELLOW}šŸ’” Tip: Use --host-list to see all available hosts and their IDs${CoR}" exit 1 elif [ "$CHECK_STATUS" -ne 200 ]; then echo -e " ā›” ${COLOR_RED}ERROR: Failed to check host. Status: $CHECK_STATUS${CoR}" if [ -n "$CHECK_BODY" ]; then echo -e " šŸ“ Error details: $CHECK_BODY" fi exit 1 fi # Extract domain name for confirmation DOMAIN_NAME=$(echo "$CHECK_BODY" | jq -r '.domain_names[0] // "unknown"') # Show confirmation prompt unless AUTO_YES is set if [ "$AUTO_YES" != true ]; then echo -e " ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€" echo -e " │ ID: ${COLOR_YELLOW}$HOST_ID${CoR}" echo -e " │ Domain: ${COLOR_GREEN}$DOMAIN_NAME${CoR}" echo -e " └───────────────────────────────────────────" echo -e " āš ļø ${COLOR_RED}WARNING: This action cannot be undone!${CoR}" read -n 1 -r -p " šŸ”” Confirm deletion? (y/n): " CONFIRM echo if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then echo -e " āŒ ${COLOR_RED}Operation cancelled${CoR}" exit 1 fi echo -e " šŸ—‘ļø Deleting proxy host ${COLOR_GREEN}$DOMAIN_NAME${CoR} (ID: ${COLOR_GREEN}$HOST_ID${CoR})" else echo -e "\n ${COLOR_YELLOW}šŸ”” -y Auto-confirming deletion${CoR}" echo -e " šŸ—‘ļø Deleting proxy host ${COLOR_GREEN}$DOMAIN_NAME${CoR} (ID: ${COLOR_GREEN}$HOST_ID${CoR})..." fi # Perform deletion RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" -X DELETE \ "$BASE_URL/nginx/proxy-hosts/$HOST_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" 2>/dev/null) HTTP_BODY=${RESPONSE//HTTPSTATUS:*/} HTTP_STATUS=${RESPONSE##*HTTPSTATUS:} if [ "${HTTP_STATUS:-0}" -eq 200 ]; then echo -e " āœ… ${COLOR_GREEN}Proxy host ${COLOR_YELLOW}$DOMAIN_NAME${CoR} (ID: $HOST_ID) deleted successfully!${CoR}\n" exit 0 else echo -e " ā›” ${COLOR_RED}Failed to delete proxy host. Status: $HTTP_STATUS${CoR}" if [ -n "$HTTP_BODY" ]; then echo -e " šŸ“ Error details: $HTTP_BODY" fi exit 1 fi } ################################ # ACL proxy host host_acl_enable() { # Check that both arguments are provided if [ -z "$HOST_ID" ] || [ -z "$ACCESS_LIST_ID" ]; then echo -e "\n ā›” ${COLOR_RED}Error: HOST_ID and ACCESS_LIST_ID are required to enable the ACL.${CoR}" echo -e " Usage: ${COLOR_ORANGE}$0 --host-acl-enable ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --host-acl-enable 42 5${CoR}" exit 1 fi check_token_notverbose echo -e "\n ${COLOR_YELLOW}šŸ”“ Enabling ACL${CoR} host šŸ†”${COLOR_CYAN}$HOST_ID${CoR} with access list šŸ†”${COLOR_CYAN}$ACCESS_LIST_ID${CoR}" DATA=$(jq -n \ --argjson access_list_id "$ACCESS_LIST_ID" \ --argjson enabled true \ '{ access_list_id: $access_list_id, enabled: $enabled }') RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" -X PUT "$BASE_URL/nginx/proxy-hosts/$HOST_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8" \ --data-raw "$DATA") HTTP_BODY=${RESPONSE//HTTPSTATUS:*/} HTTP_STATUS=${RESPONSE##*HTTPSTATUS:} if [ "$HTTP_STATUS" = "200" ]; then echo -e " āœ… ${COLOR_GREEN}ACL successfully enabled access list ${CoR}šŸ†”${COLOR_CYAN}$ACCESS_LIST_ID${CoR} for hostšŸ†”${COLOR_CYAN}$HOST_ID${CoR}\n" else echo -e " ā›” ${COLOR_RED}Failed to enable ACL (HTTP $HTTP_STATUS). Error: $(echo "$HTTP_BODY" | jq -r '.error.message // "Unknown error"')${CoR}\n" exit 1 fi } ################################ # Disable ACL for a given proxy host host_acl_disable() { if [ -z "$HOST_ID" ]; then echo -e "\n ā›” ${COLOR_RED}Error: HOST_ID is required to disable the ACL.${CoR}" echo -e " Usage: $0 --host-acl-disable \n" exit 1 fi check_token_notverbose DATA=$(jq -n \ --argjson access_list_id null \ --argjson enabled false \ '{ access_list_id: $access_list_id, enabled: $enabled }') RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" -X PUT "$BASE_URL/nginx/proxy-hosts/$HOST_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8" \ --data-raw "$DATA") HTTP_BODY=${RESPONSE//HTTPSTATUS:*/} HTTP_STATUS=${RESPONSE##*HTTPSTATUS:} if [ "$HTTP_STATUS" = "200" ]; then echo -e "\n šŸ”’ ${COLOR_ORANGE}Disabling ACL for host${CoR}šŸ†”${COLOR_CYAN} $HOST_ID${CoR}" echo -e " āœ… ${COLOR_GREEN}ACL successfully disabled ACL for host ${CoR}šŸ†”${COLOR_CYAN}$HOST_ID${CoR}\n" else echo -e "\n ā›” ${COLOR_RED}Failed to disable ACL (HTTP $HTTP_STATUS). Error: $(echo "$HTTP_BODY" | jq -r '.error.message // "Unknown error"')${CoR}\n" exit 1 fi } ################################ # Show details of a specific proxy host host_show() { #local host_id="$1" if [ -z "$host_id" ]; then echo -e "\n ā›” ${COLOR_RED}The --host-show option requires a host ID.${CoR}" echo -e " Usage: ${COLOR_ORANGE}$0 --host-show ${CoR}" echo -e " To find ID Check with ${COLOR_ORANGE}$0 --host-list${CoR}\n" return 1 fi check_token_notverbose [ "$JSON_OUTPUT" = true ] || echo -e "\n šŸ” Fetching details for proxy host ID: ${COLOR_YELLOW}$host_id${CoR}..." # get host details local response=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts/$host_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") # Check if the response contains an error if echo "$response" | jq -e '.error' >/dev/null; then if [ "$JSON_OUTPUT" = true ]; then echo "$response" | jq '{error: (.error.message // "Unknown error")}' exit 1 fi echo -e " ā›” ${COLOR_RED}Error: $(echo "$response" | jq -r '.error.message')${CoR}\n" return 1 fi if [ "$JSON_OUTPUT" = true ]; then echo "$response" | jq . exit 0 fi # Format and display details echo -e "\nšŸ“‹ ${COLOR_YELLOW}Host Details:${CoR}" echo -e "ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€" echo -e "│ šŸ†” ID: ${COLOR_GREEN_b}$(echo "$response" | jq -r '.id')${CoR}" echo -e "│ 🌐 Domains: ${COLOR_GREEN_b}$(echo "$response" | jq -r '.domain_names[]' | tr '\n' ' ')${CoR}" echo -e "│ šŸ”„ Forward Configuration:" echo -e "│ • Host: ${COLOR_GREEN_b}$(echo "$response" | jq -r '.forward_host')${CoR}" echo -e "│ • Port: ${COLOR_GREEN_b}$(echo "$response" | jq -r '.forward_port')${CoR}" echo -e "│ • Scheme: ${COLOR_GREEN_b}$(echo "$response" | jq -r '.forward_scheme')${CoR}" echo -e "│ āœ… Status: $(colorize_boolean $(echo "$response" | jq -r '.enabled | if . then "Enabled" else "Disabled" end'))" echo -e "│ šŸ”’ SSL Configuration:" echo -e "│ • Certificate ID: ${COLOR_ORANGE}$(echo "$response" | jq -r '.certificate_id')${CoR}" echo -e "│ • SSL Forced: $(colorize_boolean $(echo "$response" | jq -r '.ssl_forced | if . then "true" else "false" end'))" echo -e "│ • HTTP/2: $(colorize_boolean $(echo "$response" | jq -r '.http2_support | if . then "true" else "false" end'))" echo -e "│ • HSTS: $(colorize_boolean $(echo "$response" | jq -r '.hsts_enabled | if . then "true" else "false" end'))" echo -e "│ šŸ› ļø Features:" echo -e "│ • Block Exploits: $(colorize_boolean $(echo "$response" | jq -r '.block_exploits | if . then "true" else "false" end'))" echo -e "│ • Caching: $(colorize_boolean $(echo "$response" | jq -r '.caching_enabled | if . then "true" else "false" end'))" echo -e "│ • Websocket Upgrade: $(colorize_boolean $(echo "$response" | jq -r '.allow_websocket_upgrade | if . then "true" else "false" end'))" echo -e "│ šŸ”‘ Access List ID: ${COLOR_ORANGE}$(echo "$response" | jq -r '.access_list_id')${CoR}" # Check and display advanced configuration if [ "$(echo "$response" | jq -r '.advanced_config')" != "null" ]; then echo -e "│ āš™ļø Advanced Config: ${COLOR_GREEN}Yes${CoR}" echo -e "│" echo "$response" | jq -r '.advanced_config' | while IFS= read -r line; do if [ -n "$line" ]; then echo -e "│ ${COLOR_GRAY}$line${CoR}" else echo -e "│" fi done else echo -e "│ āš™ļø Advanced Config: ${COLOR_RED}No${CoR}" fi echo -e "└────────────────────────────────────" return 0 } ################################ # Delete a certificate in NPM # NOTE: NPM's DELETE /nginx/certificates/:id is a SOFT delete (sets is_deleted=1): # the cert leaves the list/UI but its on-disk files remain. The optional --purge # flag (see cert_purge_files) removes those leftover files. The DB row is left # untouched on purpose to avoid any referential-integrity risk. ################################ # Remove the on-disk certificate files left behind by NPM's soft-delete. # Requires NGINX_PATH_DOCKER (set in npm-api.conf) to point at the NPM data # directory that holds custom_ssl/ and letsencrypt/. No-op (with a clear # message) when that path is not configured/accessible on this host. cert_purge_files() { local id="$1" local base="${NGINX_PATH_DOCKER:-}" if [ -z "$base" ] || [ "$base" = "$DEFAULT_NGINX_PATH_DOCKER" ]; then echo -e " āš ļø ${COLOR_YELLOW}--purge skipped:${CoR} set ${COLOR_CYAN}NGINX_PATH_DOCKER${CoR} in npm-api.conf to the NPM data dir (holding ${COLOR_GREY}custom_ssl/${CoR} and ${COLOR_GREY}letsencrypt/${CoR}).\n" return 0 fi if [ ! -d "$base" ]; then echo -e " āš ļø ${COLOR_YELLOW}--purge skipped:${CoR} NGINX_PATH_DOCKER (${COLOR_GREY}$base${CoR}) is not an accessible directory on this host.\n" return 0 fi local targets=( "$base/custom_ssl/npm-$id" "$base/letsencrypt/live/npm-$id" "$base/letsencrypt/archive/npm-$id" "$base/letsencrypt/renewal/npm-$id.conf" ) local removed=0 t for t in "${targets[@]}"; do if [ -e "$t" ]; then if rm -rf "$t" 2>/dev/null; then echo -e " 🧹 Removed ${COLOR_GREY}$t${CoR}" removed=$((removed + 1)) else echo -e " ā›” ${COLOR_RED}Could not remove $t${CoR} ${COLOR_GREY}(permissions? try as root or inside the NPM container)${CoR}" fi fi done if [ "$removed" -eq 0 ]; then echo -e " ā„¹ļø ${COLOR_GREY}--purge: no on-disk files found for npm-$id under $base${CoR}\n" else echo -e " āœ… ${COLOR_GREEN}Purged $removed on-disk item(s) for cert npm-$id${CoR}\n" fi } ################################ cert_delete() { local CERT_IDENTIFIER="$1" if [ -z "$CERT_IDENTIFIER" ]; then echo -e "\n ā›” ${COLOR_RED}Error: Please specify a domain or certificate ID${CoR}" echo -e "Usage: --cert-delete " exit 1 fi check_token_notverbose # Get certificates list from API CERTIFICATES=$(curl -s -X GET "$BASE_URL/nginx/certificates" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") # Check if input is a number (ID) or domain if [[ "$CERT_IDENTIFIER" =~ ^[0-9]+$ ]]; then # It's an ID CERT_ID="$CERT_IDENTIFIER" CERT_EXISTS=$(echo "$CERTIFICATES" | jq -r --arg id "$CERT_ID" '.[] | select(.id == ($id|tonumber)) | .id') # Add verification for CERT_EXISTS if [ -z "$CERT_EXISTS" ]; then echo -e "\n ā›” ${COLOR_RED}No certificate found with ID: $CERT_ID${CoR}" exit 1 fi #echo -e " āœ… Certificate ID $CERT_ID found" else # It's a domain - Get all matching certificates MATCHING_CERTS=$(echo "$CERTIFICATES" | jq -r --arg domain "$CERT_IDENTIFIER" \ '[.[] | select(.domain_names[] == $domain or .nice_name == $domain)]') CERT_COUNT=$(echo "$MATCHING_CERTS" | jq 'length') if [ "$CERT_COUNT" -eq 0 ]; then echo -e " ā›” ${COLOR_RED}No certificates found for domain: $CERT_IDENTIFIER${CoR}" exit 1 elif [ "$CERT_COUNT" -eq 1 ]; then CERT_ID=$(echo "$MATCHING_CERTS" | jq -r '.[0].id') else # Multiple certificates found, let user choose echo -e " šŸ“œ Multiple certificates found for $CERT_IDENTIFIER:" echo "$MATCHING_CERTS" | jq -r '.[] | "ID: \(.id) - Provider: \(.provider) - Expires: \(.expires_on) - Domains: \(.domain_names|join(", "))"' | awk '{print NR ") " $0}' if [ "$AUTO_YES" = true ]; then echo -e " āš ļø Multiple certificates found with -y option. Please specify certificate ID instead." exit 1 fi read -r -p "Enter the number of the certificate to delete (1-$CERT_COUNT): " CHOICE if ! [[ "$CHOICE" =~ ^[0-9]+$ ]] || [ "$CHOICE" -lt 1 ] || [ "$CHOICE" -gt "$CERT_COUNT" ]; then echo -e " ā›” ${COLOR_RED}Invalid selection${CoR}" exit 1 fi CERT_ID=$(echo "$MATCHING_CERTS" | jq -r --arg idx "$((CHOICE - 1))" '.[$idx|tonumber].id') fi fi if [ -z "$CERT_ID" ]; then echo -e " ā›” ${COLOR_RED}No valid certificate found${CoR}" exit 1 fi # Warn if the certificate is still referenced by a proxy/redirection host. # NPM's delete is a soft-delete, so those hosts keep a dangling certificate_id # until their SSL is reconfigured; and --purge would remove files still in use. REFERENCING=$( { curl -s -X GET "$BASE_URL/nginx/proxy-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" | jq -r --argjson id "$CERT_ID" '.[]? | select(.certificate_id == $id) | "proxy host #\(.id) (\(.domain_names | join(", ")))"' 2>/dev/null curl -s -X GET "$BASE_URL/nginx/redirection-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" | jq -r --argjson id "$CERT_ID" '.[]? | select(.certificate_id == $id) | "redirection host #\(.id) (\(.domain_names | join(", ")))"' 2>/dev/null curl -s -X GET "$BASE_URL/nginx/streams" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" | jq -r --argjson id "$CERT_ID" '.[]? | select(.certificate_id == $id) | "stream #\(.id) (port \(.incoming_port))"' 2>/dev/null curl -s -X GET "$BASE_URL/nginx/dead-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" | jq -r --argjson id "$CERT_ID" '.[]? | select(.certificate_id == $id) | "dead host #\(.id) (\(.domain_names | join(", ")))"' 2>/dev/null } | grep -v '^$' || true ) if [ -n "$REFERENCING" ]; then echo -e "\n āš ļø ${COLOR_YELLOW}Certificate ID $CERT_ID is still used by:${CoR}" echo "$REFERENCING" | while IFS= read -r ref; do echo -e " • ${COLOR_GREY}$ref${CoR}" done echo -e " ${COLOR_GREY}These hosts will keep a dangling certificate_id until you reconfigure their SSL.${CoR}" if [ "$CERT_PURGE" = true ]; then echo -e " ā›” ${COLOR_RED}--purge refused:${CoR} the on-disk files are still in use. Disable/re-assign SSL on those hosts first.\n" exit 1 fi fi # Ask for confirmation unless AUTO_YES is set if [ "$AUTO_YES" = true ]; then #echo -e " šŸ”” The -y option was provided. Skipping confirmation prompt..." CONFIRM="y" else echo -e "${COLOR_YELLOW}" read -n 1 -r -p " āš ļø Are you sure you want to delete certificate ID: $CERT_ID ? (y/n): " CONFIRM echo -e "${CoR}" fi if [[ "$CONFIRM" != "y" ]]; then echo -e "${COLOR_RED} āŒ Certificate deletion aborted.${CoR}\n" exit 0 fi echo -e "\n šŸ—‘ļø Deleting certificate ID: $CERT_ID..." # Delete certificate through API HTTP_RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" -X DELETE \ "$BASE_URL/nginx/certificates/$CERT_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") HTTP_BODY=${HTTP_RESPONSE//HTTPSTATUS:*/} HTTP_STATUS=${HTTP_RESPONSE##*HTTPSTATUS:} if [ "${HTTP_STATUS:-0}" -eq 200 ]; then echo -e " āœ… ${COLOR_GREEN}Certificate successfully deleted!${CoR}\n" if [ "$CERT_PURGE" = true ]; then cert_purge_files "$CERT_ID" fi else echo -e " ā›” ${COLOR_RED}Deletion failed. Status: $HTTP_STATUS. Response: $HTTP_BODY${CoR}\n" fi } ################################ # Generate Let's Encrypt certificate if not exists cert_generate() { # 1. Local variables DOMAIN_NAMES="${1:-}" DOMAIN=$DOMAIN_NAMES EMAIL="${2:-}" DNS_PROVIDER="${3:-}" DNS_CREDENTIALS_JSON="${4:-}" EXISTING_CERT="" IS_WILDCARD=false # 2. Basic validation if [ -z "$DOMAIN_NAMES" ]; then echo -e "\n ${COLOR_RED}āŒ${CoR} Error: Domain is required $DOMAIN" echo -e " Usage: $0 --cert-generate [email] [--cert-email ] [--dns-provider ] [--dns-credentials ]\n" exit 1 fi if [ -z "$EMAIL" ]; then EMAIL="$DEFAULT_EMAIL" echo -e "\n šŸ“§ Using default email: ${COLOR_YELLOW}$EMAIL${CoR}" fi check_token_notverbose # 3. Check if wildcard and validate requirements if [[ "$DOMAIN" == \** ]]; then IS_WILDCARD=true if [ -z "$DNS_PROVIDER" ] || [ -z "$DNS_CREDENTIALS_JSON" ]; then echo -e " ā›” ${COLOR_RED}DNS provider and credentials are required for wildcard certificates${CoR}\n" exit 1 fi # Credentials: JSON object or raw certbot INI text ("key = value" lines) if [[ "$DNS_CREDENTIALS_JSON" == \{* ]] && ! echo "$DNS_CREDENTIALS_JSON" | jq -e 'type == "object"' >/dev/null 2>&1; then echo -e " ā›” ${COLOR_RED}Invalid JSON format for DNS credentials${CoR}\n" exit 1 fi fi # Proxy host using this domain (needed by --host-ssl-enable) PROXY_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") DOMAIN_EXISTS=$(echo "$PROXY_RESPONSE" | jq -r --arg DOMAIN "$DOMAIN" \ 'first(.[] | select(.domain_names | index($DOMAIN)) | .id) // empty') # Only require the host to exist in NPM if it is not a wildcard if [ "$IS_WILDCARD" = false ]; then if [ -z "$DOMAIN_EXISTS" ]; then echo -e "\n ${COLOR_RED}āŒ${CoR} Domain ${COLOR_YELLOW}$DOMAIN${CoR} is not configured in NPM." echo -e " ${COLOR_YELLOW}šŸ’”${CoR} First create a proxy host with:" echo -e " ${COLOR_CYAN}$0 --host-create $DOMAIN -i -p ${CoR}\n" exit 1 fi fi # Then check existing certificates RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/certificates" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") EXISTING_CERT=$(echo "$RESPONSE" | jq -c --arg domain "$DOMAIN" \ "$CERT_JQ_EXPIRED"' first(.[] | select(.domain_names | index($domain)) | select(is_expired | not)) // empty') if [ -n "$EXISTING_CERT" ]; then CERT_ID=$(echo "$EXISTING_CERT" | jq -r '.id') CERT_EXPIRES=$(echo "$EXISTING_CERT" | jq -r '.expires_on') echo -e " ${COLOR_GREEN}šŸ””${CoR} Valid certificate found for ${COLOR_YELLOW}$DOMAIN${CoR} (Certificate ID: ${COLOR_ORANGE}$CERT_ID${CoR}, expires in ${COLOR_YELLOW}$((($(date -d "$CERT_EXPIRES" +%s) - $(date +%s)) / 86400))${CoR} days)" # Reuse it for --host-ssl-enable GENERATED_CERT_ID="$CERT_ID" return 0 fi # 5. Display parameters and confirmation echo -e "\n ${COLOR_CYAN}šŸ“${CoR} Certificate generation parameters:" echo -e " • Domain: ${COLOR_YELLOW}$DOMAIN${CoR}" echo -e " • Email: ${COLOR_YELLOW}$EMAIL${CoR}" if [ -n "$DNS_PROVIDER" ]; then echo -e " • DNS Provider: ${COLOR_YELLOW}$DNS_PROVIDER${CoR}" fi # 6. Confirmation if [ "$AUTO_YES" = true ]; then echo -e "\n ${COLOR_YELLOW}šŸ”” The -y option was provided.${CoR} AUTO Yes activate.${CoR}" CONFIRM="y" else echo -en " ${COLOR_RED}ā“${CoR} No existing certificate found for ${COLOR_YELLOW}$DOMAIN${CoR}. Create new Let's Encrypt certificate? (y/n): " read -n 1 -r CONFIRM echo # New line after response CONFIRM=${CONFIRM:-y} # Default to 'y' if empty CONFIRM=$(echo "$CONFIRM" | tr '[:upper:]' '[:lower:]') # Convert to lowercase fi if [[ "$CONFIRM" != "y" ]]; then echo -e "${COLOR_RED} āŒ Certificate creation aborted.${CoR}\n" exit 0 fi # 7. Prepare request data echo -e " ${COLOR_YELLOW}šŸ”” Initiating certificate generation ${COLOR_GREEN}$DOMAIN${CoR}" echo -e " ${COLOR_CYAN}šŸš€ Sending certificate generation request${CoR}" echo -e " ${COLOR_ORANGE}ā³ This process may take a few minutes...${CoR}" # NPM >= 2.13 takes the Let's Encrypt email from the NPM user account and # rejects meta.letsencrypt_email / meta.letsencrypt_agree local npm_ver legacy_le=false npm_ver=$(curl -s --max-redirs 0 "$BASE_URL/" | jq -r '"\(.version.major // 0).\(.version.minor // 0)"' 2>/dev/null || echo "0.0") # Unknown version (0.0) is treated as current NPM if [ "$npm_ver" != "0.0" ] && [ $((${npm_ver%%.*} * 1000 + ${npm_ver#*.})) -lt 2013 ]; then legacy_le=true else echo -e " šŸ“§ ${COLOR_GREY}NPM $npm_ver uses the email of the NPM user account for Let's Encrypt${CoR}" fi if [ "$IS_WILDCARD" = true ]; then echo -e " šŸ”‘ Using DNS challenge with provider: $DNS_PROVIDER" # NPM writes the credentials as-is into a certbot INI file: convert JSON to "key = value" lines local dns_credentials_ini="$DNS_CREDENTIALS_JSON" if echo "$DNS_CREDENTIALS_JSON" | jq -e 'type == "object"' >/dev/null 2>&1; then # Cloudflare Global API Keys are 37 hex chars; anything else given as api_key # (e.g. "cfut_..." tokens) is an API token, which certbot expects as api_token if [[ "${DNS_PROVIDER,,}" == "cloudflare" ]] && echo "$DNS_CREDENTIALS_JSON" | jq -e \ '(.dns_cloudflare_api_token == null) and ((.dns_cloudflare_api_key // "") | test("^[0-9a-fA-F]{37}$") | not) and (.dns_cloudflare_api_key != null)' >/dev/null 2>&1; then echo -e " āš ļø ${COLOR_YELLOW}dns_cloudflare_api_key is not a Global API Key (37 hex chars): sending it as dns_cloudflare_api_token${CoR}" DNS_CREDENTIALS_JSON=$(echo "$DNS_CREDENTIALS_JSON" | jq -c '{dns_cloudflare_api_token: .dns_cloudflare_api_key}') fi dns_credentials_ini=$(echo "$DNS_CREDENTIALS_JSON" | jq -r 'to_entries[] | "\(.key) = \(.value)"') fi REQUEST_DATA=$(jq -n \ --arg domain "$DOMAIN" \ --arg dns_provider "$DNS_PROVIDER" \ --arg credentials "$dns_credentials_ini" \ '{ provider: "letsencrypt", domain_names: [$domain], meta: { dns_challenge: true, dns_provider: $dns_provider, dns_provider_credentials: $credentials, propagation_seconds: 60 } }') else REQUEST_DATA=$(jq -n \ --arg domain "$DOMAIN" \ '{ provider: "letsencrypt", domain_names: [$domain], meta: {} }') fi if [ "$legacy_le" = true ]; then REQUEST_DATA=$(echo "$REQUEST_DATA" | jq --arg email "$EMAIL" \ '.meta += {letsencrypt_agree: true, letsencrypt_email: $email}') fi # 8. Send request and handle response HTTP_RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" -X POST "$BASE_URL/nginx/certificates" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json" \ --data "$REQUEST_DATA") HTTP_BODY=${HTTP_RESPONSE//HTTPSTATUS:*/} HTTP_STATUS=${HTTP_RESPONSE##*HTTPSTATUS:} if [ "${HTTP_STATUS:-0}" -eq 201 ]; then CERT_ID=$(echo "$HTTP_BODY" | jq -r '.id') echo -e "\n āœ… ${COLOR_GREEN}Certificate generation initiated successfully!${CoR}" echo -e " šŸ“‹ Certificate Details:" echo -e " • Certificate ID: ${COLOR_YELLOW}$CERT_ID${CoR}" echo -e " • Status: ${COLOR_GREEN}Created${CoR}" echo -e " • Domain: ${COLOR_YELLOW}$DOMAIN${CoR}" echo -e " • Provider: ${COLOR_YELLOW}Let's Encrypt${CoR}\n" # Export variables for host_ssl_enable export GENERATED_CERT_ID="$CERT_ID" export DOMAIN_EXISTS if [ "$HOST_SSL_ENABLE" = true ]; then echo -e "\n ā³ ${COLOR_YELLOW}Waiting for certificate propagation (30 seconds)...${CoR}" sleep 30 if ! check_certificate_exists "$DOMAIN"; then echo -e "\n ā›” ${COLOR_RED}ERROR: Certificate not found after generation${CoR}" exit 1 fi fi #return 0 else echo -e "\n āŒ ${COLOR_RED}Certificate generation failed!${CoR}" ERROR_MSG=$(echo "$HTTP_BODY" | jq -r '.error.message // "Unknown error"') echo -e " ā›” Error: ${COLOR_RED}$ERROR_MSG${CoR}" DEBUG_STACK=$(echo "$HTTP_BODY" | jq -r '.debug.stack[]? // empty') if [ -n "$DEBUG_STACK" ]; then echo -e "\n šŸ” Debug Stack:" echo "$DEBUG_STACK" | while read -r line; do echo -e " • ${COLOR_YELLOW}$line${CoR}" done fi echo -e "\n ${COLOR_CYAN}šŸ”${CoR} Troubleshooting suggestions:" echo -e " • Verify domain DNS records are properly configured" echo -e " • Ensure domain is accessible via HTTP/HTTPS" echo -e " • Check if Let's Encrypt rate limits are not exceeded" echo -e " • Verify Nginx Proxy Manager is properly configured" echo -e " • Check if port 80 is open and accessible" echo -e " • Ensure no firewall is blocking access" echo -e " • Check Nginx Proxy Manager logs for more details" echo -e "\n ${COLOR_CYAN}šŸ’”${CoR} You can try:" echo -e " • Wait a few minutes and try again (DNS propagation)" echo -e " • Check Nginx Proxy Manager logs:" echo -e " ${COLOR_GREEN}docker logs nginx-proxy-manager${CoR}" echo -e " • Check Let's Encrypt logs:" echo -e " ${COLOR_GREEN}docker exec nginx-proxy-manager cat /data/logs/letsencrypt.log${CoR}" if [ "$IS_WILDCARD" = true ]; then echo -e " • DNS challenge: check the --dns-credentials keys expected by the provider plugin" echo -e " (Cloudflare: dns_cloudflare_api_token, or dns_cloudflare_email + dns_cloudflare_api_key for the Global API Key)" fi echo -e "\n šŸ“‹ Debug Information:" echo -e " • HTTP Status: $HTTP_STATUS" echo -e " • Response: $HTTP_BODY" # Never print DNS provider secrets echo -e " • Request Data: $(echo "$REQUEST_DATA" | jq -c 'if .meta.dns_provider_credentials then .meta.dns_provider_credentials = "***" else . end')" return 1 fi } ################################ # Enable SSL for a proxy host host_ssl_enable() { local HOST_ID="${1:-}" local CERT_ID="${2:-}" if [ "$CERT_GENERATE" = true ]; then if [ -z "$DOMAIN_EXISTS" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: No proxy host found for ${COLOR_YELLOW}$CERT_DOMAIN${CoR}" echo -e " Enable SSL manually: ${COLOR_ORANGE}$0 --host-ssl-enable ${GENERATED_CERT_ID:-}${CoR}\n" exit 1 fi if [ -z "$GENERATED_CERT_ID" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: No certificate ID available${CoR}" exit 1 fi HOST_ID="$DOMAIN_EXISTS" CERT_ID="$GENERATED_CERT_ID" else if [ -z "$HOST_ID" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR : The --host-ssl-enable option requires a host šŸ†”${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --host-ssl-enable ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --host-ssl-enable 42 240${CoR}\n" exit 1 fi if [ -z "$CERT_ID" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: Certificate ID is required${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --host-ssl-enable ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --host-ssl-enable 42 240${CoR}\n" exit 1 fi fi # Quick check if host exists check_token_notverbose local CHECK_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts/$HOST_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ "$(echo "$CHECK_RESPONSE" | jq -r .id)" = "null" ]; then echo -e " ${COLOR_RED}āŒ${CoR} Host ID $HOST_ID not found" return 1 fi # Get host domain and current certificate ID local HOST_DOMAIN=$(echo "$CHECK_RESPONSE" | jq -r '.domain_names[0]') # Update SSL configuration local DATA=$(jq -n \ --arg cert_id "$CERT_ID" \ '{ "certificate_id": ($cert_id|tonumber), "ssl_forced": true, "http2_support": true }') local HTTP_RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" -X PUT "$BASE_URL/nginx/proxy-hosts/$HOST_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8" \ --data-raw "$DATA") local HTTP_BODY=${HTTP_RESPONSE//HTTPSTATUS:*/} local HTTP_STATUS=${HTTP_RESPONSE##*HTTPSTATUS:} if [ "${HTTP_STATUS:-0}" -eq 200 ]; then echo -e "\n āœ… ${COLOR_GREEN}SSL enabled successfully for${CoR} ${COLOR_YELLOW}$HOST_DOMAIN${CoR} (ID: ${COLOR_CYAN}$HOST_ID${CoR}) (Cert ID: ${COLOR_CYAN}$CERT_ID${CoR})\n" return 0 #exit 0 else echo -e "\n ā›” ${COLOR_RED}Failed to enable SSL. HTTP status: $HTTP_STATUS${CoR}\n" echo -e " šŸ“‹ Error details: $HTTP_BODY \n" return 1 fi } ################################ # disable_ssl host_ssl_disable() { if [ -z "$HOST_ID" ]; then echo -e "\n ā›” ${COLOR_RED}INVALID command: Missing argument${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --host-ssl-disable ${CoR}" echo -e " Find ID: ${COLOR_ORANGE}$0 --host-list${CoR}\n" exit 1 fi check_token_notverbose CHECK_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts/$HOST_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if echo "$CHECK_RESPONSE" | jq -e '.id' >/dev/null 2>&1; then DATA=$(jq -n --argjson cert_id null '{ certificate_id: $cert_id, ssl_forced: false, http2_support: false, hsts_enabled: false, hsts_subdomains: false }') HTTP_RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" -X PUT "$BASE_URL/nginx/proxy-hosts/$HOST_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8" \ --data-raw "$DATA") HTTP_BODY=${HTTP_RESPONSE//HTTPSTATUS:*/} HTTP_STATUS=${HTTP_RESPONSE##*HTTPSTATUS:} if [ "${HTTP_STATUS:-0}" -eq 200 ]; then echo -e "\n 🚫 Disabling šŸ”“ SSL for proxy šŸ†”${COLOR_YELLOW}$HOST_ID${CoR}" echo -e " āœ… ${COLOR_GREEN}SSL disabled successfully!${CoR} šŸ†”${COLOR_CYAN}$HOST_ID${CoR}\n" else echo " Data sent: $DATA" # Log the data sent echo -e " ā›” ${COLOR_RED}Failed to disable SSL. HTTP status: $HTTP_STATUS. Response: $HTTP_BODY${CoR}\n" fi else echo -e " ā›” ${COLOR_RED}Proxy host with ID $HOST_ID does not exist.${CoR}\n" fi } ################################ # Create a new access list with various options # Usage: # access_list_create [options] # Options: # --auth Add basic authentication # --access allow|deny Add IP access rule (legacy syntax) # --allow "ip1,ip2" Add allowed IPs/ranges (comma-separated) # --deny "ip1,ip2" Add denied IPs/ranges (comma-separated) # --users "user1,user2" Add users with password prompts (comma-separated) # --satisfy any|all Set satisfy condition (default: all) # --pass-auth Enable pass auth access_list_create() { check_token_notverbose if [ $# -lt 2 ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: Insufficient arguments${CoR}" echo -e " ${COLOR_CYAN}Usage:${CoR}" echo -e " ${COLOR_ORANGE}$0 --access-list-create [options]${CoR}" echo -e "\n ${COLOR_CYAN}Available options:${CoR}" echo -e " ${COLOR_GREEN}--satisfy [any|all]${CoR} Set access list satisfaction mode" echo -e " ${COLOR_GREEN}--pass-auth [true|false]${CoR} Enable/disable password authentication" echo -e " ${COLOR_GREEN}--users \"user1,user2\"${CoR} List of users (comma-separated)" echo -e " ${COLOR_GREEN}--allow \"ip1,ip2\"${CoR} List of allowed IPs/ranges" echo -e " ${COLOR_GREEN}--deny \"ip1,ip2\"${CoR} List of denied IPs/ranges" echo -e " ${COLOR_GREEN}--auth ${CoR} Add authentication user" echo -e " ${COLOR_GREEN}--access allow|deny ${CoR} Add single IP rule" echo -e "\n ${COLOR_CYAN}Examples:${CoR}" echo -e " ${COLOR_GREEN}$0 --access-list-create secure_area --auth admin secret123${CoR}" echo -e " ${COLOR_GREEN}$0 --access-list-create office --allow \"192.168.1.0/24\"${CoR}" echo -e " ${COLOR_GREEN}$0 --access-list-create full_options --users \"user1,user2\" --allow \"10.0.0.0/8\" --satisfy any --pass-auth true${CoR}\n" return 1 fi local NAME="$1" shift # Initialize variables local SATISFY_ANY="false" local PASS_AUTH="false" local AUTH_ITEMS="[]" local IP_CLIENTS="[]" echo -e "\nšŸ”‘ Creating access list: ${COLOR_GREEN}$NAME${CoR}" # Process all arguments while [ $# -gt 0 ]; do case "$1" in --auth) if [ $# -lt 3 ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: --auth requires username and password${CoR}" return 1 fi AUTH_ITEMS=$(echo "$AUTH_ITEMS" | jq --arg user "$2" --arg pass "$3" '. + [{ username: $user, password: $pass }]') shift 3 ;; --access) if [ $# -lt 3 ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: --access requires allow/deny and IP${CoR}" return 1 fi if [[ ! "$2" =~ ^(allow|deny)$ ]]; then echo -e "\n ā›” ${COLOR_RED}ERROR: Invalid access type. Must be 'allow' or 'deny'${CoR}" return 1 fi IP_CLIENTS=$(echo "$IP_CLIENTS" | jq --arg ip "$3" --arg dir "$2" '. + [{ address: $ip, directive: $dir }]') shift 3 ;; --allow) if [ $# -lt 2 ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: --allow requires IP addresses/ranges${CoR}" return 1 fi # Split comma-separated IPs and add each one IFS=',' read -ra IPS <<<"$2" for ip in "${IPS[@]}"; do ip=$(echo "$ip" | xargs) # trim whitespace IP_CLIENTS=$(echo "$IP_CLIENTS" | jq --arg ip "$ip" '. + [{ address: $ip, directive: "allow" }]') done shift 2 ;; --deny) if [ $# -lt 2 ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: --deny requires IP addresses/ranges${CoR}" return 1 fi # Split comma-separated IPs and add each one IFS=',' read -ra IPS <<<"$2" for ip in "${IPS[@]}"; do ip=$(echo "$ip" | xargs) # trim whitespace IP_CLIENTS=$(echo "$IP_CLIENTS" | jq --arg ip "$ip" '. + [{ address: $ip, directive: "deny" }]') done shift 2 ;; --users) if [ $# -lt 2 ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: --users requires user list${CoR}" return 1 fi # Split comma-separated users and add each one (will need password prompt or default) IFS=',' read -ra USERS <<<"$2" for user in "${USERS[@]}"; do user=$(echo "$user" | xargs) # trim whitespace echo -e " šŸ” Enter password for user ${COLOR_CYAN}$user${CoR}:" read -s password echo AUTH_ITEMS=$(echo "$AUTH_ITEMS" | jq --arg user "$user" --arg pass "$password" '. + [{ username: $user, password: $pass }]') done shift 2 ;; --satisfy) if [ $# -lt 2 ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: --satisfy requires any/all${CoR}" return 1 fi if [ "$2" = "any" ]; then SATISFY_ANY="true" elif [ "$2" = "all" ]; then SATISFY_ANY="false" else echo -e "\n ā›” ${COLOR_RED}ERROR: Invalid satisfy value. Must be 'any' or 'all'${CoR}" return 1 fi shift 2 ;; --pass-auth) if [ $# -lt 2 ] || { [ "$2" != "true" ] && [ "$2" != "false" ]; }; then echo -e "\n ā›” ${COLOR_RED}ERROR: --pass-auth requires true/false${CoR}" return 1 fi PASS_AUTH="$2" shift 2 ;; *) echo -e "\n ā›” ${COLOR_RED}ERROR: Unknown option $1${CoR}" return 1 ;; esac done # Check that at least one rule is defined if [ "$AUTH_ITEMS" = "[]" ] && [ "$IP_CLIENTS" = "[]" ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: At least one --auth or --access rule is required${CoR}" return 1 fi # Build payload local PAYLOAD=$(jq -n \ --arg name "$NAME" \ --argjson satisfy_any "$SATISFY_ANY" \ --argjson pass_auth "$PASS_AUTH" \ --argjson items "$AUTH_ITEMS" \ --argjson clients "$IP_CLIENTS" \ '{ name: $name, satisfy_any: $satisfy_any, pass_auth: $pass_auth, items: $items, clients: $clients }') # Create access list via API RESPONSE=$(curl -s -X POST "$BASE_URL/nginx/access-lists" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json" \ -d "$PAYLOAD") # Check for errors if [ "$(echo "$RESPONSE" | jq -r '.error.message // empty')" != "" ]; then echo -e " ā›” ${COLOR_RED}Failed to create access list${CoR}" echo -e " Error: $(echo "$RESPONSE" | jq -r '.error.message')" return 1 fi # Display results local NEW_ID=$(echo "$RESPONSE" | jq -r '.id') echo -e " āœ… ${COLOR_GREEN}Access list created successfully!${CoR}" echo -e " ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€" echo -e " │ ID: ${COLOR_YELLOW}$NEW_ID${CoR}" echo -e " │ Name: ${COLOR_GREEN}$NAME${CoR}" echo -e " │ Satisfy: ${COLOR_CYAN}$([ "$SATISFY_ANY" = "true" ] && echo "any" || echo "all")${CoR}" echo -e " │ Pass Auth: ${COLOR_CYAN}$([ "$PASS_AUTH" = "true" ] && echo "yes" || echo "no")${CoR}" if [ "$AUTH_ITEMS" != "[]" ]; then echo -e " │ šŸ‘¤ Authentication Rules:" echo "$AUTH_ITEMS" | jq -r '.[] | " │ • User: \(.username)"' fi if [ "$IP_CLIENTS" != "[]" ]; then echo -e " │ 🌐 Access Rules:" echo "$IP_CLIENTS" | jq -r '.[] | " │ • \(.directive) \(.address)"' fi echo -e " └───────────────────────────────────────────" } # Update an existing access list with various options # Usage: # access_list_update [options] # Options: # --name "new_name" Update access list name # --satisfy any|all Update satisfaction mode # --pass-auth true|false Update password authentication # --allow "ip1,ip2" Add allowed IPs/ranges (comma-separated) # --deny "ip1,ip2" Add denied IPs/ranges (comma-separated) # --users "user1,user2" Add users with password prompts (comma-separated) access_list_update() { check_token_notverbose if [ -z "$ACCESS_LIST_ID" ]; then echo -e "\n ā›” ${COLOR_RED}Error: ACCESS_LIST_ID is required.${CoR}" echo -e " Usage: $0 --access-list-update [options]" return 1 fi local access_list_id="$ACCESS_LIST_ID" # Get the current access list details with expanded items and clients # Note: The expand parameter is required to get full details of items and clients local current_list=$(curl -s -X GET "$BASE_URL/nginx/access-lists/$access_list_id?expand=items%2Cclients" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ "$(echo "$current_list" | jq -r '.error | length')" -ne 0 ]; then echo -e " ā›” ${COLOR_RED}Failed to fetch access list details. Error: $(echo "$current_list" | jq -r '.error')${CoR}" return 1 fi # Extract current values with proper null handling local current_name=$(echo "$current_list" | jq -r '.name // "unnamed"') local current_satisfy_any=$(echo "$current_list" | jq -r '.satisfy_any // false') local current_pass_auth=$(echo "$current_list" | jq -r '.pass_auth // false') # Keep only the fields accepted by PUT (GET adds id, hint, created_on...); # an empty password tells NPM to keep the existing one local current_items=$(echo "$current_list" | jq '[(.items // [])[] | {username, password: ""}]') local current_clients=$(echo "$current_list" | jq '[(.clients // [])[] | {address, directive}]') echo -e "\nšŸ”‘ ${COLOR_CYAN}Updating access list ID: ${COLOR_YELLOW}$access_list_id${CoR}" echo -e "\n${COLOR_CYAN}Current Access List Details:${CoR}" echo -e "Name: $current_name" echo -e "Satisfy Any: $current_satisfy_any" echo -e "Pass Auth: $current_pass_auth" # Initialize variables with current values local new_name="$current_name" local satisfy_any="$current_satisfy_any" local pass_auth="$current_pass_auth" local items="$current_items" local clients="$current_clients" # Process command line arguments if provided if [ $# -gt 0 ]; then echo -e "\n${COLOR_CYAN}Processing command line arguments...${CoR}" while [ $# -gt 0 ]; do case "$1" in --name) if [ $# -lt 2 ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: --name requires a value${CoR}" return 1 fi new_name="$2" echo -e " šŸ“ Setting name to: ${COLOR_GREEN}$new_name${CoR}" shift 2 ;; --satisfy) if [ $# -lt 2 ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: --satisfy requires any/all${CoR}" return 1 fi if [ "$2" = "any" ]; then satisfy_any="true" echo -e " šŸ”„ Setting satisfy to: ${COLOR_GREEN}any${CoR}" elif [ "$2" = "all" ]; then satisfy_any="false" echo -e " šŸ”„ Setting satisfy to: ${COLOR_GREEN}all${CoR}" else echo -e "\n ā›” ${COLOR_RED}ERROR: Invalid satisfy value. Must be 'any' or 'all'${CoR}" return 1 fi shift 2 ;; --pass-auth) if [ $# -lt 2 ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: --pass-auth requires true/false${CoR}" return 1 fi if [ "$2" = "true" ] || [ "$2" = "false" ]; then pass_auth="$2" echo -e " šŸ”“ Setting pass auth to: ${COLOR_GREEN}$pass_auth${CoR}" else echo -e "\n ā›” ${COLOR_RED}ERROR: Invalid pass auth value. Must be 'true' or 'false'${CoR}" return 1 fi shift 2 ;; --allow) if [ $# -lt 2 ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: --allow requires IP addresses/ranges${CoR}" return 1 fi # Split comma-separated IPs and add each one IFS=',' read -ra IPS <<<"$2" for ip in "${IPS[@]}"; do ip=$(echo "$ip" | xargs) # trim whitespace clients=$(echo "$clients" | jq --arg ip "$ip" '. + [{ address: $ip, directive: "allow" }]') done echo -e " āœ… Added allow rules for: ${COLOR_GREEN}$2${CoR}" shift 2 ;; --deny) if [ $# -lt 2 ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: --deny requires IP addresses/ranges${CoR}" return 1 fi # Split comma-separated IPs and add each one IFS=',' read -ra IPS <<<"$2" for ip in "${IPS[@]}"; do ip=$(echo "$ip" | xargs) # trim whitespace clients=$(echo "$clients" | jq --arg ip "$ip" '. + [{ address: $ip, directive: "deny" }]') done echo -e " āœ… Added deny rules for: ${COLOR_GREEN}$2${CoR}" shift 2 ;; --users) if [ $# -lt 2 ]; then echo -e "\n ā›” ${COLOR_RED}ERROR: --users requires user list${CoR}" return 1 fi # Split comma-separated users and add each one (will need password prompt or default) IFS=',' read -ra USERS <<<"$2" for user in "${USERS[@]}"; do user=$(echo "$user" | xargs) # trim whitespace echo -e " šŸ” Enter password for user ${COLOR_CYAN}$user${CoR}:" read -s password echo items=$(echo "$items" | jq --arg user "$user" --arg pass "$password" '. + [{ username: $user, password: $pass }]') done echo -e " āœ… Added users: ${COLOR_GREEN}$2${CoR}" shift 2 ;; *) echo -e "\n ā›” ${COLOR_RED}ERROR: Unknown option $1${CoR}" echo -e " ${COLOR_CYAN}Available options:${CoR}" echo -e " ${COLOR_GREEN}--name ${CoR} Update access list name" echo -e " ${COLOR_GREEN}--satisfy any|all${CoR} Update satisfaction mode" echo -e " ${COLOR_GREEN}--pass-auth true|false${CoR} Update pass auth setting" echo -e " ${COLOR_GREEN}--allow \"ip1,ip2\"${CoR} Update allowed IPs/ranges" echo -e " ${COLOR_GREEN}--deny \"ip1,ip2\"${CoR} Update denied IPs/ranges" echo -e " ${COLOR_GREEN}--users \"user1,user2\"${CoR} Update list of users" return 1 ;; esac done else # Interactive mode if no arguments provided echo -e "\n${COLOR_CYAN}No arguments provided, using current values.${CoR}" echo -e "šŸ’” ${COLOR_YELLOW}Tip: Use --name, --satisfy, --auth-type, or --pass-auth options for non-interactive updates${CoR}" fi # Prepare the JSON payload using jq for proper formatting (same structure as create) local payload=$(jq -n \ --arg name "$new_name" \ --argjson satisfy_any "$satisfy_any" \ --argjson pass_auth "$pass_auth" \ --argjson items "$items" \ --argjson clients "$clients" \ '{ name: $name, satisfy_any: $satisfy_any, pass_auth: $pass_auth, items: $items, clients: $clients }') # Update the access list local response=$(curl -s -X PUT "$BASE_URL/nginx/access-lists/$access_list_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Content-Type: application/json; charset=UTF-8" \ -d "$payload") if [ "$(echo "$response" | jq -r '.error | length')" -eq 0 ]; then echo -e "\n āœ… ${COLOR_GREEN}Access list updated successfully!${CoR}" echo -e "\n${COLOR_CYAN}Updated Access List Details:${CoR}" echo -e "ID: $access_list_id" echo -e "Name: $new_name" echo -e "Satisfy: $([ "$satisfy_any" = "true" ] && echo "any" || echo "all")" echo -e "Pass Auth: $pass_auth" else echo -e "\n ā›” ${COLOR_RED}Failed to update access list. Error: $(echo "$response" | jq -r '.error')${CoR}" return 1 fi } ################################ # Delete an access list access_list_delete() { if [ -z "$ACCESS_LIST_ID" ]; then echo -e "\n ā›” ${COLOR_RED}Error: ACCESS_LIST_ID is required.${CoR}" echo -e " Usage: $0 --access-list-delete [-y]" exit 1 fi check_token_notverbose echo -e " šŸ” Checking access list ID: ${COLOR_YELLOW}$ACCESS_LIST_ID${CoR}" # Check if access list exists RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/access-lists/$ACCESS_LIST_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ "$(echo "$RESPONSE" | jq -r '.error.code')" = "404" ]; then echo -e " ā›” ${COLOR_RED}Access list ID $ACCESS_LIST_ID not found${CoR}" exit 1 fi # Show details before deletion echo -e " ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€" echo -e " │ ID: ${COLOR_YELLOW}$ACCESS_LIST_ID${CoR}" echo -e " │ Name: ${COLOR_GREEN}$(echo "$RESPONSE" | jq -r '.name')${CoR}" echo -e " └───────────────────────────────────────────" # Confirm unless AUTO_YES if [ "$AUTO_YES" != true ]; then echo -e " āš ļø ${COLOR_RED}WARNING: This action cannot be undone!${CoR}" read -n 1 -r -p " šŸ”” Confirm deletion? (y/n): " CONFIRM echo if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then echo -e " āŒ ${COLOR_RED}Operation cancelled${CoR}" exit 1 fi fi # Delete access list RESPONSE=$(curl -s -X DELETE "$BASE_URL/nginx/access-lists/$ACCESS_LIST_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ "$RESPONSE" = "true" ]; then echo -e " āœ… ${COLOR_GREEN}Access list successfully deleted! ${CoR}šŸ—‘ļø " else echo -e " ā›” ${COLOR_RED}Failed to delete access list.${CoR}" if [ -n "$RESPONSE" ]; then echo -e " Error: $RESPONSE" fi exit 1 fi } ################################ # Show all access lists with detailed information ################################ # Show all access lists with detailed information access_list() { check_token_notverbose # Get all access lists RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/access-lists" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") # Check for API errors if [ "$(echo "$RESPONSE" | jq -r 'if type=="object" then .error.message else empty end')" != "" ]; then if [ "$JSON_OUTPUT" = true ]; then echo "$RESPONSE" | jq '{error: (.error.message // "Failed to fetch access lists")}' exit 1 fi echo -e " ā›” ${COLOR_RED}Failed to fetch access lists${CoR}" echo -e " Error: $(echo "$RESPONSE" | jq -r '.error.message')" return 1 fi if [ "$JSON_OUTPUT" = true ]; then echo "$RESPONSE" | jq . exit 0 fi echo -e "\nšŸ“‹ ${COLOR_CYAN}Access Lists Management${CoR}\n" # Display table header echo "ā”Œā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā”" echo -e "│ ${COLOR_CYAN}ID │ Name │ Authorization │ Access │ Satisfy │ Proxy Hosts ${CoR}│" echo "ā”œā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā”¤" # Process each access list and format output echo "$RESPONSE" | jq -r '.[] | "\(.id)|\(.name)|\(if .items then .items|length else 0 end) User\(if (.items|length//0) != 1 then "s" else "" end)|\(if .clients then .clients|length else 0 end) Rule\(if (.clients|length//0) != 1 then "s" else "" end)|\(.satisfy_any)|\(.proxy_host_count // 0)"' | while IFS="|" read -r id name users rules satisfy proxy_hosts; do # Format satisfy display (Any/All) satisfy_display=$([ "$satisfy" = "true" ] && echo "Any" || echo "All") # Print formatted line (name truncated to column width; count guarded above) printf "│ %-3s │ %s │ %-13s │ %-8s │ %-7s │ %-15s│\n" \ "$id" \ "$(truncate_pad "$name" 22)" \ "$users" \ "$rules" \ "$satisfy_display" \ "$proxy_hosts Proxy Hosts" done # Close table echo "ā””ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā”˜" # Display creation date info echo -e "\n${COLOR_CYAN}Note:${CoR} Use --access-list-show to see detailed information about a specific access list\n" } ################################ # Show detailed information for a specific access list access_list_show() { local id="$1" check_token_notverbose if [ -z "$id" ]; then echo -e "\nā›” ${COLOR_RED}Error: Access List ID is required${CoR}" echo -e "Usage: $0 --access-list-show " return 1 fi [ "$JSON_OUTPUT" = true ] || echo -e "\nšŸ“‹ ${COLOR_CYAN}Access List Details${CoR}" # Get specific access list with expanded items and clients # Note: The expand parameter is required to get full details of items and clients local response=$(curl -s -X GET "$BASE_URL/nginx/access-lists/$id?expand=items%2Cclients" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") # Check if response is valid JSON if ! echo "$response" | jq empty 2>/dev/null; then if [ "$JSON_OUTPUT" = true ]; then echo '{"error": "Invalid response from API"}' exit 1 fi echo -e "\nā›” ${COLOR_RED}Invalid response from API${CoR}" return 1 fi # Check if access list exists if [ "$(echo "$response" | jq 'has("error")')" = "true" ]; then if [ "$JSON_OUTPUT" = true ]; then echo '{"error": "Access List not found"}' exit 1 fi echo -e "\nā›” ${COLOR_RED}Access List not found${CoR}" return 1 fi if [ "$JSON_OUTPUT" = true ]; then echo "$response" | jq . exit 0 fi # Create horizontal border local h_border=$(printf '%*s' "80" '' | tr ' ' "=") # Display basic information echo -e "\n${COLOR_GREY}ā”Œ$h_border┐${CoR}" echo -e "${COLOR_GREY}│${CoR} ${COLOR_CYAN}Basic Information${CoR}" echo -e "${COLOR_GREY}ā”œ$h_border┤${CoR}" echo -e "${COLOR_GREY}│${CoR} ID: ${COLOR_GREEN}$(echo "$response" | jq -r '.id')${CoR}" echo -e "${COLOR_GREY}│${CoR} Name: ${COLOR_YELLOW}$(echo "$response" | jq -r '.name')${CoR}" echo -e "${COLOR_GREY}│${CoR} Auth Type: ${COLOR_ORANGE}$(echo "$response" | jq -r '.auth_type // "none"')${CoR}" # Display authentication settings local pass_auth=$(echo "$response" | jq -r '.pass_auth') local satisfy=$(echo "$response" | jq -r '.satisfy_any') echo -e "${COLOR_GREY}│${CoR} Pass Auth: $([ "$pass_auth" = "true" ] && echo "${COLOR_GREEN}āœ“${CoR}" || echo "${COLOR_RED}āœ—${CoR}")" echo -e "${COLOR_GREY}│${CoR} Satisfy: ${COLOR_YELLOW}$([ "$satisfy" = "true" ] && echo "Any" || echo "All")${CoR}" # Display users echo -e "${COLOR_GREY}ā”œ$h_border┤${CoR}" echo -e "${COLOR_GREY}│${CoR} ${COLOR_CYAN}Authorized Users${CoR}" echo -e "${COLOR_GREY}ā”œ$h_border┤${CoR}" if [ "$(echo "$response" | jq 'has("items")')" = "true" ] && [ "$(echo "$response" | jq '.items != null')" = "true" ]; then local users_count=$(echo "$response" | jq '.items | length') if [ "$users_count" -gt 0 ]; then echo "$response" | jq -r '.items[] | "│ • \(.username)"' else echo -e "${COLOR_GREY}│${CoR} No users configured" fi else echo -e "${COLOR_GREY}│${CoR} No users configured" fi # Display IP whitelist echo -e "${COLOR_GREY}ā”œ$h_border┤${CoR}" echo -e "${COLOR_GREY}│${CoR} ${COLOR_CYAN}IP Whitelist${CoR}" echo -e "${COLOR_GREY}ā”œ$h_border┤${CoR}" if [ "$(echo "$response" | jq 'has("clients")')" = "true" ] && [ "$(echo "$response" | jq '.clients != null')" = "true" ]; then local ips_count=$(echo "$response" | jq '.clients | length') if [ "$ips_count" -gt 0 ]; then echo "$response" | jq -r '.clients[] | "│ • \(.address) (\(.directive // "allow"))"' | while IFS= read -r line; do if [[ $line == *"allow"* ]]; then echo -e "${COLOR_GREY}│${CoR} • ${COLOR_GREEN}${line#* • }${CoR}" else echo -e "${COLOR_GREY}│${CoR} • ${COLOR_RED}${line#* • }${CoR}" fi done else echo -e "${COLOR_GREY}│${CoR} No IPs whitelisted" fi else echo -e "${COLOR_GREY}│${CoR} No IPs whitelisted" fi # Close the box echo -e "${COLOR_GREY}ā””$h_borderā”˜${CoR}" # Display legend echo -e "\n${COLOR_YELLOW}Legend:${CoR}" echo -e " • Pass Auth: ${COLOR_GREEN}āœ“${CoR} = Enabled, ${COLOR_RED}āœ—${CoR} = Disabled" echo -e " • IP Rules: ${COLOR_GREEN}allow${CoR} = Allowed, ${COLOR_RED}deny${CoR} = Denied\n" } ################################ ## backup # Function to make a full backup full_backup() { check_dependencies check_nginx_access check_token_notverbose DATE=$(date +"_%Y_%m_%d__%H_%M_%S") echo -e "\nšŸ“¦ ${COLOR_YELLOW}Starting full backup...${CoR}" # Initialize counters local users_count=0 local certs_count=0 local hosts_count=0 local redirect_hosts_count=0 local stream_hosts_count=0 local dead_hosts_count=0 local custom_certs_count=0 local letsencrypt_certs_count=0 local access_lists_count=0 local success_count=0 local error_count=0 # Create main backup directory with timestamp BACKUP_PATH="$BACKUP_DIR" mkdir -p "$BACKUP_PATH" # Create required subdirectories echo -e "\nšŸ“‚ ${COLOR_CYAN}Creating backup directories...${CoR}" for dir in ".user" ".settings" ".access_lists" ".Proxy_Hosts" ".Redirection_Hosts" ".Stream_Hosts" ".Dead_Hosts" ".ssl"; do mkdir -p "$BACKUP_PATH/$dir" || { echo -e " ā›” ${COLOR_RED}Failed to create $dir directory${CoR}" return 1 } echo -e " āœ“ Created: ${COLOR_GREY}$BACKUP_PATH/$dir${CoR}" done # Initialize empty JSON for full configuration echo "{}" >"$BACKUP_PATH/full_config${DATE}.json" echo -e "\nšŸ”„ ${COLOR_CYAN}Starting configuration backup...${CoR}" # 1. Backup Users # trap 'echo "Error on line $LINENO"' ERR # set -x # Debug mode ON echo -e "\nšŸ‘„ ${COLOR_CYAN}Backing up users...${CoR}" USERS_RESPONSE=$(curl -s -X GET "$BASE_URL/users" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ -n "$USERS_RESPONSE" ] && echo "$USERS_RESPONSE" | jq empty 2>/dev/null; then users_count=$(echo "$USERS_RESPONSE" | jq '. | length') # Save users to dedicated file echo "$USERS_RESPONSE" | jq '.' >"$BACKUP_PATH/.user/users_${NGINX_IP//./_}$DATE.json" # Add users to full configuration jq --argjson users "$USERS_RESPONSE" '. + {users: $users}' \ "$BACKUP_PATH/full_config${DATE}.json" >"$BACKUP_PATH/full_config${DATE}.json.tmp" mv "$BACKUP_PATH/full_config${DATE}.json.tmp" "$BACKUP_PATH/full_config${DATE}.json" echo -e " āœ… ${COLOR_GREEN}Backed up $users_count users${CoR}" success_count=$((success_count + 1)) else echo -e " āš ļø ${COLOR_YELLOW}No users found or invalid response${CoR}" error_count=$((error_count + 1)) fi # trap - ERR # Reset trap # set +x # Debug mode OFF # 2. Backup settings echo -e "\nāš™ļø ${COLOR_CYAN}Backing up settings...${CoR}" SETTINGS_RESPONSE=$(curl -s -X GET "$BASE_URL/settings" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") #echo -e "\nšŸ” DEBUG Settings Response: $SETTINGS_RESPONSE" # Debug line if [ -n "$SETTINGS_RESPONSE" ] && echo "$SETTINGS_RESPONSE" | jq empty 2>/dev/null; then # Save settings to dedicated file echo "$SETTINGS_RESPONSE" | jq '.' >"$BACKUP_PATH/.settings/settings_${NGINX_IP//./_}$DATE.json" # Add settings to full configuration jq --argjson settings "$SETTINGS_RESPONSE" '. + {settings: $settings}' \ "$BACKUP_PATH/full_config${DATE}.json" >"$BACKUP_PATH/full_config${DATE}.json.tmp" mv "$BACKUP_PATH/full_config${DATE}.json.tmp" "$BACKUP_PATH/full_config${DATE}.json" echo -e " āœ… ${COLOR_GREEN}Settings backed up successfully${CoR}" success_count=$((success_count + 1)) else echo -e " āš ļø ${COLOR_YELLOW}Invalid settings response${CoR}" error_count=$((error_count + 1)) fi # 3. Backup access lists echo -e "\nšŸ”‘ ${COLOR_CYAN}Backing up access lists...${CoR}" # Get list of access list IDs first ACCESS_LISTS_IDS=$(curl -s -X GET "$BASE_URL/nginx/access-lists" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" | jq -r '.[].id' 2>/dev/null) if [ -n "$ACCESS_LISTS_IDS" ]; then access_lists_count=$(echo "$ACCESS_LISTS_IDS" | wc -l | tr -d ' ') # Initialize JSON array for complete access lists echo "[" >"$BACKUP_PATH/.access_lists/access_lists_${NGINX_IP//./_}$DATE.json" local first=true # Get each access list with expanded items and clients for complete backup # Note: The expand parameter is required to get full details of items and clients for list_id in $ACCESS_LISTS_IDS; do if [ "$first" = true ]; then first=false else echo "," >>"$BACKUP_PATH/.access_lists/access_lists_${NGINX_IP//./_}$DATE.json" fi curl -s -X GET "$BASE_URL/nginx/access-lists/$list_id?expand=items%2Cclients" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" >>"$BACKUP_PATH/.access_lists/access_lists_${NGINX_IP//./_}$DATE.json" done echo "]" >>"$BACKUP_PATH/.access_lists/access_lists_${NGINX_IP//./_}$DATE.json" # Verify JSON is valid if jq empty "$BACKUP_PATH/.access_lists/access_lists_${NGINX_IP//./_}$DATE.json" 2>/dev/null; then # Add access lists to full configuration ACCESS_LISTS_RESPONSE=$(cat "$BACKUP_PATH/.access_lists/access_lists_${NGINX_IP//./_}$DATE.json") jq --argjson lists "$ACCESS_LISTS_RESPONSE" '. + {access_lists: $lists}' \ "$BACKUP_PATH/full_config${DATE}.json" >"$BACKUP_PATH/full_config${DATE}.json.tmp" mv "$BACKUP_PATH/full_config${DATE}.json.tmp" "$BACKUP_PATH/full_config${DATE}.json" echo -e " āœ… ${COLOR_GREEN}Backed up $access_lists_count access lists with complete details${CoR}" success_count=$((success_count + 1)) else echo -e " āš ļø ${COLOR_YELLOW}Failed to create valid access lists backup${CoR}" error_count=$((error_count + 1)) fi else echo -e " āš ļø ${COLOR_YELLOW}No access lists found${CoR}" error_count=$((error_count + 1)) fi # 4. Backup proxy hosts echo -e "\n🌐 ${COLOR_CYAN}Backing up proxy hosts...${CoR}" ALL_HOSTS_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ -n "$ALL_HOSTS_RESPONSE" ] && echo "$ALL_HOSTS_RESPONSE" | jq empty 2>/dev/null; then hosts_count=$(echo "$ALL_HOSTS_RESPONSE" | jq '. | length') # Save all hosts metadata echo "$ALL_HOSTS_RESPONSE" | jq '.' >"$BACKUP_PATH/.Proxy_Hosts/all_hosts_${NGINX_IP//./_}$DATE.json" # Process each proxy host - use while read with pipe to preserve variables while IFS= read -r host; do local host_id=$(echo "$host" | jq -r '.id') local domain_name=$(echo "$host" | jq -r '.domain_names[0]' | sed 's/[^a-zA-Z0-9.]/_/g') local cert_id=$(echo "$host" | jq -r '.certificate_id') echo -e "\n šŸ“„ Processing host: ${COLOR_GREEN}$domain_name${CoR} (ID: ${COLOR_YELLOW}$host_id${CoR})" # Create directory for this proxy host local PROXY_DIR="$BACKUP_PATH/.Proxy_Hosts/$domain_name" mkdir -p "$PROXY_DIR/ssl" "$PROXY_DIR/logs" # Save proxy host configuration echo "$host" | jq '.' >"$PROXY_DIR/proxy_config.json" # Get and save nginx configuration local NGINX_CONFIG NGINX_CONFIG=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts/$host_id/nginx" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ -n "$NGINX_CONFIG" ]; then echo "$NGINX_CONFIG" >"$PROXY_DIR/nginx.conf" fi # Get and save logs if they exist curl -s -X GET "$BASE_URL/nginx/proxy-hosts/$host_id/access.log" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" >"$PROXY_DIR/logs/access.log" curl -s -X GET "$BASE_URL/nginx/proxy-hosts/$host_id/error.log" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" >"$PROXY_DIR/logs/error.log" # Process SSL certificate if exists if [ -n "$cert_id" ] && [ "$cert_id" != "null" ] && [ "$cert_id" != "0" ]; then echo -e " šŸ”’ Downloading SSL certificate (ID: $cert_id)" # Get certificate metadata first local CERT_META CERT_META=$(curl -s -X GET "$BASE_URL/nginx/certificates/$cert_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ -n "$CERT_META" ] && echo "$CERT_META" | jq empty 2>/dev/null; then echo "$CERT_META" | jq '.' >"$PROXY_DIR/ssl/certificate_meta.json" # Get certificate content local CERT_CONTENT CERT_CONTENT=$(curl -s -X GET "$BASE_URL/nginx/certificates/$cert_id/certificates" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Accept: application/json") if [ -n "$CERT_CONTENT" ] && echo "$CERT_CONTENT" | jq empty 2>/dev/null; then # Save certificate files in proxy host directory echo "$CERT_CONTENT" | jq -r '.certificate' >"$PROXY_DIR/ssl/certificate.pem" echo "$CERT_CONTENT" | jq -r '.private' >"$PROXY_DIR/ssl/private.key" chmod 600 "$PROXY_DIR/ssl/private.key" if echo "$CERT_CONTENT" | jq -r '.intermediate' >/dev/null 2>&1; then echo "$CERT_CONTENT" | jq -r '.intermediate' >"$PROXY_DIR/ssl/chain.pem" fi # Create centralized SSL directory structure local CERT_NAME CERT_NAME=$(echo "$CERT_META" | jq -r '.nice_name // .domain_names[0]' | sed 's/[^a-zA-Z0-9.]/_/g') local CENTRAL_SSL_DIR="$BACKUP_PATH/.ssl/$CERT_NAME" mkdir -p "$CENTRAL_SSL_DIR" # Save metadata and create symbolic links echo "$CERT_META" | jq '.' >"$CENTRAL_SSL_DIR/certificate_meta.json" ln -sf "$PROXY_DIR/ssl/certificate.pem" "$CENTRAL_SSL_DIR/certificate.pem" ln -sf "$PROXY_DIR/ssl/private.key" "$CENTRAL_SSL_DIR/private.key" [ -f "$PROXY_DIR/ssl/chain.pem" ] && ln -sf "$PROXY_DIR/ssl/chain.pem" "$CENTRAL_SSL_DIR/chain.pem" # Add symlink to latest version ln -sf "$CENTRAL_SSL_DIR" "$BACKUP_PATH/.ssl/${CERT_NAME}_latest" echo -e " āœ… ${COLOR_GREEN}SSL certificate backed up successfully${CoR}" # Count certificate type - maintenant les compteurs fonctionneront if echo "$CERT_META" | jq -e '.provider // empty | contains("letsencrypt")' >/dev/null 2>&1; then letsencrypt_certs_count=$((letsencrypt_certs_count + 1)) else custom_certs_count=$((custom_certs_count + 1)) fi certs_count=$((certs_count + 1)) success_count=$((success_count + 1)) else echo -e " āš ļø ${COLOR_YELLOW}Failed to download certificate content${CoR}" error_count=$((error_count + 1)) fi else echo -e " āš ļø ${COLOR_YELLOW}Failed to get certificate metadata${CoR}" error_count=$((error_count + 1)) fi fi echo -e " āœ… ${COLOR_GREEN}Host $domain_name backed up successfully${CoR}" done < <(echo "$ALL_HOSTS_RESPONSE" | jq -c '.[]') # Create latest symlink for hosts ln -sf "$BACKUP_PATH/.Proxy_Hosts/all_hosts_${NGINX_IP//./_}$DATE.json" \ "$BACKUP_PATH/.Proxy_Hosts/all_hosts_latest.json" echo -e "\n āœ… ${COLOR_GREEN}Backed up $hosts_count proxy hosts${CoR}" else echo -e " āš ļø ${COLOR_YELLOW}No proxy hosts found or invalid response${CoR}" error_count=$((error_count + 1)) fi # 5. Backup redirection hosts echo -e "\nšŸ”„ ${COLOR_CYAN}Backing up redirection hosts...${CoR}" REDIRECTION_HOSTS_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/redirection-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ -n "$REDIRECTION_HOSTS_RESPONSE" ] && echo "$REDIRECTION_HOSTS_RESPONSE" | jq empty 2>/dev/null; then redirect_hosts_count=$(echo "$REDIRECTION_HOSTS_RESPONSE" | jq '. | length') echo "$REDIRECTION_HOSTS_RESPONSE" | jq '.' >"$BACKUP_PATH/.Redirection_Hosts/redirection_hosts_${NGINX_IP//./_}$DATE.json" ln -sf "$BACKUP_PATH/.Redirection_Hosts/redirection_hosts_${NGINX_IP//./_}$DATE.json" \ "$BACKUP_PATH/.Redirection_Hosts/redirection_hosts_latest.json" jq --argjson hosts "$REDIRECTION_HOSTS_RESPONSE" '. + {redirection_hosts: $hosts}' \ "$BACKUP_PATH/full_config${DATE}.json" >"$BACKUP_PATH/full_config${DATE}.json.tmp" mv "$BACKUP_PATH/full_config${DATE}.json.tmp" "$BACKUP_PATH/full_config${DATE}.json" echo -e " āœ… ${COLOR_GREEN}Backed up $redirect_hosts_count redirection hosts${CoR}" success_count=$((success_count + 1)) else echo -e " āš ļø ${COLOR_YELLOW}No redirection hosts found or invalid response${CoR}" error_count=$((error_count + 1)) fi # 6. Backup stream hosts echo -e "\nšŸ”Œ ${COLOR_CYAN}Backing up stream hosts...${CoR}" STREAM_HOSTS_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/streams" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ -n "$STREAM_HOSTS_RESPONSE" ] && echo "$STREAM_HOSTS_RESPONSE" | jq empty 2>/dev/null; then stream_hosts_count=$(echo "$STREAM_HOSTS_RESPONSE" | jq '. | length') echo "$STREAM_HOSTS_RESPONSE" | jq '.' >"$BACKUP_PATH/.Stream_Hosts/stream_hosts_${NGINX_IP//./_}$DATE.json" ln -sf "$BACKUP_PATH/.Stream_Hosts/stream_hosts_${NGINX_IP//./_}$DATE.json" \ "$BACKUP_PATH/.Stream_Hosts/stream_hosts_latest.json" jq --argjson hosts "$STREAM_HOSTS_RESPONSE" '. + {streams: $hosts}' \ "$BACKUP_PATH/full_config${DATE}.json" >"$BACKUP_PATH/full_config${DATE}.json.tmp" mv "$BACKUP_PATH/full_config${DATE}.json.tmp" "$BACKUP_PATH/full_config${DATE}.json" echo -e " āœ… ${COLOR_GREEN}Backed up $stream_hosts_count stream hosts${CoR}" success_count=$((success_count + 1)) else echo -e " āš ļø ${COLOR_YELLOW}No stream hosts found or invalid response${CoR}" error_count=$((error_count + 1)) fi # 7. Backup 404 hosts (dead hosts) echo -e "\n🚫 ${COLOR_CYAN}Backing up 404 hosts...${CoR}" DEAD_HOSTS_RESPONSE=$(curl -s -X GET "$BASE_URL/nginx/dead-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ -n "$DEAD_HOSTS_RESPONSE" ] && echo "$DEAD_HOSTS_RESPONSE" | jq empty 2>/dev/null; then dead_hosts_count=$(echo "$DEAD_HOSTS_RESPONSE" | jq '. | length') echo "$DEAD_HOSTS_RESPONSE" | jq '.' >"$BACKUP_PATH/.Dead_Hosts/dead_hosts_${NGINX_IP//./_}$DATE.json" ln -sf "$BACKUP_PATH/.Dead_Hosts/dead_hosts_${NGINX_IP//./_}$DATE.json" \ "$BACKUP_PATH/.Dead_Hosts/dead_hosts_latest.json" jq --argjson hosts "$DEAD_HOSTS_RESPONSE" '. + {dead_hosts: $hosts}' \ "$BACKUP_PATH/full_config${DATE}.json" >"$BACKUP_PATH/full_config${DATE}.json.tmp" mv "$BACKUP_PATH/full_config${DATE}.json.tmp" "$BACKUP_PATH/full_config${DATE}.json" echo -e " āœ… ${COLOR_GREEN}Backed up $dead_hosts_count 404 hosts${CoR}" success_count=$((success_count + 1)) else echo -e " āš ļø ${COLOR_YELLOW}No 404 hosts found or invalid response${CoR}" error_count=$((error_count + 1)) fi # Generate backup report and statistics echo -e "\nšŸ“Š ${COLOR_YELLOW}Backup Summary:${CoR}" echo -e " • ${COLOR_CYAN}Users:${CoR} $users_count" echo -e " • ${COLOR_CYAN}Proxy Hosts:${CoR} $hosts_count" echo -e " • ${COLOR_CYAN}Redirection Hosts:${CoR} $redirect_hosts_count" echo -e " • ${COLOR_CYAN}Stream Hosts:${CoR} $stream_hosts_count" echo -e " • ${COLOR_CYAN}404 Hosts:${CoR} $dead_hosts_count" echo -e " • ${COLOR_CYAN}SSL Certificates:${CoR} $certs_count" echo -e " ā”œā”€ Custom: $custom_certs_count" echo -e " └─ Let's Encrypt: $letsencrypt_certs_count" echo -e " • ${COLOR_CYAN}Access Lists:${CoR} $access_lists_count" echo -e " • ${COLOR_CYAN}Success/Error:${CoR} $success_count/$error_count" # Count backup files by type local total_files=$(find "$BACKUP_PATH" -type f \( \ -name "*.json" -o \ -name "*.conf" -o \ -name "*.log" -o \ -name "*.pem" -o \ -name "*.key" \ \) | wc -l) local config_files=$(find "$BACKUP_PATH" -maxdepth 1 -type f -name "full_config*.json" | wc -l) local proxy_files=$(find "$BACKUP_PATH/.Proxy_Hosts" -type f -name "proxy_config.json" | wc -l) local redirect_files=$(find "$BACKUP_PATH/.Redirection_Hosts" -type f -name "*.json" | wc -l) local stream_files=$(find "$BACKUP_PATH/.Stream_Hosts" -type f -name "*.json" | wc -l) local dead_files=$(find "$BACKUP_PATH/.Dead_Hosts" -type f -name "*.json" | wc -l) local ssl_files=$(find "$BACKUP_PATH" -type f \( \ -name "certificate*.json" -o \ -name "*.pem" -o \ -name "*.key" \ \) | wc -l) local access_files=$(find "$BACKUP_PATH/.access_lists" -type f -name "*.json" | wc -l) local settings_files=$(find "$BACKUP_PATH/.settings" -type f -name "*.json" | wc -l) local user_files=$(find "$BACKUP_PATH/.user" -type f -name "*.json" | wc -l) echo -e "\nšŸ“ ${COLOR_YELLOW}Backup Files Count:${CoR}" echo -e " • Full Configurations: ${COLOR_GREY}$config_files files${CoR}" echo -e " • Proxy Host Files: ${COLOR_GREY}$proxy_files files${CoR}" echo -e " • Redirection Host Files: ${COLOR_GREY}$redirect_files files${CoR}" echo -e " • Stream Host Files: ${COLOR_GREY}$stream_files files${CoR}" echo -e " • 404 Host Files: ${COLOR_GREY}$dead_files files${CoR}" echo -e " • SSL Certificate Files: ${COLOR_GREY}$ssl_files files${CoR}" echo -e " • Access List Files: ${COLOR_GREY}$access_files files${CoR}" echo -e " • Settings Files: ${COLOR_GREY}$settings_files files${CoR}" echo -e " • User Files: ${COLOR_GREY}$user_files files${CoR}" echo -e " • ${COLOR_GREEN}Total Backup Files: ${COLOR_GREY}$total_files files${CoR}" # Display backup locations echo -e "\nšŸ“‚ ${COLOR_YELLOW}Backup Locations:${CoR}" echo -e " • Full config: ${COLOR_GREY}$BACKUP_PATH/full_config${DATE}.json${CoR}" echo -e " • Latest symlink: ${COLOR_GREY}$BACKUP_PATH/full_config_latest.json${CoR}" echo -e " • Proxy configs: ${COLOR_GREY}$BACKUP_PATH/.Proxy_Hosts/${CoR}" echo -e " • Host list full: ${COLOR_GREY}$BACKUP_PATH/.Proxy_Hosts/all_hosts_latest.json${CoR}" # Calculate and display total backup size local backup_size=$(du -sh "$BACKUP_PATH" | cut -f1) echo -e "\nšŸ’¾ ${COLOR_YELLOW}Backup Size:${CoR} ${COLOR_GREY}$backup_size${CoR}" # Create latest symlink for full configuration ln -sf "$BACKUP_PATH/full_config${DATE}.json" "$BACKUP_PATH/full_config_latest.json" # Check for any errors during backup if [ $error_count -gt 0 ]; then echo -e "\nāš ļø ${COLOR_YELLOW}Backup completed with $error_count errors${CoR}" echo -e " Please check the logs above for details." else echo -e "\nāœ… ${COLOR_GREEN}Backup completed successfully!${CoR}" fi echo -e "\nšŸ“ ${COLOR_GREY}Backup completed at: $(date '+%Y-%m-%d %H:%M:%S')${CoR}\n" return $error_count } # Backup a single proxy host (via $HOST_ID) or all proxy hosts (if $HOST_ID is empty), # including its SSL certificate, using the same on-disk layout as full_backup(). backup_host() { check_dependencies check_nginx_access check_token_notverbose BACKUP_PATH="$BACKUP_DIR" mkdir -p "$BACKUP_PATH/.Proxy_Hosts" local hosts_json if [ -n "$HOST_ID" ]; then echo -e "\nšŸ“¦ ${COLOR_YELLOW}Backing up proxy host ID $HOST_ID...${CoR}" local single_host single_host=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts/$HOST_ID" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if ! echo "$single_host" | jq -e '.id' >/dev/null 2>&1; then echo -e " ā›” ${COLOR_RED}Proxy host ID $HOST_ID not found.${CoR}\n" return 1 fi hosts_json="[$single_host]" else echo -e "\nšŸ“¦ ${COLOR_YELLOW}Backing up all proxy hosts...${CoR}" hosts_json=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") fi if [ -z "$hosts_json" ] || ! echo "$hosts_json" | jq empty 2>/dev/null; then echo -e " āš ļø ${COLOR_YELLOW}No proxy hosts found or invalid response${CoR}\n" return 1 fi local count=0 while IFS= read -r host; do local host_id=$(echo "$host" | jq -r '.id') local domain_name=$(echo "$host" | jq -r '.domain_names[0]' | sed 's/[^a-zA-Z0-9.]/_/g') local cert_id=$(echo "$host" | jq -r '.certificate_id') echo -e "\n šŸ“„ Processing host: ${COLOR_GREEN}$domain_name${CoR} (ID: ${COLOR_YELLOW}$host_id${CoR})" local PROXY_DIR="$BACKUP_PATH/.Proxy_Hosts/$domain_name" mkdir -p "$PROXY_DIR/ssl" "$PROXY_DIR/logs" echo "$host" | jq '.' >"$PROXY_DIR/proxy_config.json" local NGINX_CONFIG NGINX_CONFIG=$(curl -s -X GET "$BASE_URL/nginx/proxy-hosts/$host_id/nginx" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") [ -n "$NGINX_CONFIG" ] && echo "$NGINX_CONFIG" >"$PROXY_DIR/nginx.conf" curl -s -X GET "$BASE_URL/nginx/proxy-hosts/$host_id/access.log" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" >"$PROXY_DIR/logs/access.log" curl -s -X GET "$BASE_URL/nginx/proxy-hosts/$host_id/error.log" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" >"$PROXY_DIR/logs/error.log" if [ -n "$cert_id" ] && [ "$cert_id" != "null" ] && [ "$cert_id" != "0" ]; then echo -e " šŸ”’ Downloading SSL certificate (ID: $cert_id)" local CERT_META CERT_META=$(curl -s -X GET "$BASE_URL/nginx/certificates/$cert_id" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}") if [ -n "$CERT_META" ] && echo "$CERT_META" | jq empty 2>/dev/null; then echo "$CERT_META" | jq '.' >"$PROXY_DIR/ssl/certificate_meta.json" local CERT_CONTENT CERT_CONTENT=$(curl -s -X GET "$BASE_URL/nginx/certificates/$cert_id/certificates" \ -H "Authorization: Bearer ${TOKEN:-$(cat "$TOKEN_FILE")}" \ -H "Accept: application/json") if [ -n "$CERT_CONTENT" ] && echo "$CERT_CONTENT" | jq empty 2>/dev/null; then echo "$CERT_CONTENT" | jq -r '.certificate' >"$PROXY_DIR/ssl/certificate.pem" echo "$CERT_CONTENT" | jq -r '.private' >"$PROXY_DIR/ssl/private.key" chmod 600 "$PROXY_DIR/ssl/private.key" echo "$CERT_CONTENT" | jq -r '.intermediate // empty' >"$PROXY_DIR/ssl/chain.pem" echo -e " āœ… ${COLOR_GREEN}SSL certificate backed up${CoR}" else echo -e " āš ļø ${COLOR_YELLOW}Failed to download certificate content${CoR}" fi else echo -e " āš ļø ${COLOR_YELLOW}Failed to get certificate metadata${CoR}" fi fi echo -e " āœ… ${COLOR_GREEN}Host $domain_name backed up successfully${CoR}" count=$((count + 1)) done < <(echo "$hosts_json" | jq -c '.[]') echo -e "\nšŸ“ ${COLOR_GREY}Backup completed at: $(date '+%Y-%m-%d %H:%M:%S')${CoR}" echo -e " ${COLOR_CYAN}$count${CoR} proxy host(s) backed up in ${COLOR_GREY}$BACKUP_PATH/.Proxy_Hosts/${CoR}\n" } # List available per-host proxy backups (written by full_backup() or backup_host()). # Purely local (no API call), so it works even without a valid/fresh token. list_backups() { check_dependencies local ip_port_dir="${NGINX_IP//[.:]/_}_${NGINX_PORT}" BACKUP_DIR="${BACKUP_DIR:-$DATA_DIR/$ip_port_dir/backups}" BACKUP_PATH="$BACKUP_DIR" local dir="$BACKUP_PATH/.Proxy_Hosts" if [ ! -d "$dir" ] || [ -z "$(find "$dir" -mindepth 2 -maxdepth 2 -name "proxy_config.json" -print -quit 2>/dev/null)" ]; then echo -e "\n āš ļø ${COLOR_YELLOW}No proxy host backups found in ${COLOR_GREY}$dir${CoR}" echo -e " Run ${COLOR_CYAN}--backup-host${CoR} or ${COLOR_CYAN}--backup${CoR} first.\n" return 1 fi echo -e "\n šŸ“‹ ${COLOR_YELLOW}Available proxy host backups:${CoR}" echo -e " ${COLOR_GREY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${CoR}" printf " %-4s %-35s %s\n" "ID" "Domain" "Last backup" while IFS= read -r file; do local id domain mtime id=$(jq -r '.id // "?"' "$file" 2>/dev/null) domain=$(jq -r '.domain_names[0] // "?"' "$file" 2>/dev/null) mtime=$(date -r "$file" '+%Y-%m-%d %H:%M:%S' 2>/dev/null) printf " ${COLOR_CYAN}%-4s${CoR} %-35s ${COLOR_GREY}%s${CoR}\n" "$id" "$domain" "$mtime" done < <(find "$dir" -mindepth 2 -maxdepth 2 -name "proxy_config.json" | sort) echo "" } ###################################### # Main menu logic ###################################### for arg in "$@"; do if [ "$arg" = "-y" ]; then AUTO_YES=true #echo "Debug global: Setting AUTO_YES=true" break fi done # Pre-scan for --purge so its position on the command line does not matter # (e.g. "--cert-delete 240 --purge" or "--cert-delete --purge 240"). for arg in "$@"; do if [ "$arg" = "--purge" ]; then CERT_PURGE=true break fi done while [[ "$#" -gt 0 ]]; do case "$1" in -y) AUTO_YES=true shift ;; --json) JSON_OUTPUT=true shift ;; --list) LIST_JSON_COMMANDS=true shift ;; --purge) # Handled via pre-scan (CERT_PURGE); only used together with --cert-delete. CERT_PURGE=true shift ;; --help) SHOW_HELP=true shift ;; --examples) EXAMPLES=true shift ;; --info) INFO=true shift ;; --show-default) SHOW_DEFAULT=true shift ;; --check-token) CHECK_TOKEN=true shift ;; --check-update) CHECK_UPDATE=true shift ;; --backup) BACKUP=true shift ;; --backup-host) shift if [[ -n "$1" && "$1" != -* ]]; then HOST_ID="$1" shift fi BACKUP_HOST=true ;; --backup-host-list) BACKUP_LIST=true shift ;; --clean-hosts) exit 1 # not use ! CLEAN_HOSTS=true shift if [[ -n "$1" && "$1" != -* ]]; then BACKUP_FILE="$1" shift else BACKUP_FILE="$DEFAULT_BACKUP_FILE" fi ;; --user-list) USER_LIST=true shift ;; --user-create) shift if [[ $# -lt 3 ]]; then echo -e "\n šŸ‘¤ ${COLOR_RED}The --user-create option requires username, password, and email.${CoR}" echo -e " Usage: ${COLOR_ORANGE}$0 --user-create ${CoR}" echo -e " Example:" echo -e " ${COLOR_GREEN}$0 --user-create john secretpass john@domain.com${CoR}\n" exit 1 fi USERNAME="$1" PASSWORD="$2" EMAIL="$3" USER_CREATE=true shift 3 # Optional: grant admin role (default is a standard, non-admin user) if [ $# -gt 0 ] && [ "$1" = "--admin" ]; then USER_IS_ADMIN=true shift fi ;; --user-delete) shift if [[ $# -eq 0 ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: The --user-delete option requires a user šŸ†”.${CoR}" echo -e " Usage: ${COLOR_ORANGE}$0 --user-delete ${CoR}" exit 1 fi USER_ID="$1" USER_DELETE=true shift ;; --host-show) shift if [[ $# -eq 0 ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: The --host-show option requires a host ${CoR}šŸ†”" echo -e " Usage : ${COLOR_ORANGE}$0 --host-show ${CoR}" echo -e " Example: $0 --host-show 42" echo -e " Find ID: $0 --host-list\n" exit 1 fi host_id="$1" HOST_SHOW=true shift ;; --host-list) HOST_LIST=true shift ;; --host-list-full) HOST_LIST_FULL=true shift ;; --host-search) shift HOST_SEARCHNAME="${1:-}" if [ -z "$HOST_SEARCHNAME" ]; then echo -e "\n ā›” ${COLOR_RED}INVALID: The --host-search option requires a host name.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --host-search hostname${CoR}" echo -e " Example: $0 --host-search domain.com ${COLOR_YELLOW}or${CoR} dom ${COLOR_YELLOW}or${CoR} .com" echo -e " Find ID: $0 --host-list\n" exit 1 fi HOST_SEARCH=true shift ;; --host-enable) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: The --host-enable option requires a host${CoR} šŸ†”" echo -e " Usage : ${COLOR_ORANGE}$0 --host-enable ${CoR}" echo -e " Example: $0 --host-enable 42" echo -e " Find ID: $0 --host-list\n" exit 1 fi HOST_ID="$1" HOST_ENABLE=true shift ;; --host-disable) shift if [[ $# -eq 0 ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: The --host-disable option requires host${CoR} šŸ†”" echo -e " Usage : ${COLOR_GREEN}$0 --host-disable ${CoR}" echo -e " Example: $0 --host-disable 42" echo -e " Find ID: $0 --host-list\n" exit 1 fi HOST_ID="$1" HOST_DISABLE=true shift ;; --host-delete) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: The --host-delete option requires a host${CoR} šŸ†”" echo -e " ${COLOR_CYAN}Usage:${CoR}" echo -e " ${COLOR_ORANGE}$0 --host-delete [-y]${CoR}" echo -e " ${COLOR_CYAN}Examples:${CoR}" echo -e " ${COLOR_GREEN}$0 --host-delete 42${CoR}" echo -e " ${COLOR_GREEN}$0 --host-delete 42 -y${CoR} ${COLOR_GREY}# Skip confirmation${CoR}" echo -e "\n ${COLOR_YELLOW}šŸ’” Tip: Use --host-list to see all available hosts and their IDs${CoR}" exit 1 fi if [[ "$1" =~ ^[0-9]+$ ]]; then HOST_ID="$1" HOST_DELETE=true shift else echo -e "\n ā›” ${COLOR_RED}INVALID: Invalid host ID '$1' - must be a number${CoR}" echo -e "\n${COLOR_CYAN}Example:${CoR}" echo -e " ${COLOR_GREEN}$0 --host-delete 42${CoR}" echo -e "\n${COLOR_YELLOW}šŸ’” Tip: Use --host-list to see all available hosts and their IDs${CoR}" exit 1 fi ;; --host-update) if [[ "$#" -lt 3 ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: The --host-update option requires a host šŸ†” and a field=value pair.${CoR}" echo -e " Usage : ${COLOR_GREEN}$0 --host-update ${CoR}" echo -e " Find ID: $0 --host-list${CoR}\n" exit 1 fi # Check if $2 is a number if [[ "$2" =~ ^[0-9]+$ ]]; then HOST_ID="$2" FIELD_VALUE="$3" # Split FIELD and VALUE if [[ "$FIELD_VALUE" == *"="* ]]; then FIELD=$(echo "$FIELD_VALUE" | cut -d '=' -f1) VALUE=$(echo "$FIELD_VALUE" | cut -d '=' -f2-) HOST_UPDATE=true else echo -e "\n ā›” ${COLOR_RED}INVALID: The field=value pair is malformed.${CoR}" echo -e " Example: $0 --host-update 42 forward_host=new.backend.local" exit 1 fi shift 3 #host_update "$HOST_ID" "$FIELD" "$VALUE" #HOST_UPDATE=true else echo -e "\n ā›” ${COLOR_RED}INVALID: The --host-update option requires a valid (numeric) host šŸ†”.${CoR}" exit 1 fi ;; --host-create) HOST_CREATE=true shift # Check if there are any remaining arguments after shift if [ $# -eq 0 ]; then echo -e "\n ā›” ${COLOR_RED}INVALID: The --host-create option requires arguments${CoR}" echo -e "\n Required options:" echo -e " • Domain name ${COLOR_GREY}(positional argument)${CoR}" echo -e " • -i, --forward-host ${COLOR_GREY}Forward host (e.g., 127.0.0.1)${CoR}" echo -e " • -p, --forward-port ${COLOR_GREY}Forward port (e.g., 8080)${CoR}" echo -e "\n Optional:" echo -e " • -f, --forward-scheme ${COLOR_GREY}Protocol (http/https, default: http)${CoR}" echo -e " • -b, --block-exploits ${COLOR_GREY}Block common exploits (true/false)${CoR}" echo -e " • -c, --cache ${COLOR_GREY}Enable caching (true/false)${CoR}" echo -e " • -w, --websocket ${COLOR_GREY}Allow websocket upgrade (true/false)${CoR}" echo -e " • -h, --http2 ${COLOR_GREY}Enable HTTP/2 support (true/false)${CoR}" echo -e " • -s, --ssl-force ${COLOR_GREY}Force SSL (true/false)${CoR}" echo -e " • -l, --custom-locations ${COLOR_GREY}Custom location rules (JSON array)${CoR}" echo -e " • -a, --advanced-config ${COLOR_GREY}Advanced Nginx configuration (string)${CoR}" echo -e "\n Can be combined with:" echo -e " • --cert-generate ${COLOR_GREY}Generate SSL certificate${CoR}" echo -e " • --host-ssl-enable ${COLOR_GREY}Enable SSL after creation${CoR}" echo -e " • -y ${COLOR_GREY}Skip all confirmations${CoR}" echo -e "\n Examples:" echo -e " ${COLOR_GREEN}$0 --host-create example.com -i 127.0.0.1 -p 8080${CoR}" echo -e " ${COLOR_GREEN}$0 --host-create example.com -i 127.0.0.1 -p 8080 --cert-generate --host-ssl-enable -y${CoR}\n" exit 1 fi # Check if first argument is a valid domain (not starting with -) if [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: First argument after --host-create must be a domain name${CoR}" exit 1 fi DOMAIN_NAMES="$1" shift # Process remaining options while [[ $# -gt 0 ]]; do case "$1" in -i | --forward-host) if [[ -n "$2" && "$2" != -* ]]; then FORWARD_HOST="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}INVALID: The --forward-host option requires a valid value${CoR}" echo -e "\n Required options:" echo -e " • Domain name ${COLOR_GREY}(positional argument)${CoR}" echo -e " • -i, --forward-host ${COLOR_GREY}Forward host (e.g., 127.0.0.1)${CoR}" echo -e " • -p, --forward-port ${COLOR_GREY}Forward port (e.g., 8080)${CoR}" echo -e "\n Optional:" echo -e " • -f, --forward-scheme ${COLOR_GREY}Protocol (http/https, default: http)${CoR}" echo -e " • -b, --block-exploits ${COLOR_GREY}Block common exploits (true/false, default: false)${CoR}" echo -e " • -c, --cache ${COLOR_GREY}Enable caching (true/false, default: false)${CoR}" echo -e " • -w, --websocket ${COLOR_GREY}Allow websocket upgrade (true/false, default: false)${CoR}" echo -e " • -h, --http2 ${COLOR_GREY}Enable HTTP/2 support (true/false, default: false)${CoR}" echo -e " • -s, --ssl-force ${COLOR_GREY}Force SSL (true/false, default: false)${CoR}" echo -e " • -l, --custom-locations ${COLOR_GREY}Custom location rules (JSON array)${CoR}" echo -e " • -a, --advanced-config ${COLOR_GREY}Advanced Nginx configuration (string)${CoR}" exit 1 fi ;; -p | --forward-port) if [[ -n "$2" && "$2" != -* && "$2" =~ ^[0-9]+$ ]]; then FORWARD_PORT="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}INVALID: The --forward-port option requires a valid number${CoR}" echo -e "\n Required options:" echo -e " • Domain name ${COLOR_GREY}(positional argument)${CoR}" echo -e " • -i, --forward-host ${COLOR_GREY}Forward host (e.g., 127.0.0.1)${CoR}" echo -e " • -p, --forward-port ${COLOR_GREY}Forward port (e.g., 8080)${CoR}" exit 1 fi ;; -f | --forward-scheme) if [[ -n "$2" && "$2" != -* && "$2" =~ ^(http|https)$ ]]; then FORWARD_SCHEME="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}INVALID: The --forward-scheme option must be 'http' or 'https'${CoR}" exit 1 fi ;; -b | --block-exploits | -c | --cache | -w | --websocket | -h | --http2 | -s | --ssl-force) # Process boolean options # Note: Variable names must match those used in create_or_update_proxy_host() function opt_name=${1#-?} opt_name=${opt_name#--} if [[ "$2" =~ ^(true|false)$ ]]; then case "$opt_name" in block-exploits) BLOCK_EXPLOITS="$2" ;; cache) CACHING_ENABLED="$2" ;; # Fixed: was CACHE_ENABLED (Issue #23) websocket) ALLOW_WEBSOCKET_UPGRADE="$2" ;; # Fixed: was WEBSOCKET_SUPPORT (Issue #23) http2) HTTP2_SUPPORT="$2" ;; ssl-force) SSL_FORCED="$2" ;; esac shift 2 else echo -e "\n ā›” ${COLOR_RED}INVALID: The $1 option must be 'true' or 'false'${CoR}" exit 1 fi ;; -l | --custom-locations) # Fixed: Added support for custom locations option (Issue #22) if [[ -n "$2" && "$2" != -* ]]; then CUSTOM_LOCATIONS="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}INVALID: The --custom-locations option requires a JSON value${CoR}" echo -e " ${COLOR_CYAN}Example:${CoR}" echo -e " ${COLOR_GREEN}$0 --host-create example.com -i 192.168.1.10 -p 8080 -l '[{\"path\":\"/api\",\"forward_host\":\"192.168.1.11\",\"forward_port\":8081}]'${CoR}" exit 1 fi ;; -a | --advanced-config) # Fixed: Added support for advanced config option (Issue #22) if [[ -n "$2" && "$2" != -* ]]; then ADVANCED_CONFIG="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}INVALID: The --advanced-config option requires a configuration string${CoR}" echo -e " ${COLOR_CYAN}Example:${CoR}" echo -e " ${COLOR_GREEN}$0 --host-create example.com -i 192.168.1.10 -p 8080 -a 'proxy_set_header X-Real-IP \\\$remote_addr;'${CoR}" exit 1 fi ;; # Support for chaining commands --cert-generate | --host-ssl-enable | -y | --cert-email | --dns-provider | --dns-credentials) # Just store these flags, they'll be processed later case "$1" in --cert-generate) CERT_GENERATE=true shift if [ $# -gt 0 ] && [[ "$1" != --* ]]; then CERT_DOMAIN="$1" shift else # If no specific argument for --cert-generate, use the host domain CERT_DOMAIN="$DOMAIN_NAMES" fi ;; --cert-email) shift CERT_EMAIL="$1" shift ;; --dns-provider) shift CERT_DNS_PROVIDER="$1" shift ;; --dns-credentials) shift CERT_DNS_CREDENTIALS="$1" shift ;; --host-ssl-enable) HOST_SSL_ENABLE=true shift ;; -y) AUTO_YES=true shift ;; esac ;; *) if [[ "$1" == -* ]]; then echo -e "\n āš ļø ${COLOR_YELLOW}WARNING: Unknown option ignored -> $1${CoR}" fi shift ;; esac done # check settings if [ -z "$FORWARD_HOST" ] || [ -z "$FORWARD_PORT" ]; then echo -e "\n ā›” ${COLOR_RED}INVALID: Missing required parameters${CoR}" echo -e " Required options:" echo -e " • Domain name: ${COLOR_GREEN}$DOMAIN_NAMES${CoR} ${COLOR_GREY}(provided)${CoR}" [ -z "$FORWARD_HOST" ] && echo -e " • -i, --forward-host ${COLOR_RED}Missing${CoR}" [ -z "$FORWARD_PORT" ] && echo -e " • -p, --forward-port ${COLOR_RED}Missing${CoR}" echo -e "\n Example:" echo -e " ${COLOR_GREEN}$0 --host-create example.com -i 127.0.0.1 -p 8080${CoR}\n" exit 1 fi # Create proxy host create_or_update_proxy_host "$DOMAIN_NAMES" "$FORWARD_HOST" "$FORWARD_PORT" \ "${FORWARD_SCHEME:-http}" "${BLOCK_EXPLOITS:-false}" "${CACHING_ENABLED:-false}" \ "${ALLOW_WEBSOCKET_UPGRADE:-false}" "${HTTP2_SUPPORT:-false}" "${SSL_FORCED:-false}" ;; --host-ssl-enable) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}The --host-ssl-enable option requires a host ID and certificate ID.${CoR}" echo -e "\n ${COLOR_CYAN}Usage:${CoR}" echo -e " ${COLOR_ORANGE}$0 --host-ssl-enable ${CoR}" echo -e " ${COLOR_CYAN}Examples:${CoR}" echo -e " ${COLOR_GREEN}$0 --host-ssl-enable 42 240${CoR}" echo -e " ${COLOR_GREEN}$0 --host-ssl-enable 42 240 -y${CoR} ${COLOR_GREY}# Skip confirmation${CoR}" echo -e " ${COLOR_YELLOW}šŸ’” Tips:${CoR}" echo -e " • Use ${COLOR_GREEN}--host-list${CoR} to see all available hosts" echo -e " • Use ${COLOR_GREEN}--cert-list${CoR} to see all available certificates\n" exit 1 fi if [[ "$1" =~ ^[0-9]+$ ]]; then HOST_ID="$1" shift # Check for mandatory cert_id if [ $# -gt 0 ] && [[ "$1" =~ ^[0-9]+$ ]]; then CERT_ID="$1" shift else echo -e "\n ā›” ${COLOR_RED}ERROR: Certificate ID is required${CoR}" echo -e " ${COLOR_CYAN}Usage:${CoR} $0 --host-ssl-enable " echo -e " ${COLOR_YELLOW}šŸ’” Use --cert-list to see all available certificates${CoR}\n" exit 1 fi HOST_SSL_ENABLE=true else echo -e "\n ā›” ${COLOR_RED}ERROR: Invalid host ID '$1' - must be a number${CoR}" echo -e " ${COLOR_CYAN}Usage:${CoR} $0 --host-ssl-enable " echo -e " ${COLOR_YELLOW}šŸ’” Use --host-list to see all available hosts${CoR}\n" exit 1 fi ;; --host-ssl-disable) HOST_SSL_DISABLE=true shift if [ $# -gt 0 ]; then HOST_ID="$1" shift else echo -e "\n ā›” ${COLOR_RED}INVALID command: Missing argument${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --host-ssl-disable ${CoR}" echo -e " Find HostID: ${COLOR_GREEN}$0 --host-list${CoR}\n" exit 1 fi ;; --host-acl-enable) HOST_ACL_ENABLE=true shift if [ $# -lt 2 ]; then echo -e "\n ā›” ${COLOR_RED}INVALID: The --host-acl-enable option requires two arguments: host_id and access_list_id${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --host-acl-enable ${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --host-acl-enable 42 5${CoR}" echo -e " Find ID: ${COLOR_ORANGE}$0 --host-list${CoR} AND ${COLOR_ORANGE}--access-list${CoR}\n" exit 1 fi HOST_ID="$1" ACCESS_LIST_ID="$2" shift 2 ;; --host-acl-disable) HOST_ACL_DISABLE=true shift if [ $# -gt 0 ]; then HOST_ID="$1" shift else echo -e "\n ā›” ${COLOR_RED}The --host-acl-disable option requires a host ID.${CoR}" echo -e " Usage: $0 --host-acl-disable \n" exit 1 fi #host_acl_disable "$HOST_ID" ;; --cert-generate) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n šŸ›”ļø ${COLOR_RED}The --cert-generate option requires a domain.${CoR}" echo -e "\n ${COLOR_ORANGE}Usage: $0 --cert-generate domain [email] [--cert-email ] [--dns-provider ] [--dns-credentials ]${CoR}" echo -e "\n ${COLOR_YELLOW}Options${CoR}:" echo -e " • --cert-email : Email for Let's Encrypt (alternative to positional email)" echo -e " • --dns-provider : DNS provider for wildcard certificates" echo -e " • --dns-credentials : DNS credentials in JSON format" echo -e " • --host-ssl-enable : Enable SSL after certificate generation" echo -e " • -y : Skip confirmations" echo -e " ${COLOR_YELLOW}Note${CoR}:\n • If email is not provided, default email ${COLOR_YELLOW}$DEFAULT_EMAIL${CoR} will be used" echo -e " • For wildcard certificates (${COLOR_YELLOW}*.domain.com${CoR}), DNS challenge is required\n" echo -e " Examples:" echo -e " ${COLOR_GREEN}$0 --cert-generate example.com${CoR}" echo -e " ${COLOR_GREEN}$0 --cert-generate example.com admin@example.com${CoR}" echo -e " ${COLOR_GREEN}$0 --cert-generate example.com --cert-email admin@example.com${CoR}" echo -e " ${COLOR_GREEN}$0 --cert-generate \"*.example.com\" --dns-provider cloudflare --dns-credentials '{\"dns_cloudflare_api_token\":\"your_token\"}'${CoR}\n" exit 1 fi # Store domain CERT_DOMAIN="$1" CERT_DNS_PROVIDER="" CERT_DNS_CREDENTIALS="" HOST_SSL_ENABLE=false shift # Check and store email if [ $# -gt 0 ] && [[ "$1" != -* ]]; then CERT_EMAIL="$1" shift else CERT_EMAIL="$DEFAULT_EMAIL" fi # Parse DNS options while [ $# -gt 0 ] && [[ "$1" == --* ]]; do case "$1" in --cert-email) shift if [ $# -gt 0 ] && [[ "$1" != --* ]]; then CERT_EMAIL="$1" shift else echo -e "\n ā›” ${COLOR_RED}Missing certificate email${CoR}" exit 1 fi ;; --dns-provider) shift if [ $# -gt 0 ] && [[ "$1" != --* ]]; then CERT_DNS_PROVIDER="$1" shift else echo -e "\n ā›” ${COLOR_RED}Missing DNS provider value${CoR}" exit 1 fi ;; --dns-credentials) shift if [ $# -gt 0 ] && [[ "$1" != --* ]]; then CERT_DNS_CREDENTIALS="$1" shift else echo -e "\n ā›” ${COLOR_RED}Missing DNS credentials${CoR}" exit 1 fi ;; --host-ssl-enable) HOST_SSL_ENABLE=true shift ;; *) echo -e "\n āš ļø ${COLOR_YELLOW}Unknown option: $1${CoR}" shift ;; esac done # Validate wildcard certificate requirements if [[ "$CERT_DOMAIN" == \** ]]; then if [ -z "$CERT_DNS_PROVIDER" ] || [ -z "$CERT_DNS_CREDENTIALS" ]; then echo -e "\n ā›” ${COLOR_RED}Wildcard certificates require DNS challenge. Please provide --dns-provider and --dns-credentials.${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --cert-generate \"*.example.com\" --dns-provider cloudflare --dns-credentials '{\"dns_cloudflare_api_token\":\"your_token\"}'${CoR}\n" exit 1 fi fi # Set flag after validating all arguments CERT_GENERATE=true ;; --cert-delete) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}The --cert-delete option requires a certificate ID.${CoR}" echo -e "\n ${COLOR_CYAN}Usage:${CoR}" echo -e " ${COLOR_ORANGE}$0 --cert-delete [-y] [--purge]${CoR}" echo -e "\n ${COLOR_CYAN}Examples:${CoR}" echo -e " ${COLOR_GREEN}$0 --cert-delete 240${CoR}" echo -e " ${COLOR_GREEN}$0 --cert-delete 240 -y${CoR} ${COLOR_GREY}# Skip confirmation${CoR}" echo -e " ${COLOR_GREEN}$0 --cert-delete 240 --purge${CoR} ${COLOR_GREY}# Also remove on-disk cert files (requires NGINX_PATH_DOCKER)${CoR}" echo -e "\n ${COLOR_YELLOW}šŸ’” Tips:${CoR}" echo -e " • Use ${COLOR_GREEN}--cert-list${CoR} to see all certificates and their IDs" echo -e " • ${COLOR_GREY}Without --purge the API does a soft-delete only (the cert leaves the UI but its files remain)${CoR}\n" exit 1 fi CERT_DELETE=true CERT_ID="$1" shift ;; --cert-show) shift search_term="${1:-}" CERT_SHOW=true if [ -n "$search_term" ]; then shift fi ;; --cert-list | --cert-show-all) LIST_CERT_ALL=true shift ;; --cert-download) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}The --cert-download option requires a certificate ID.${CoR}" echo -e "\n ${COLOR_CYAN}Usage:${CoR}" echo -e " ${COLOR_ORANGE}$0 --cert-download [output_dir] [cert_name]${CoR}" echo -e "\n ${COLOR_CYAN}Examples:${CoR}" echo -e " ${COLOR_GREEN}$0 --cert-download 240${CoR}" echo -e " ${COLOR_GREEN}$0 --cert-download 240 ./certs${CoR}" echo -e " ${COLOR_GREEN}$0 --cert-download 240 ./certs mydomain${CoR}" echo -e "\n ${COLOR_YELLOW}šŸ’” Tips:${CoR}" echo -e " • Use ${COLOR_GREEN}--cert-list${CoR} to see all certificates and their IDs" echo -e " • Supports both new API (JSON) and legacy API (ZIP) with automatic fallback" echo -e " • Creates standardized certificate files (.crt, .key, .chain.crt) and ZIP archive\n" exit 1 fi CERT_DOWNLOAD=true CERT_ID="$1" shift # Optional output directory if [ $# -gt 0 ] && [[ "$1" != -* ]]; then CERT_OUTPUT_DIR="$1" shift fi # Optional certificate name if [ $# -gt 0 ] && [[ "$1" != -* ]]; then CERT_NAME="$1" shift fi ;; --access-list) ACCESS_LIST=true shift ;; --access-list-show) ACCESS_LIST_SHOW=true shift # Check if access list ID is provided if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\nā›” ${COLOR_RED}Error: The access list ID is required.${CoR}" echo -e "\n ${COLOR_CYAN}Usage:${CoR}" echo -e " ${COLOR_ORANGE}$0 --access-list-show ${CoR}" echo -e "\n ${COLOR_CYAN}Examples:${CoR}" echo -e " ${COLOR_GREEN}$0 --access-list-show 5${CoR}" echo -e " ${COLOR_GREEN}$0 --access-list-show 123${CoR}" echo -e "\n ${COLOR_YELLOW}šŸ’” Tip:${CoR}" echo -e " • Use ${COLOR_GREEN}--access-list${CoR} to see all access lists and their IDs\n" exit 1 fi ACCESS_LIST_ID="$1" shift ;; --access-list-create) shift # Stash remaining args and dispatch at the bottom (like --access-list-update), # instead of running mid-parse which left the bottom dispatch unreachable. ACCESS_LIST_CREATE=true ACCESS_LIST_CREATE_ARGS=() while [ $# -gt 0 ]; do ACCESS_LIST_CREATE_ARGS+=("$1") shift done ;; --access-list-update) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}The --access-list-update option requires an access list ID.${CoR}" echo -e " ${COLOR_CYAN}Usage:${CoR}" echo -e " ${COLOR_ORANGE}$0 --access-list-update [options]${CoR}" echo -e " ${COLOR_CYAN}Examples:${CoR}" echo -e " ${COLOR_GREEN}$0 --access-list-update 123 --name \"new_name\"${CoR}" echo -e " ${COLOR_GREEN}$0 --access-list-update 123 --name \"new_name\" --satisfy any${CoR}" echo -e " ${COLOR_YELLOW}šŸ’” Tip: Use --access-list to see all available access lists${CoR}" exit 1 fi if [[ "$1" =~ ^[0-9]+$ ]]; then ACCESS_LIST_ID="$1" ACCESS_LIST_UPDATE=true shift else echo -e "\n ā›” ${COLOR_RED}INVALID: Invalid access list ID '$1' - must be a number${CoR}" echo -e " ${COLOR_CYAN}Examples:${CoR}" echo -e " ${COLOR_GREEN}$0 --access-list-update 123 --name \"new_name\"${CoR}" echo -e " ${COLOR_GREEN}$0 --access-list-update 123 --name \"new_name\" --satisfy any${CoR}" echo -e " ${COLOR_YELLOW}šŸ’” Tip: Use --access-list to see all available access lists${CoR}" exit 1 fi # Process remaining arguments for access list update # Store them to pass to the function later ACCESS_LIST_UPDATE_ARGS=() while [ $# -gt 0 ]; do ACCESS_LIST_UPDATE_ARGS+=("$1") shift done ;; --access-list-delete) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}The --access-list-delete option requires an access list ID.${CoR}" echo -e " ${COLOR_CYAN}Usage:${CoR}" echo -e " ${COLOR_ORANGE}$0 --access-list-delete [-y]${CoR}" echo -e " ${COLOR_CYAN}Examples:${CoR}" echo -e " ${COLOR_GREEN}$0 --access-list-delete 42${CoR}" echo -e " ${COLOR_GREEN}$0 --access-list-delete 42 -y${CoR} ${COLOR_GREY}# Skip confirmation${CoR}" echo -e " ${COLOR_YELLOW}šŸ’” Tip: Use --access-list to see all available access lists${CoR}" exit 1 fi if [[ "$1" =~ ^[0-9]+$ ]]; then ACCESS_LIST_ID="$1" ACCESS_LIST_DELETE=true shift else echo -e "\n ā›” ${COLOR_RED}INVALID: Invalid access list ID '$1' - must be a number${CoR}" echo -e " ${COLOR_CYAN}Examples:${CoR}" echo -e " ${COLOR_GREEN}$0 --access-list-delete 42${CoR}" echo -e " ${COLOR_GREEN}$0 --access-list-delete 42 -y${CoR} ${COLOR_GREY}# Skip confirmation${CoR}" echo -e " ${COLOR_YELLOW}šŸ’” Tip: Use --access-list to see all available access lists${CoR}" exit 1 fi ;; --redirect-host-list) REDIRECT_HOST_LIST=true shift ;; --redirect-host-create) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: --redirect-host-create requires a domain name.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --redirect-host-create domain.com --forward-domain target.com [options]${CoR}" echo -e " Options:" echo -e " --forward-domain domain Target domain (${COLOR_RED}required${CoR})" echo -e " --forward-scheme http|https (default: http)" echo -e " --http-code 301|302|303|307|308 (default: 301)" echo -e " --preserve-path true|false (default: false)" exit 1 fi DOMAIN_NAMES="$1" shift while [[ $# -gt 0 ]]; do case "$1" in --forward-domain) if [[ -n "$2" && "$2" != -* ]]; then FORWARD_DOMAIN="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}--forward-domain requires a value${CoR}" exit 1 fi ;; --forward-scheme | -f) if [[ -n "$2" && "$2" =~ ^(http|https)$ ]]; then FORWARD_SCHEME="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}--forward-scheme must be 'http' or 'https'${CoR}" exit 1 fi ;; --http-code) if [[ -n "$2" && "$2" =~ ^(301|302|303|307|308)$ ]]; then FORWARD_HTTP_CODE="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}--http-code must be 301, 302, 303, 307, or 308${CoR}" exit 1 fi ;; --preserve-path) if [[ -n "$2" && "$2" =~ ^(true|false)$ ]]; then PRESERVE_PATH="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}--preserve-path must be 'true' or 'false'${CoR}" exit 1 fi ;; -b | --block-exploits) if [[ -n "$2" && "$2" =~ ^(true|false)$ ]]; then BLOCK_EXPLOITS="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}--block-exploits must be 'true' or 'false'${CoR}" exit 1 fi ;; -a | --advanced-config) if [[ -n "$2" && "$2" != -* ]]; then ADVANCED_CONFIG="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}--advanced-config requires a value${CoR}" exit 1 fi ;; -y) AUTO_YES=true shift ;; *) if [[ "$1" == -* ]]; then echo -e "\n āš ļø ${COLOR_YELLOW}WARNING: Unknown option ignored -> $1${CoR}"; fi shift ;; esac done if [ -z "$FORWARD_DOMAIN" ]; then echo -e "\n ā›” ${COLOR_RED}INVALID: --forward-domain is required${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --redirect-host-create example.com --forward-domain target.com${CoR}" exit 1 fi REDIRECT_HOST_CREATE=true ;; --redirect-host-delete) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: --redirect-host-delete requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --redirect-host-delete [-y]${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --redirect-host-delete 5${CoR}" exit 1 fi if [[ "$1" =~ ^[0-9]+$ ]]; then REDIRECT_HOST_ID="$1" REDIRECT_HOST_DELETE=true shift else echo -e "\n ā›” ${COLOR_RED}INVALID: ID must be a number.${CoR}" exit 1 fi ;; --redirect-host-enable) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: --redirect-host-enable requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --redirect-host-enable ${CoR}" exit 1 fi REDIRECT_HOST_ID="$1" REDIRECT_HOST_ENABLE=true shift ;; --redirect-host-disable) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: --redirect-host-disable requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --redirect-host-disable ${CoR}" exit 1 fi REDIRECT_HOST_ID="$1" REDIRECT_HOST_DISABLE=true shift ;; --stream-host-list) STREAM_HOST_LIST=true shift ;; --stream-host-create) shift if [ $# -eq 0 ] || [[ "$1" == -* ]] || ! [[ "$1" =~ ^[0-9]+$ ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: --stream-host-create requires an incoming port (number).${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --stream-host-create --forward-host --forward-port [options]${CoR}" echo -e " Options:" echo -e " --forward-host host|ip Target host (${COLOR_RED}required${CoR})" echo -e " --forward-port port Target port (${COLOR_RED}required${CoR})" echo -e " --tcp Enable TCP forwarding" echo -e " --udp Enable UDP forwarding" echo -e " --cert-id id Certificate ID (default: 0)" exit 1 fi INCOMING_PORT="$1" shift while [[ $# -gt 0 ]]; do case "$1" in --forward-host) if [[ -n "$2" && "$2" != -* ]]; then FORWARD_HOST="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}--forward-host requires a value${CoR}" exit 1 fi ;; --forward-port) if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then FORWARD_PORT="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}--forward-port must be a number${CoR}" exit 1 fi ;; --tcp) TCP_FORWARDING=true shift ;; --udp) UDP_FORWARDING=true shift ;; --cert-id) if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then CERT_ID="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}--cert-id must be a number${CoR}" exit 1 fi ;; -y) AUTO_YES=true shift ;; *) if [[ "$1" == -* ]]; then echo -e "\n āš ļø ${COLOR_YELLOW}WARNING: Unknown option ignored -> $1${CoR}"; fi shift ;; esac done if [ -z "$FORWARD_HOST" ] || [ -z "$FORWARD_PORT" ]; then echo -e "\n ā›” ${COLOR_RED}INVALID: --forward-host and --forward-port are required${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --stream-host-create 53 --forward-host pihole --forward-port 53 --tcp --udp${CoR}" exit 1 fi STREAM_HOST_CREATE=true ;; --stream-host-delete) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: --stream-host-delete requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --stream-host-delete [-y]${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --stream-host-delete 5${CoR}" exit 1 fi if [[ "$1" =~ ^[0-9]+$ ]]; then STREAM_HOST_ID="$1" STREAM_HOST_DELETE=true shift else echo -e "\n ā›” ${COLOR_RED}INVALID: ID must be a number.${CoR}" exit 1 fi ;; --stream-host-enable) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: --stream-host-enable requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --stream-host-enable ${CoR}" exit 1 fi STREAM_HOST_ID="$1" STREAM_HOST_ENABLE=true shift ;; --stream-host-disable) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: --stream-host-disable requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --stream-host-disable ${CoR}" exit 1 fi STREAM_HOST_ID="$1" STREAM_HOST_DISABLE=true shift ;; --dead-host-list) DEAD_HOST_LIST=true shift ;; --dead-host-create) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: --dead-host-create requires a domain name.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --dead-host-create domain.com [options]${CoR}" echo -e " Options:" echo -e " --cert-id id Certificate ID (default: 0)" echo -e " --ssl-forced true|false (default: false)" echo -e " --http2 true|false (default: false)" echo -e " --hsts true|false (default: false)" echo -e " --hsts-subdomains true|false (default: false)" echo -e " --advanced-config string Custom nginx config" exit 1 fi DOMAIN_NAMES="$1" shift while [[ $# -gt 0 ]]; do case "$1" in --cert-id) if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then CERT_ID="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}--cert-id must be a number${CoR}" exit 1 fi ;; --ssl-forced) if [[ -n "$2" && "$2" =~ ^(true|false)$ ]]; then SSL_FORCED="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}--ssl-forced must be 'true' or 'false'${CoR}" exit 1 fi ;; --http2) if [[ -n "$2" && "$2" =~ ^(true|false)$ ]]; then HTTP2_SUPPORT="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}--http2 must be 'true' or 'false'${CoR}" exit 1 fi ;; --hsts) if [[ -n "$2" && "$2" =~ ^(true|false)$ ]]; then HSTS_ENABLED="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}--hsts must be 'true' or 'false'${CoR}" exit 1 fi ;; --hsts-subdomains) if [[ -n "$2" && "$2" =~ ^(true|false)$ ]]; then HSTS_SUBDOMAINS="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}--hsts-subdomains must be 'true' or 'false'${CoR}" exit 1 fi ;; -a | --advanced-config) if [[ -n "$2" && "$2" != -* ]]; then ADVANCED_CONFIG="$2" shift 2 else echo -e "\n ā›” ${COLOR_RED}--advanced-config requires a value${CoR}" exit 1 fi ;; -y) AUTO_YES=true shift ;; *) if [[ "$1" == -* ]]; then echo -e "\n āš ļø ${COLOR_YELLOW}WARNING: Unknown option ignored -> $1${CoR}"; fi shift ;; esac done DEAD_HOST_CREATE=true ;; --dead-host-delete) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: --dead-host-delete requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --dead-host-delete [-y]${CoR}" echo -e " Example: ${COLOR_GREEN}$0 --dead-host-delete 5${CoR}" exit 1 fi if [[ "$1" =~ ^[0-9]+$ ]]; then DEAD_HOST_ID="$1" DEAD_HOST_DELETE=true shift else echo -e "\n ā›” ${COLOR_RED}INVALID: ID must be a number.${CoR}" exit 1 fi ;; --dead-host-enable) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: --dead-host-enable requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --dead-host-enable ${CoR}" exit 1 fi DEAD_HOST_ID="$1" DEAD_HOST_ENABLE=true shift ;; --dead-host-disable) shift if [ $# -eq 0 ] || [[ "$1" == -* ]]; then echo -e "\n ā›” ${COLOR_RED}INVALID: --dead-host-disable requires a host šŸ†”.${CoR}" echo -e " Usage : ${COLOR_ORANGE}$0 --dead-host-disable ${CoR}" exit 1 fi DEAD_HOST_ID="$1" DEAD_HOST_DISABLE=true shift ;; *) echo -e "\n ${COLOR_RED}ā›” Unknown option:${CoR} $1" echo -e " ${COLOR_GREY}Use --help to see available commands.${CoR}\n" exit 1 ;; esac #shift done ############################################################## # logic ############################################################## #echo "Debug after case: ACCESS_LIST_DELETE=$ACCESS_LIST_DELETE ACCESS_LIST_ID=$ACCESS_LIST_ID AUTO_YES=$AUTO_YES" if [ "$LIST_JSON_COMMANDS" = true ]; then list_json_commands elif [ "$SHOW_HELP" = true ]; then show_help elif [ "$SHOW_DEFAULT" = true ]; then show_default elif [ "$EXAMPLES" = true ]; then examples_cli elif [ "$CHECK_TOKEN" = true ]; then check_token true elif [ "$CHECK_UPDATE" = true ]; then check_update elif [ "$INFO" = true ]; then display_info # Actions users elif [ "$USER_CREATE" = true ]; then user_create "$USERNAME" "$PASSWORD" "$EMAIL" elif [ "$USER_DELETE" = true ]; then user_delete "$USER_ID" elif [ "$USER_LIST" = true ]; then user_list elif [ "$CERT_DELETE" = true ]; then cert_delete "$CERT_ID" elif [ "$CERT_SHOW" = true ]; then cert_show "$search_term" elif [ "$LIST_CERT_ALL" = true ]; then list_cert_all elif [ "$CERT_DOWNLOAD" = true ]; then cert_download "$CERT_ID" "${CERT_OUTPUT_DIR:-./certificates}" "${CERT_NAME:-certificate_$CERT_ID}" elif [ "$ACCESS_LIST" = true ]; then access_list elif [ "$ACCESS_LIST_CREATE" = true ]; then access_list_create "${ACCESS_LIST_CREATE_ARGS[@]}" elif [ "$ACCESS_LIST_UPDATE" = true ]; then access_list_update "${ACCESS_LIST_UPDATE_ARGS[@]}" elif [ "$ACCESS_LIST_DELETE" = true ]; then access_list_delete elif [ "$ACCESS_LIST_SHOW" = true ]; then access_list_show "$ACCESS_LIST_ID" # Actions hotes elif [ "$HOST_LIST" = true ]; then host_list elif [ "$HOST_LIST_FULL" = true ]; then host_list_full elif [ "$HOST_SEARCH" = true ]; then host_search elif [ "$HOST_SHOW" = true ]; then host_show "$HOST_ID" elif [ "$HOST_CREATE" = true ]; then # Certificate generation (--cert-generate) is handled inside create_or_update_proxy_host create_or_update_proxy_host "$DOMAIN_NAMES" "$FORWARD_HOST" "$FORWARD_PORT" exit 0 # Actions SSL elif [ "$CERT_GENERATE" = true ] && [ "$HOST_CREATE" != true ]; then # āœ… Ajout de la condition cert_generate "$CERT_DOMAIN" "$CERT_EMAIL" "$CERT_DNS_PROVIDER" "$CERT_DNS_CREDENTIALS" if [ "$HOST_SSL_ENABLE" = true ]; then host_ssl_enable "$HOST_ID" exit 0 fi #elif [ "$CERT_GENERATE" = true ]; then # cert_generate "$CERT_DOMAIN" "$CERT_EMAIL" "$CERT_DNS_PROVIDER" "$CERT_DNS_CREDENTIALS" # If --host-ssl-enable # if [ "$HOST_SSL_ENABLE" = true ]; then # host_ssl_enable "$HOST_ID" # exit 0 # fi elif [ "$HOST_DELETE" = true ]; then host_delete "$HOST_ID" elif [ "$HOST_ENABLE" = true ]; then host_enable "$HOST_ID" elif [ "$HOST_DISABLE" = true ]; then host_disable "$HOST_ID" elif [ "$HOST_UPDATE" = true ]; then host_update "$HOST_ID" "$FIELD" "$VALUE" exit 0 # Actions ACL elif [ "$HOST_ACL_ENABLE" = true ]; then host_acl_enable elif [ "$HOST_ACL_DISABLE" = true ]; then host_acl_disable # Redirection Host Management elif [ "$REDIRECT_HOST_LIST" = true ]; then redirect_host_list elif [ "$REDIRECT_HOST_CREATE" = true ]; then create_or_update_redirect_host elif [ "$REDIRECT_HOST_DELETE" = true ]; then redirect_host_delete "$REDIRECT_HOST_ID" elif [ "$REDIRECT_HOST_ENABLE" = true ]; then redirect_host_enable "$REDIRECT_HOST_ID" elif [ "$REDIRECT_HOST_DISABLE" = true ]; then redirect_host_disable "$REDIRECT_HOST_ID" # Stream Host Management elif [ "$STREAM_HOST_LIST" = true ]; then stream_host_list elif [ "$STREAM_HOST_CREATE" = true ]; then create_or_update_stream_host elif [ "$STREAM_HOST_DELETE" = true ]; then stream_host_delete "$STREAM_HOST_ID" elif [ "$STREAM_HOST_ENABLE" = true ]; then stream_host_enable "$STREAM_HOST_ID" elif [ "$STREAM_HOST_DISABLE" = true ]; then stream_host_disable "$STREAM_HOST_ID" # Dead Host (404) Management elif [ "$DEAD_HOST_LIST" = true ]; then dead_host_list elif [ "$DEAD_HOST_CREATE" = true ]; then create_or_update_dead_host elif [ "$DEAD_HOST_DELETE" = true ]; then dead_host_delete "$DEAD_HOST_ID" elif [ "$DEAD_HOST_ENABLE" = true ]; then dead_host_enable "$DEAD_HOST_ID" elif [ "$DEAD_HOST_DISABLE" = true ]; then dead_host_disable "$DEAD_HOST_ID" elif [ "$HOST_SSL_ENABLE" = true ]; then host_ssl_enable "$HOST_ID" "$CERT_ID" elif [ "$HOST_SSL_DISABLE" = true ]; then host_ssl_disable elif [ "$SSL_RESTORE" = true ]; then restore_ssl_certificates # Actions backup/restore elif [ "$BACKUP" = true ]; then full_backup elif [ "$BACKUP_HOST" = true ]; then backup_host elif [ "$BACKUP_LIST" = true ]; then list_backups elif [ "$CLEAN_HOSTS" = true ]; then clean-hosts #reimport_hosts "$@" #reimport_hosts "$BACKUP_FILE" #exit $? #check_validity_of_backup_file "$BACKUP_FILE" else display_info exit 0 # # echo -e "\n ā›” ${COLOR_RED}No valid option provided${CoR}" # echo -e " Use --help to see available commands." # exit 1 fi