#!/bin/bash # ------------------------------ # Script réutilisable Docker + Nginx + Certbot Cloudflare # Arguments : # $1 = Domaine (ex: exemple.com) # $2 = Port de l'app principale (ex: 3000) # $3 = Email pour SSL (ex: email@example.com) # $4+ = (optionnel) sous-domaines supplémentaires, au format sous-domaine:port # # Exemples : # set_new_nginx_docker_app.sh exemple.com 3000 moi@exemple.com # set_new_nginx_docker_app.sh exemple.com 3000 moi@exemple.com docs:3001 api:3002 # # Variables d'environnement : # MAX_BODY_SIZE taille max du corps des requêtes (défaut: 10m) # ------------------------------ # Un provisionnement qui continue après une erreur laisse un serveur à moitié configuré, # plus difficile à diagnostiquer qu'un arrêt net. set -euo pipefail if [ "$#" -lt 3 ]; then echo "Usage: $0 [sous-domaine:port ...]" exit 1 fi DOMAIN="$1" APP_PORT="$2" EMAIL="$3" shift 3 EXTRA_APPS=("$@") MAX_BODY_SIZE="${MAX_BODY_SIZE:-10m}" CLOUDFLARE_CREDENTIALS="$HOME/.secrets/certbot/cloudflare.ini" echo "⚙️ Configuration pour :" echo "Domaine: $DOMAIN" echo "Port principal: $APP_PORT" echo "Email SSL: $EMAIL" if [ ${#EXTRA_APPS[@]} -gt 0 ]; then echo "Sous-domaines: ${EXTRA_APPS[*]+"${EXTRA_APPS[*]}"}" fi if [ ! -f "$CLOUDFLARE_CREDENTIALS" ]; then echo "❌ Identifiants Cloudflare absents : $CLOUDFLARE_CREDENTIALS" echo " Crée le fichier avec dns_cloudflare_api_token = , puis chmod 600." exit 1 fi # ------------------------------ # Fonction pour installer un paquet si absent # ------------------------------ install_if_missing() { if ! command -v "$1" &> /dev/null; then echo "Installation de $1..." sudo apt install -y "$2" else echo "$1 déjà installé, skipping..." fi } # ------------------------------ # Mise à jour système # ------------------------------ sudo apt update && sudo apt upgrade -y install_if_missing curl curl install_if_missing git git # ------------------------------ # Docker # ------------------------------ # Le script officiel plutôt que docker.io d'apt : celui-ci installe une version souvent ancienne et # le paquet docker-compose d'Ubuntu est la v1 Python, abandonnée, incapable de lire un fichier # compose moderne. get.docker.com pose docker-ce et le plugin compose v2 ensemble. if ! command -v docker &> /dev/null; then echo "Installation de Docker..." curl -fsSL https://get.docker.com | sudo sh # Sans ça, docker n'est utilisable qu'en sudo. Prend effet à la prochaine session. sudo usermod -aG docker "$USER" echo "ℹ️ $USER ajouté au groupe docker — reconnecte-toi pour que ça prenne effet." else echo "Docker déjà installé, skipping..." fi if ! docker compose version &> /dev/null; then echo "Installation du plugin compose v2..." sudo apt install -y docker-compose-plugin fi # ------------------------------ # Nginx # ------------------------------ install_if_missing nginx nginx sudo systemctl enable nginx sudo systemctl start nginx # ------------------------------ # Bloc de proxy partagé par tous les vhosts # ------------------------------ proxy_block() { local port="$1" cat < /dev/null < /dev/null; then sudo apt install -y python3-certbot-dns-cloudflare fi # Wildcard via DNS-01 : aucun port à exposer, et un seul certificat pour tous les sous-domaines. # Attention, un wildcard ne couvre qu'un niveau : *.exemple.com vaut pour a.exemple.com mais pas # pour b.a.exemple.com. if [ ! -d "/etc/letsencrypt/live/$DOMAIN" ]; then sudo certbot certonly \ --dns-cloudflare \ --dns-cloudflare-credentials "$CLOUDFLARE_CREDENTIALS" \ -d "$DOMAIN" -d "*.$DOMAIN" \ -m "$EMAIL" \ --agree-tos \ --non-interactive else echo "Certificat SSL déjà existant pour $DOMAIN, skipping..." fi # ------------------------------ # Nginx en HTTPS # ------------------------------ # Les vhosts explicites viennent avant le wildcard : nginx retient le server_name exact en priorité, # donc chaque sous-domaine listé part vers son propre port et le reste tombe sur l'app principale. { echo "server {" echo " listen 80;" echo " server_name $DOMAIN *.$DOMAIN;" echo " return 301 https://\$host\$request_uri;" echo "}" for app in ${EXTRA_APPS[@]+"${EXTRA_APPS[@]}"}; do sub="${app%%:*}" port="${app##*:}" if [ -z "$sub" ] || [ -z "$port" ] || [ "$sub" = "$app" ]; then echo "❌ Format invalide : '$app' (attendu sous-domaine:port)" >&2 exit 1 fi echo "" echo "server {" echo " listen 443 ssl;" echo " http2 on;" echo " server_name $sub.$DOMAIN;" echo "" echo " ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem;" echo " ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem;" echo "" proxy_block "$port" echo "}" done echo "" echo "server {" echo " listen 443 ssl;" echo " http2 on;" echo " server_name $DOMAIN *.$DOMAIN;" echo "" echo " ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem;" echo " ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem;" echo "" proxy_block "$APP_PORT" echo "}" } | sudo tee "$NGINX_CONF" > /dev/null sudo nginx -t && sudo systemctl reload nginx echo "✅ Installation terminée !" echo " $DOMAIN et *.$DOMAIN → port $APP_PORT" for app in ${EXTRA_APPS[@]+"${EXTRA_APPS[@]}"}; do echo " ${app%%:*}.$DOMAIN → port ${app##*:}" done