#!/bin/bash ################################################################################ # Script Name: hestia-geoip-setup.sh # Version: 1.0 # Description: Configure GeoIP2 enriched logging on HestiaCP / VestaCP / # myVesta servers. Installs GeoIP2 module, downloads databases # (MaxMind or DB-IP free), creates nginx geoip2 variable mappings # and enriched log format in conf.d, builds custom nginx templates # with enriched access_log, and optionally applies to domains. # # Author: Phil Connor # Contact: contact@mylinux.work # Website: https://mylinux.work # License: MIT # # Prerequisites: # - HestiaCP or VestaCP/myVesta installed and running # - nginx as proxy (default HestiaCP setup) # - Root access # - MaxMind account OR use --db-ip for no-signup alternative # # Usage: # sudo ./hestia-geoip-setup.sh --db-ip # sudo ./hestia-geoip-setup.sh --db-ip --base-template default-botblock # sudo ./hestia-geoip-setup.sh --account-id 123456 --license-key ABCDEF # sudo ./hestia-geoip-setup.sh --db-ip --apply admin example.com # sudo ./hestia-geoip-setup.sh --db-ip --apply-all admin # sudo ./hestia-geoip-setup.sh --dry-run --db-ip # sudo ./hestia-geoip-setup.sh --remove # ################################################################################ set -euo pipefail # --- Configuration --- GEOIP_CONF="/etc/GeoIP.conf" GEOIP_DB_DIR="/usr/share/GeoIP" NGINX_GEOIP_CONF="/etc/nginx/conf.d/geoip2.conf" NGINX_LOG_CONF="/etc/nginx/conf.d/enriched-log-format.conf" APACHE_LOG_CONF="/etc/apache2/conf-available/enriched-log-format.conf" CRON_WEEKLY="/etc/cron.weekly/geoip-db-update" CRON_MONTHLY="/etc/cron.monthly/geoip-db-update" MARKER_START="# geoip-managed-start" MARKER_END="# geoip-managed-end" TIMESTAMP=$(date +%s) TEMPLATE_NAME="default-geoip" BASE_TEMPLATE="default" PANEL_TPL_DIR="" PANEL_NAME="" APPLY_USER="" APPLY_DOMAIN="" APPLY_ALL=false DRY_RUN=false REMOVE=false SKIP_PACKAGES=false USE_DBIP=false ACCOUNT_ID="${MAXMIND_ACCOUNT_ID:-}" LICENSE_KEY="${MAXMIND_LICENSE_KEY:-}" # --- Colors --- RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' info() { echo -e "${GREEN}[OK]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } step() { echo -e "${CYAN}[STEP]${NC} $*"; } detect_panel() { if [[ -d "/usr/local/hestia/data/templates/web/nginx" ]]; then PANEL_TPL_DIR="/usr/local/hestia/data/templates/web/nginx" PANEL_NAME="HestiaCP" elif [[ -d "/usr/local/vesta/data/templates/web/nginx" ]]; then PANEL_TPL_DIR="/usr/local/vesta/data/templates/web/nginx" PANEL_NAME="VestaCP/myVesta" else echo -e "${RED}Error: Neither HestiaCP nor VestaCP/myVesta found${NC}" >&2 exit 1 fi info "Detected ${PANEL_NAME} (${PANEL_TPL_DIR})" } usage() { cat <&2 exit 1 fi detect_panel if ! command -v nginx &>/dev/null; then echo -e "${RED}Error: nginx not found${NC}" >&2 exit 1 fi backup_file() { local file="$1" if [[ -f "$file" ]]; then cp "$file" "${file}.bak.${TIMESTAMP}" fi } # ===================================================== # REMOVE MODE # ===================================================== if [[ "$REMOVE" == "true" ]]; then step "Removing GeoIP2 configuration" for file in "$NGINX_GEOIP_CONF" "$NGINX_LOG_CONF" "$APACHE_LOG_CONF" "$CRON_WEEKLY" "$CRON_MONTHLY"; do if [[ -f "$file" ]]; then if [[ "$DRY_RUN" == "true" ]]; then echo " Would remove: ${file}" else rm -f "$file" info "Removed: ${file}" fi else warn "Not found: ${file} (already removed?)" fi done # Remove module load config (leave .so in case of reinstall) LOAD_MOD_CONF="/etc/nginx/modules-enabled/50-mod-http-geoip2.conf" if [[ -f "$LOAD_MOD_CONF" ]]; then if [[ "$DRY_RUN" == "true" ]]; then echo " Would remove: ${LOAD_MOD_CONF}" else rm -f "$LOAD_MOD_CONF" info "Removed: ${LOAD_MOD_CONF}" fi fi # Remove nginx templates for ext in tpl stpl; do tpl_file="${PANEL_TPL_DIR}/${TEMPLATE_NAME}.${ext}" if [[ -f "$tpl_file" ]]; then if [[ "$DRY_RUN" == "true" ]]; then echo " Would remove: ${tpl_file}" else rm -f "$tpl_file" info "Removed: ${tpl_file}" fi fi # Also check php-fpm directory tpl_file="${PANEL_TPL_DIR}/php-fpm/${TEMPLATE_NAME}.${ext}" if [[ -f "$tpl_file" ]]; then if [[ "$DRY_RUN" == "true" ]]; then echo " Would remove: ${tpl_file}" else rm -f "$tpl_file" info "Removed: ${tpl_file}" fi fi done # Remove Apache templates for apache_dir in "/usr/local/hestia/data/templates/web/apache2/php-fpm" "/usr/local/hestia/data/templates/web/apache2"; do for ext in tpl stpl; do tpl_file="${apache_dir}/${TEMPLATE_NAME}.${ext}" if [[ -f "$tpl_file" ]]; then if [[ "$DRY_RUN" == "true" ]]; then echo " Would remove: ${tpl_file}" else rm -f "$tpl_file" info "Removed: ${tpl_file}" fi fi done done # Disable Apache enriched log config if [[ -f "$APACHE_LOG_CONF" ]]; then if [[ "$DRY_RUN" == "true" ]]; then echo " Would run: a2disconf enriched-log-format" else a2disconf -q enriched-log-format 2>/dev/null || true fi fi if [[ "$DRY_RUN" == "true" ]]; then echo " Would run: nginx -t" echo " Would run: systemctl reload nginx" else step "Testing nginx configuration" if nginx -t 2>&1; then info "nginx config valid" else echo -e "${RED}[ERROR] nginx config test failed — restore .bak files${NC}" >&2 exit 1 fi step "Reloading nginx" systemctl reload nginx info "nginx reloaded" if systemctl is-active --quiet apache2 2>/dev/null; then step "Reloading Apache" systemctl reload apache2 info "Apache reloaded" fi fi echo "" echo -e "${BOLD}GeoIP2 configuration removed.${NC}" echo " Domains still using the ${TEMPLATE_NAME} template should be switched back." exit 0 fi # --- Prompt for credentials if not using DB-IP --- if [[ "$USE_DBIP" != "true" ]]; then if [[ -z "$ACCOUNT_ID" ]]; then read -rp "MaxMind Account ID (or Ctrl+C and rerun with --db-ip): " ACCOUNT_ID fi if [[ -z "$LICENSE_KEY" ]]; then read -rp "MaxMind License Key: " LICENSE_KEY fi if [[ -z "$ACCOUNT_ID" || -z "$LICENSE_KEY" ]]; then echo -e "${RED}Error: MaxMind credentials required (or use --db-ip for no-signup alternative)${NC}" >&2 exit 1 fi fi # ===================================================== # Step 1: Install packages and GeoIP2 nginx module # ===================================================== NGINX_MOD_DIR="/usr/lib/nginx/modules" GEOIP2_MOD="${NGINX_MOD_DIR}/ngx_http_geoip2_module.so" LOAD_MOD_CONF="/etc/nginx/modules-enabled/50-mod-http-geoip2.conf" if [[ "$SKIP_PACKAGES" == "true" ]]; then warn "Skipping package installation (--skip-packages)" else # Hestia uses nginx from nginx.org — Ubuntu/Debian libnginx-mod-* packages # conflict with it. Install only libmaxminddb-dev + build tools, then compile # the geoip2 module as a dynamic .so against the installed nginx. step "Installing build dependencies" if [[ "$USE_DBIP" == "true" ]]; then PACKAGES="libmaxminddb0 libmaxminddb-dev mmdb-bin curl build-essential git libpcre2-dev libssl-dev zlib1g-dev" else PACKAGES="libmaxminddb0 libmaxminddb-dev mmdb-bin geoipupdate build-essential git libpcre2-dev libssl-dev zlib1g-dev" fi if [[ "$DRY_RUN" == "true" ]]; then echo " Would run: apt-get update && apt-get install -y ${PACKAGES}" else apt-get update -qq # shellcheck disable=SC2086 apt-get install -y $PACKAGES info "Packages installed: ${PACKAGES}" fi # Build the geoip2 dynamic module if not already present if [[ -f "$GEOIP2_MOD" ]]; then info "GeoIP2 module already exists: ${GEOIP2_MOD}" else step "Building ngx_http_geoip2_module for nginx $(nginx -v 2>&1 | grep -oP '[\d.]+')" if [[ "$DRY_RUN" == "true" ]]; then echo " Would compile ngx_http_geoip2_module.so" else NGINX_VER=$(nginx -v 2>&1 | grep -oP '[\d.]+') BUILD_DIR=$(mktemp -d) cd "$BUILD_DIR" curl -fsSL "https://nginx.org/download/nginx-${NGINX_VER}.tar.gz" -o nginx.tar.gz tar xzf nginx.tar.gz git clone --depth 1 https://github.com/leev/ngx_http_geoip2_module.git cd "nginx-${NGINX_VER}" ./configure --with-compat --add-dynamic-module=../ngx_http_geoip2_module make modules mkdir -p "$NGINX_MOD_DIR" cp objs/ngx_http_geoip2_module.so "$NGINX_MOD_DIR/" cp objs/ngx_stream_geoip2_module.so "$NGINX_MOD_DIR/" 2>/dev/null || true cd / rm -rf "$BUILD_DIR" info "Built and installed: ${GEOIP2_MOD}" fi fi # Ensure the module is loaded if [[ ! -f "$LOAD_MOD_CONF" ]] && [[ "$DRY_RUN" != "true" ]]; then mkdir -p "$(dirname "$LOAD_MOD_CONF")" echo "load_module ${GEOIP2_MOD};" > "$LOAD_MOD_CONF" info "Created: ${LOAD_MOD_CONF}" fi fi # ===================================================== # Step 2: Write GeoIP.conf (MaxMind only) # ===================================================== if [[ "$USE_DBIP" != "true" ]]; then step "Writing MaxMind configuration to ${GEOIP_CONF}" GEOIP_CONF_CONTENT="${MARKER_START} AccountID ${ACCOUNT_ID} LicenseKey ${LICENSE_KEY} EditionIDs GeoLite2-City GeoLite2-ASN GeoLite2-Country DatabaseDirectory ${GEOIP_DB_DIR} ${MARKER_END}" if [[ "$DRY_RUN" == "true" ]]; then echo " Would create: ${GEOIP_CONF}" else backup_file "$GEOIP_CONF" echo "$GEOIP_CONF_CONTENT" > "$GEOIP_CONF" chmod 600 "$GEOIP_CONF" info "Created: ${GEOIP_CONF}" fi fi # ===================================================== # Step 3: Download GeoIP databases # ===================================================== if [[ "$USE_DBIP" == "true" ]]; then step "Downloading DB-IP free databases to ${GEOIP_DB_DIR}" DBIP_MONTH=$(date +%Y-%m) if [[ "$DRY_RUN" == "true" ]]; then echo " Would download: dbip-country-lite-${DBIP_MONTH}.mmdb.gz" echo " Would download: dbip-asn-lite-${DBIP_MONTH}.mmdb.gz" else mkdir -p "$GEOIP_DB_DIR" cd "$GEOIP_DB_DIR" curl -fsSL "https://download.db-ip.com/free/dbip-country-lite-${DBIP_MONTH}.mmdb.gz" -o dbip-country.mmdb.gz gunzip -f dbip-country.mmdb.gz mv dbip-country.mmdb GeoLite2-City.mmdb info "Downloaded: DB-IP Country → GeoLite2-City.mmdb" curl -fsSL "https://download.db-ip.com/free/dbip-asn-lite-${DBIP_MONTH}.mmdb.gz" -o dbip-asn.mmdb.gz gunzip -f dbip-asn.mmdb.gz mv dbip-asn.mmdb GeoLite2-ASN.mmdb info "Downloaded: DB-IP ASN → GeoLite2-ASN.mmdb" cd - >/dev/null fi else step "Downloading MaxMind GeoIP2 databases to ${GEOIP_DB_DIR}" if [[ "$DRY_RUN" == "true" ]]; then echo " Would run: mkdir -p ${GEOIP_DB_DIR}" echo " Would run: geoipupdate" else mkdir -p "$GEOIP_DB_DIR" geoipupdate -v info "GeoIP2 databases downloaded" fi fi # ===================================================== # Step 4: Create nginx geoip2 config in conf.d # ===================================================== step "Creating nginx GeoIP2 config at ${NGINX_GEOIP_CONF}" NGINX_GEOIP_CONTENT="${MARKER_START} # GeoIP2 variable mappings — generated by hestia-geoip-setup.sh geoip2 ${GEOIP_DB_DIR}/GeoLite2-City.mmdb { auto_reload 60m; \$geoip2_country_code country iso_code; \$geoip2_country_name country names en; \$geoip2_city_name city names en; } geoip2 ${GEOIP_DB_DIR}/GeoLite2-ASN.mmdb { auto_reload 60m; \$geoip2_asn autonomous_system_number; \$geoip2_asn_org autonomous_system_organization; } ${MARKER_END}" if [[ "$DRY_RUN" == "true" ]]; then echo " Would create: ${NGINX_GEOIP_CONF}" else backup_file "$NGINX_GEOIP_CONF" echo "$NGINX_GEOIP_CONTENT" > "$NGINX_GEOIP_CONF" info "Created: ${NGINX_GEOIP_CONF}" fi # ===================================================== # Step 5: Create enriched log format in conf.d # ===================================================== step "Creating enriched log format at ${NGINX_LOG_CONF}" NGINX_LOG_CONTENT="${MARKER_START} # Enriched log format with GeoIP2 data — generated by hestia-geoip-setup.sh log_format enriched '\$remote_addr - \$remote_user [\$time_local] ' '\"\$request\" \$status \$body_bytes_sent ' '\"\$http_referer\" \"\$http_user_agent\" ' '\$geoip2_country_code \"\$geoip2_asn_org\"'; ${MARKER_END}" if [[ "$DRY_RUN" == "true" ]]; then echo " Would create: ${NGINX_LOG_CONF}" else backup_file "$NGINX_LOG_CONF" echo "$NGINX_LOG_CONTENT" > "$NGINX_LOG_CONF" info "Created: ${NGINX_LOG_CONF}" fi # ===================================================== # Step 5b: Create Apache enriched log format # ===================================================== if [[ -d "/etc/apache2/conf-available" ]]; then step "Creating Apache enriched log format at ${APACHE_LOG_CONF}" APACHE_LOG_CONTENT="${MARKER_START} # Enriched log format with GeoIP2 data (passed from nginx) — generated by hestia-geoip-setup.sh LogFormat \"%h %l %u %t \\\"%r\\\" %>s %b \\\"%{Referer}i\\\" \\\"%{User-Agent}i\\\" %{X-GeoIP-Country}i \\\"%{X-GeoIP-ASN}i\\\"\" enriched ${MARKER_END}" if [[ "$DRY_RUN" == "true" ]]; then echo " Would create: ${APACHE_LOG_CONF}" echo " Would enable: a2enconf enriched-log-format" else backup_file "$APACHE_LOG_CONF" echo "$APACHE_LOG_CONTENT" > "$APACHE_LOG_CONF" a2enconf -q enriched-log-format 2>/dev/null || true info "Created and enabled: ${APACHE_LOG_CONF}" fi else warn "Apache conf-available directory not found — skipping Apache log format" fi # ===================================================== # Step 6: Create custom Hestia templates # ===================================================== # --- Hestia Apache template patching --- APACHE_TPL_DIR="" if [[ -d "/usr/local/hestia/data/templates/web/apache2/php-fpm" ]]; then APACHE_TPL_DIR="/usr/local/hestia/data/templates/web/apache2/php-fpm" elif [[ -d "/usr/local/hestia/data/templates/web/apache2" ]]; then APACHE_TPL_DIR="/usr/local/hestia/data/templates/web/apache2" fi create_apache_template() { local src="$1" dst="$2" label="$3" if [[ ! -f "$src" ]]; then warn "Source Apache template not found: ${src} — skipping ${label}" return fi if [[ "$DRY_RUN" == "true" ]]; then echo " Would create: ${dst} (from ${src})" return fi backup_file "$dst" # Define enriched LogFormat inside the vhost and replace 'combined' with 'enriched' APACHE_ENRICHED_FORMAT=' LogFormat "%h %l %u %t \\"%r\\" %>s %b \\"%{Referer}i\\" \\"%{User-Agent}i\\" %{X-GeoIP-Country}i \\"%{X-GeoIP-ASN}i\\"" enriched' sed 's/\(CustomLog.*\.log\) combined/\1 enriched/g' "$src" | \ awk -v fmt="$APACHE_ENRICHED_FORMAT" ' !injected && /CustomLog/ { print fmt injected = 1 } { print } ' > "$dst" if grep -q "enriched" "$dst"; then info "Created ${label} template: ${dst}" else cp "$src" "$dst" warn "No 'combined' format found in ${src} — copied unchanged" fi } if [[ -n "$APACHE_TPL_DIR" ]]; then # Apache templates may use a different base than nginx (e.g., bot-blocking # only exists in the nginx layer). Fall back to 'default' if base not found. APACHE_BASE="$BASE_TEMPLATE" if [[ ! -f "${APACHE_TPL_DIR}/${APACHE_BASE}.tpl" ]]; then APACHE_BASE="default" warn "Apache base template '${BASE_TEMPLATE}' not found — falling back to 'default'" fi step "Creating custom Apache templates (${TEMPLATE_NAME}) from ${APACHE_BASE}" create_apache_template \ "${APACHE_TPL_DIR}/${APACHE_BASE}.tpl" \ "${APACHE_TPL_DIR}/${TEMPLATE_NAME}.tpl" \ "Apache HTTP (.tpl)" create_apache_template \ "${APACHE_TPL_DIR}/${APACHE_BASE}.stpl" \ "${APACHE_TPL_DIR}/${TEMPLATE_NAME}.stpl" \ "Apache SSL (.stpl)" fi # --- Nginx template patching --- create_template() { local src="$1" dst="$2" label="$3" if [[ ! -f "$src" ]]; then warn "Source template not found: ${src} — skipping ${label}" return fi if [[ "$DRY_RUN" == "true" ]]; then echo " Would create: ${dst} (from ${src})" return fi backup_file "$dst" # Replace 'combined' with 'enriched' in access_log directives # and inject proxy_set_header lines after the first proxy_pass directive. # If the base template has no proxy_set_header directives, nginx defaults # (Host, Connection) are lost when ANY proxy_set_header is added — the # classic inheritance trap. Include the standard set to prevent 421 errors. local has_proxy_headers has_proxy_headers=$(grep -c 'proxy_set_header' "$src" || true) sed 's/\(access_log.*\.log\) combined;/\1 enriched;/g' "$src" | \ awk -v d='$' -v need_std="$has_proxy_headers" ' !injected && /proxy_pass/ { print if (need_std == 0) { print "\t\tproxy_set_header Host " d "host;" print "\t\tproxy_set_header X-Real-IP " d "remote_addr;" print "\t\tproxy_set_header X-Forwarded-For " d "proxy_add_x_forwarded_for;" print "\t\tproxy_set_header X-Forwarded-Proto " d "scheme;" } print "\t\tproxy_set_header X-GeoIP-Country " d "geoip2_country_code;" print "\t\tproxy_set_header X-GeoIP-ASN " d "geoip2_asn_org;" injected = 1 next } { print } ' > "$dst" # Verify changes if grep -q "enriched" "$dst"; then info "Created ${label} template: ${dst} (log format updated)" else warn "No 'combined' log format found in ${src} — format not changed" warn " Manually change access_log format to 'enriched' in ${dst}" fi if grep -q "proxy_set_header X-GeoIP-Country" "$dst"; then info "Injected GeoIP proxy headers into ${dst}" else warn "No proxy_pass found in ${src} — GeoIP headers not injected" fi } step "Creating custom ${PANEL_NAME} templates (${TEMPLATE_NAME}) from ${BASE_TEMPLATE}" # Proxy templates (nginx as proxy for apache) create_template \ "${PANEL_TPL_DIR}/${BASE_TEMPLATE}.tpl" \ "${PANEL_TPL_DIR}/${TEMPLATE_NAME}.tpl" \ "proxy HTTP (.tpl)" create_template \ "${PANEL_TPL_DIR}/${BASE_TEMPLATE}.stpl" \ "${PANEL_TPL_DIR}/${TEMPLATE_NAME}.stpl" \ "proxy SSL (.stpl)" # php-fpm templates (nginx standalone) if [[ -d "${PANEL_TPL_DIR}/php-fpm" ]]; then if [[ -f "${PANEL_TPL_DIR}/php-fpm/${BASE_TEMPLATE}.tpl" ]]; then create_template \ "${PANEL_TPL_DIR}/php-fpm/${BASE_TEMPLATE}.tpl" \ "${PANEL_TPL_DIR}/php-fpm/${TEMPLATE_NAME}.tpl" \ "php-fpm HTTP (.tpl)" create_template \ "${PANEL_TPL_DIR}/php-fpm/${BASE_TEMPLATE}.stpl" \ "${PANEL_TPL_DIR}/php-fpm/${TEMPLATE_NAME}.stpl" \ "php-fpm SSL (.stpl)" fi fi # Copy .sh hook if base template has one for tpl_dir in "${PANEL_TPL_DIR}" "${PANEL_TPL_DIR}/php-fpm"; do if [[ -f "${tpl_dir}/${BASE_TEMPLATE}.sh" ]] && [[ ! -f "${tpl_dir}/${TEMPLATE_NAME}.sh" ]]; then if [[ "$DRY_RUN" == "true" ]]; then echo " Would copy: ${tpl_dir}/${BASE_TEMPLATE}.sh → ${tpl_dir}/${TEMPLATE_NAME}.sh" else cp "${tpl_dir}/${BASE_TEMPLATE}.sh" "${tpl_dir}/${TEMPLATE_NAME}.sh" info "Copied hook: ${tpl_dir}/${TEMPLATE_NAME}.sh" fi fi done # ===================================================== # Step 7: Create cron job for database updates # ===================================================== if [[ "$USE_DBIP" == "true" ]]; then CRON_FILE="$CRON_MONTHLY" step "Creating monthly DB-IP update script at ${CRON_FILE}" CRON_CONTENT="#!/bin/sh # Monthly DB-IP database update — generated by hestia-geoip-setup.sh MONTH=\$(date +%Y-%m) cd ${GEOIP_DB_DIR} && \\ curl -fsSL \"https://download.db-ip.com/free/dbip-country-lite-\${MONTH}.mmdb.gz\" | gunzip > GeoLite2-City.mmdb && \\ curl -fsSL \"https://download.db-ip.com/free/dbip-asn-lite-\${MONTH}.mmdb.gz\" | gunzip > GeoLite2-ASN.mmdb && \\ logger -t dbip-update \"DB-IP databases updated\"" else CRON_FILE="$CRON_WEEKLY" step "Creating weekly geoipupdate script at ${CRON_FILE}" CRON_CONTENT="#!/bin/sh # Weekly GeoIP2 database update — generated by hestia-geoip-setup.sh /usr/bin/geoipupdate -s 2>&1 | logger -t geoipupdate" fi if [[ "$DRY_RUN" == "true" ]]; then echo " Would create: ${CRON_FILE}" else backup_file "$CRON_FILE" echo "$CRON_CONTENT" > "$CRON_FILE" chmod 755 "$CRON_FILE" info "Created: ${CRON_FILE}" fi # ===================================================== # Step 8: Validate nginx config # ===================================================== step "Testing nginx configuration" if [[ "$DRY_RUN" == "true" ]]; then echo " Would run: nginx -t" else if nginx -t 2>&1; then info "nginx config valid" else echo -e "${RED}[ERROR] nginx config test failed${NC}" >&2 echo " Restore backups (.bak.${TIMESTAMP}) and check templates" >&2 exit 1 fi fi # ===================================================== # Step 9: Apply template (optional) # ===================================================== if [[ -n "$APPLY_USER" ]]; then if ! command -v v-change-web-domain-proxy-tpl &>/dev/null; then echo -e "${RED}Error: v-change-web-domain-proxy-tpl not found${NC}" >&2 exit 1 fi # Apply nginx proxy template if [[ "$APPLY_ALL" == "true" ]]; then step "Applying nginx proxy template to all domains for user: ${APPLY_USER}" domains=$(v-list-web-domains "$APPLY_USER" plain 2>/dev/null | awk '{print $1}') if [[ -z "$domains" ]]; then warn "No domains found for user: ${APPLY_USER}" else while IFS= read -r domain; do if [[ "$DRY_RUN" == "true" ]]; then echo " Would apply: v-change-web-domain-proxy-tpl ${APPLY_USER} ${domain} ${TEMPLATE_NAME}" else v-change-web-domain-proxy-tpl "$APPLY_USER" "$domain" "$TEMPLATE_NAME" info "Nginx proxy template applied to: ${domain}" fi done <<< "$domains" fi else step "Applying nginx proxy template to: ${APPLY_DOMAIN}" if [[ "$DRY_RUN" == "true" ]]; then echo " Would apply: v-change-web-domain-proxy-tpl ${APPLY_USER} ${APPLY_DOMAIN} ${TEMPLATE_NAME}" else v-change-web-domain-proxy-tpl "$APPLY_USER" "$APPLY_DOMAIN" "$TEMPLATE_NAME" info "Nginx proxy template applied to: ${APPLY_DOMAIN}" fi fi # Apply Apache backend template (if Apache templates were created) if [[ -n "$APACHE_TPL_DIR" ]] && [[ -f "${APACHE_TPL_DIR}/${TEMPLATE_NAME}.tpl" ]]; then if command -v v-change-web-domain-tpl &>/dev/null; then if [[ "$APPLY_ALL" == "true" ]]; then step "Applying Apache backend template to all domains for user: ${APPLY_USER}" while IFS= read -r domain; do if [[ "$DRY_RUN" == "true" ]]; then echo " Would apply: v-change-web-domain-tpl ${APPLY_USER} ${domain} ${TEMPLATE_NAME}" else v-change-web-domain-tpl "$APPLY_USER" "$domain" "$TEMPLATE_NAME" info "Apache template applied to: ${domain}" fi done <<< "$domains" else step "Applying Apache backend template to: ${APPLY_DOMAIN}" if [[ "$DRY_RUN" == "true" ]]; then echo " Would apply: v-change-web-domain-tpl ${APPLY_USER} ${APPLY_DOMAIN} ${TEMPLATE_NAME}" else v-change-web-domain-tpl "$APPLY_USER" "$APPLY_DOMAIN" "$TEMPLATE_NAME" info "Apache template applied to: ${APPLY_DOMAIN}" fi fi fi fi fi # ===================================================== # Step 10: Reload nginx and Apache # ===================================================== step "Reloading web servers" if [[ "$DRY_RUN" == "true" ]]; then echo " Would run: systemctl reload nginx" echo " Would run: systemctl reload apache2" else systemctl reload nginx info "nginx reloaded" if systemctl is-active --quiet apache2 2>/dev/null; then systemctl reload apache2 info "Apache reloaded" fi fi # ===================================================== # Summary # ===================================================== echo "" echo -e "${BOLD}Done.${NC}" echo "" echo " GeoIP config: ${NGINX_GEOIP_CONF}" echo " Log format: ${NGINX_LOG_CONF}" echo " Template: ${TEMPLATE_NAME} (.tpl + .stpl)" echo " Base: ${BASE_TEMPLATE}" echo " DB updates: ${CRON_FILE}" if [[ "$USE_DBIP" == "true" ]]; then echo " DB source: DB-IP Lite (no signup)" else echo " DB source: MaxMind GeoLite2" fi if [[ -n "$APPLY_USER" ]]; then if [[ "$APPLY_ALL" == "true" ]]; then echo " Applied: All domains for ${APPLY_USER}" else echo " Applied: ${APPLY_DOMAIN}" fi else echo "" echo " To apply to a domain:" echo " v-change-web-domain-proxy-tpl ${TEMPLATE_NAME}" echo "" echo " To apply to all domains for a user:" echo " sudo $(basename "$0") --apply-all " fi echo "" echo " To remove: sudo $(basename "$0") --remove"