33800 Docs

← Retour

Proposition : Système de Déploiement Hybride (GitLab CI/CD + Script Intelligent)

Date : 20/01/2026 21:30 Statut : PROPOSITION Priorité : HAUTE Version : 3.0 - Approche hybride (remplace v2 daemon)


1. Vision

GitLab CI/CD fait le travail lourd (webhooks, queue, logs, retry), un script intelligent fait la logique.

git push main
     │
     ▼
GitLab CI/CD (webhook natif)
     │
     ▼
Pipeline déclenché (.gitlab-ci.yml template)
     │
     ▼
smart-deploy.sh
     │
     ├─ Parse conf.prod.gouroubleu.yml
     ├─ Détecte target (prod-portainer, o2switch, nginx...)
     ├─ Injecte variables (supabase: true → DATABASE_URL)
     ├─ Crée DNS wildcard si besoin
     ├─ Build + Deploy selon target
     ├─ Met à jour registres JSON
     └─ Notifie via ntfy

Plus de daemon custom à maintenir.


2. Pourquoi cette approche ?

Aspect Daemon custom (v2) Hybride GitLab + Script (v3)
Webhook Custom à maintenir ✅ GitLab natif
Queue jobs Custom (Redis) ✅ GitLab natif
Logs Custom (SQLite) ✅ GitLab natif
Retry en cas d'échec Custom ✅ GitLab natif
UI pipelines Custom dashboard ✅ GitLab UI + dashboard custom
Format config conf.gouroubleu.yml conf.gouroubleu.yml (identique)
DNS auto ✅ (dans le script)
Injection vars ✅ (dans le script)
Service à maintenir 1 daemon Python + Redis 0 (juste des scripts)
Auto-deploy possible ❌ (exception) ✅ (pas de SPOF)
Mise à jour Rebuild container Modifier le script

3. Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                              ARCHITECTURE HYBRIDE                            │
│                                                                              │
│   ┌──────────────────────────────────────────────────────────────────────┐  │
│   │                         GITLAB (gitlab.33800)                         │  │
│   │                                                                       │  │
│   │   ┌─────────────┐     ┌─────────────┐     ┌─────────────┐           │  │
│   │   │  Webhooks   │     │   Queue     │     │    Logs     │           │  │
│   │   │   (natif)   │     │   (natif)   │     │   (natif)   │           │  │
│   │   └─────────────┘     └─────────────┘     └─────────────┘           │  │
│   │                              │                                        │  │
│   │                              ▼                                        │  │
│   │                    ┌─────────────────┐                               │  │
│   │                    │  GitLab Runner  │                               │  │
│   │                    │ (prod-portainer)│                               │  │
│   │                    └────────┬────────┘                               │  │
│   └─────────────────────────────┼────────────────────────────────────────┘  │
│                                 │                                            │
│                                 ▼                                            │
│   ┌─────────────────────────────────────────────────────────────────────┐   │
│   │                      SMART-DEPLOY.SH (sur NFS)                       │   │
│   │                                                                      │   │
│   │   1. Parse conf.{branch}.gouroubleu.yml                             │   │
│   │   2. Résout target → stratégie                                      │   │
│   │   3. Injecte variables services                                     │   │
│   │   4. Crée DNS wildcard (API O2switch)                               │   │
│   │   5. Build Docker / rsync / scp selon target                        │   │
│   │   6. Deploy (local ou SSH)                                          │   │
│   │   7. Met à jour registres JSON                                      │   │
│   │   8. Notifie ntfy                                                   │   │
│   └─────────────────────────────────────────────────────────────────────┘   │
│                                 │                                            │
│              ┌──────────────────┼──────────────────────┐                    │
│              ▼                  ▼                      ▼                    │
│      ┌─────────────┐    ┌─────────────┐       ┌─────────────┐              │
│      │prod-portainer│   │dev-portainer│       │  o2switch   │              │
│      │   nginx     │    │             │       │   win11     │              │
│      │   pve       │    │             │       │             │              │
│      └─────────────┘    └─────────────┘       └─────────────┘              │
│                                                                              │
│   ┌─────────────────────────────────────────────────────────────────────┐   │
│   │                         DASHBOARD (generate.sh)                      │   │
│   │                                                                      │   │
│   │   Collecte via API GitLab :                                         │   │
│   │   - Pipelines récents (status, durée)                               │   │
│   │   - Commits déployés                                                │   │
│   │   - Erreurs                                                         │   │
│   │                                                                      │   │
│   │   Génère : deploys.html, services.html, crons.html                  │   │
│   └─────────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────────┘

4. Composants

4.1 Template GitLab CI/CD (repo ci-templates)

Repo : gouroubleu/ci-templates

Fichier : deploy.yml

# /gouroubleu/ci-templates/deploy.yml
# Template réutilisable par tous les projets

stages:
  - deploy

.deploy-base:
  stage: deploy
  tags:
    - prod-runner  # Runner sur prod-portainer
  script:
    - /mnt/stock_8to/33800-stack/scripts/deploy/smart-deploy.sh
  after_script:
    - /mnt/stock_8to/33800-stack/scripts/deploy/notify-result.sh

deploy-prod:
  extends: .deploy-base
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  variables:
    DEPLOY_ENV: "prod"
    DEPLOY_CONFIG: "conf.prod.gouroubleu.yml"

deploy-dev:
  extends: .deploy-base
  rules:
    - if: $CI_COMMIT_BRANCH == "develop"
  variables:
    DEPLOY_ENV: "dev"
    DEPLOY_CONFIG: "conf.dev.gouroubleu.yml"

deploy-staging:
  extends: .deploy-base
  rules:
    - if: $CI_COMMIT_BRANCH == "staging"
  variables:
    DEPLOY_ENV: "staging"
    DEPLOY_CONFIG: "conf.staging.gouroubleu.yml"

deploy-custom:
  extends: .deploy-base
  rules:
    - if: $CI_COMMIT_BRANCH =~ /^deploy\/.+$/
  variables:
    DEPLOY_ENV: "custom"
    DEPLOY_CONFIG: "conf.${CI_COMMIT_BRANCH#deploy/}.gouroubleu.yml"

4.2 .gitlab-ci.yml dans chaque projet

2 lignes seulement :

include:
  - project: 'gouroubleu/ci-templates'
    file: '/deploy.yml'

C'est tout ! La logique est centralisée.

4.3 conf.gouroubleu.yml (format inchangé)

Le format de configuration reste exactement le même que dans la proposition v2.

# conf.prod.gouroubleu.yml
name: "mon-api"
target: "prod-portainer"
type: "node"
port: 3000
domain: "api"

supabase: true
redis: true
ntfy: true

storage:
  data: true

logs: true
health: "/health"

4.4 smart-deploy.sh (script intelligent)

Emplacement : /stock_8to/33800-stack/scripts/deploy/smart-deploy.sh

#!/bin/bash
# ═══════════════════════════════════════════════════════════════════════════
# SMART-DEPLOY.SH - Script de déploiement intelligent
# Appelé par GitLab CI/CD, parse conf.gouroubleu.yml et déploie
# ═══════════════════════════════════════════════════════════════════════════

set -euo pipefail

# Variables GitLab CI
PROJECT_DIR="${CI_PROJECT_DIR}"
PROJECT_NAME="${CI_PROJECT_NAME}"
BRANCH="${CI_COMMIT_BRANCH}"
COMMIT="${CI_COMMIT_SHORT_SHA}"
CONFIG_FILE="${DEPLOY_CONFIG:-conf.prod.gouroubleu.yml}"

# Paths
SCRIPTS_DIR="/mnt/stock_8to/33800-stack/scripts/deploy"
REGISTRY_DIR="/mnt/stock_8to/33800-stack/monitoring"
TEMPLATES_DIR="/mnt/stock_8to/33800-stack/scripts/deploy/templates"

# ───────────────────────────────────────────────────────────────────────────
# 1. PARSE CONFIG
# ───────────────────────────────────────────────────────────────────────────

if [[ ! -f "${PROJECT_DIR}/${CONFIG_FILE}" ]]; then
    echo "❌ Config file not found: ${CONFIG_FILE}"
    echo "   Skipping deployment (no config = no auto-deploy)"
    exit 0
fi

echo "📋 Parsing ${CONFIG_FILE}..."
source "${SCRIPTS_DIR}/lib/parse-yaml.sh"
parse_yaml "${PROJECT_DIR}/${CONFIG_FILE}" "CFG_"

# Variables extraites : CFG_name, CFG_target, CFG_type, CFG_port, etc.

echo "   Name: ${CFG_name}"
echo "   Target: ${CFG_target}"
echo "   Type: ${CFG_type:-auto}"

# ───────────────────────────────────────────────────────────────────────────
# 2. RESOLVE TARGET
# ───────────────────────────────────────────────────────────────────────────

source "${SCRIPTS_DIR}/lib/targets.sh"
resolve_target "${CFG_target}"

# Variables : TARGET_TYPE (docker|files|remote), TARGET_HOST, TARGET_USER, etc.

# ───────────────────────────────────────────────────────────────────────────
# 3. INJECT SERVICE VARIABLES
# ───────────────────────────────────────────────────────────────────────────

source "${SCRIPTS_DIR}/lib/services.sh"
inject_services

# Génère les variables d'environnement selon les services déclarés
# supabase: true → DATABASE_URL, SUPABASE_URL, etc.

# ───────────────────────────────────────────────────────────────────────────
# 4. DNS WILDCARD (si domain déclaré)
# ───────────────────────────────────────────────────────────────────────────

if [[ -n "${CFG_domain:-}" ]]; then
    source "${SCRIPTS_DIR}/lib/dns.sh"
    ensure_dns_wildcard "${CFG_domain}"
fi

# ───────────────────────────────────────────────────────────────────────────
# 5. BUILD + DEPLOY selon target
# ───────────────────────────────────────────────────────────────────────────

case "${TARGET_TYPE}" in
    docker)
        source "${SCRIPTS_DIR}/strategies/docker.sh"
        deploy_docker
        ;;
    files)
        source "${SCRIPTS_DIR}/strategies/files.sh"
        deploy_files
        ;;
    remote)
        source "${SCRIPTS_DIR}/strategies/remote.sh"
        deploy_remote
        ;;
    *)
        echo "❌ Unknown target type: ${TARGET_TYPE}"
        exit 1
        ;;
esac

# ───────────────────────────────────────────────────────────────────────────
# 6. UPDATE REGISTRIES
# ───────────────────────────────────────────────────────────────────────────

source "${SCRIPTS_DIR}/lib/registry.sh"
update_services_registry
update_deploys_history

# ───────────────────────────────────────────────────────────────────────────
# 7. NGINX CONFIG (si domain)
# ───────────────────────────────────────────────────────────────────────────

if [[ -n "${CFG_domain:-}" ]]; then
    source "${SCRIPTS_DIR}/lib/nginx.sh"
    generate_and_deploy_nginx_config
fi

echo "✅ Deploy successful: ${CFG_name} → ${CFG_target}"

5. Structure des scripts

/stock_8to/33800-stack/scripts/deploy/
├── smart-deploy.sh              # Script principal (appelé par GitLab CI)
├── notify-result.sh             # Notification ntfy après deploy
│
├── lib/
│   ├── parse-yaml.sh            # Parser YAML en variables bash
│   ├── targets.sh               # Résolution target → host/user/type
│   ├── services.sh              # Injection variables services
│   ├── dns.sh                   # Création wildcard DNS (API O2switch)
│   ├── nginx.sh                 # Génération config nginx
│   ├── registry.sh              # Mise à jour registres JSON
│   └── common.sh                # Fonctions utilitaires
│
├── strategies/
│   ├── docker.sh                # Deploy Docker (build, push, compose up)
│   ├── files.sh                 # Deploy fichiers (releases + symlinks)
│   └── remote.sh                # Deploy distant (rsync, git push)
│
├── templates/
│   ├── Dockerfile.node
│   ├── Dockerfile.bun
│   ├── Dockerfile.python
│   ├── Dockerfile.rust
│   ├── Dockerfile.static
│   ├── docker-compose.yml.j2
│   └── nginx.conf.j2
│
└── machines-registry.json       # Registre des targets

6. Targets et machines

6.1 machines-registry.json

{
  "machines": {
    "prod-portainer": {
      "ip": "192.168.1.12",
      "type": "docker",
      "ssh_user": "gouroubleu",
      "local": true,
      "services_path": "/mnt/stock_8to/33800-stack/services",
      "docker_data_root": "/mnt/stock_8to/33800-stack/docker/prod"
    },
    "dev-portainer": {
      "ip": "192.168.1.51",
      "type": "docker",
      "ssh_user": "gouroubleu",
      "local": false,
      "services_path": "/mnt/stock_1to/services"
    },
    "o2switch": {
      "host": "yellow.o2switch.net",
      "type": "remote",
      "ssh_user": "deas8499",
      "services_path": "~/services",
      "methods": ["rsync", "git"]
    },
    "nginx": {
      "ip": "192.168.1.104",
      "type": "files",
      "ssh_user": "gouroubleu",
      "services_path": "/opt/33800-services"
    },
    "win11": {
      "ip": "192.168.1.30",
      "type": "files",
      "ssh_user": "gouro",
      "services_path": "I:/33800-services"
    },
    "pve": {
      "ip": "192.168.1.4",
      "type": "files",
      "ssh_user": "gouroubleu",
      "services_path": "/stock_8to/33800-stack/services"
    }
  }
}

6.2 Stratégies par type

Type Stratégie Targets
docker Build → Registry → docker compose up prod-portainer, dev-portainer
files Releases + Symlinks nginx, win11, pve, gitlab
remote rsync ou git push o2switch

7. Services Stack (injection variables)

Le fichier lib/services.sh gère l'injection automatique des variables.

7.1 Mapping services → variables

# lib/services.sh

inject_services() {
    local env_file="${PROJECT_DIR}/.env.deploy"
    > "${env_file}"

    # Supabase
    if [[ "${CFG_supabase:-false}" == "true" ]]; then
        if [[ "${DEPLOY_ENV}" == "prod" ]]; then
            echo "DATABASE_URL=postgresql://postgres:postgres@192.168.1.12:5433/postgres" >> "${env_file}"
            echo "SUPABASE_URL=http://192.168.1.12:8200" >> "${env_file}"
        else
            echo "DATABASE_URL=postgresql://postgres:postgres@192.168.1.51:5432/postgres" >> "${env_file}"
            echo "SUPABASE_URL=http://192.168.1.51:8100" >> "${env_file}"
        fi
        echo "SUPABASE_KEY=${SUPABASE_ANON_KEY}" >> "${env_file}"
    fi

    # Redis
    if [[ "${CFG_redis:-false}" == "true" ]]; then
        if [[ "${DEPLOY_ENV}" == "prod" ]]; then
            echo "REDIS_URL=redis://192.168.1.12:6379" >> "${env_file}"
        else
            echo "REDIS_URL=redis://192.168.1.51:6379" >> "${env_file}"
        fi
    fi

    # Ntfy
    if [[ "${CFG_ntfy:-false}" == "true" ]]; then
        echo "NTFY_URL=http://192.168.1.12:8080" >> "${env_file}"
        echo "NTFY_TOPIC=${CFG_name}" >> "${env_file}"
    fi

    # AI Orchestrator
    if [[ "${CFG_ollama:-false}" == "true" ]] || \
       [[ "${CFG_comfyui:-false}" == "true" ]] || \
       [[ "${CFG_fooocus:-false}" == "true" ]]; then
        echo "ORCHESTRATOR_URL=http://192.168.1.12:5501" >> "${env_file}"
    fi

    # ... autres services
}

7.2 Tableau récapitulatif

Service Variables injectées (PROD)
supabase: true DATABASE_URL, SUPABASE_URL, SUPABASE_KEY
redis: true REDIS_URL
ntfy: true NTFY_URL, NTFY_TOPIC
ollama: true ORCHESTRATOR_URL
auth: true AUTH_URL, AUTH_KEY
linkedin: true CONNECTORS_URL, BROWSER_CONNECTOR_URL

8. Dashboard (via API GitLab)

8.1 Collecte des données

Le dashboard utilise l'API GitLab au lieu d'un daemon custom.

Nouveau module : generate.d/25-deploys.sh

#!/bin/bash
# generate.d/25-deploys.sh - Page des déploiements

GITLAB_URL="https://gitlab.33800.nowhere84.com"
GITLAB_TOKEN="glpat-yaowLwWBJhXfzJEC8UBC"

generate_deploys_page() {
    local output="$1"

    # Récupère les pipelines récents (tous projets)
    local pipelines=$(curl -s -H "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
        "${GITLAB_URL}/api/v4/projects?per_page=50" | \
        jq -r '.[].id' | while read pid; do
            curl -s -H "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
                "${GITLAB_URL}/api/v4/projects/${pid}/pipelines?per_page=5"
        done | jq -s 'add | sort_by(.created_at) | reverse | .[0:20]')

    cat > "${output}" <<EOF
<!DOCTYPE html>
<html>
<head>
    <title>Déploiements - 33800</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    $(generate_nav "deploys")

    <main>
        <h1>Déploiements récents</h1>

        <div class="deploys-grid">
EOF

    echo "${pipelines}" | jq -r '.[] | @base64' | while read row; do
        local pipeline=$(echo "${row}" | base64 -d)
        local id=$(echo "${pipeline}" | jq -r '.id')
        local status=$(echo "${pipeline}" | jq -r '.status')
        local ref=$(echo "${pipeline}" | jq -r '.ref')
        local created=$(echo "${pipeline}" | jq -r '.created_at')
        local project_id=$(echo "${pipeline}" | jq -r '.project_id')

        # Récupère le nom du projet
        local project_name=$(curl -s -H "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
            "${GITLAB_URL}/api/v4/projects/${project_id}" | jq -r '.name')

        local status_icon="⏳"
        [[ "${status}" == "success" ]] && status_icon="✅"
        [[ "${status}" == "failed" ]] && status_icon="❌"
        [[ "${status}" == "running" ]] && status_icon="🔄"

        cat >> "${output}" <<EOF
            <div class="deploy-card status-${status}">
                <span class="status-icon">${status_icon}</span>
                <span class="project">${project_name}</span>
                <span class="branch">${ref}</span>
                <span class="time">${created}</span>
                <a href="${GITLAB_URL}/-/pipelines/${id}" target="_blank">Logs</a>
            </div>
EOF
    done

    cat >> "${output}" <<EOF
        </div>
    </main>
</body>
</html>
EOF
}

generate_deploys_page "${OUTPUT_DIR}/deploys.html"

8.2 Avantages


9. Migration projets existants

9.1 Étapes par projet

  1. Créer conf.prod.gouroubleu.yml (et conf.dev.gouroubleu.yml si besoin)
  2. Remplacer .gitlab-ci.yml par le template 2 lignes
  3. Tester : git push → pipeline → deploy auto

9.2 Projets à migrer

Projet Action
ai-orchestrator Créer conf.prod, remplacer .gitlab-ci.yml
notif-logger Créer conf.prod, remplacer .gitlab-ci.yml
browser-connector Créer conf.prod, remplacer .gitlab-ci.yml
connectors-api Créer conf.prod, remplacer .gitlab-ci.yml
needfinder Créer conf.dev, remplacer .gitlab-ci.yml
ffmpeg-api Créer conf.prod, remplacer .gitlab-ci.yml
authentificator Créer conf.prod, remplacer .gitlab-ci.yml
... ...

Pas d'exception : tous les projets utilisent le même système.


10. Plan d'implémentation

Phase 1 : Fondations (1-2 jours)

Phase 2 : Stratégie Docker (1 jour)

Phase 3 : Services & Variables (1 jour)

Phase 4 : DNS & Nginx (1 jour)

Phase 5 : Autres stratégies (1 jour)

Phase 6 : Dashboard (1 jour)

Phase 7 : Migration (2-3 jours)


11. Avantages vs Daemon custom

Aspect Cette approche
Complexité Scripts bash (simple) vs daemon Python (complexe)
Maintenance Modifier un script vs rebuild container
SPOF Aucun (GitLab gère tout)
Logs GitLab natif (searchable, retention)
Queue GitLab natif (retry, timeout, cancel)
Webhook GitLab natif (fiable)
Dashboard Collecte API (simple) vs WebSocket custom (complexe)
Auto-deploy ✅ Tous les projets (pas d'exception)

12. Validation demandée


Auteur : Claude Prochaine étape : Validation puis implémentation Phase 1