├── LICENSE ├── README.md ├── cfnginx-patched.sh ├── cfnginx.sh ├── install_webhook.sh ├── installwithpocketid.sh └── readme_pocketid.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Timothy Schneider 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloudflare Nginx SSL Automation 2 | 3 | *Automated SSL configuration with 30 day renewal with Let's Encrypt for Nginx with Cloudflare DNS validation* 4 | 5 | ## Features 6 | 7 | - **Automatic SSL Certificates**: Uses Let's Encrypt via Cloudflare DNS validation 8 | - **Modern Security Configuration**: 9 | - TLS 1.2/1.3 only 10 | - Strong cipher suites 11 | - OCSP stapling 12 | - HTTP/2 support 13 | - **WebSocket Ready**: Built-in proxy configuration for WebSocket support 14 | - **Automatic Redirects**: Forces HTTPS and handles port redirection 15 | - **Cloudflare Integration**: Securely stores API credentials 16 | - **Firewall Configuration**: Automatic UFW setup (if installed) 17 | - **Webhook Alerts**: Get alerts to Discord, Slack, or Google Chat 18 | 19 | ## 🛠️ What Problem This Solves 20 | 21 | This script automates the complex process of: 22 | 1. Setting up proper SSL configuration with Nginx 23 | 2. Cloudflare API integration for DNS validation 24 | 3. Configuring modern security protocols 25 | 4. Creating production-ready reverse proxy setup 26 | 5. Implementing best practices for web server security 27 | 28 | ## 📋 Requirements 29 | 30 | - Proxmox LXC container (or any Debian/Ubuntu server) 31 | - Root access 32 | - Domain name with DNS managed through Cloudflare 33 | 34 | ## 🔧 Installation 35 | 36 | 1. Download the script and make it executable: 37 | ``` 38 | curl -LO https://raw.githubusercontent.com/taslabs-net/CloudflareNginx/main/cfnginx.sh && chmod +x cfnginx.sh 39 | ``` 40 | 41 | 2. Run with parameters: 42 | ``` 43 | sudo ./cfnginx.sh \ 44 | --domain your-domain.com \ 45 | --email your-cloudflare@email.com \ 46 | --key your-cloudflare-api-key 47 | ``` 48 | | Parameter | Flag | Description | Required | 49 | |--------------|--------------------|-------------------------------------------------|----------| 50 | | Domain | `-d, --domain` | Your domain name | Yes | 51 | | Port | `-p, --port` | Application port (default: 3000) | No | 52 | | Email | `-e, --email` | Cloudflare account email | Yes | 53 | | API Key | `-k, --key` | Cloudflare Global API key | Yes | 54 | | Webhook URL | `-w, --webhook` | Notification webhook URL | No | 55 | | Webhook Mode | `-m, --webhook-mode` | S=Success, F=Failure, B=Both (default: B) | No | 56 | | Webhook Type | `-t, --webhook-type` | D=Discord, S=Slack, G=Google Chat (default: D) | No | 57 | | Quiet Mode | `-q, --quiet` | Minimal console output | No | 58 | | Help | `-h, --help` | Show help information | No | 59 | 60 | ``` 61 | sudo ./cfnginx.sh --domain example.com --email user@example.com --key abc123def456 62 | ``` 63 | 64 | ``` 65 | sudo ./cfnginx.sh --domain example.com --port 8080 --email user@example.com --key abc123def456 66 | ``` 67 | 68 | ``` 69 | sudo ./cfnginx.sh --domain example.com --email user@example.com --key abc123def456 --webhook "https://discord.com/api/webhooks/your-webhook-url" 70 | ``` 71 | 72 | ``` 73 | sudo ./cfnginx.sh --domain example.com --email user@example.com --key abc123def456 --quiet 74 | ``` 75 | 76 | ## 🖥️ What Happens During Installation 77 | 78 | 1. **System Preparation**: 79 | - Updates packages 80 | - Installs requirements (Nginx, Certbot, Cloudflare plugin) 81 | 82 | 2. **SSL Configuration**: 83 | - Creates secure Cloudflare credential file 84 | - Generates Let's Encrypt certificate using DNS challenge 85 | 86 | 3. **Nginx Setup**: 87 | - Creates optimized SSL configuration 88 | - Sets up HTTPS redirect 89 | - Configures reverse proxy with WebSocket support 90 | 91 | 4. **Security Hardening**: 92 | - Configures UFW firewall (if present) 93 | - Sets proper file permissions 94 | - Implements modern TLS settings 95 | 96 | ## 🔒 Security Notes 97 | 98 | 1. **Firewall**: 99 | - Ensure Proxmox host firewall allows ports 80/443 100 | - Script automatically configures container firewall if UFW is present 101 | 102 | 2. **Credential Storage**: 103 | - Cloudflare API keys stored in `/etc/letsencrypt/cloudflare.ini` 104 | - File permissions set to `600` 105 | 106 | ## 🐛 Troubleshooting 107 | 108 | Common Issues: 109 | 110 | 1. **SSL Certificate Errors**: 111 | ```bash 112 | certbot certificates # Check certificate status 113 | systemctl status nginx # Verify Nginx running 114 | ``` 115 | 116 | 2. **Port Conflicts**: 117 | ```bash 118 | ss -tulpn | grep ':443' 119 | ``` 120 | 121 | ## 🧹 Uninstallation 122 | 123 | 1. Remove Nginx configuration: 124 | ```bash 125 | rm /etc/nginx/sites-enabled/yourdomain.com 126 | ``` 127 | 128 | 2. Remove certificates: 129 | ```bash 130 | certbot delete --cert-name yourdomain.com 131 | ``` 132 | 133 | 3. Remove Cloudflare credentials: 134 | ```bash 135 | rm /etc/letsencrypt/cloudflare.ini 136 | ``` 137 | 138 | Logs and Configuration Files 139 | 140 | Log file: /var/log/cloudflarenginx-install.log 141 | 142 | Configuration: /etc/cloudflarenginx.conf 143 | 144 | Nginx config: /etc/nginx/sites-available/your-domain.com 145 | 146 | SSL certificates: /etc/letsencrypt/live/your-domain.com/ 147 | 148 | For detailed troubleshooting, check the logs at /var/log/cloudflarenginx-install.log -------------------------------------------------------------------------------- /cfnginx-patched.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # CloudflareNginx Installer with WebSocket Support, Persistence, and Fixed Webhook Notifications 3 | # PATCHED VERSION - Security hardened with input validation and proper escaping 4 | 5 | # Configuration 6 | BLUE='\033[0;34m' 7 | GREEN='\033[0;32m' 8 | YELLOW='\033[0;33m' 9 | RED='\033[0;31m' 10 | NC='\033[0m' 11 | DEFAULT_PORT="443" 12 | CLOUDFLARE_CRED_PATH="/etc/letsencrypt/cloudflare.ini" 13 | TMP_DIR=$(mktemp -d) 14 | WEBHOOK_URL="" 15 | WEBHOOK_MODE="" 16 | WEBHOOK_PLATFORM="D" # Default to Discord 17 | LOG_FILE="/var/log/cloudflarenginx-install.log" 18 | CONFIG_FILE="/etc/cloudflarenginx.conf" 19 | 20 | # Create log file and ensure it's writable with secure permissions 21 | touch "$LOG_FILE" 2>/dev/null || true 22 | chmod 600 "$LOG_FILE" 2>/dev/null || true 23 | 24 | # Backup existing nginx configs before modifications 25 | NGINX_BACKUP_DIR="/etc/nginx/backups/$(date +%Y%m%d_%H%M%S)" 26 | 27 | cleanup() { 28 | rm -rf "$TMP_DIR" 29 | } 30 | trap cleanup EXIT 31 | 32 | log() { 33 | echo "$(date): $1" >> "$LOG_FILE" 34 | } 35 | 36 | log_and_print() { 37 | echo -e "${BLUE}$1${NC}" 38 | log "$1" 39 | } 40 | 41 | log_success() { 42 | echo -e "${GREEN}✓ $1${NC}" 43 | log "[SUCCESS] $1" 44 | } 45 | 46 | log_warning() { 47 | echo -e "${YELLOW}⚠ $1${NC}" 48 | log "[WARNING] $1" 49 | } 50 | 51 | log_error() { 52 | echo -e "${RED}✗ $1${NC}" 53 | log "[ERROR] $1" 54 | } 55 | 56 | # Input sanitization functions 57 | sanitize_domain() { 58 | # Allow only valid domain characters 59 | echo "$1" | sed 's/[^a-zA-Z0-9.-]//g' | tr '[:upper:]' '[:lower:]' 60 | } 61 | 62 | sanitize_email() { 63 | # Basic email sanitization 64 | echo "$1" | sed 's/[^a-zA-Z0-9.@_+-]//g' 65 | } 66 | 67 | sanitize_api_key() { 68 | # API keys should only contain alphanumeric and some special chars 69 | echo "$1" | sed 's/[^a-zA-Z0-9_-]//g' 70 | } 71 | 72 | sanitize_port() { 73 | # Ensure port is numeric only 74 | echo "$1" | sed 's/[^0-9]//g' 75 | } 76 | 77 | validate_url() { 78 | local url="$1" 79 | # Check if URL starts with https:// 80 | if [[ ! "$url" =~ ^https:// ]]; then 81 | return 1 82 | fi 83 | # Basic URL validation 84 | if [[ ! "$url" =~ ^https://[a-zA-Z0-9.-]+(/.*)?$ ]]; then 85 | return 1 86 | fi 87 | return 0 88 | } 89 | 90 | escape_json() { 91 | # Properly escape string for JSON 92 | printf '%s' "$1" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))' 93 | } 94 | 95 | show_header() { 96 | clear 97 | echo -e "${BLUE}" 98 | echo "===============================================" 99 | echo " Cloudflare Nginx Automated Setup (PATCHED)" 100 | echo "===============================================" 101 | echo -e "${NC}" 102 | log "CloudflareNginx installation started (patched version)" 103 | } 104 | 105 | check_root() { 106 | [ "$EUID" -eq 0 ] || { log_error "Please run as root"; exit 1; } 107 | } 108 | 109 | ask_question() { 110 | echo -e "${BLUE}" 111 | read -r -p "$1: " ${2} 112 | echo -e "${NC}" 113 | # Don't log sensitive data 114 | if [[ "$1" == *"API Key"* ]] || [[ "$1" == *"password"* ]]; then 115 | log "User input for '$1': [REDACTED]" 116 | else 117 | log "User input for '$1': ${!2}" 118 | fi 119 | } 120 | 121 | validate_domain() { 122 | local domain="$1" 123 | # Comprehensive domain validation 124 | if [[ ! "$domain" =~ ^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$ ]]; then 125 | log_error "Invalid domain format" 126 | return 1 127 | fi 128 | # Check domain length 129 | if [ ${#domain} -gt 253 ]; then 130 | log_error "Domain name too long (max 253 characters)" 131 | return 1 132 | fi 133 | return 0 134 | } 135 | 136 | validate_port() { 137 | local port="$1" 138 | # Check if port is numeric 139 | if ! [[ "$port" =~ ^[0-9]+$ ]]; then 140 | log_error "Port must be a number" 141 | return 1 142 | fi 143 | # Check port range 144 | if (( port < 1 || port > 65535 )); then 145 | log_error "Port must be between 1 and 65535" 146 | return 1 147 | fi 148 | # Check if port is already in use 149 | if ss -tuln | grep -q ":$port "; then 150 | log_warning "Port $port appears to be in use" 151 | fi 152 | return 0 153 | } 154 | 155 | install_core_dependencies() { 156 | log_and_print "Updating system packages..." 157 | apt-get update -qq >> "$LOG_FILE" 2>&1 && apt-get upgrade -y -qq >> "$LOG_FILE" 2>&1 158 | if [ $? -eq 0 ]; then 159 | log_success "System packages updated" 160 | else 161 | log_warning "Some updates may have failed. Check $LOG_FILE for details" 162 | fi 163 | 164 | log_and_print "Installing required components..." 165 | # Also install jq for proper JSON handling 166 | apt-get install -y -qq nginx python3-certbot-dns-cloudflare curl ufw jq >> "$LOG_FILE" 2>&1 167 | if [ $? -eq 0 ]; then 168 | log_success "Required components installed" 169 | else 170 | log_warning "Some components may not have installed correctly. Check $LOG_FILE for details" 171 | fi 172 | } 173 | 174 | handle_cloudflare_credentials() { 175 | ask_question "Enter your Cloudflare Email" CF_EMAIL 176 | CF_EMAIL=$(sanitize_email "$CF_EMAIL") 177 | 178 | ask_question "Enter your Cloudflare API Key" CF_API_KEY 179 | CF_API_KEY=$(sanitize_api_key "$CF_API_KEY") 180 | 181 | ask_question "Enter your Hostname (FQDN)" DOMAIN 182 | DOMAIN=$(sanitize_domain "$DOMAIN") 183 | 184 | if ! validate_domain "$DOMAIN"; then 185 | exit 1 186 | fi 187 | 188 | # Save configuration to persistent file (excluding sensitive data) 189 | cat > "$CONFIG_FILE" <> "$LOG_FILE" 2>&1 198 | cat > "$CLOUDFLARE_CRED_PATH" <> "$LOG_FILE" 2>&1 203 | log_success "Cloudflare credentials saved" 204 | 205 | # Ask if the user wants webhook notifications 206 | ask_question "Do you want a webhook notification? (Y/N)" WEBHOOK_CHOICE 207 | WEBHOOK_CHOICE=$(echo "$WEBHOOK_CHOICE" | tr '[:lower:]' '[:upper:]') 208 | 209 | if [[ "$WEBHOOK_CHOICE" == "Y" ]]; then 210 | # Ask for webhook platform 211 | ask_question "Which webhook platform? (D)Discord, (S)Slack, or (G)Google Chat (default: D)" WEBHOOK_PLATFORM 212 | WEBHOOK_PLATFORM=${WEBHOOK_PLATFORM:-D} 213 | WEBHOOK_PLATFORM=$(echo "$WEBHOOK_PLATFORM" | tr '[:lower:]' '[:upper:]') 214 | 215 | # Validate platform selection 216 | if [[ ! "$WEBHOOK_PLATFORM" =~ ^[DSG]$ ]]; then 217 | log_warning "Invalid platform choice. Defaulting to Discord." 218 | WEBHOOK_PLATFORM="D" 219 | fi 220 | 221 | echo -e "${BLUE}Selected webhook platform: $(case "$WEBHOOK_PLATFORM" in 222 | D) echo "Discord" ;; 223 | S) echo "Slack" ;; 224 | G) echo "Google Chat" ;; 225 | esac)${NC}" 226 | 227 | ask_question "Do you want notifications for (S)Success, (F)Failure, or (B)Both? (default: B)" WEBHOOK_MODE 228 | WEBHOOK_MODE=${WEBHOOK_MODE:-B} 229 | WEBHOOK_MODE=$(echo "$WEBHOOK_MODE" | tr '[:lower:]' '[:upper:]') 230 | 231 | if [[ "$WEBHOOK_MODE" =~ ^[SBF]$ ]]; then 232 | ask_question "Webhook URL?" WEBHOOK_URL 233 | 234 | # Validate webhook URL 235 | if ! validate_url "$WEBHOOK_URL"; then 236 | log_error "Invalid webhook URL. Must start with https://" 237 | WEBHOOK_URL="" 238 | else 239 | # Test webhook immediately 240 | log_and_print "Testing webhook on $(case "$WEBHOOK_PLATFORM" in 241 | D) echo "Discord" ;; 242 | S) echo "Slack" ;; 243 | G) echo "Google Chat" ;; 244 | esac)..." 245 | 246 | # Use jq for safe JSON creation 247 | local test_message="Testing webhook for domain: $DOMAIN" 248 | local TEST_PAYLOAD="" 249 | 250 | case "$WEBHOOK_PLATFORM" in 251 | D) # Discord 252 | TEST_PAYLOAD=$(jq -n \ 253 | --arg content "CloudflareNginx Webhook Test" \ 254 | --arg title "Test Successful" \ 255 | --arg desc "$test_message" \ 256 | '{ 257 | content: $content, 258 | embeds: [{ 259 | title: $title, 260 | description: $desc, 261 | color: 65280 262 | }] 263 | }') 264 | ;; 265 | S) # Slack 266 | TEST_PAYLOAD=$(jq -n \ 267 | --arg text "CloudflareNginx Webhook Test" \ 268 | --arg msg "$test_message" \ 269 | '{ 270 | text: $text, 271 | blocks: [{ 272 | type: "section", 273 | text: { 274 | type: "mrkdwn", 275 | text: ("*Test Successful*\n" + $msg) 276 | } 277 | }] 278 | }') 279 | ;; 280 | G) # Google Chat 281 | TEST_PAYLOAD=$(jq -n \ 282 | --arg text "CloudflareNginx Webhook Test" \ 283 | --arg msg "$test_message" \ 284 | '{ 285 | text: $text, 286 | cards: [{ 287 | header: { 288 | title: "Test Successful" 289 | }, 290 | sections: [{ 291 | widgets: [{ 292 | textParagraph: { 293 | text: $msg 294 | } 295 | }] 296 | }] 297 | }] 298 | }') 299 | ;; 300 | esac 301 | 302 | # Send test webhook 303 | HTTP_CODE=$(curl -s -o /tmp/webhook_response -w "%{http_code}" -X POST -H "Content-Type: application/json" \ 304 | -d "$TEST_PAYLOAD" "$WEBHOOK_URL" 2>> "$LOG_FILE") 305 | 306 | # Log the response and HTTP code 307 | cat /tmp/webhook_response >> "$LOG_FILE" 2>/dev/null 308 | echo "Webhook test HTTP response code: $HTTP_CODE" >> "$LOG_FILE" 309 | 310 | # Check if webhook succeeded 311 | WEBHOOK_SUCCESS=0 312 | case "$WEBHOOK_PLATFORM" in 313 | D) [[ "$HTTP_CODE" == "204" || "$HTTP_CODE" == "200" ]] && WEBHOOK_SUCCESS=1 ;; 314 | S) [[ $(cat /tmp/webhook_response 2>/dev/null) == "ok" ]] && WEBHOOK_SUCCESS=1 ;; 315 | G) [[ $(cat /tmp/webhook_response 2>/dev/null) == *"name"* ]] && WEBHOOK_SUCCESS=1 ;; 316 | esac 317 | 318 | if [ $WEBHOOK_SUCCESS -eq 1 ]; then 319 | log_success "Webhook test successful" 320 | # Save webhook URL to config 321 | echo "WEBHOOK_URL=$WEBHOOK_URL" >> "$CONFIG_FILE" 322 | else 323 | log_warning "Webhook test might have failed. Check $LOG_FILE for details" 324 | ask_question "Continue anyway? (Y/N)" CONTINUE 325 | if [[ "${CONTINUE^^}" != "Y" ]]; then 326 | exit 1 327 | fi 328 | fi 329 | fi 330 | else 331 | log_warning "Invalid choice. Webhook notifications will not be configured." 332 | fi 333 | else 334 | log_and_print "Webhook notifications will not be configured." 335 | fi 336 | } 337 | 338 | send_webhook() { 339 | local status=$1 340 | local message=$2 341 | 342 | if [ -n "$WEBHOOK_URL" ] && validate_url "$WEBHOOK_URL"; then 343 | log "Sending webhook notification: $status - $message" 344 | 345 | # Check if should send based on webhook mode 346 | local should_send=0 347 | case "$WEBHOOK_MODE" in 348 | S) [[ "$status" == "success" ]] && should_send=1 ;; 349 | F) [[ "$status" == "failure" ]] && should_send=1 ;; 350 | B) should_send=1 ;; 351 | esac 352 | 353 | if [ $should_send -eq 1 ]; then 354 | # Create payload using jq for proper escaping 355 | local PAYLOAD="" 356 | local SUCCESS_COLOR="65280" 357 | local FAILURE_COLOR="16711680" 358 | local COLOR=$([ "$status" = "success" ] && echo "$SUCCESS_COLOR" || echo "$FAILURE_COLOR") 359 | 360 | case "$WEBHOOK_PLATFORM" in 361 | D) # Discord 362 | PAYLOAD=$(jq -n \ 363 | --arg content "$message" \ 364 | --arg status "$status" \ 365 | --arg domain "$DOMAIN" \ 366 | --arg color "$COLOR" \ 367 | '{ 368 | content: $content, 369 | embeds: [{ 370 | title: ("CloudNginx " + $status + " Notification"), 371 | description: ("Domain: " + $domain), 372 | color: ($color | tonumber) 373 | }] 374 | }') 375 | ;; 376 | S) # Slack 377 | PAYLOAD=$(jq -n \ 378 | --arg status "$status" \ 379 | --arg message "$message" \ 380 | --arg domain "$DOMAIN" \ 381 | '{ 382 | text: ("CloudNginx " + $status + " Notification"), 383 | blocks: [{ 384 | type: "section", 385 | text: { 386 | type: "mrkdwn", 387 | text: ("*" + $message + "*\nDomain: " + $domain) 388 | } 389 | }] 390 | }') 391 | ;; 392 | G) # Google Chat 393 | PAYLOAD=$(jq -n \ 394 | --arg status "$status" \ 395 | --arg message "$message" \ 396 | --arg domain "$DOMAIN" \ 397 | '{ 398 | text: ("CloudflareNginx " + $status + " Notification"), 399 | cards: [{ 400 | header: { 401 | title: ("CloudflareNginx " + $status + " Notification") 402 | }, 403 | sections: [{ 404 | widgets: [{ 405 | textParagraph: { 406 | text: ($message + "\nDomain: " + $domain) 407 | } 408 | }] 409 | }] 410 | }] 411 | }') 412 | ;; 413 | esac 414 | 415 | # Send the webhook 416 | curl -s -X POST -H "Content-Type: application/json" -d "$PAYLOAD" "$WEBHOOK_URL" >> "$LOG_FILE" 2>&1 417 | 418 | if [ "$status" == "success" ]; then 419 | log_success "Webhook notification sent" 420 | else 421 | log_warning "Webhook failure notification sent" 422 | fi 423 | fi 424 | fi 425 | } 426 | 427 | generate_ssl() { 428 | log_and_print "Generating SSL certificate..." 429 | if certbot certonly --dns-cloudflare \ 430 | --dns-cloudflare-credentials "$CLOUDFLARE_CRED_PATH" \ 431 | -d "$DOMAIN" \ 432 | --non-interactive \ 433 | --agree-tos \ 434 | --email "$CF_EMAIL" >> "$LOG_FILE" 2>&1; then 435 | log_success "SSL certificate generated successfully!" 436 | send_webhook "success" "SSL certificate generated successfully for domain: $DOMAIN" 437 | return 0 438 | else 439 | log_error "SSL certificate generation failed!" 440 | send_webhook "failure" "SSL certificate generation failed for domain: $DOMAIN" 441 | # Ask if user wants to continue setup despite SSL failure 442 | ask_question "SSL certificate generation failed! Continue with the setup anyway? (Y/N)" CONTINUE_SSL_FAIL 443 | CONTINUE_SSL_FAIL=$(echo "$CONTINUE_SSL_FAIL" | tr '[:lower:]' '[:upper:]') 444 | if [[ "$CONTINUE_SSL_FAIL" == "Y" ]]; then 445 | log_warning "Continuing setup despite SSL certificate failure" 446 | return 1 447 | else 448 | log_error "Setup aborted due to SSL certificate failure" 449 | exit 1 450 | fi 451 | fi 452 | } 453 | 454 | configure_nginx() { 455 | local DOMAIN=$1 456 | local PORT=$2 457 | local SSL_SUCCESS=$3 458 | 459 | log_and_print "Configuring NGINX for ${DOMAIN}..." 460 | 461 | # Create backup directory 462 | mkdir -p "$NGINX_BACKUP_DIR" 463 | 464 | # Backup existing config if it exists 465 | if [ -f "/etc/nginx/sites-available/${DOMAIN}" ]; then 466 | cp "/etc/nginx/sites-available/${DOMAIN}" "$NGINX_BACKUP_DIR/${DOMAIN}.bak" 467 | log_and_print "Backed up existing nginx config" 468 | fi 469 | 470 | # Create nginx configuration with escaped variables 471 | if [ "$SSL_SUCCESS" -eq 1 ]; then 472 | # Create full nginx configuration with SSL 473 | cat > "/etc/nginx/sites-available/${DOMAIN}" <<'EOF' 474 | server { 475 | listen 80; 476 | server_name DOMAIN_PLACEHOLDER; 477 | 478 | # Redirect HTTP to HTTPS 479 | location / { 480 | return 301 https://$host$request_uri; 481 | } 482 | } 483 | 484 | server { 485 | listen 443 ssl http2; 486 | server_name DOMAIN_PLACEHOLDER; 487 | 488 | # SSL Configuration 489 | ssl_certificate /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/fullchain.pem; 490 | ssl_certificate_key /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/privkey.pem; 491 | ssl_protocols TLSv1.2 TLSv1.3; 492 | ssl_prefer_server_ciphers on; 493 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; 494 | 495 | # OCSP Stapling 496 | ssl_stapling on; 497 | ssl_stapling_verify on; 498 | ssl_trusted_certificate /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/chain.pem; 499 | 500 | # Security headers 501 | add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; 502 | add_header X-Frame-Options "DENY" always; 503 | add_header X-Content-Type-Options "nosniff" always; 504 | add_header X-XSS-Protection "1; mode=block" always; 505 | 506 | # Proxy configuration 507 | location / { 508 | proxy_pass http://127.0.0.1:PORT_PLACEHOLDER; 509 | proxy_http_version 1.1; 510 | proxy_set_header Host $host; 511 | proxy_set_header X-Real-IP $remote_addr; 512 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 513 | proxy_set_header X-Forwarded-Proto $scheme; 514 | proxy_set_header Upgrade $http_upgrade; 515 | proxy_set_header Connection "upgrade"; 516 | proxy_cache_bypass $http_upgrade; 517 | proxy_buffering off; 518 | } 519 | } 520 | EOF 521 | else 522 | # Create nginx configuration without SSL (HTTP only) 523 | cat > "/etc/nginx/sites-available/${DOMAIN}" <<'EOF' 524 | server { 525 | listen 80; 526 | server_name DOMAIN_PLACEHOLDER; 527 | 528 | # Proxy configuration 529 | location / { 530 | proxy_pass http://127.0.0.1:PORT_PLACEHOLDER; 531 | proxy_http_version 1.1; 532 | proxy_set_header Upgrade $http_upgrade; 533 | proxy_set_header Connection 'upgrade'; 534 | proxy_set_header Host $host; 535 | proxy_set_header X-Real-IP $remote_addr; 536 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 537 | proxy_set_header X-Forwarded-Proto $scheme; 538 | proxy_cache_bypass $http_upgrade; 539 | } 540 | } 541 | EOF 542 | log_warning "Configured Nginx without SSL due to certificate failure" 543 | fi 544 | 545 | # Replace placeholders with actual values (safely) 546 | sed -i "s/DOMAIN_PLACEHOLDER/${DOMAIN}/g" "/etc/nginx/sites-available/${DOMAIN}" 547 | sed -i "s/PORT_PLACEHOLDER/${PORT}/g" "/etc/nginx/sites-available/${DOMAIN}" 548 | 549 | # Enable site configuration 550 | ln -sf "/etc/nginx/sites-available/${DOMAIN}" "/etc/nginx/sites-enabled/" >> "$LOG_FILE" 2>&1 551 | 552 | # Test nginx configuration before reloading 553 | if nginx -t >> "$LOG_FILE" 2>&1; then 554 | systemctl reload nginx >> "$LOG_FILE" 2>&1 555 | if [ $? -eq 0 ]; then 556 | log_success "Nginx configured successfully" 557 | return 0 558 | else 559 | log_error "Nginx reload failed!" 560 | # Attempt to restore backup 561 | if [ -f "$NGINX_BACKUP_DIR/${DOMAIN}.bak" ]; then 562 | cp "$NGINX_BACKUP_DIR/${DOMAIN}.bak" "/etc/nginx/sites-available/${DOMAIN}" 563 | nginx -t && systemctl reload nginx 564 | log_warning "Restored previous nginx configuration" 565 | fi 566 | return 1 567 | fi 568 | else 569 | log_error "Nginx configuration test failed! Check $LOG_FILE for details." 570 | rm -f "/etc/nginx/sites-enabled/${DOMAIN}" 571 | return 1 572 | fi 573 | } 574 | 575 | configure_firewall() { 576 | log_and_print "Configuring firewall..." 577 | if command -v ufw >/dev/null 2>&1; then 578 | ufw allow 80/tcp >> "$LOG_FILE" 2>&1 579 | ufw allow 443/tcp >> "$LOG_FILE" 2>&1 580 | ufw reload >> "$LOG_FILE" 2>&1 581 | log_success "Firewall rules added" 582 | else 583 | log_warning "UFW not found, firewall not configured" 584 | fi 585 | } 586 | 587 | ensure_service_persistence() { 588 | log_and_print "Ensuring service persistence..." 589 | 590 | # Enable and start Nginx if not already active 591 | if ! systemctl is-active --quiet nginx; then 592 | systemctl enable --now nginx >> "$LOG_FILE" 2>&1 593 | fi 594 | 595 | # Enable Certbot renewal timer 596 | if systemctl list-timers | grep -q certbot; then 597 | systemctl enable --now certbot.timer >> "$LOG_FILE" 2>&1 598 | fi 599 | 600 | log_success "Services configured for auto-start" 601 | } 602 | 603 | setup_certbot_renewal() { 604 | log_and_print "Setting up Certbot renewal..." 605 | 606 | # Create renewal hooks if webhook is enabled 607 | if [ -n "$WEBHOOK_URL" ] && validate_url "$WEBHOOK_URL"; then 608 | mkdir -p /etc/letsencrypt/renewal-hooks/deploy >> "$LOG_FILE" 2>&1 609 | mkdir -p /etc/letsencrypt/renewal-hooks/post >> "$LOG_FILE" 2>&1 610 | 611 | # Deploy hook for successful renewals 612 | cat > /etc/letsencrypt/renewal-hooks/deploy/webhook-notify.sh <<'EOFHOOK' 613 | #!/bin/bash 614 | # Load configuration 615 | source /etc/cloudflarenginx.conf 2>/dev/null || exit 1 616 | 617 | # Webhook configuration from config file 618 | LOG_FILE="/var/log/cloudflarenginx-install.log" 619 | 620 | # Function to validate URL 621 | validate_url() { 622 | [[ "$1" =~ ^https:// ]] 623 | } 624 | 625 | # Send notification 626 | echo "Sending successful renewal webhook notification for $DOMAIN" >> "$LOG_FILE" 627 | 628 | if [[ "$WEBHOOK_MODE" == "S" || "$WEBHOOK_MODE" == "B" ]] && validate_url "$WEBHOOK_URL"; then 629 | MESSAGE="SSL certificate renewed successfully for domain: $DOMAIN" 630 | 631 | case "$WEBHOOK_PLATFORM" in 632 | D) # Discord 633 | PAYLOAD=$(jq -n \ 634 | --arg content "$MESSAGE" \ 635 | --arg domain "$DOMAIN" \ 636 | '{ 637 | content: $content, 638 | embeds: [{ 639 | title: "CloudflareNginx Success Notification", 640 | description: ("Domain: " + $domain), 641 | color: 65280 642 | }] 643 | }') 644 | ;; 645 | S) # Slack 646 | PAYLOAD=$(jq -n \ 647 | --arg message "$MESSAGE" \ 648 | --arg domain "$DOMAIN" \ 649 | '{ 650 | text: "CloudflareNginx Success Notification", 651 | blocks: [{ 652 | type: "section", 653 | text: { 654 | type: "mrkdwn", 655 | text: ("*" + $message + "*\nDomain: " + $domain) 656 | } 657 | }] 658 | }') 659 | ;; 660 | G) # Google Chat 661 | PAYLOAD=$(jq -n \ 662 | --arg message "$MESSAGE" \ 663 | --arg domain "$DOMAIN" \ 664 | '{ 665 | text: "CloudflareNginx Success Notification", 666 | cards: [{ 667 | header: { 668 | title: "SSL Certificate Renewed" 669 | }, 670 | sections: [{ 671 | widgets: [{ 672 | textParagraph: { 673 | text: ($message + "\nDomain: " + $domain) 674 | } 675 | }] 676 | }] 677 | }] 678 | }') 679 | ;; 680 | esac 681 | 682 | if [ -n "$PAYLOAD" ]; then 683 | curl -s -X POST -H "Content-Type: application/json" -d "$PAYLOAD" "$WEBHOOK_URL" >> "$LOG_FILE" 2>&1 684 | fi 685 | fi 686 | EOFHOOK 687 | chmod +x /etc/letsencrypt/renewal-hooks/deploy/webhook-notify.sh >> "$LOG_FILE" 2>&1 688 | 689 | # Post hook for failed renewals 690 | cat > /etc/letsencrypt/renewal-hooks/post/webhook-notify-failure.sh <<'EOFHOOK' 691 | #!/bin/bash 692 | # Load configuration 693 | source /etc/cloudflarenginx.conf 2>/dev/null || exit 1 694 | 695 | LOG_FILE="/var/log/cloudflarenginx-install.log" 696 | 697 | # Function to validate URL 698 | validate_url() { 699 | [[ "$1" =~ ^https:// ]] 700 | } 701 | 702 | # Check if certificate exists and is about to expire 703 | CERT_FILE="/etc/letsencrypt/live/$DOMAIN/cert.pem" 704 | if [ -f "$CERT_FILE" ]; then 705 | EXPIRY=$(openssl x509 -enddate -noout -in "$CERT_FILE" | cut -d= -f2) 706 | EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "$EXPIRY" +%s) 707 | NOW_EPOCH=$(date +%s) 708 | DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 )) 709 | 710 | # If less than 7 days until expiry, renewal likely failed 711 | if [ $DAYS_LEFT -lt 7 ]; then 712 | echo "Certificate has $DAYS_LEFT days left and renewal likely failed. Sending webhook." >> "$LOG_FILE" 713 | 714 | if [[ "$WEBHOOK_MODE" == "F" || "$WEBHOOK_MODE" == "B" ]] && validate_url "$WEBHOOK_URL"; then 715 | MESSAGE="SSL certificate renewal failed for domain: $DOMAIN. Certificate will expire in $DAYS_LEFT days" 716 | 717 | case "$WEBHOOK_PLATFORM" in 718 | D) # Discord 719 | PAYLOAD=$(jq -n \ 720 | --arg content "$MESSAGE" \ 721 | --arg domain "$DOMAIN" \ 722 | --argjson days "$DAYS_LEFT" \ 723 | '{ 724 | content: $content, 725 | embeds: [{ 726 | title: "CloudflareNginx Failure Alert", 727 | description: ("Domain: " + $domain + "\nDays until expiry: " + ($days | tostring)), 728 | color: 16711680 729 | }] 730 | }') 731 | ;; 732 | S) # Slack 733 | PAYLOAD=$(jq -n \ 734 | --arg message "$MESSAGE" \ 735 | --arg domain "$DOMAIN" \ 736 | --argjson days "$DAYS_LEFT" \ 737 | '{ 738 | text: "CloudflareNginx Failure Alert", 739 | blocks: [{ 740 | type: "section", 741 | text: { 742 | type: "mrkdwn", 743 | text: ("*" + $message + "*\nDomain: " + $domain + "\nDays until expiry: " + ($days | tostring)) 744 | } 745 | }] 746 | }') 747 | ;; 748 | G) # Google Chat 749 | PAYLOAD=$(jq -n \ 750 | --arg message "$MESSAGE" \ 751 | --arg domain "$DOMAIN" \ 752 | --argjson days "$DAYS_LEFT" \ 753 | '{ 754 | text: "CloudflareNginx Failure Alert", 755 | cards: [{ 756 | header: { 757 | title: "SSL Certificate Renewal Failed" 758 | }, 759 | sections: [{ 760 | widgets: [{ 761 | textParagraph: { 762 | text: ($message + "\nDomain: " + $domain + "\nDays until expiry: " + ($days | tostring)) 763 | } 764 | }] 765 | }] 766 | }] 767 | }') 768 | ;; 769 | esac 770 | 771 | if [ -n "$PAYLOAD" ]; then 772 | curl -s -X POST -H "Content-Type: application/json" -d "$PAYLOAD" "$WEBHOOK_URL" >> "$LOG_FILE" 2>&1 773 | fi 774 | fi 775 | fi 776 | fi 777 | EOFHOOK 778 | chmod +x /etc/letsencrypt/renewal-hooks/post/webhook-notify-failure.sh >> "$LOG_FILE" 2>&1 779 | log_success "Webhook renewal hooks configured" 780 | fi 781 | 782 | # Test renewal 783 | log_and_print "Testing certificate renewal..." 784 | if certbot renew --dry-run >> "$LOG_FILE" 2>&1; then 785 | log_success "Certificate renewal configured successfully" 786 | send_webhook "success" "Certificate renewal system configured successfully for domain: $DOMAIN" 787 | return 0 788 | else 789 | log_warning "Certificate renewal test failed! Check $LOG_FILE for details. Continuing setup anyway." 790 | send_webhook "failure" "Certificate renewal configuration failed for domain: $DOMAIN, but setup will continue" 791 | return 1 792 | fi 793 | } 794 | 795 | main() { 796 | show_header 797 | check_root 798 | 799 | # Parse command line arguments 800 | while [[ $# -gt 0 ]]; do 801 | case $1 in 802 | -d|--domain) 803 | DOMAIN="$2" 804 | shift 2 805 | ;; 806 | -p|--port) 807 | PORT="$2" 808 | shift 2 809 | ;; 810 | -e|--email) 811 | CF_EMAIL="$2" 812 | shift 2 813 | ;; 814 | -k|--key) 815 | CF_API_KEY="$2" 816 | shift 2 817 | ;; 818 | -w|--webhook) 819 | WEBHOOK_URL="$2" 820 | shift 2 821 | ;; 822 | -m|--webhook-mode) 823 | WEBHOOK_MODE="$2" 824 | shift 2 825 | ;; 826 | -t|--webhook-type) 827 | WEBHOOK_PLATFORM="$2" 828 | shift 2 829 | ;; 830 | -q|--quiet) 831 | QUIET_MODE=1 832 | shift 833 | ;; 834 | -h|--help) 835 | echo "Usage: $0 [options]" 836 | echo "Options:" 837 | echo " -d, --domain DOMAIN Domain name (required)" 838 | echo " -p, --port PORT Application port (default: 3000)" 839 | echo " -e, --email EMAIL Cloudflare email (required)" 840 | echo " -k, --key KEY Cloudflare API key (required)" 841 | echo " -w, --webhook URL Webhook URL" 842 | echo " -m, --webhook-mode MODE S=Success, F=Failure, B=Both (default: B)" 843 | echo " -t, --webhook-type TYPE D=Discord, S=Slack, G=Google Chat (default: D)" 844 | echo " -q, --quiet Minimal output" 845 | echo " -h, --help Show this help" 846 | exit 0 847 | ;; 848 | *) 849 | echo "Unknown option: $1" 850 | exit 1 851 | ;; 852 | esac 853 | done 854 | 855 | # If arguments provided, validate them 856 | if [ -n "$DOMAIN" ] || [ -n "$CF_EMAIL" ] || [ -n "$CF_API_KEY" ]; then 857 | # Sanitize and validate provided arguments 858 | [ -n "$DOMAIN" ] && DOMAIN=$(sanitize_domain "$DOMAIN") 859 | [ -n "$CF_EMAIL" ] && CF_EMAIL=$(sanitize_email "$CF_EMAIL") 860 | [ -n "$CF_API_KEY" ] && CF_API_KEY=$(sanitize_api_key "$CF_API_KEY") 861 | [ -n "$PORT" ] && PORT=$(sanitize_port "$PORT") 862 | 863 | # Validate required fields 864 | if [ -z "$DOMAIN" ] || [ -z "$CF_EMAIL" ] || [ -z "$CF_API_KEY" ]; then 865 | log_error "Domain, email, and API key are required when using command line arguments" 866 | exit 1 867 | fi 868 | 869 | if ! validate_domain "$DOMAIN"; then 870 | exit 1 871 | fi 872 | 873 | # Save credentials 874 | mkdir -p $(dirname "$CLOUDFLARE_CRED_PATH") >> "$LOG_FILE" 2>&1 875 | cat > "$CLOUDFLARE_CRED_PATH" <> "$LOG_FILE" 2>&1 880 | 881 | # Validate webhook if provided 882 | if [ -n "$WEBHOOK_URL" ] && ! validate_url "$WEBHOOK_URL"; then 883 | log_error "Invalid webhook URL" 884 | exit 1 885 | fi 886 | else 887 | # Interactive mode 888 | handle_cloudflare_credentials 889 | fi 890 | 891 | install_core_dependencies 892 | 893 | # Validate port early 894 | if [ -z "$PORT" ]; then 895 | ask_question "Enter your application port (default: 3000)" PORT 896 | PORT=${PORT:-3000} 897 | fi 898 | PORT=$(sanitize_port "$PORT") 899 | 900 | if ! validate_port "$PORT"; then 901 | exit 1 902 | fi 903 | 904 | # Track SSL success/failure 905 | SSL_SUCCESS=1 906 | if ! generate_ssl; then 907 | SSL_SUCCESS=0 908 | fi 909 | 910 | # Setup renewal hooks regardless of SSL success 911 | RENEWAL_SUCCESS=1 912 | if ! setup_certbot_renewal; then 913 | RENEWAL_SUCCESS=0 914 | fi 915 | 916 | # Configure nginx with SSL_SUCCESS status 917 | NGINX_SUCCESS=1 918 | if ! configure_nginx "$DOMAIN" "$PORT" "$SSL_SUCCESS"; then 919 | NGINX_SUCCESS=0 920 | ask_question "Nginx configuration failed. Continue with the remaining setup? (Y/N)" CONTINUE_NGINX_FAIL 921 | CONTINUE_NGINX_FAIL=$(echo "$CONTINUE_NGINX_FAIL" | tr '[:lower:]' '[:upper:]') 922 | if [[ "$CONTINUE_NGINX_FAIL" != "Y" ]]; then 923 | exit 1 924 | fi 925 | fi 926 | 927 | configure_firewall 928 | ensure_service_persistence 929 | 930 | # Final status message 931 | log_success "Setup completed!" 932 | 933 | if [ "$SSL_SUCCESS" -eq 1 ]; then 934 | echo -e "${GREEN}Access your site at: https://${DOMAIN}${NC}" 935 | else 936 | echo -e "${YELLOW}Access your site at: http://${DOMAIN}${NC}" 937 | echo -e "${YELLOW}Note: SSL was not configured successfully.${NC}" 938 | fi 939 | 940 | if [ "$RENEWAL_SUCCESS" -eq 0 ]; then 941 | echo -e "${YELLOW}Certificate renewal test failed, but setup continued.${NC}" 942 | fi 943 | 944 | if [ "$NGINX_SUCCESS" -eq 0 ]; then 945 | echo -e "${YELLOW}Nginx configuration had issues. Please check manually.${NC}" 946 | fi 947 | 948 | # Final success webhook 949 | if [ "$SSL_SUCCESS" -eq 1 ] && [ "$NGINX_SUCCESS" -eq 1 ]; then 950 | send_webhook "success" "Full CloudflareNginx setup completed successfully for domain: $DOMAIN" 951 | else 952 | send_webhook "warning" "CloudflareNginx setup completed with warnings for domain: $DOMAIN" 953 | fi 954 | 955 | # Display important information 956 | echo -e "\n${BLUE}Important Notes:${NC}" 957 | echo -e "1. Your Cloudflare credentials are stored securely at ${CLOUDFLARE_CRED_PATH}" 958 | 959 | if [ "$SSL_SUCCESS" -eq 1 ]; then 960 | echo -e "2. SSL certificates will auto-renew before expiration" 961 | if [ "$RENEWAL_SUCCESS" -eq 0 ]; then 962 | echo -e " - Warning: Renewal test failed, but this might be a temporary issue" 963 | fi 964 | else 965 | echo -e "2. SSL certificates were not configured successfully" 966 | fi 967 | 968 | echo -e "3. Nginx is configured to start automatically on boot" 969 | echo -e "4. Firewall rules (if UFW is present) are persistent" 970 | if [ -n "$WEBHOOK_URL" ]; then 971 | echo -e "5. Webhook notifications are enabled for: $WEBHOOK_MODE" 972 | echo -e " - Webhook Platform: $(case "$WEBHOOK_PLATFORM" in 973 | D) echo "Discord" ;; 974 | S) echo "Slack" ;; 975 | G) echo "Google Chat" ;; 976 | esac)" 977 | fi 978 | echo -e "6. Configuration saved at: ${CONFIG_FILE}" 979 | echo -e "7. Nginx backups stored at: /etc/nginx/backups/" 980 | echo -e "\n${YELLOW}Detailed logs available at: ${LOG_FILE}${NC}" 981 | } 982 | 983 | main "$@" -------------------------------------------------------------------------------- /cfnginx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # CloudflareNginx Installer v2.4 3 | # Non-interactive version with webhook support and robust error handling 4 | 5 | # Configuration 6 | BLUE='\033[0;34m' 7 | GREEN='\033[0;32m' 8 | YELLOW='\033[0;33m' 9 | RED='\033[0;31m' 10 | NC='\033[0m' 11 | LOG_FILE="/var/log/cloudflarenginx-install.log" 12 | CONFIG_FILE="/etc/cloudflarenginx.conf" 13 | TMP_DIR=$(mktemp -d) 14 | 15 | # Initialize variables 16 | DOMAIN="" 17 | PORT="" 18 | CF_EMAIL="" 19 | CF_API_KEY="" 20 | WEBHOOK_URL="" 21 | WEBHOOK_MODE="B" 22 | WEBHOOK_PLATFORM="D" 23 | SSL_SUCCESS=0 24 | # Explicitly set QUIET_MODE with explicit type 25 | QUIET_MODE=0 26 | 27 | # Cleanup function 28 | cleanup() { 29 | rm -rf "$TMP_DIR" 30 | } 31 | trap cleanup EXIT 32 | 33 | # Enhanced logging functions 34 | log() { 35 | echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" 36 | } 37 | 38 | # Modified logging functions with simplified conditions 39 | log_and_print() { 40 | if [ "$QUIET_MODE" != "1" ]; then 41 | echo -e "${BLUE}$1${NC}" 42 | fi 43 | log "INFO: $1" 44 | } 45 | 46 | log_success() { 47 | if [ "$QUIET_MODE" != "1" ]; then 48 | echo -e "${GREEN}✓ $1${NC}" 49 | fi 50 | log "SUCCESS: $1" 51 | } 52 | 53 | log_warning() { 54 | if [ "$QUIET_MODE" != "1" ]; then 55 | echo -e "${YELLOW}⚠ $1${NC}" 56 | fi 57 | log "WARNING: $1" 58 | } 59 | 60 | log_error() { 61 | if [ "$QUIET_MODE" != "1" ]; then 62 | echo -e "${RED}✗ $1${NC}" 63 | fi 64 | log "ERROR: $1" 65 | } 66 | 67 | # Validation functions 68 | validate_domain() { 69 | [[ "$1" =~ ^([a-zA-Z0-9](-?[a-zA-Z0-9])*\.)+[a-zA-Z]{2,}$ ]] || { 70 | log_error "Invalid domain format: $1" 71 | echo -e "${RED}Please enter a valid domain name (e.g., example.com)${NC}" >&2 72 | return 1 73 | } 74 | return 0 75 | } 76 | 77 | validate_port() { 78 | [[ "$1" =~ ^[0-9]+$ ]] && (( $1 >= 1 && $1 <= 65535 )) || { 79 | log_error "Invalid port number: $1" 80 | echo -e "${RED}Port must be a number between 1 and 65535${NC}" >&2 81 | return 1 82 | } 83 | return 0 84 | } 85 | 86 | validate_email() { 87 | [[ "$1" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]] || { 88 | log_error "Invalid email format: $1" 89 | echo -e "${RED}Please enter a valid email address${NC}" >&2 90 | return 1 91 | } 92 | return 0 93 | } 94 | 95 | validate_webhook_url() { 96 | [[ "$1" =~ ^https?://.+ ]] || { 97 | log_error "Invalid webhook URL: $1" 98 | echo -e "${RED}Webhook URL must start with http:// or https://${NC}" >&2 99 | return 1 100 | } 101 | return 0 102 | } 103 | 104 | show_header() { 105 | if [ "$QUIET_MODE" != "1" ]; then 106 | clear 107 | echo -e "${BLUE}" 108 | echo "===============================================" 109 | echo " Cloudflare Nginx Automated Setup v2.4 " 110 | echo "===============================================" 111 | echo -e "${NC}" 112 | fi 113 | log "Installation started" 114 | } 115 | 116 | show_help() { 117 | echo "Usage: $0 [options]" 118 | echo 119 | echo "Options:" 120 | echo " -d, --domain DOMAIN Set the domain name (required)" 121 | echo " -p, --port PORT Set the application port (default: 3000)" 122 | echo " -e, --email EMAIL Set the Cloudflare email (required)" 123 | echo " -k, --key API_KEY Set the Cloudflare API key (required)" 124 | echo " -w, --webhook URL Set the webhook URL" 125 | echo " -m, --webhook-mode MODE Set webhook mode (S=Success, F=Failure, B=Both)" 126 | echo " -t, --webhook-type TYPE Set webhook type (D=Discord, S=Slack, G=Google Chat)" 127 | echo " -c, --config FILE Use configuration file" 128 | echo " -q, --quiet Run in quiet mode (minimal output)" 129 | echo " -h, --help Show this help message" 130 | echo 131 | echo "Examples:" 132 | echo " Basic usage: $0 --domain example.com --port 3000 --email user@example.com --key abc123" 133 | echo " With webhook: $0 --domain example.com --webhook \"https://discord.com/webhook\" --webhook-type D" 134 | echo " Quiet mode: $0 --domain example.com --email user@example.com --key abc123 --quiet" 135 | exit 0 136 | } 137 | 138 | # Configuration management 139 | save_config() { 140 | log_and_print "Saving configuration to $CONFIG_FILE" 141 | 142 | # Create directory if it doesn't exist 143 | mkdir -p "$(dirname "$CONFIG_FILE")" || { 144 | log_error "Failed to create directory for config file" 145 | return 1 146 | } 147 | 148 | cat > "$CONFIG_FILE" <> "$LOG_FILE" 2>&1; then 181 | log_error "Failed to update package lists" 182 | return 1 183 | fi 184 | 185 | if ! apt-get install -y -qq "${dependencies[@]}" >> "$LOG_FILE" 2>&1; then 186 | log_error "Failed to install dependencies" 187 | return 1 188 | fi 189 | 190 | log_success "Dependencies installed successfully" 191 | return 0 192 | } 193 | 194 | setup_cloudflare_credentials() { 195 | local cloudflare_cred_path="/etc/letsencrypt/cloudflare.ini" 196 | 197 | log_and_print "Setting up Cloudflare credentials..." 198 | 199 | mkdir -p "$(dirname "$cloudflare_cred_path")" || { 200 | log_error "Failed to create directory for Cloudflare credentials" 201 | return 1 202 | } 203 | 204 | cat > "$cloudflare_cred_path" <> "$LOG_FILE" 2>&1; then 228 | log_success "SSL certificate generated successfully" 229 | return 0 230 | else 231 | log_error "SSL certificate generation failed" 232 | echo -e "${YELLOW}Check /var/log/letsencrypt/letsencrypt.log for details${NC}" 233 | return 1 234 | fi 235 | } 236 | 237 | configure_nginx() { 238 | local domain=$1 239 | local port=$2 240 | local ssl_success=$3 241 | 242 | log_and_print "Configuring Nginx for $domain..." 243 | 244 | # Create Nginx configuration 245 | local nginx_config="/etc/nginx/sites-available/$domain" 246 | 247 | if [ "$ssl_success" -eq 1 ]; then 248 | log_and_print "Creating Nginx configuration with SSL" 249 | cat > "$nginx_config" <