#!/bin/bash # ============================================================================ # install.sh — Déploiement infrastructure Seize from scratch # ============================================================================ # Usage : ./install.sh [étape] # Sans argument : affiche le menu # Avec étape : lance directement cette étape # # Étapes : # 1) check - Vérifications pré-install (DNS, Docker, etc.) # 2) networks - Créer les réseaux Docker # 3) traefik - Déployer Traefik # 4) zitadel - Déployer Zitadel # 5) postgres - Déployer Postgres principal # 6) gitea - Déployer Gitea # 7) intranet - Déployer l'intranet # 8) oauth2-proxy - Déployer un OAuth2-Proxy (interactif) # 9) apps - Déployer les autres apps (n8n, portainer, etc.) # all - Tout déployer dans l'ordre (interactif) # # Variables : voir .env (à créer depuis .env.example) # ============================================================================ set -e # ===== COULEURS ===== RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # ===== CHARGEMENT .env ===== SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" if [ -f "$SCRIPT_DIR/.env" ]; then set -a source "$SCRIPT_DIR/.env" set +a else echo -e "${RED}❌ Fichier .env manquant. Copier .env.example vers .env et adapter.${NC}" exit 1 fi # ===== FONCTIONS UTILITAIRES ===== log() { echo -e "${BLUE}[$(date +%H:%M:%S)]${NC} $1"; } ok() { echo -e "${GREEN}✅ $1${NC}"; } warn(){ echo -e "${YELLOW}⚠️ $1${NC}"; } err() { echo -e "${RED}❌ $1${NC}"; } confirm() { read -p "$1 (y/N) " -n 1 -r echo [[ $REPLY =~ ^[Yy]$ ]] } # ===== ÉTAPES ===== step_check() { log "===== ÉTAPE 1 : Vérifications pré-install =====" # Vérifier l'OS if [ -f /etc/os-release ]; then . /etc/os-release log "OS : $PRETTY_NAME" fi # Vérifier Docker if ! command -v docker &> /dev/null; then err "Docker n'est pas installé. Voir INSTALL.md étape 2." return 1 fi ok "Docker installé : $(docker --version)" # Vérifier docker compose if ! docker compose version &> /dev/null; then err "Docker Compose v2 n'est pas installé." return 1 fi ok "Docker Compose installé : $(docker compose version | head -1)" # Vérifier DNS pour chaque sous-domaine log "Vérification DNS..." for sub in sso intranet git n8n portainer code status traefik; do DNS_IP=$(dig +short "$sub.$DOMAIN" 2>/dev/null | head -1) if [ "$DNS_IP" == "$SERVER_IP" ]; then ok "DNS $sub.$DOMAIN → $DNS_IP" else warn "DNS $sub.$DOMAIN → '$DNS_IP' (attendu : $SERVER_IP)" fi done # Vérifier les ports 80/443 disponibles if sudo netstat -tlnp 2>/dev/null | grep -q ":80 "; then warn "Port 80 déjà utilisé (vérifier que c'est Traefik et pas autre chose)" fi if sudo netstat -tlnp 2>/dev/null | grep -q ":443 "; then warn "Port 443 déjà utilisé (vérifier que c'est Traefik et pas autre chose)" fi # Vérifier l'espace disque DISK_FREE=$(df -h / | awk 'NR==2 {print $4}') log "Espace libre disque : $DISK_FREE" ok "Vérifications terminées." } step_networks() { log "===== ÉTAPE 2 : Création des réseaux Docker =====" for net in web backend; do if docker network inspect $net &> /dev/null; then warn "Réseau '$net' existe déjà" else docker network create $net ok "Réseau '$net' créé" fi done } step_traefik() { log "===== ÉTAPE 3 : Déploiement Traefik =====" mkdir -p ~/docker/traefik/letsencrypt cd ~/docker/traefik # Copier les fichiers depuis configs/ if [ ! -f docker-compose.yml ]; then cp $SCRIPT_DIR/configs/traefik/docker-compose.yml.template docker-compose.yml warn "docker-compose.yml créé depuis le template. À adapter si besoin." fi if [ ! -f traefik.yml ]; then cp $SCRIPT_DIR/configs/traefik/traefik.yml.template traefik.yml fi if [ ! -f dynamic.yml ]; then cp $SCRIPT_DIR/configs/traefik/dynamic.yml.template dynamic.yml warn "⚠️ Penser à mettre à jour LETSENCRYPT_EMAIL et TRAEFIK_DASHBOARD_AUTH dans dynamic.yml" fi # Permissions touch letsencrypt/acme.json chmod 600 letsencrypt/acme.json # Lancement docker compose up -d sleep 5 if docker ps | grep -q traefik; then ok "Traefik démarré" else err "Traefik n'a pas démarré. Logs :" docker compose logs --tail 50 return 1 fi } step_postgres() { log "===== ÉTAPE 4 : Déploiement Postgres principal =====" mkdir -p ~/docker/postgres/data cd ~/docker/postgres if [ ! -f docker-compose.yml ]; then cp $SCRIPT_DIR/configs/postgres/docker-compose.yml.template docker-compose.yml fi if [ ! -f .env ]; then cat > .env << EOF POSTGRES_PASSWORD=${POSTGRES_PASSWORD} EOF chmod 600 .env fi docker compose up -d log "Attente démarrage Postgres..." sleep 10 if docker exec postgres pg_isready -U postgres &> /dev/null; then ok "Postgres prêt" else err "Postgres pas prêt" return 1 fi # Créer les databases dédiées log "Création des databases gitea et n8n..." docker exec postgres psql -U postgres << EOF CREATE USER gitea WITH PASSWORD '${GITEA_DB_PASSWORD}'; CREATE DATABASE gitea OWNER gitea; CREATE USER n8n WITH PASSWORD '${N8N_DB_PASSWORD}'; CREATE DATABASE n8n OWNER n8n; EOF ok "Databases créées" } step_zitadel() { log "===== ÉTAPE 5 : Déploiement Zitadel =====" warn "⚠️ Zitadel est un déploiement complexe. Voir docs/03-zitadel-setup.md." warn "Cette étape lance le docker-compose Zitadel uniquement." warn "La création du compte zitadel-admin se fait MANUELLEMENT via la console après." if ! confirm "Continuer ?"; then return 0 fi mkdir -p ~/docker/zitadel/machinekey cd ~/docker/zitadel if [ ! -f docker-compose.yml ]; then cp $SCRIPT_DIR/configs/zitadel/docker-compose.yml.template docker-compose.yml fi if [ ! -f .env ]; then cp $SCRIPT_DIR/configs/zitadel/.env.template .env warn "Adapter le .env avant de lancer (ZITADEL_MASTER_KEY notamment)" return 1 fi docker compose up -d log "Attente démarrage Zitadel (peut prendre 1-2 min)..." sleep 30 ok "Zitadel démarré. Aller sur https://sso.${DOMAIN}/ui/console pour finaliser." } step_intranet() { log "===== ÉTAPE 6 : Déploiement Intranet =====" mkdir -p ~/docker/intranet cd ~/docker/intranet # Copier les fichiers depuis configs/ if [ ! -f docker-compose.yml ]; then cp $SCRIPT_DIR/configs/intranet/docker-compose.yml.template docker-compose.yml fi if [ ! -f nginx-rbac.conf ]; then cp $SCRIPT_DIR/configs/intranet/nginx-rbac.conf.template nginx-rbac.conf fi if [ ! -f index.html ]; then cp $SCRIPT_DIR/scripts/intranet/index.html.template index.html warn "index.html créé depuis le template. À personnaliser (logo, couleurs, etc.)" fi if [ ! -f roles-mapping.json ]; then echo '{}' > roles-mapping.json warn "roles-mapping.json vide créé. Sera généré par generate-roles-mapping.py après import." fi docker compose up -d if docker ps | grep -q intranet; then ok "Intranet démarré" else err "Intranet n'a pas démarré. Logs :" docker compose logs --tail 30 fi } step_gitea() { log "===== ÉTAPE 7 : Déploiement Gitea =====" warn "⚠️ La config SSO Zitadel pour Gitea se fait MANUELLEMENT via l'UI Gitea après ce script." mkdir -p ~/docker/gitea/data cd ~/docker/gitea if [ ! -f docker-compose.yml ]; then cp $SCRIPT_DIR/configs/gitea/docker-compose.yml.template docker-compose.yml fi if [ ! -f .env ]; then cat > .env << EOF GITEA_DB_PASSWORD=${GITEA_DB_PASSWORD} EOF chmod 600 .env fi docker compose up -d ok "Gitea démarré. Aller sur https://git.${DOMAIN} pour setup initial." } step_oauth2_proxy() { log "===== ÉTAPE 8 : Déploiement OAuth2-Proxy (générique) =====" warn "Cette étape déploie 1 OAuth2-Proxy pour 1 outil." read -p "Nom de l'outil (intranet, n8n, portainer, code, status) : " TOOL read -p "Host (ex: intranet.seizeconsulting.com) : " TOOL_HOST read -p "Upstream URL (ex: http://intranet:80) : " TOOL_UPSTREAM read -p "Client ID Zitadel : " ZITADEL_CLIENT_ID read -s -p "Client Secret Zitadel : " ZITADEL_CLIENT_SECRET echo COOKIE_SECRET=$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))') DIR=~/docker/oauth2-proxy-${TOOL} if [ "$TOOL" == "intranet" ]; then DIR=~/docker/oauth2-proxy fi mkdir -p $DIR cd $DIR # docker-compose.yml cat > docker-compose.yml << EOF services: oauth2-proxy-${TOOL}: image: quay.io/oauth2-proxy/oauth2-proxy:v7.6.0 container_name: oauth2-proxy-${TOOL} restart: unless-stopped env_file: - .env networks: - web labels: - "traefik.enable=true" - "traefik.http.routers.${TOOL}-sso.rule=Host(\`${TOOL_HOST}\`)" - "traefik.http.routers.${TOOL}-sso.entrypoints=websecure" - "traefik.http.routers.${TOOL}-sso.tls.certresolver=letsencrypt" - "traefik.http.routers.${TOOL}-sso.service=${TOOL}-sso" - "traefik.http.routers.${TOOL}-sso.priority=300" - "traefik.http.services.${TOOL}-sso.loadbalancer.server.port=4180" networks: web: external: true EOF # .env cat > .env << EOF OAUTH2_PROXY_PROVIDER=oidc OAUTH2_PROXY_CLIENT_ID=${ZITADEL_CLIENT_ID} OAUTH2_PROXY_CLIENT_SECRET=${ZITADEL_CLIENT_SECRET} OAUTH2_PROXY_COOKIE_SECRET=${COOKIE_SECRET} OAUTH2_PROXY_OIDC_ISSUER_URL=https://sso.${DOMAIN} OAUTH2_PROXY_REDIRECT_URL=https://${TOOL_HOST}/oauth2/callback OAUTH2_PROXY_UPSTREAMS=${TOOL_UPSTREAM} OAUTH2_PROXY_SCOPE=openid email profile urn:zitadel:iam:org:project:roles OAUTH2_PROXY_EMAIL_DOMAINS=* OAUTH2_PROXY_COOKIE_DOMAINS=.${DOMAIN} OAUTH2_PROXY_COOKIE_SECURE=true OAUTH2_PROXY_COOKIE_HTTPONLY=true OAUTH2_PROXY_COOKIE_SAMESITE=lax OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180 OAUTH2_PROXY_REVERSE_PROXY=true OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER=true OAUTH2_PROXY_PASS_ACCESS_TOKEN=true OAUTH2_PROXY_SET_AUTHORIZATION_HEADER=true OAUTH2_PROXY_SET_XAUTHREQUEST=true OAUTH2_PROXY_WHITELIST_DOMAINS=.${DOMAIN} OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true EOF chmod 600 .env docker compose up -d if docker ps | grep -q "oauth2-proxy-${TOOL}"; then ok "OAuth2-Proxy ${TOOL} démarré" else err "Échec démarrage. Logs :" docker compose logs --tail 30 fi } step_apps() { log "===== ÉTAPE 9 : Déploiement des apps tierces =====" warn "Chaque app nécessite sa propre config. Cette étape ne fait que créer la structure." for app in n8n portainer code-server uptime-kuma; do if confirm "Déployer $app ?"; then mkdir -p ~/docker/$app cd ~/docker/$app if [ -f $SCRIPT_DIR/configs/$app/docker-compose.yml.template ]; then cp $SCRIPT_DIR/configs/$app/docker-compose.yml.template docker-compose.yml fi if [ -f $SCRIPT_DIR/configs/$app/.env.template ]; then cp $SCRIPT_DIR/configs/$app/.env.template .env warn "Adapter $app/.env" fi docker compose up -d ok "$app déployé" fi done } step_all() { log "===== INSTALL COMPLET (mode interactif) =====" step_check && \ step_networks && \ step_traefik && \ step_postgres && \ step_zitadel && \ step_intranet && \ step_gitea && \ step_apps warn "Reste manuellement :" warn " - Setup Zitadel via UI (créer admin, configurer policies, créer apps)" warn " - Setup Gitea via UI (premier admin)" warn " - OAuth2-Proxy par outil (étape 8, à répéter pour chaque)" warn " - Import users Furious (voir scripts/furious-to-zitadel/)" warn " - Attribution des rôles RBAC" } # ===== MENU PRINCIPAL ===== show_menu() { echo "============================================================" echo " Install Infrastructure Seize Consulting" echo "============================================================" echo "" echo "Étapes disponibles :" echo " 1) check - Vérifications pré-install" echo " 2) networks - Créer les réseaux Docker" echo " 3) traefik - Déployer Traefik" echo " 4) postgres - Déployer Postgres principal" echo " 5) zitadel - Déployer Zitadel" echo " 6) intranet - Déployer l'intranet" echo " 7) gitea - Déployer Gitea" echo " 8) oauth2-proxy - Déployer 1 OAuth2-Proxy (interactif)" echo " 9) apps - Déployer n8n, portainer, code, uptime" echo " all - Tout déployer (interactif)" echo " q - Quitter" echo "" read -p "Choix : " choice case $choice in 1|check) step_check ;; 2|networks) step_networks ;; 3|traefik) step_traefik ;; 4|postgres) step_postgres ;; 5|zitadel) step_zitadel ;; 6|intranet) step_intranet ;; 7|gitea) step_gitea ;; 8|oauth2-proxy) step_oauth2_proxy ;; 9|apps) step_apps ;; all) step_all ;; q|Q) exit 0 ;; *) err "Choix invalide" ;; esac } # ===== ENTRY POINT ===== if [ -n "$1" ]; then case $1 in check) step_check ;; networks) step_networks ;; traefik) step_traefik ;; postgres) step_postgres ;; zitadel) step_zitadel ;; intranet) step_intranet ;; gitea) step_gitea ;; oauth2-proxy) step_oauth2_proxy ;; apps) step_apps ;; all) step_all ;; *) err "Étape inconnue : $1" ; exit 1 ;; esac else show_menu fi