← Retour
#!/bin/bash
# ═══════════════════════════════════════════════════════════════════════════
# NGINX.SH - Génération et déploiement automatique de configs Nginx
# ═══════════════════════════════════════════════════════════════════════════
# ───────────────────────────────────────────────────────────────────────────
# Configuration
# ───────────────────────────────────────────────────────────────────────────
NGINX_HOST="${NGINX_HOST:-192.168.1.104}"
NGINX_USER="${NGINX_USER:-gouroubleu}"
NGINX_SITES_AVAILABLE="${NGINX_SITES_AVAILABLE:-/etc/nginx/sites-available}"
NGINX_SITES_ENABLED="${NGINX_SITES_ENABLED:-/etc/nginx/sites-enabled}"
# Mapping des certificats SSL par domaine
declare -A SSL_PATHS=(
["33800.nowhere84.com"]="/etc/letsencrypt/live/33800.nowhere84.com"
["86000.nowhere84.com"]="/etc/letsencrypt/live/86000.nowhere84.com"
["nowhere84.com"]="/etc/letsencrypt/live/nowhere84.com"
)
DEFAULT_SSL_PATH="/etc/letsencrypt/live/33800.nowhere84.com"
# ───────────────────────────────────────────────────────────────────────────
# Trouver le certificat SSL approprié pour un domaine
# ───────────────────────────────────────────────────────────────────────────
find_ssl_path() {
local domain="$1"
for base_domain in "${!SSL_PATHS[@]}"; do
if [[ "$domain" == *"$base_domain" ]]; then
echo "${SSL_PATHS[$base_domain]}"
return
fi
done
echo "$DEFAULT_SSL_PATH"
}
# ───────────────────────────────────────────────────────────────────────────
# Résoudre le domaine complet depuis la config
# ───────────────────────────────────────────────────────────────────────────
resolve_domain() {
local domain="${CFG_nginx_domain:-}"
local name="${CFG_name}"
if [[ -z "$domain" ]]; then
# Pas de domaine configuré
echo ""
return
fi
# Si c'est déjà un FQDN
if [[ "$domain" == *.nowhere84.com ]]; then
echo "$domain"
return
fi
# Sinon, construire le FQDN
# Patterns: "api" -> "api.33800.nowhere84.com"
# "api.33800" -> "api.33800.nowhere84.com"
if [[ "$domain" == *.* ]]; then
echo "${domain}.nowhere84.com"
else
echo "${domain}.33800.nowhere84.com"
fi
}
# ───────────────────────────────────────────────────────────────────────────
# Générer la configuration Nginx
# ───────────────────────────────────────────────────────────────────────────
generate_nginx_config() {
local domain="$1"
local port="${CFG_port:-3000}"
local name="${CFG_name}"
local websocket="${CFG_nginx_websocket:-false}"
local private="${CFG_nginx_private:-true}" # Private par défaut = true
local output_file="${PROJECT_DIR}/.nginx.conf"
# Déterminer le backend
local backend_host="$TARGET_HOST"
[[ "$TARGET_LOCAL" == "true" ]] && backend_host="192.168.1.12"
# Trouver le certificat SSL
local ssl_path=$(find_ssl_path "$domain")
local ssl_cert="${ssl_path}/fullchain.pem"
local ssl_key="${ssl_path}/privkey.pem"
local mode_info="PUBLIC"
[[ "$private" == "true" ]] && mode_info="PRIVÉ (réseau local uniquement)"
log_step "Génération config Nginx pour ${domain} [${mode_info}]..." >&2
cat > "$output_file" << EOF
# ═══════════════════════════════════════════════════════════════════════════
# ${name} - Configuration Nginx
# Auto-generated by smart-deploy.sh
# Date: $(date '+%Y-%m-%d %H:%M:%S')
# ═══════════════════════════════════════════════════════════════════════════
# Redirect HTTP to HTTPS
server {
listen 80;
server_name ${domain};
return 301 https://\$server_name\$request_uri;
}
# HTTPS Server
server {
listen 443 ssl http2;
server_name ${domain};
# SSL Configuration
ssl_certificate ${ssl_cert};
ssl_certificate_key ${ssl_key};
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
EOF
# Ajouter restriction IP si private (utilise le snippet centralisé)
if [[ "$private" == "true" ]]; then
# Lire les chemins publics (bypass) depuis le YAML
local public_paths=()
if [[ -n "${CONFIG_PATH:-}" ]]; then
while IFS= read -r path; do
[[ -n "$path" ]] && public_paths+=("$path")
done < <(parse_yaml_list "$CONFIG_PATH" "nginx" "public_paths" 2>/dev/null)
fi
# Générer les locations publiques AVANT la restriction globale
if [[ ${#public_paths[@]} -gt 0 ]]; then
cat >> "$output_file" << 'EOF'
# ── Chemins publics (bypass restrictions IP) ──
EOF
for pub_path in "${public_paths[@]}"; do
cat >> "$output_file" << EOF
location = ${pub_path} {
# Public: bypass IP restrictions (ex: OAuth callback)
allow all;
proxy_pass http://${backend_host}:${port};
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
client_max_body_size 1M;
}
EOF
log_info " → Chemin public: ${pub_path}" >&2
done
fi
cat >> "$output_file" << 'EOF'
# Restrictions IP - voir /etc/nginx/snippets/allowed-ips.conf
include /etc/nginx/snippets/allowed-ips.conf;
EOF
fi
cat >> "$output_file" << EOF
# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Logging
access_log /var/log/nginx/${name}.access.log;
error_log /var/log/nginx/${name}.error.log;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://${backend_host}:${port};
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
expires 30d;
add_header Cache-Control "public, immutable";
}
# Main location
location / {
proxy_pass http://${backend_host}:${port};
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
EOF
# Ajouter support WebSocket si configuré
if [[ "$websocket" == "true" ]]; then
cat >> "$output_file" << 'EOF'
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
EOF
fi
cat >> "$output_file" << 'EOF'
# Timeouts
proxy_connect_timeout 60s;
EOF
# Only add read/send timeouts if not already set by WebSocket block
if [[ "$websocket" != "true" ]]; then
cat >> "$output_file" << 'EOF'
proxy_read_timeout 60s;
proxy_send_timeout 60s;
EOF
fi
cat >> "$output_file" << 'EOF'
# Body size
client_max_body_size 100M;
}
}
EOF
log_success "Config Nginx générée: ${output_file}" >&2
echo "$output_file"
}
# ───────────────────────────────────────────────────────────────────────────
# Déployer la configuration sur le serveur Nginx
# ───────────────────────────────────────────────────────────────────────────
deploy_nginx_config() {
local config_file="$1"
local domain="$2"
local config_name="${domain}.conf"
log_step "Déploiement config Nginx vers ${NGINX_HOST}..."
# Test connexion SSH
if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "${NGINX_USER}@${NGINX_HOST}" "echo 'OK'" > /dev/null 2>&1; then
log_error "Impossible de se connecter à ${NGINX_HOST}"
return 1
fi
# Backup si le fichier existe
local remote_file="${NGINX_SITES_AVAILABLE}/${config_name}"
if ssh "${NGINX_USER}@${NGINX_HOST}" "[ -f '${remote_file}' ]" 2>/dev/null; then
log_info "Backup de l'ancienne config..."
ssh "${NGINX_USER}@${NGINX_HOST}" "sudo cp '${remote_file}' '${remote_file}.backup.$(date +%Y%m%d%H%M%S)'"
fi
# Copier le fichier
scp -q "$config_file" "${NGINX_USER}@${NGINX_HOST}:/tmp/${config_name}"
ssh "${NGINX_USER}@${NGINX_HOST}" "sudo mv /tmp/${config_name} ${remote_file}"
# Créer le symlink si nécessaire
local symlink="${NGINX_SITES_ENABLED}/${config_name}"
if ! ssh "${NGINX_USER}@${NGINX_HOST}" "[ -L '${symlink}' ]" 2>/dev/null; then
log_info "Création du symlink..."
ssh "${NGINX_USER}@${NGINX_HOST}" "sudo ln -sf '${remote_file}' '${symlink}'"
fi
# Test de la configuration
log_step "Test de la configuration Nginx..."
if ! ssh "${NGINX_USER}@${NGINX_HOST}" "sudo nginx -t" 2>&1; then
log_error "Configuration Nginx invalide!"
return 1
fi
# Reload Nginx
log_step "Rechargement de Nginx..."
ssh "${NGINX_USER}@${NGINX_HOST}" "sudo systemctl reload nginx"
log_success "Config Nginx déployée: https://${domain}"
return 0
}
# ───────────────────────────────────────────────────────────────────────────
# Fonction principale : génère et déploie si domain configuré
# ───────────────────────────────────────────────────────────────────────────
generate_and_deploy_nginx_config() {
# Résoudre le domaine
local domain=$(resolve_domain)
if [[ -z "$domain" ]]; then
log_info "Pas de domaine configuré, skip Nginx"
return 0
fi
# Vérifier si nginx est activé
if [[ "${CFG_nginx_enabled:-true}" == "false" ]]; then
log_info "Nginx désactivé dans la config, skip"
return 0
fi
log_info "Configuration Nginx pour: ${domain}"
# Vérifier si la config existe déjà sur nginx (symlink ou fichier)
local config_name="${domain}.conf"
local remote_file="${NGINX_SITES_ENABLED}/${config_name}"
if ssh -o ConnectTimeout=5 -o BatchMode=yes "${NGINX_USER}@${NGINX_HOST}" "[ -e '${remote_file}' ]" 2>/dev/null; then
log_info "Config Nginx existe déjà: ${config_name}, skip"
return 0
fi
# Générer la config
local config_file=$(generate_nginx_config "$domain")
# Déployer
if deploy_nginx_config "$config_file" "$domain"; then
log_success "Nginx configuré: https://${domain}"
else
log_warning "Échec déploiement Nginx (le service fonctionne peut-être sans)"
fi
}
# ───────────────────────────────────────────────────────────────────────────
# Switch upstream backend (blue-green)
# Met à jour le port dans l'upstream block et reload nginx
# ───────────────────────────────────────────────────────────────────────────
switch_nginx_upstream() {
local service_name="$1"
local new_port="$2"
local domain="$3"
local config_name="${domain}.conf"
log_step "Switch upstream ${service_name} → port ${new_port}..."
# Déterminer le backend host
local backend_host="$TARGET_HOST"
[[ "$TARGET_LOCAL" == "true" ]] && backend_host="192.168.1.12"
# Vérifier que la config existe
if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "${NGINX_USER}@${NGINX_HOST}" \
"[ -f '${NGINX_SITES_AVAILABLE}/${config_name}' ]" 2>/dev/null; then
log_warning "Config Nginx ${config_name} non trouvée, génération complète..."
# Générer la config complète avec upstream
local config_file=$(generate_nginx_config_upstream "$domain" "$new_port")
deploy_nginx_config "$config_file" "$domain"
return $?
fi
# Vérifier si la config utilise déjà un upstream block
local has_upstream
has_upstream=$(ssh "${NGINX_USER}@${NGINX_HOST}" \
"grep -c 'upstream ${service_name}-backend' ${NGINX_SITES_AVAILABLE}/${config_name} 2>/dev/null") || has_upstream=0
if [[ "$has_upstream" -gt 0 ]]; then
# Config avec upstream : juste changer le port dans le server line
log_info "Upstream existant, update port..."
ssh "${NGINX_USER}@${NGINX_HOST}" \
"sudo sed -i 's|server ${backend_host}:[0-9]*;|server ${backend_host}:${new_port};|' \
${NGINX_SITES_AVAILABLE}/${config_name}"
else
# Config sans upstream (ancien format proxy_pass direct) : migrer vers upstream
log_info "Migration vers upstream block..."
local config_file=$(generate_nginx_config_upstream "$domain" "$new_port")
deploy_nginx_config "$config_file" "$domain"
return $?
fi
# Test et reload
log_step "Test et reload Nginx..."
if ! ssh "${NGINX_USER}@${NGINX_HOST}" "sudo nginx -t" 2>&1; then
log_error "Configuration Nginx invalide après switch!"
return 1
fi
ssh "${NGINX_USER}@${NGINX_HOST}" "sudo systemctl reload nginx"
log_success "Nginx switched: ${service_name} → port ${new_port}"
return 0
}
# ───────────────────────────────────────────────────────────────────────────
# Rollback upstream (retour au port précédent)
# ───────────────────────────────────────────────────────────────────────────
rollback_nginx_upstream() {
local service_name="$1"
local old_port="$2"
local domain="$3"
log_step "Rollback Nginx: ${service_name} → port ${old_port}..."
switch_nginx_upstream "$service_name" "$old_port" "$domain"
}
# ───────────────────────────────────────────────────────────────────────────
# Générer config Nginx avec upstream block (pour blue-green)
# ───────────────────────────────────────────────────────────────────────────
generate_nginx_config_upstream() {
local domain="$1"
local port="${2:-${CFG_port:-3000}}"
local name="${CFG_name}"
local websocket="${CFG_nginx_websocket:-false}"
local private="${CFG_nginx_private:-true}"
local output_file="${PROJECT_DIR}/.nginx.conf"
# Déterminer le backend
local backend_host="$TARGET_HOST"
[[ "$TARGET_LOCAL" == "true" ]] && backend_host="192.168.1.12"
# Trouver le certificat SSL
local ssl_path=$(find_ssl_path "$domain")
local ssl_cert="${ssl_path}/fullchain.pem"
local ssl_key="${ssl_path}/privkey.pem"
local mode_info="PUBLIC"
[[ "$private" == "true" ]] && mode_info="PRIVÉ (réseau local uniquement)"
log_step "Génération config Nginx (upstream) pour ${domain} [${mode_info}]..." >&2
cat > "$output_file" << EOF
# ═══════════════════════════════════════════════════════════════════════════
# ${name} - Configuration Nginx (blue-green upstream)
# Auto-generated by smart-deploy.sh
# Date: $(date '+%Y-%m-%d %H:%M:%S')
# ═══════════════════════════════════════════════════════════════════════════
# Upstream backend (port updated by blue-green switch)
upstream ${name}-backend {
server ${backend_host}:${port};
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name ${domain};
return 301 https://\$server_name\$request_uri;
}
# HTTPS Server
server {
listen 443 ssl http2;
server_name ${domain};
# SSL Configuration
ssl_certificate ${ssl_cert};
ssl_certificate_key ${ssl_key};
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
EOF
# Ajouter restriction IP si private
if [[ "$private" == "true" ]]; then
local public_paths=()
if [[ -n "${CONFIG_PATH:-}" ]]; then
while IFS= read -r path; do
[[ -n "$path" ]] && public_paths+=("$path")
done < <(parse_yaml_list "$CONFIG_PATH" "nginx" "public_paths" 2>/dev/null)
fi
if [[ ${#public_paths[@]} -gt 0 ]]; then
cat >> "$output_file" << 'EOF'
# ── Chemins publics (bypass restrictions IP) ──
EOF
for pub_path in "${public_paths[@]}"; do
cat >> "$output_file" << EOF
location = ${pub_path} {
allow all;
proxy_pass http://${name}-backend;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
client_max_body_size 1M;
}
EOF
log_info " → Chemin public: ${pub_path}" >&2
done
fi
cat >> "$output_file" << 'EOF'
# Restrictions IP - voir /etc/nginx/snippets/allowed-ips.conf
include /etc/nginx/snippets/allowed-ips.conf;
EOF
fi
cat >> "$output_file" << EOF
# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Logging
access_log /var/log/nginx/${name}.access.log;
error_log /var/log/nginx/${name}.error.log;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://${name}-backend;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
expires 30d;
add_header Cache-Control "public, immutable";
}
# Main location
location / {
proxy_pass http://${name}-backend;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
EOF
if [[ "$websocket" == "true" ]]; then
cat >> "$output_file" << 'EOF'
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
EOF
fi
cat >> "$output_file" << 'EOF'
# Timeouts
proxy_connect_timeout 60s;
EOF
# Only add read/send timeouts if not already set by WebSocket block
if [[ "$websocket" != "true" ]]; then
cat >> "$output_file" << 'EOF'
proxy_read_timeout 60s;
proxy_send_timeout 60s;
EOF
fi
cat >> "$output_file" << 'EOF'
# Body size
client_max_body_size 100M;
}
}
EOF
log_success "Config Nginx (upstream) générée: ${output_file}" >&2
echo "$output_file"
}
# ───────────────────────────────────────────────────────────────────────────
# Désactiver une config Nginx
# ───────────────────────────────────────────────────────────────────────────
disable_nginx_config() {
local domain="$1"
local config_name="${domain}.conf"
log_step "Désactivation config Nginx: ${domain}..."
ssh "${NGINX_USER}@${NGINX_HOST}" "sudo rm -f '${NGINX_SITES_ENABLED}/${config_name}'"
ssh "${NGINX_USER}@${NGINX_HOST}" "sudo nginx -t && sudo systemctl reload nginx"
log_success "Config Nginx désactivée"
}
# ───────────────────────────────────────────────────────────────────────────
# Lister les configs Nginx actives
# ───────────────────────────────────────────────────────────────────────────
list_nginx_configs() {
log_step "Configs Nginx actives sur ${NGINX_HOST}..."
ssh "${NGINX_USER}@${NGINX_HOST}" "ls -la ${NGINX_SITES_ENABLED}/*.conf 2>/dev/null" | \
awk '{print $NF}' | xargs -I{} basename {} .conf
}