diff --git a/scripts/deploy.sh b/scripts/deploy.sh index d908915..d45a3f6 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -3,123 +3,349 @@ # Imago — Script de déploiement automatisé # ============================================================================= # Usage : -# ./scripts/deploy.sh # Premier déploiement -# ./scripts/deploy.sh --update # Mise à jour (pull + recréation) -# ./scripts/deploy.sh --status # Afficher l'état des conteneurs -# ./scripts/deploy.sh --logs # Logs en temps réel +# ./scripts/deploy.sh # Déploiement interactif +# ./scripts/deploy.sh --update # Mise à jour +# ./scripts/deploy.sh --reconfigure # Reconfigurer le .env +# ./scripts/deploy.sh --status # État des conteneurs +# ./scripts/deploy.sh --logs # Logs en temps réel # -# Prérequis : Docker + Docker Compose installés (voir scripts/setup.sh) +# Prérequis : Docker + Docker Compose (voir scripts/setup.sh) # ============================================================================= set -euo pipefail # ── Couleurs ────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' -BLUE='\033[0;34m'; NC='\033[0m' +BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m' info() { echo -e "${BLUE}[INFO]${NC} $*"; } ok() { echo -e "${GREEN}[OK]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } err() { echo -e "${RED}[ERROR]${NC} $*" >&2; } +ask() { echo -ne "${CYAN}[?]${NC} $* "; } # ── Configuration ───────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" COMPOSE_FILE="${PROJECT_DIR}/docker-compose/docker-compose.prod.yml" ENV_FILE="${PROJECT_DIR}/.env" -ENV_EXAMPLE="${PROJECT_DIR}/docker-compose/.env.example" SERVICE_NAME="imago" -# ── Fonctions ───────────────────────────────────────────────── +# ── Fonctions utilitaires ──────────────────────────────────── + +generate_secret() { python3 -c "import secrets; print(secrets.token_urlsafe(32))" 2>/dev/null || openssl rand -base64 32 2>/dev/null || date +%s | sha256sum | head -c 44; } + +press_enter() { echo ""; read -rp "Appuyez sur Entrée pour continuer..."; } + +# ── 1. Prérequis ───────────────────────────────────────────── check_prerequisites() { info "Vérification des prérequis..." if ! command -v docker &>/dev/null; then - err "Docker n'est pas installé. Lancez : scripts/setup.sh" + err "Docker n'est pas installé. Lancez : sudo ./scripts/setup.sh" exit 1 fi - if ! docker compose version &>/dev/null; then - err "Docker Compose (plugin) n'est pas disponible." + err "Docker Compose (plugin) requis." exit 1 fi - if ! docker info &>/dev/null; then - err "Docker ne tourne pas ou vous n'avez pas les droits." - err "Vérifiez : sudo systemctl start docker && sudo usermod -aG docker $USER" + err "Docker ne tourne pas. Vérifiez : sudo systemctl start docker" exit 1 fi - ok "Docker $(docker --version | awk '{print $3}' | tr -d ',') — Docker Compose disponible" + ok "Docker $(docker --version | awk '{print $3}' | tr -d ',')" } -setup_env() { - if [[ -f "$ENV_FILE" ]]; then - ok "Fichier .env existant" +# ── 2. Assistant de configuration interactif ───────────────── + +configure_env() { + local reconfigure="${1:-false}" + + if [[ -f "$ENV_FILE" ]] && [[ "$reconfigure" != "true" ]]; then + ok "Fichier .env existant — utilisez --reconfigure pour le régénérer" return fi - warn "Fichier .env absent — création depuis le template..." - if [[ ! -f "$ENV_EXAMPLE" ]]; then - err "Template introuvable : $ENV_EXAMPLE" - exit 1 - fi - - cp "$ENV_EXAMPLE" "$ENV_FILE" - warn "╔══════════════════════════════════════════════════════════╗" - warn "║ Fichier .env créé. ÉDITEZ-LE avant de continuer : ║" - warn "║ nano ${ENV_FILE} ║" - warn "║ ║" - warn "║ Variables OBLIGATOIRES à renseigner : ║" - warn "║ SECRET_KEY — clé secrète aléatoire ║" - warn "║ SIGNED_URL_SECRET — clé pour URLs signées ║" - warn "║ OPENROUTER_API_KEY — clé API OpenRouter ║" - warn "║ ADMIN_API_KEY — (optionnel) master key admin ║" - warn "╚══════════════════════════════════════════════════════════╝" - echo "" - read -rp "Avez-vous édité le fichier .env ? (o/N) " confirm - if [[ ! "$confirm" =~ ^[oO]$ ]]; then - err "Déploiement annulé. Éditez .env puis relancez." + echo -e "${BOLD}${BLUE}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${BOLD}${BLUE}║ Imago — Assistant de configuration ║${NC}" + echo -e "${BOLD}${BLUE}╚══════════════════════════════════════════════════════════════╝${NC}" + echo "" + echo -e "Cet assistant va créer le fichier ${BOLD}.env${NC} pour votre déploiement." + echo -e "Les valeurs entre [crochets] sont les défauts (appuyez sur Entrée)." + echo "" + + # ── Génération des secrets ───────────────────────────── + local SECRET_KEY=$(generate_secret) + local SIGNED_URL_SECRET=$(generate_secret) + + # ── Section 1 : Application ──────────────────────────── + echo -e "${BOLD}── Application ──${NC}" + ask "Nom de l'application [Imago]:"; read -r APP_NAME; APP_NAME="${APP_NAME:-Imago}" + ask "Port admin UI [3001]:"; read -r ADMIN_PORT; ADMIN_PORT="${ADMIN_PORT:-3001}" + ask "Port API backend [8001]:"; read -r BACKEND_PORT; BACKEND_PORT="${BACKEND_PORT:-8001}" + echo "" + + # ── Section 2 : Stockage ─────────────────────────────── + echo -e "${BOLD}── Stockage ──${NC}" + echo " Type de stockage :" + echo " [1] Local — fichiers sur le disque du serveur (simple, recommandé)" + echo " [2] S3 — MinIO / AWS S3 / Cloudflare R2 (multi-serveur)" + ask "Choix [1]:"; read -r STORAGE_CHOICE + + local STORAGE_BACKEND="local" + local UPLOAD_DIR="/app/data/uploads" + local THUMBNAILS_DIR="/app/data/thumbnails" + local UPLOAD_VOLUME="" + local THUMBNAIL_VOLUME="" + + if [[ "$STORAGE_CHOICE" == "2" ]]; then + STORAGE_BACKEND="s3" + ask "Bucket S3 [imago]:"; read -r S3_BUCKET; S3_BUCKET="${S3_BUCKET:-imago}" + ask "URL endpoint S3 [http://minio:9000]:"; read -r S3_ENDPOINT_URL; S3_ENDPOINT_URL="${S3_ENDPOINT_URL:-http://minio:9000}" + ask "URL publique S3 [identique]:"; read -r S3_ENDPOINT_PUBLIC; S3_ENDPOINT_PUBLIC="${S3_ENDPOINT_PUBLIC:-$S3_ENDPOINT_URL}" + ask "Access Key [minioadmin]:"; read -r S3_ACCESS_KEY; S3_ACCESS_KEY="${S3_ACCESS_KEY:-minioadmin}" + ask "Secret Key [minioadmin]:"; read -r S3_SECRET_KEY; S3_SECRET_KEY="${S3_SECRET_KEY:-minioadmin}" + ask "Région [us-east-1]:"; read -r S3_REGION; S3_REGION="${S3_REGION:-us-east-1}" + else + ask "Répertoire uploads dans le conteneur [/app/data/uploads]:"; read -r UPLOAD_DIR; UPLOAD_DIR="${UPLOAD_DIR:-/app/data/uploads}" + ask "Répertoire thumbnails dans le conteneur [/app/data/thumbnails]:"; read -r THUMBNAILS_DIR; THUMBNAILS_DIR="${THUMBNAILS_DIR:-/app/data/thumbnails}" + echo "" + echo " Volume de stockage (laisser vide = volume Docker nommé) :" + echo " Exemples : /NFS/IMAGO/uploads ou /data/imago/uploads" + ask "Volume uploads [volume Docker]:"; read -r UPLOAD_VOLUME + ask "Volume thumbnails [volume Docker]:"; read -r THUMBNAIL_VOLUME + fi + + ask "Taille max upload en MB [50]:"; read -r MAX_UPLOAD_SIZE_MB; MAX_UPLOAD_SIZE_MB="${MAX_UPLOAD_SIZE_MB:-50}" + echo "" + + # ── Section 3 : AI ───────────────────────────────────── + echo -e "${BOLD}── Intelligence Artificielle ──${NC}" + echo " Fournisseur AI :" + echo " [1] OpenRouter (recommandé, multi-modèles)" + echo " [2] Google Gemini" + echo " [3] Désactiver l'AI" + ask "Choix [1]:"; read -r AI_CHOICE; AI_CHOICE="${AI_CHOICE:-1}" + + local AI_ENABLED="true" + local AI_PROVIDER="openrouter" + local OPENROUTER_API_KEY="" + local OPENROUTER_MODEL="qwen/qwen2.5-vl-72b-instruct" + local GEMINI_API_KEY="" + local GEMINI_MODEL="gemini-3.1-pro-preview" + + case "$AI_CHOICE" in + 2) + AI_PROVIDER="gemini" + ask "Clé API Gemini:"; read -r GEMINI_API_KEY + ask "Modèle [$GEMINI_MODEL]:"; read -r GEMINI_MODEL; GEMINI_MODEL="${GEMINI_MODEL:-$GEMINI_MODEL}" + ;; + 3) + AI_ENABLED="false" + ok "AI désactivée" + ;; + *) + ask "Clé API OpenRouter:"; read -r OPENROUTER_API_KEY + ask "Modèle [$OPENROUTER_MODEL]:"; read -r OPENROUTER_MODEL; OPENROUTER_MODEL="${OPENROUTER_MODEL:-$OPENROUTER_MODEL}" + ;; + esac + + ask "Langue des descriptions AI [francais]:"; read -r AI_DESCRIPTION_LANGUAGE; AI_DESCRIPTION_LANGUAGE="${AI_DESCRIPTION_LANGUAGE:-francais}" + echo "" + + # ── Section 4 : OCR ──────────────────────────────────── + echo -e "${BOLD}── OCR ──${NC}" + ask "Activer l'OCR ? (O/n):"; read -r OCR_ENABLED + OCR_ENABLED=$( [[ ! "$OCR_ENABLED" =~ ^[nN]$ ]] && echo "true" || echo "false" ) + if [[ "$OCR_ENABLED" == "true" ]]; then + ask "Commande Tesseract [/usr/bin/tesseract]:"; read -r TESSERACT_CMD; TESSERACT_CMD="${TESSERACT_CMD:-/usr/bin/tesseract}" + ask "Langues OCR [fra+eng]:"; read -r OCR_LANGUAGES; OCR_LANGUAGES="${OCR_LANGUAGES:-fra+eng}" + fi + echo "" + + # ── Section 5 : CORS ─────────────────────────────────── + echo -e "${BOLD}── CORS (origines autorisées) ──${NC}" + ask "Origines supplémentaires (ex: https://mon-domaine.com) [aucune]:"; read -r EXTRA_CORS + local CORS_ORIGINS="[\"http://localhost:3000\", \"http://localhost:3001\"" + if [[ -n "$EXTRA_CORS" ]]; then + for origin in $EXTRA_CORS; do + CORS_ORIGINS+=", \"$origin\"" + done + fi + CORS_ORIGINS+="]" + echo "" + + # ── Section 6 : Backup ───────────────────────────────── + echo -e "${BOLD}── Sauvegardes ──${NC}" + ask "Répertoire des backups DB [./backups]:"; read -r BACKUP_DIR; BACKUP_DIR="${BACKUP_DIR:-./backups}" + echo "" + + # ── Récapitulatif ────────────────────────────────────── + echo "" + echo -e "${BOLD}${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${BOLD}${GREEN}║ Récapitulatif ║${NC}" + echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}" + echo -e " Application : ${BOLD}$APP_NAME${NC} (admin:$ADMIN_PORT, api:$BACKEND_PORT)" + echo -e " Stockage : ${BOLD}$STORAGE_BACKEND${NC}" + if [[ "$STORAGE_BACKEND" == "s3" ]]; then + echo -e " Bucket : $S3_BUCKET @ $S3_ENDPOINT_URL" + else + echo -e " Uploads : $UPLOAD_DIR${UPLOAD_VOLUME:+ → $UPLOAD_VOLUME}" + echo -e " Thumbnails: $THUMBNAILS_DIR${THUMBNAIL_VOLUME:+ → $THUMBNAIL_VOLUME}" + fi + echo -e " AI : ${BOLD}${AI_ENABLED}${NC} (${AI_PROVIDER})" + echo -e " OCR : ${BOLD}${OCR_ENABLED}${NC}" + echo -e " Backups : $BACKUP_DIR" + echo "" + + ask "Écrire cette configuration ? (O/n):"; read -r confirm + if [[ "$confirm" =~ ^[nN]$ ]]; then + err "Configuration annulée." exit 1 fi + + # ── Écriture du .env ─────────────────────────────────── + cat > "$ENV_FILE" << EOF +# ═══════════════════════════════════════════════════════════════ +# Imago — Configuration générée le $(date '+%Y-%m-%d %H:%M') +# ═══════════════════════════════════════════════════════════════ + +# ── Application ────────────────────────────────────────────── +APP_NAME=$APP_NAME +DEBUG=false +SECRET_KEY=$SECRET_KEY +ADMIN_PORT=$ADMIN_PORT +BACKEND_PORT=$BACKEND_PORT + +# ── Stockage ───────────────────────────────────────────────── +STORAGE_BACKEND=$STORAGE_BACKEND +UPLOAD_DIR=$UPLOAD_DIR +THUMBNAILS_DIR=$THUMBNAILS_DIR +MAX_UPLOAD_SIZE_MB=$MAX_UPLOAD_SIZE_MB +SIGNED_URL_SECRET=$SIGNED_URL_SECRET +EOF + + if [[ -n "$UPLOAD_VOLUME" ]]; then + echo "UPLOAD_VOLUME=$UPLOAD_VOLUME" >> "$ENV_FILE" + fi + if [[ -n "$THUMBNAIL_VOLUME" ]]; then + echo "THUMBNAIL_VOLUME=$THUMBNAIL_VOLUME" >> "$ENV_FILE" + fi + + if [[ "$STORAGE_BACKEND" == "s3" ]]; then + cat >> "$ENV_FILE" << EOF +S3_BUCKET=$S3_BUCKET +S3_REGION=$S3_REGION +S3_ENDPOINT_URL=$S3_ENDPOINT_URL +S3_ENDPOINT_PUBLIC=$S3_ENDPOINT_PUBLIC +S3_ACCESS_KEY=$S3_ACCESS_KEY +S3_SECRET_KEY=$S3_SECRET_KEY +EOF + fi + + cat >> "$ENV_FILE" << EOF + +# ── AI ─────────────────────────────────────────────────────── +AI_ENABLED=$AI_ENABLED +AI_PROVIDER=$AI_PROVIDER +AI_DESCRIPTION_LANGUAGE=$AI_DESCRIPTION_LANGUAGE +AI_TAGS_MIN=5 +AI_TAGS_MAX=10 +AI_CACHE_DAYS=30 +AI_REQUEST_TIMEOUT=60 +AI_MAX_RETRIES=2 +EOF + + if [[ "$AI_PROVIDER" == "openrouter" ]]; then + echo "OPENROUTER_API_KEY=$OPENROUTER_API_KEY" >> "$ENV_FILE" + echo "OPENROUTER_MODEL=$OPENROUTER_MODEL" >> "$ENV_FILE" + elif [[ "$AI_PROVIDER" == "gemini" ]]; then + echo "GEMINI_API_KEY=$GEMINI_API_KEY" >> "$ENV_FILE" + echo "GEMINI_MODEL=$GEMINI_MODEL" >> "$ENV_FILE" + fi + + cat >> "$ENV_FILE" << EOF + +# ── OCR ────────────────────────────────────────────────────── +OCR_ENABLED=$OCR_ENABLED +TESSERACT_CMD=$TESSERACT_CMD +OCR_LANGUAGES=$OCR_LANGUAGES + +# ── CORS ───────────────────────────────────────────────────── +CORS_ORIGINS=$CORS_ORIGINS + +# ── Authentification ───────────────────────────────────────── +ADMIN_API_KEY= +JWT_SECRET_KEY=$(generate_secret) +JWT_ALGORITHM=HS256 + +# ── Rate Limiting ──────────────────────────────────────────── +RATE_LIMIT_FREE_UPLOAD=20 +RATE_LIMIT_FREE_AI=50 +RATE_LIMIT_STANDARD_UPLOAD=100 +RATE_LIMIT_STANDARD_AI=200 +RATE_LIMIT_PREMIUM_UPLOAD=500 +RATE_LIMIT_PREMIUM_AI=1000 + +# ── Sauvegardes ───────────────────────────────────────────── +BACKUP_DIR=$BACKUP_DIR +EOF + + ok "Fichier .env créé : $ENV_FILE" + press_enter } +# ── 3. Création des répertoires ────────────────────────────── + create_directories() { - info "Création des répertoires..." + info "Préparation des répertoires..." - # Répertoire de backups - local backup_dir="${BACKUP_DIR:-./backups}" - if [[ ! -d "$PROJECT_DIR/$backup_dir" ]]; then - mkdir -p "$PROJECT_DIR/$backup_dir" - ok "Backups : $backup_dir" + # Charger .env s'il existe + if [[ -f "$ENV_FILE" ]]; then + set -a; source "$ENV_FILE"; set +a fi - # Répertoires NFS si configurés + local backup_dir="${BACKUP_DIR:-./backups}" + if [[ ! -d "${PROJECT_DIR}/${backup_dir}" ]]; then + mkdir -p "${PROJECT_DIR}/${backup_dir}" + ok "Backups : ${backup_dir}" + fi + + # Répertoires de stockage si chemins locaux (pas volumes Docker) local upload_vol="${UPLOAD_VOLUME:-}" - if [[ -n "$upload_vol" ]] && [[ ! "$upload_vol" =~ ^[a-zA-Z0-9_-]+$ ]]; then - mkdir -p "$upload_vol" 2>/dev/null || warn "Impossible de créer $upload_vol (NFS ?)" + if [[ -n "$upload_vol" ]] && [[ "$upload_vol" == /* ]]; then + mkdir -p "$upload_vol" 2>/dev/null || warn "Impossible de créer $upload_vol" + fi + local thumb_vol="${THUMBNAIL_VOLUME:-}" + if [[ -n "$thumb_vol" ]] && [[ "$thumb_vol" == /* ]]; then + mkdir -p "$thumb_vol" 2>/dev/null || warn "Impossible de créer $thumb_vol" fi } +# ── 4. Pull images ─────────────────────────────────────────── + pull_images() { info "Récupération des images Docker..." - docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" pull --quiet 2>&1 | tail -3 + docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" pull 2>&1 | grep -E "Pull|Image" || true ok "Images à jour" } +# ── 5. Démarrage ───────────────────────────────────────────── + start_services() { info "Démarrage des services..." docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" up -d --remove-orphans - info "Attente de la santé des services..." - local max_wait=60 + info "Attente de la santé du backend..." + local max_wait=90 local waited=0 while [[ $waited -lt $max_wait ]]; do - local backend_ok=$(docker compose -f "$COMPOSE_FILE" ps backend --format json 2>/dev/null | grep -c '"Health":"healthy"' || echo 0) - if [[ "$backend_ok" -ge 1 ]]; then + local health=$(docker compose -f "$COMPOSE_FILE" ps backend --format '{{.Health}}' 2>/dev/null || echo "") + if [[ "$health" == "healthy" ]]; then ok "Backend healthy après ${waited}s" - break + return fi sleep 3 waited=$((waited + 3)) @@ -128,38 +354,58 @@ start_services() { echo "" if [[ $waited -ge $max_wait ]]; then - warn "Le backend tarde à démarrer (>${max_wait}s). Vérifiez les logs :" - warn " docker compose -f $COMPOSE_FILE logs backend" + warn "Le backend tarde à démarrer (>${max_wait}s)." + warn "Logs : docker compose -f $COMPOSE_FILE logs backend" + warn "Si c'est un premier déploiement, cela peut être normal (init BDD)." fi } +# ── 6. Affichage final ─────────────────────────────────────── + show_bootstrap_key() { echo "" - info "Clé API bootstrap (admin) — si c'est un premier déploiement :" - docker compose -f "$COMPOSE_FILE" logs backend 2>/dev/null | \ - grep "API_KEY:" | tail -1 | \ - sed 's/.*API_KEY: / /' | sed 's/".*//' || true + info "Clé API bootstrap (admin) — conservez-la précieusement :" + local key=$(docker compose -f "$COMPOSE_FILE" logs backend 2>/dev/null | grep "API_KEY:" | tail -1 | sed 's/.*API_KEY: //;s/".*//' || echo "") + if [[ -n "$key" ]]; then + echo -e " ${BOLD}${GREEN}${key}${NC}" + else + echo " (aucune clé trouvée — vérifiez les logs si premier déploiement)" + fi } show_status() { echo "" info "État des services :" - docker compose -f "$COMPOSE_FILE" ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" + docker compose -f "$COMPOSE_FILE" ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || true } show_urls() { - echo "" - ok "═══ Imago est prêt ═══" + local ip=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "localhost") + + # Charger les ports depuis .env + if [[ -f "$ENV_FILE" ]]; then + set -a; source "$ENV_FILE"; set +a + fi local admin_port="${ADMIN_PORT:-3001}" local backend_port="${BACKEND_PORT:-8001}" - echo " Admin UI : http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo 'localhost'):${admin_port}" - echo " API Docs : http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo 'localhost'):${backend_port}/docs" - echo " Health : http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo 'localhost'):${backend_port}/health" + + echo "" + echo -e "${BOLD}${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${BOLD}${GREEN}║ Imago est prêt ! ║${NC}" + echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}" + echo "" + echo -e " ${BOLD}Admin UI :${NC} http://${ip}:${admin_port}" + echo -e " ${BOLD}API Docs :${NC} http://${ip}:${backend_port}/docs" + echo -e " ${BOLD}Health :${NC} http://${ip}:${backend_port}/health" + echo "" } +# ── Mise à jour ────────────────────────────────────────────── + restart_updated() { - info "Mise à jour : pull + recréation des services modifiés..." - docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" pull --quiet + source "$ENV_FILE" 2>/dev/null || true + info "Mise à jour : pull + recréation..." + docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" pull 2>&1 | grep -E "Pull|Image|up to date" || true docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" up -d --remove-orphans ok "Services mis à jour" } @@ -171,29 +417,38 @@ cd "$PROJECT_DIR" case "${1:-}" in --update|-u) check_prerequisites - pull_images restart_updated show_status show_urls ;; + --reconfigure|--config) + check_prerequisites + configure_env "true" + create_directories + restart_updated + show_status + show_bootstrap_key + show_urls + ;; --status|-s) show_status ;; --logs|-l) - docker compose -f "$COMPOSE_FILE" logs -f --tail=50 + docker compose -f "$COMPOSE_FILE" logs -f --tail=100 ;; --help|-h) - echo "Usage: $0 [--update|--status|--logs|--help]" + echo "Usage: $0 [OPTION]" echo "" - echo " (sans argument) Premier déploiement complet" - echo " --update, -u Mise à jour des containers" - echo " --status, -s Afficher l'état" - echo " --logs, -l Logs en temps réel" - echo " --help, -h Cette aide" + echo " (sans argument) Premier déploiement (assistant interactif)" + echo " --update, -u Mise à jour des conteneurs" + echo " --reconfigure Reconfigurer le fichier .env" + echo " --status, -s Afficher l'état des services" + echo " --logs, -l Logs en temps réel" + echo " --help, -h Cette aide" ;; *) check_prerequisites - setup_env + configure_env "false" create_directories pull_images start_services