From a626ab49815204452b118e2cb0a59037c3bae05e Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Tue, 23 Jun 2026 22:17:26 -0400 Subject: [PATCH] refactor: structure docker-compose/ + scripts deploy.sh et setup.sh --- .env.example | 93 +------- docker-compose.yml | 84 -------- docker-compose/.env.example | 66 ++++++ .../docker-compose.prod.yml | 45 ++-- docker-compose/docker-compose.yml | 117 ++++++++++ scripts/deploy.sh | 204 ++++++++++++++++++ scripts/setup.sh | 153 +++++++++++++ 7 files changed, 565 insertions(+), 197 deletions(-) delete mode 100644 docker-compose.yml create mode 100644 docker-compose/.env.example rename docker-compose.production.yml => docker-compose/docker-compose.prod.yml (75%) create mode 100644 docker-compose/docker-compose.yml create mode 100644 scripts/deploy.sh create mode 100644 scripts/setup.sh diff --git a/.env.example b/.env.example index 040af65..f1d33ea 100644 --- a/.env.example +++ b/.env.example @@ -1,86 +1,7 @@ -# ═══════════════════════════════════════════════════════════════ -# Imago — Configuration -# ═══════════════════════════════════════════════════════════════ -# Copiez ce fichier en .env et ajustez les valeurs. -# cp .env.example .env - -# ── Application ────────────────────────────────────────────── -APP_NAME=Imago -APP_VERSION=2.0.0 -DEBUG=false -SECRET_KEY=changez-moi-par-une-valeur-longue-et-aleatoire - -# ── Serveur (pour dev seulement) ────────────────────────────── -# HOST=0.0.0.0 -# PORT=8000 - -# ── Base de données ────────────────────────────────────────── -# SQLite (dev): sqlite+aiosqlite:///./data/imago.db -PostgreSQL: postgresql+asyncpg://user:pass@host:5432/imago -# DATABASE_URL=sqlite+aiosqlite:///./data/imago.db - -# ── Stockage ───────────────────────────────────────────────── -# local (fichiers sur disque) ou s3 (MinIO / AWS S3 / Cloudflare R2) -STORAGE_BACKEND=local -UPLOAD_DIR=/app/data/uploads -THUMBNAILS_DIR=/app/data/thumbnails -MAX_UPLOAD_SIZE_MB=50 -SIGNED_URL_SECRET=changez-moi-en-prod - -# ── Stockage S3 (si STORAGE_BACKEND=s3) ────────────────────── -S3_BUCKET=imago -S3_PREFIX= -S3_REGION=us-east-1 -S3_ENDPOINT_URL=http://localhost:9000 -S3_ENDPOINT_PUBLIC=http://localhost:9000 -S3_ACCESS_KEY=minioadmin -S3_SECRET_KEY=minioadmin -SIGNED_URL_SECRET=changez-moi-en-prod - -# ── AI ─────────────────────────────────────────────────────── -AI_ENABLED=true -AI_PROVIDER=openrouter - -# Google Gemini -GEMINI_API_KEY= -GEMINI_MODEL=gemini-3.1-pro-preview -GEMINI_MAX_TOKENS=1024 - -# OpenRouter -OPENROUTER_API_KEY= -OPENROUTER_MODEL=qwen/qwen2.5-vl-72b-instruct - -# AI Comportement -AI_TAGS_MIN=5 -AI_TAGS_MAX=10 -AI_DESCRIPTION_LANGUAGE=francais -AI_CACHE_DAYS=30 -AI_REQUEST_TIMEOUT=60 -AI_MAX_RETRIES=2 - -# ── OCR ────────────────────────────────────────────────────── -OCR_ENABLED=true -TESSERACT_CMD=/usr/bin/tesseract -OCR_LANGUAGES=fra+eng - -# ── CORS ───────────────────────────────────────────────────── -# Format JSON : ["http://localhost:3000", "http://localhost:5173"] -CORS_ORIGINS=["http://localhost:3000", "http://localhost:5173"] - -# ── Authentification ───────────────────────────────────────── -ADMIN_API_KEY= -JWT_SECRET_KEY=changez-moi-jwt-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 -# Redis pour persistence des compteurs (optionnel, mémoire par défaut) -# Format: redis://host:***@RATE_LIMIT_STORAGE_URL= - -# ── Redis + ARQ Worker ─────────────────────────────────────── -REDIS_URL=redis://localhost:***@PIPELINE_TIMEOUT=300 +# Ce fichier est déprécié. +# Le template de configuration se trouve désormais dans : +# docker-compose/.env.example +# +# Pour déployer : +# 1. sudo ./scripts/setup.sh (une seule fois, prépare le serveur) +# 2. ./scripts/deploy.sh (déploiement ou mise à jour) diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 34eaae3..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,84 +0,0 @@ -version: '3.9' - -services: - postgres: - image: postgres:16-alpine - environment: - POSTGRES_USER: imago - POSTGRES_PASSWORD: imago - POSTGRES_DB: imago - ports: - - '5432:5432' - volumes: - - pgdata:/var/lib/postgresql/data - healthcheck: - test: ['CMD-SHELL', 'pg_isready -U imago'] - interval: 5s - timeout: 5s - retries: 5 - - redis: - image: redis:7-alpine - ports: - - '6379:6379' - volumes: - - redisdata:/data - healthcheck: - test: ['CMD', 'redis-cli', 'ping'] - interval: 5s - timeout: 5s - retries: 5 - - minio: - image: minio/minio:latest - command: server /data --console-address ':9001' - environment: - MINIO_ROOT_USER: minioadmin - MINIO_ROOT_PASSWORD: minioadmin - ports: - - '9000:9000' - - '9001:9001' - volumes: - - miniodata:/data - healthcheck: - test: ['CMD', 'mc', 'ready', 'local'] - interval: 5s - timeout: 5s - retries: 5 - - api: - build: . - ports: - - '8000:8000' - environment: - - DATABASE_URL=postgresql+asyncpg://imago:imago@postgres:5432/imago - - REDIS_URL=redis://redis:6379/0 - - STORAGE_BACKEND=s3 - - S3_BUCKET=imago - - S3_ENDPOINT_URL=http://minio:9000 - - S3_ENDPOINT_PUBLIC=http://localhost:9000 - - S3_ACCESS_KEY=minioadmin - - S3_SECRET_KEY=minioadmin - - S3_REGION=us-east-1 - - SIGNED_URL_SECRET=dev-secret-change-in-prod - - SECRET_KEY=dev-secret-change-in-prod - - TESSERACT_CMD=/usr/bin/tesseract - - GEMINI_API_KEY=${GEMINI_API_KEY:-} - - OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-} - depends_on: - postgres: - condition: service_healthy - redis: - condition: service_healthy - minio: - condition: service_healthy - volumes: - - uploads:/app/data/uploads - - thumbnails:/app/data/thumbnails - -volumes: - pgdata: - redisdata: - miniodata: - uploads: - thumbnails: diff --git a/docker-compose/.env.example b/docker-compose/.env.example new file mode 100644 index 0000000..7cecd6d --- /dev/null +++ b/docker-compose/.env.example @@ -0,0 +1,66 @@ +# ═══════════════════════════════════════════════════════════════ +# Imago — Configuration de production +# ═══════════════════════════════════════════════════════════════ +# Copiez ce fichier en .env dans le répertoire racine du projet : +# cp docker-compose/.env.example .env +# +# Puis éditez .env avec vos valeurs. + +# ── Application ────────────────────────────────────────────── +APP_NAME=Imago +DEBUG=false +SECRET_KEY=changez-moi-par-une-valeur-longue-et-aleatoire + +# ── Ports (exposés sur l'hôte) ─────────────────────────────── +ADMIN_PORT=3001 +BACKEND_PORT=8001 + +# ── Stockage ───────────────────────────────────────────────── +# "local" = fichiers sur disque +# "s3" = MinIO / AWS S3 / Cloudflare R2 +STORAGE_BACKEND=local +UPLOAD_DIR=/app/data/uploads +THUMBNAILS_DIR=/app/data/thumbnails +MAX_UPLOAD_SIZE_MB=50 + +# Volume de stockage (défaut: volume Docker nommé "uploads"/"thumbnails") +# Pour NFS : UPLOAD_VOLUME=/NFS/IMAGO/uploads +# UPLOAD_VOLUME= +# THUMBNAIL_VOLUME= + +# URLs signées pour le stockage local +SIGNED_URL_SECRET=changez-moi-en-prod + +# ── Sauvegardes DB ─────────────────────────────────────────── +# BACKUP_DIR=./backups + +# ── AI ─────────────────────────────────────────────────────── +AI_ENABLED=true +AI_PROVIDER=openrouter +OPENROUTER_API_KEY=sk-or-v1-votre-cle +OPENROUTER_MODEL=qwen/qwen2.5-vl-72b-instruct + +# ── OCR ────────────────────────────────────────────────────── +OCR_ENABLED=true +TESSERACT_CMD=/usr/bin/tesseract +OCR_LANGUAGES=fra+eng + +# ── CORS ───────────────────────────────────────────────────── +# Format JSON : ["http://localhost:3000", "https://votre-domaine.com"] +CORS_ORIGINS=["http://localhost:3000", "http://localhost:3001"] + +# ── Authentification ───────────────────────────────────────── +ADMIN_API_KEY= +JWT_SECRET_KEY=changez-moi-jwt-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 + +# ── Redis (interne Docker — ne pas changer sauf besoin spécifique) +# REDIS_URL=redis://redis:***@REDIS_URL=redis://redis:***@... \ No newline at end of file diff --git a/docker-compose.production.yml b/docker-compose/docker-compose.prod.yml similarity index 75% rename from docker-compose.production.yml rename to docker-compose/docker-compose.prod.yml index 69852a3..591e59f 100644 --- a/docker-compose.production.yml +++ b/docker-compose/docker-compose.prod.yml @@ -1,10 +1,12 @@ -# Imago — Docker Compose Production +# ============================================================================= +# Imago — Production (images pré-buildées, stockage local) +# ============================================================================= # Usage: # 1. Copier .env.example → .env et remplir les secrets -# 2. docker compose -f docker-compose.production.yml up -d +# 2. docker compose -f docker-compose/docker-compose.prod.yml up -d # # Docker Compose lit automatiquement le fichier .env du répertoire courant. -# Les variables d'environnement sont référencées avec ${VAR} ou ${VAR:-défaut}. +# ============================================================================= services: # ── Admin Panel (React + Nginx) ─────────────────────────── @@ -19,23 +21,22 @@ services: # ── API Backend ──────────────────────────────────────────── backend: image: docker-registry.dev.home:5000/imago-backend:latest - user: "1000:4000" ports: - "${BACKEND_PORT:-8001}:8000" - env_file: .env + env_file: + - .env environment: - # Connexions internes Docker (hostnames fixes) DATABASE_URL: postgresql+asyncpg://imago:imago@db:5432/imago + REDIS_URL: redis://redis:***@db:5432/imago REDIS_URL: redis://redis:6379/0 - # Variables de l'application (lues depuis .env, défaut local) STORAGE_BACKEND: ${STORAGE_BACKEND:-local} UPLOAD_DIR: ${UPLOAD_DIR:-/app/data/uploads} THUMBNAILS_DIR: ${THUMBNAILS_DIR:-/app/data/thumbnails} MAX_UPLOAD_SIZE_MB: ${MAX_UPLOAD_SIZE_MB:-50} SIGNED_URL_SECRET: ${SIGNED_URL_SECRET:-changez-moi-en-prod} volumes: - - /NFS/IMAGO/uploads:/app/data/uploads - - /NFS/IMAGO/thumbnails:/app/data/thumbnails + - ${UPLOAD_VOLUME:-uploads}:/app/data/uploads + - ${THUMBNAIL_VOLUME:-thumbnails}:/app/data/thumbnails depends_on: db: condition: service_healthy @@ -46,11 +47,12 @@ services: # ── ARQ Worker ──────────────────────────────────────────── worker: image: docker-registry.dev.home:5000/imago-backend:latest - user: "1000:4000" command: python worker.py - env_file: .env + env_file: + - .env environment: DATABASE_URL: postgresql+asyncpg://imago:imago@db:5432/imago + REDIS_URL: redis://redis:***@db:5432/imago REDIS_URL: redis://redis:6379/0 STORAGE_BACKEND: ${STORAGE_BACKEND:-local} UPLOAD_DIR: ${UPLOAD_DIR:-/app/data/uploads} @@ -58,8 +60,8 @@ services: MAX_UPLOAD_SIZE_MB: ${MAX_UPLOAD_SIZE_MB:-50} SIGNED_URL_SECRET: ${SIGNED_URL_SECRET:-changez-moi-en-prod} volumes: - - /NFS/IMAGO/uploads:/app/data/uploads - - /NFS/IMAGO/thumbnails:/app/data/thumbnails + - ${UPLOAD_VOLUME:-uploads}:/app/data/uploads + - ${THUMBNAIL_VOLUME:-thumbnails}:/app/data/thumbnails depends_on: db: condition: service_healthy @@ -70,14 +72,13 @@ services: # ── Base de données ─────────────────────────────────────── db: image: postgres:16-alpine - user: "1000:4000" environment: POSTGRES_USER: imago POSTGRES_PASSWORD: imago POSTGRES_DB: imago volumes: - pgdata:/var/lib/postgresql/data - - /NFS/IMAGO/DB/backups:/backups + - ${BACKUP_DIR:-./backups}:/backups healthcheck: test: ["CMD-SHELL", "pg_isready -U imago -d imago"] interval: 5s @@ -98,18 +99,8 @@ services: retries: 3 restart: unless-stopped - # ── MinIO (optionnel — utilisé seulement si STORAGE_BACKEND=s3) - # minio: - # image: minio/minio - # command: server /data --console-address ':9001' - # environment: - # MINIO_ROOT_USER: minioadmin - # MINIO_ROOT_PASSWORD: minioadmin - # volumes: - # - miniodata:/data - # restart: unless-stopped - volumes: pgdata: redisdata: - miniodata: + uploads: + thumbnails: diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml new file mode 100644 index 0000000..26d80ad --- /dev/null +++ b/docker-compose/docker-compose.yml @@ -0,0 +1,117 @@ +# ============================================================================= +# Imago — Développement local (build from source, stack complet) +# ============================================================================= +# Usage: +# docker compose -f docker-compose/docker-compose.yml up -d +# +# Ce compose build les images depuis le code source et démarre tous les +# services nécessaires : PostgreSQL, Redis, MinIO (S3), API, Worker. +# ============================================================================= + +services: + # ── Base de données ─────────────────────────────────────── + db: + image: postgres:16-alpine + environment: + POSTGRES_USER: imago + POSTGRES_PASSWORD: imago + POSTGRES_DB: imago + ports: + - "5432:5432" + volumes: + - pgdata_dev:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U imago -d imago"] + interval: 5s + timeout: 5s + retries: 5 + + # ── Redis ───────────────────────────────────────────────── + redis: + image: redis:7-alpine + ports: + - "6379:6379" + volumes: + - redisdata_dev:/data + command: redis-server --appendonly yes + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 5 + + # ── MinIO (stockage S3 local) ───────────────────────────── + minio: + image: minio/minio + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + ports: + - "9000:9000" + - "9001:9001" + volumes: + - miniodata_dev:/data + + # ── API Backend ─────────────────────────────────────────── + backend: + build: + context: .. + dockerfile: Dockerfile + ports: + - "8000:8000" + env_file: + - ../.env + environment: + DATABASE_URL: postgresql+asyncpg://imago:imago@db:5432/imago + REDIS_URL: redis://redis:6379/0 + STORAGE_BACKEND: s3 + S3_BUCKET: imago + S3_ENDPOINT_URL: http://minio:9000 + S3_ENDPOINT_PUBLIC: http://localhost:9000 + S3_ACCESS_KEY: minioadmin + S3_SECRET_KEY: minioadmin + S3_REGION: us-east-1 + volumes: + - uploads_dev:/app/data/uploads + - thumbnails_dev:/app/data/thumbnails + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + restart: unless-stopped + + # ── ARQ Worker ──────────────────────────────────────────── + worker: + build: + context: .. + dockerfile: Dockerfile + command: python worker.py + env_file: + - ../.env + environment: + DATABASE_URL: postgresql+asyncpg://imago:imago@db:5432/imago + REDIS_URL: redis://redis:6379/0 + STORAGE_BACKEND: s3 + S3_BUCKET: imago + S3_ENDPOINT_URL: http://minio:9000 + S3_ACCESS_KEY: minioadmin + S3_SECRET_KEY: minioadmin + S3_REGION: us-east-1 + volumes: + - uploads_dev:/app/data/uploads + - thumbnails_dev:/app/data/thumbnails + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + restart: unless-stopped + +volumes: + pgdata_dev: + redisdata_dev: + miniodata_dev: + uploads_dev: + thumbnails_dev: diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..d908915 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,204 @@ +#!/usr/bin/env bash +# ============================================================================= +# 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 +# +# Prérequis : Docker + Docker Compose installés (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' +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; } + +# ── 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 ───────────────────────────────────────────────── + +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" + exit 1 + fi + + if ! docker compose version &>/dev/null; then + err "Docker Compose (plugin) n'est pas disponible." + 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" + exit 1 + fi + + ok "Docker $(docker --version | awk '{print $3}' | tr -d ',') — Docker Compose disponible" +} + +setup_env() { + if [[ -f "$ENV_FILE" ]]; then + ok "Fichier .env existant" + 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." + exit 1 + fi +} + +create_directories() { + info "Création 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" + fi + + # Répertoires NFS si configurés + 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 ?)" + fi +} + +pull_images() { + info "Récupération des images Docker..." + docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" pull --quiet 2>&1 | tail -3 + ok "Images à jour" +} + +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 + 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 + ok "Backend healthy après ${waited}s" + break + fi + sleep 3 + waited=$((waited + 3)) + echo -n "." + done + 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" + fi +} + +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 +} + +show_status() { + echo "" + info "État des services :" + docker compose -f "$COMPOSE_FILE" ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" +} + +show_urls() { + echo "" + ok "═══ Imago est prêt ═══" + 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" +} + +restart_updated() { + info "Mise à jour : pull + recréation des services modifiés..." + docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" pull --quiet + docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" up -d --remove-orphans + ok "Services mis à jour" +} + +# ── Point d'entrée ─────────────────────────────────────────── + +cd "$PROJECT_DIR" + +case "${1:-}" in + --update|-u) + check_prerequisites + pull_images + restart_updated + show_status + show_urls + ;; + --status|-s) + show_status + ;; + --logs|-l) + docker compose -f "$COMPOSE_FILE" logs -f --tail=50 + ;; + --help|-h) + echo "Usage: $0 [--update|--status|--logs|--help]" + 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" + ;; + *) + check_prerequisites + setup_env + create_directories + pull_images + start_services + show_bootstrap_key + show_status + show_urls + ;; +esac diff --git a/scripts/setup.sh b/scripts/setup.sh new file mode 100644 index 0000000..b289e1e --- /dev/null +++ b/scripts/setup.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env bash +# ============================================================================= +# Imago — Script de préparation du serveur (one-time setup) +# ============================================================================= +# Usage : +# sudo ./scripts/setup.sh +# +# Ce script : +# 1. Installe Docker + Docker Compose si absents +# 2. Configure les droits utilisateur (groupe docker) +# 3. Vérifie les prérequis système +# 4. Crée les répertoires nécessaires +# ============================================================================= + +set -euo pipefail + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' +BLUE='\033[0;34m'; 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; } + +# ── Vérification root ──────────────────────────────────────── +if [[ $EUID -ne 0 ]]; then + err "Ce script doit être lancé en root : sudo ./scripts/setup.sh" + exit 1 +fi + +# Récupérer l'utilisateur réel (même si lancé via sudo) +REAL_USER="${SUDO_USER:-$USER}" +REAL_HOME=$(eval echo "~$REAL_USER") + +echo "" +echo "═══════════════════════════════════════════════════════════" +echo " Imago — Préparation du serveur" +echo " Utilisateur : $REAL_USER" +echo "═══════════════════════════════════════════════════════════" +echo "" + +# ── 1. Installer Docker ───────────────────────────────────── +install_docker() { + if command -v docker &>/dev/null; then + ok "Docker déjà installé : $(docker --version)" + return + fi + + info "Installation de Docker..." + + if command -v apt-get &>/dev/null; then + # Debian / Ubuntu + apt-get update -qq + apt-get install -y -qq ca-certificates curl + install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc + chmod a+r /etc/apt/keyrings/docker.asc + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" > /etc/apt/sources.list.d/docker.list + apt-get update -qq + apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + ok "Docker installé via apt" + + elif command -v dnf &>/dev/null; then + # Fedora / RHEL + dnf -y install dnf-plugins-core + dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo + dnf -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + ok "Docker installé via dnf" + + else + err "Distribution non supportée. Installez Docker manuellement :" + err " https://docs.docker.com/engine/install/" + exit 1 + fi + + systemctl enable docker --now +} + +# ── 2. Ajouter l'utilisateur au groupe docker ─────────────── +configure_user() { + if groups "$REAL_USER" | grep -q docker; then + ok "Utilisateur $REAL_USER déjà dans le groupe docker" + return + fi + + info "Ajout de $REAL_USER au groupe docker..." + usermod -aG docker "$REAL_USER" + ok "Utilisateur $REAL_USER ajouté au groupe docker" + warn "╔══════════════════════════════════════════════════════════╗" + warn "║ Déconnectez-vous puis reconnectez-vous pour que les ║" + warn "║ changements de groupe prennent effet. ║" + warn "║ Ou lancez : newgrp docker ║" + warn "╚══════════════════════════════════════════════════════════╝" +} + +# ── 3. Vérifier le système ────────────────────────────────── +check_system() { + info "Vérification système..." + + # Espace disque + local disk_avail=$(df -BG /var/lib/docker 2>/dev/null | awk 'NR==2 {print $4}' | tr -d 'G' || df -BG / | awk 'NR==2 {print $4}' | tr -d 'G') + if [[ ${disk_avail:-0} -lt 10 ]]; then + warn "Espace disque faible : ${disk_avail}G disponibles. 10G+ recommandé." + else + ok "Espace disque : ${disk_avail}G disponibles" + fi + + # RAM + local mem_total=$(free -m | awk '/^Mem:/{print $2}') + if [[ ${mem_total:-0} -lt 2048 ]]; then + warn "RAM faible : ${mem_total}MB. 2GB+ recommandé." + else + ok "RAM : ${mem_total}MB" + fi + + # Docker fonctionnel + if docker info &>/dev/null; then + ok "Docker opérationnel" + else + err "Docker ne répond pas. Vérifiez : systemctl start docker" + fi +} + +# ── 4. Créer le répertoire de travail ──────────────────────── +create_workdir() { + local workdir="${1:-/opt/imago}" + if [[ -d "$workdir" ]]; then + ok "Répertoire de travail existant : $workdir" + return + fi + + info "Création du répertoire de travail : $workdir" + mkdir -p "$workdir" + chown "$REAL_USER:$REAL_USER" "$workdir" + ok "Répertoire créé : $workdir" +} + +# ── Exécution ──────────────────────────────────────────────── + +install_docker +configure_user +check_system +create_workdir "/opt/imago" + +echo "" +ok "═══ Serveur prêt pour Imago ═══" +echo "" +echo " Prochaines étapes :" +echo " 1. Déconnectez/reconnectez-vous (groupe docker)" +echo " 2. Clonez le dépôt :" +echo " git clone /opt/imago" +echo " 3. Déployez :" +echo " cd /opt/imago && ./scripts/deploy.sh" +echo ""