seize-infra-doc/install.sh
Hedi Kalboussi 1e90a85359 Documentation initiale infra Seize — passation J7
- 6 docs racine (README, HANDOVER, ARCHITECTURE, DECISIONS, INSTALL, OPERATIONS)
- 9 docs thématiques dans docs/
- 10 templates docker-compose dans configs/
- 7 scripts Python/Shell d'import Furious vers Zitadel
- 2 matrices RBAC

Etat infra au 5 juin 2026 :
- 88 users Furious importes dans Zitadel
- 8 roles RBAC crees et attribues
- Intranet filtre les cartes selon role (Approche A)
- 7 outils SSO operationnels
2026-06-05 17:49:43 +00:00

469 lines
13 KiB
Bash
Executable File

#!/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