- 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
469 lines
13 KiB
Bash
Executable File
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
|