Date : 20/01/2026 21:30 Statut : PROPOSITION Priorité : HAUTE Version : 3.0 - Approche hybride (remplace v2 daemon)
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.
| 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 |
┌─────────────────────────────────────────────────────────────────────────────┐
│ 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 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
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"
2 lignes seulement :
include:
- project: 'gouroubleu/ci-templates'
file: '/deploy.yml'
C'est tout ! La logique est centralisée.
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"
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}"
/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
{
"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"
}
}
}
| 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 |
Le fichier lib/services.sh gère l'injection automatique des 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
}
| 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 |
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"
conf.prod.gouroubleu.yml (et conf.dev.gouroubleu.yml si besoin).gitlab-ci.yml par le template 2 lignes| 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.
gouroubleu/ci-templates sur GitLabdeploy.yml (template)/scripts/deploy/smart-deploy.sh (base)lib/parse-yaml.shstrategies/docker.shlib/services.shlib/dns.sh (API O2switch)lib/nginx.shstrategies/files.sh (releases + symlinks)strategies/remote.sh (rsync, git push)generate.d/25-deploys.sh| 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) |
Auteur : Claude Prochaine étape : Validation puis implémentation Phase 1