Date : 02-01-2026 Demandeur : gouroubleu Statut : EN ATTENTE DE VALIDATION
Mise en place d'une infrastructure Docker complète pour Rhinov, inspirée de 33800-stack, avec :
LLM par défaut : Gemini (Google) - Claude (Anthropic) disponible en alternative
┌──────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ DEV-STACK │ │ PROD-NEW │ │ PROD-CURRENT │ │ PROD LIVE │ │
│ │ (interne) │ │ (interne) │ │ (interne) │ │ (publique) │ │
│ │ │ │ │ │ │ │ │ │
│ │ *.dev.rhinov │──▶│ *.new.rhinov │──▶│ *.cur.rhinov │◀──│ *.rhinov.fr │ │
│ │ .local │ │ .local │ │ .local │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ Développement│ │ Prochaine │ │ Miroir exact │ │ Production │ │
│ │ Tests/Debug │ │ version │ │ de LIVE │ │ Clients │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │
│ └───────────────────┘ │
│ SYNCHRONISÉS │
│ │
│ RÉSEAU INTERNE RHINOV INTERNET │
└──────────────────────────────────────────────────────────────────────────────────────────┘
| Environnement | Accès | Domaine | Usage |
|---|---|---|---|
| DEV-STACK | Réseau interne | *.dev.rhinov.local |
Développement, tests, debug |
| PROD-NEW-STACK | Réseau interne | *.new.rhinov.local |
Prochaine version en préparation |
| PROD-CURRENT-STACK | Réseau interne | *.cur.rhinov.local |
Miroir exact de ce qui est LIVE |
| PROD LIVE | Internet | *.rhinov.fr |
Production publique, clients |
┌─────────────────────────────────────────┐
│ CYCLE DE RELEASE │
└─────────────────────────────────────────┘
┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ DEV │ │ PROD-NEW │ │ PROD-CURRENT │ │ PROD LIVE │
│ STACK │ │ STACK │ │ STACK │ │ *.rhinov.fr │
└────┬─────┘ └────┬─────┘ └──────┬───────┘ └──────┬───────┘
│ │ │ │
1. Develop │ │ │
│ │ │ │
├────────────────▶│ │ │
│ 2. Deploy │ │
│ to NEW │ │
│ │ │ │
│ │ 3. Validation OK │ │
│ ├──────────────────▶│ │
│ │ │ │
│ │ │ 4. Bascule │
│ │ ├────────────────────▶│
│ │ │ (même images) │
│ │ │ │
│ │◀──────────────────┤ │
│ │ 5. NEW devient │ │
│ │ "vide" pour │ │
│ │ prochaine │ │
│ │ release │ │
| Situation | DEV | PROD-NEW | PROD-CURRENT | PROD LIVE |
|---|---|---|---|---|
| Normal | v2.1-dev | v2.0-rc1 | v1.9 | v1.9 |
| Après bascule | v2.1-dev | (vide) | v2.0 | v2.0 |
| Rollback urgent | - | - | v1.9 | v1.9 |
Règle d'or : PROD-CURRENT et PROD LIVE sont toujours synchronisés. Si LIVE = v1.9, alors CURRENT = v1.9.
INTERNET
│
┌────────────────────┴────────────────────┐
│ FIREWALL / ROUTEUR │
│ (IP publique) │
└────────────────────┬────────────────────┘
│
│ NAT/Port forward :443 :80
│ (uniquement vers NGINX-PUB)
▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ RÉSEAU INTERNE RHINOV │
│ 192.168.X.0/24 │
│ │
│ ┌───────────────────────────────────────────────────────────────────────────────────┐ │
│ │ PROXMOX VE │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ VM-IA │ │ NGINX-INT │ │ NGINX-PUB │ │ DNS-INT │ │ │
│ │ │ .10 │ │ .11 │ │ .12 │ │ .13 │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ Claude/Gemini│ │ Proxy interne│ │ Proxy LIVE │ │ dnsmasq │ │ │
│ │ │ Orchestration│ │ DEV+NEW+CUR │ │ *.rhinov.fr │ │ *.local │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ ┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐ │ │
│ │ │ DEV-STACK │ │ PROD-NEW-STACK │ │ PROD-CURRENT-STACK │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ ┌──────┐ ┌──────┐ │ │ ┌──────┐ ┌──────┐ │ │ ┌──────┐ ┌──────┐ │ │ │
│ │ │ │DEV-01│ │DEV-02│ │ │ │NEW-01│ │NEW-02│ │ │ │CUR-01│ │CUR-02│ │ │ │
│ │ │ │ .20 │ │ .21 │ │ │ │ .30 │ │ .31 │ │ │ │ .40 │ │ .41 │ │ │ │
│ │ │ └──────┘ └──────┘ │ │ └──────┘ └──────┘ │ │ └──────┘ └──────┘ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ *.dev.rhinov.local │ │ *.new.rhinov.local │ │ *.cur.rhinov.local │ │ │
│ │ │ (développement) │ │ (prochaine version)│ │ (miroir LIVE) │ │ │
│ │ └────────────────────┘ └────────────────────┘ └────────────────────┘ │ │
│ │ ║ │ │
│ │ ║ SYNC │ │
│ │ ▼ │ │
│ │ ┌────────────────┐ │ │
│ │ │ PROD LIVE │ │ │
│ │ │ *.rhinov.fr │ │ │
│ │ │ (via NGINX-PUB) │ │
│ │ └────────────────┘ │ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ STOCKAGE ZFS + NFS │ │ │
│ │ │ /rhinov-stack/ │ │ │
│ │ │ ├── docker/dev/ (data DEV-STACK) │ │ │
│ │ │ ├── docker/prod-new/ (data PROD-NEW-STACK) │ │ │
│ │ │ ├── docker/prod-current/ (data PROD-CURRENT-STACK = LIVE) │ │ │
│ │ │ ├── secrets/ │ │ │
│ │ │ └── ... │ │ │
│ │ └────────────────────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────────────────────────┐ │
│ │ VMs WINDOWS RENDU 3D │ │
│ │ WIN-3D-01 (.60) WIN-3D-02 (.61) WIN-3D-03 (.62) ... │ │
│ └───────────────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
| VM | Hostname | IP suggérée | mDNS | Rôle |
|---|---|---|---|---|
| VM-IA | ia | 192.168.X.10 | ia.local | Orchestration LLM (Claude/Gemini) |
| NGINX-INT | nginx-int | 192.168.X.11 | nginx-int.local | Reverse Proxy interne (DEV + NEW + CUR) |
| NGINX-PUB | nginx-pub | 192.168.X.12 | nginx-pub.local | Reverse Proxy public (PROD LIVE) |
| DNS-INT | dns | 192.168.X.13 | dns.local | DNS interne (dnsmasq) |
| VM | Hostname | IP suggérée | mDNS | Rôle |
|---|---|---|---|---|
| DEV-01 | dev-01 | 192.168.X.20 | dev-01.local | Portainer DEV #1 |
| DEV-02 | dev-02 | 192.168.X.21 | dev-02.local | Portainer DEV #2 (futur) |
| DEV-03 | dev-03 | 192.168.X.22 | dev-03.local | Portainer DEV #3 (futur) |
| DEV-04 | dev-04 | 192.168.X.23 | dev-04.local | Portainer DEV #4 (futur) |
| DEV-05 | dev-05 | 192.168.X.24 | dev-05.local | Portainer DEV #5 (futur) |
| VM | Hostname | IP suggérée | mDNS | Rôle |
|---|---|---|---|---|
| NEW-01 | new-01 | 192.168.X.30 | new-01.local | Portainer PROD-NEW #1 |
| NEW-02 | new-02 | 192.168.X.31 | new-02.local | Portainer PROD-NEW #2 (futur) |
| NEW-03 | new-03 | 192.168.X.32 | new-03.local | Portainer PROD-NEW #3 (futur) |
| NEW-04 | new-04 | 192.168.X.33 | new-04.local | Portainer PROD-NEW #4 (futur) |
| NEW-05 | new-05 | 192.168.X.34 | new-05.local | Portainer PROD-NEW #5 (futur) |
| VM | Hostname | IP suggérée | mDNS | Rôle |
|---|---|---|---|---|
| CUR-01 | cur-01 | 192.168.X.40 | cur-01.local | Portainer PROD-CURRENT #1 |
| CUR-02 | cur-02 | 192.168.X.41 | cur-02.local | Portainer PROD-CURRENT #2 (futur) |
| CUR-03 | cur-03 | 192.168.X.42 | cur-03.local | Portainer PROD-CURRENT #3 (futur) |
| CUR-04 | cur-04 | 192.168.X.43 | cur-04.local | Portainer PROD-CURRENT #4 (futur) |
| CUR-05 | cur-05 | 192.168.X.44 | cur-05.local | Portainer PROD-CURRENT #5 (futur) |
| VM | Hostname | IP suggérée | mDNS | GPU |
|---|---|---|---|---|
| WIN-3D-01 | win3d-01 | 192.168.X.60 | win3d-01.local | NVIDIA RTX |
| WIN-3D-02 | win3d-02 | 192.168.X.61 | win3d-02.local | NVIDIA RTX |
| WIN-3D-03 | win3d-03 | 192.168.X.62 | win3d-03.local | NVIDIA RTX |
| ... | ... | ... | ... | ... |
| Environnement | Format domaine | Exemple | Accès |
|---|---|---|---|
| DEV-STACK | {service}.dev.rhinov.local |
api.dev.rhinov.local |
Interne |
| PROD-NEW-STACK | {service}.new.rhinov.local |
api.new.rhinov.local |
Interne |
| PROD-CURRENT-STACK | {service}.cur.rhinov.local |
api.cur.rhinov.local |
Interne |
| PROD LIVE | {service}.rhinov.fr |
api.rhinov.fr |
Internet |
Règle : PROD-CURRENT = miroir exact de PROD LIVE. Même images Docker, même configs (seul le domaine diffère).
| Propriété | Valeur |
|---|---|
| OS | Debian 12 (bookworm) |
| CPU | 2 vCPU |
| RAM | 4 Go |
| Disque | 32 Go |
| User | rhinov (sudoer) |
| Rôle | LLM (Claude/Gemini) + Scripts + SSH vers toutes VMs |
Packages :
Configuration LLM :
# /mnt/rhinov-stack/secrets/llm.env
LLM_PROVIDER=gemini # gemini (défaut) ou claude
GOOGLE_API_KEY=AIza... # Gemini (requis)
ANTHROPIC_API_KEY=sk-ant-... # Claude (optionnel, si switch)
| Propriété | Valeur |
|---|---|
| OS | Debian 12 (bookworm) |
| CPU | 1 vCPU |
| RAM | 512 Mo |
| Disque | 10 Go |
| User | rhinov (sudoer) |
| Rôle | Résolution DNS interne (dnsmasq) |
Configuration dnsmasq :
# /etc/dnsmasq.d/rhinov.conf
# ========================================
# WILDCARDS ENVIRONNEMENTS → NGINX-INT
# ========================================
# DEV-STACK
address=/.dev.rhinov.local/192.168.X.11
# PROD-NEW-STACK (prochaine version)
address=/.new.rhinov.local/192.168.X.11
# PROD-CURRENT-STACK (miroir LIVE)
address=/.cur.rhinov.local/192.168.X.11
# ========================================
# MACHINES INDIVIDUELLES
# ========================================
# Infrastructure
address=/ia.local/192.168.X.10
address=/nginx-int.local/192.168.X.11
address=/nginx-pub.local/192.168.X.12
address=/dns.local/192.168.X.13
# DEV-STACK
address=/dev-01.local/192.168.X.20
address=/dev-02.local/192.168.X.21
# PROD-NEW-STACK
address=/new-01.local/192.168.X.30
address=/new-02.local/192.168.X.31
# PROD-CURRENT-STACK
address=/cur-01.local/192.168.X.40
address=/cur-02.local/192.168.X.41
# Windows 3D
address=/win3d-01.local/192.168.X.60
address=/win3d-02.local/192.168.X.61
Usage : Toutes les machines du réseau utilisent DNS-INT comme serveur DNS primaire (192.168.X.13).
| Propriété | Valeur |
|---|---|
| OS | Debian 12 (bookworm) |
| CPU | 2 vCPU |
| RAM | 2 Go |
| Disque | 20 Go |
| User | rhinov (sudoer) |
| Rôle | Nginx + Certbot + SSL Wildcard |
Packages :
| Propriété | Valeur |
|---|---|
| OS | Debian 12 (bookworm) |
| CPU | 4 vCPU |
| RAM | 8 Go |
| Disque | 50 Go |
| User | rhinov (sudoer) |
| Rôle | Docker + Portainer DEV |
Docker data-root : /mnt/nfs/rhinov-stack/docker/dev/
| Propriété | Valeur |
|---|---|
| OS | Debian 12 (bookworm) |
| CPU | 4 vCPU |
| RAM | 8 Go |
| Disque | 50 Go |
| User | rhinov (sudoer) |
| Rôle | Docker + Portainer PROD |
Docker data-root : /mnt/nfs/rhinov-stack/docker/prod/
/rhinov-stack/
├── docker/
│ ├── dev/ # data-root Docker DEV
│ ├── prod/ # data-root Docker PROD
│ ├── secrets/
│ │ ├── dev.env # Variables DEV
│ │ └── prod.env # Variables PROD
│ ├── shortcuts/ # Dockerfiles templates
│ │ ├── node/
│ │ ├── bun/
│ │ └── qwik/
│ └── stacks/ # Compose files
│
├── scripts/
│ ├── setup/ # Scripts d'installation
│ │ ├── 01-proxmox-init.sh
│ │ ├── 02-vm-claude.sh
│ │ ├── 03-vm-nginx.sh
│ │ ├── 04-vm-dev.sh
│ │ └── 05-vm-prod.sh
│ ├── nginx/ # Génération configs Nginx
│ ├── dns/ # Scripts DNS Gandi/OVH
│ ├── docker/ # Management Docker
│ ├── deploy/ # Déploiement automatisé
│ └── lib/ # Fonctions partagées
│
├── claude/
│ ├── propositions/ # Propositions Claude
│ ├── tasks/ # Tâches en cours
│ ├── backlog/ # Backlog
│ └── CLAUDE.md # Instructions Claude
│
├── databases/
│ ├── dev/ # Données DB DEV
│ └── prod/ # Données DB PROD
│
└── docs/ # Documentation
# ============================================
# RHINOV STACK - VARIABLES D'ENVIRONNEMENT
# ============================================
# Ce fichier doit être copié en dev.env et prod.env
# avec les valeurs appropriées pour chaque environnement
# ============================================
# -----------------------------
# INFRASTRUCTURE
# -----------------------------
STACK_ENV=dev # dev ou prod
STACK_DOMAIN=rhinov.fr # Domaine principal
STACK_WILDCARD=*.stack.rhinov.fr # Wildcard pour les services
# -----------------------------
# PROXMOX
# -----------------------------
PROXMOX_HOST=192.168.X.1
PROXMOX_USER=root@pam
PROXMOX_TOKEN_ID=rhinov-stack
PROXMOX_TOKEN_SECRET=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# -----------------------------
# VMS - IPs
# -----------------------------
VM_CLAUDE_IP=192.168.X.10
VM_NGINX_IP=192.168.X.11
VM_DEV_01_IP=192.168.X.20
VM_PROD_01_IP=192.168.X.30
# -----------------------------
# SSH
# -----------------------------
SSH_USER=rhinov
SSH_KEY_PATH=/home/rhinov/.ssh/id_ed25519
# -----------------------------
# PORTAINER
# -----------------------------
PORTAINER_DEV_URL=https://192.168.X.20:9443
PORTAINER_DEV_API_KEY=ptr_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
PORTAINER_PROD_URL=https://192.168.X.30:9443
PORTAINER_PROD_API_KEY=ptr_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# -----------------------------
# DNS - GANDI
# -----------------------------
GANDI_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxx
GANDI_DOMAIN=rhinov.fr
GANDI_SHARING_ID= # Si compte organisation
# -----------------------------
# DNS - OVH
# -----------------------------
OVH_ENDPOINT=ovh-eu
OVH_APPLICATION_KEY=xxxxxxxxxxxxxxxx
OVH_APPLICATION_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OVH_CONSUMER_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OVH_DOMAIN=rhinov.com
# -----------------------------
# EMAIL - MAILJET
# -----------------------------
MAILJET_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
MAILJET_SECRET_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
MAILJET_SENDER_EMAIL=noreply@rhinov.fr
MAILJET_SENDER_NAME=Rhinov
# -----------------------------
# NOTIFICATIONS - NTFY
# -----------------------------
NTFY_SERVER=https://ntfy.sh # ou serveur self-hosted
NTFY_TOPIC=rhinov-stack
NTFY_TOKEN=tk_xxxxxxxxxxxxxxxxxxxxxxxx # si auth requise
# -----------------------------
# NOTIFICATIONS - TELEGRAM
# -----------------------------
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
TELEGRAM_CHAT_ID=-123456789 # ID du groupe/channel
# -----------------------------
# DATABASES (si utilisées)
# -----------------------------
# PostgreSQL
POSTGRES_HOST=
POSTGRES_PORT=5432
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_DB=
# Redis
REDIS_HOST=
REDIS_PORT=6379
REDIS_PASSWORD=
# -----------------------------
# GITLAB (si utilisé)
# -----------------------------
GITLAB_URL=https://gitlab.rhinov.fr
GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx
# -----------------------------
# GITHUB (si utilisé)
# -----------------------------
GITHUB_USER=
GITHUB_PAT=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# -----------------------------
# SSL CERTIFICATES
# -----------------------------
SSL_EMAIL=admin@rhinov.fr
SSL_CERT_PATH=/etc/letsencrypt/live/${STACK_WILDCARD}/
┌─────────────────────────────────────────────────────────────────┐
│ UTILISATEUR HUMAIN │
│ (compte: edouard) │
│ │
│ - Accès root sur toutes les VMs │
│ - Peut désactiver Claude à tout moment │
│ - Détient les clés SSH master │
└─────────────────────────────┬───────────────────────────────────┘
│
│ Peut révoquer l'accès
▼
┌─────────────────────────────────────────────────────────────────┐
│ COMPTE CLAUDE │
│ (compte: rhinov) │
│ │
│ - Sudo SANS password sur VM-CLAUDE uniquement │
│ - SSH vers autres VMs avec clé dédiée │
│ - Droits limités (pas de rm -rf /, pas de reboot Proxmox) │
│ - Toutes les actions loguées │
└─────────────────────────────────────────────────────────────────┘
# /etc/sudoers.d/rhinov-claude
rhinov ALL=(ALL) NOPASSWD: ALL
# Commandes interdites (blacklist)
rhinov ALL=(ALL) !NOPASSWD: /sbin/reboot, /sbin/shutdown, /sbin/init, /usr/bin/rm -rf /
| Clé | Usage |
|---|---|
/home/rhinov/.ssh/id_ed25519 |
Clé Claude → autres VMs |
/home/edouard/.ssh/id_ed25519 |
Clé admin → toutes VMs |
Toutes les commandes exécutées par Claude sont loguées dans :
/var/log/claude-actions.log (sur VM-CLAUDE)#!/bin/bash
# 01-proxmox-init.sh
# À exécuter sur le Proxmox
set -e
# Création du pool ZFS (si pas déjà fait)
# zpool create rhinov-pool /dev/sdX
# Création du dataset NFS
zfs create rhinov-pool/rhinov-stack
zfs set sharenfs="rw=@192.168.X.0/24,no_root_squash" rhinov-pool/rhinov-stack
# Structure de base
mkdir -p /rhinov-pool/rhinov-stack/{docker/{dev,prod,secrets,shortcuts,stacks},scripts/{setup,nginx,dns,docker,deploy,lib},claude/{propositions,tasks,backlog},databases/{dev,prod},docs}
# Templates VMs Debian 12
pveam download local debian-12-standard_12.2-1_amd64.tar.zst 2>/dev/null || true
echo "Proxmox initialisé. Créer les VMs avec les scripts suivants."
#!/bin/bash
# 02-vm-claude.sh
# À exécuter sur la VM Claude après installation Debian
set -e
# Variables
NFS_SERVER="192.168.X.1"
NFS_PATH="/rhinov-pool/rhinov-stack"
MOUNT_POINT="/mnt/rhinov-stack"
# Mise à jour
apt update && apt upgrade -y
# Packages essentiels
apt install -y \
git curl wget jq htop tree \
sudo openssh-server \
nfs-common autofs \
avahi-daemon libnss-mdns
# Création utilisateur rhinov
useradd -m -s /bin/bash rhinov
echo "rhinov ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/rhinov-claude
# Configuration SSH
mkdir -p /home/rhinov/.ssh
ssh-keygen -t ed25519 -N "" -f /home/rhinov/.ssh/id_ed25519
chown -R rhinov:rhinov /home/rhinov/.ssh
chmod 700 /home/rhinov/.ssh
chmod 600 /home/rhinov/.ssh/id_ed25519
# Montage NFS
mkdir -p $MOUNT_POINT
echo "$NFS_SERVER:$NFS_PATH $MOUNT_POINT nfs defaults,_netdev 0 0" >> /etc/fstab
mount -a
# Symlinks Claude
ln -sf $MOUNT_POINT/claude/propositions /home/rhinov/propositions
ln -sf $MOUNT_POINT/claude/tasks /home/rhinov/tasks
ln -sf $MOUNT_POINT/claude/backlog /home/rhinov/backlog
# Installation Node.js (pour Claude Code)
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs
# Installation Claude Code (à adapter selon la méthode officielle)
# npm install -g @anthropic-ai/claude-code
# mDNS
hostnamectl set-hostname claude
systemctl enable avahi-daemon
systemctl start avahi-daemon
# Logging des actions
cat > /etc/rsyslog.d/claude-actions.conf << 'EOF'
if $programname == 'sudo' and $msg contains 'rhinov' then /var/log/claude-actions.log
EOF
systemctl restart rsyslog
echo "VM Claude configurée. Clé publique à distribuer :"
cat /home/rhinov/.ssh/id_ed25519.pub
#!/bin/bash
# 03-vm-nginx.sh
# À exécuter sur la VM Nginx après installation Debian
set -e
# Variables
NFS_SERVER="192.168.X.1"
NFS_PATH="/rhinov-pool/rhinov-stack"
# Mise à jour
apt update && apt upgrade -y
# Packages
apt install -y \
nginx certbot python3-certbot-nginx \
sudo openssh-server \
nfs-common \
avahi-daemon libnss-mdns
# Création utilisateur rhinov
useradd -m -s /bin/bash rhinov
usermod -aG sudo rhinov
# Configuration SSH (accepter clé Claude)
mkdir -p /home/rhinov/.ssh
# La clé publique de Claude sera ajoutée ici
chown -R rhinov:rhinov /home/rhinov/.ssh
chmod 700 /home/rhinov/.ssh
# Montage NFS (lecture seule pour les configs)
mkdir -p /mnt/rhinov-stack
echo "$NFS_SERVER:$NFS_PATH /mnt/rhinov-stack nfs defaults,ro,_netdev 0 0" >> /etc/fstab
mount -a
# Configuration Nginx de base
cat > /etc/nginx/nginx.conf << 'EOF'
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024;
}
http {
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
EOF
# mDNS
hostnamectl set-hostname nginx
systemctl enable avahi-daemon nginx
systemctl start avahi-daemon nginx
echo "VM Nginx configurée. Ajouter la clé publique Claude dans /home/rhinov/.ssh/authorized_keys"
#!/bin/bash
# 04-vm-portainer.sh
# À exécuter sur les VMs DEV ou PROD
set -e
# Variables (à personnaliser)
ENV_TYPE="${1:-dev}" # dev ou prod
NFS_SERVER="192.168.X.1"
NFS_PATH="/rhinov-pool/rhinov-stack"
DOCKER_DATA_ROOT="/mnt/rhinov-stack/docker/${ENV_TYPE}"
# Mise à jour
apt update && apt upgrade -y
# Packages
apt install -y \
ca-certificates curl gnupg \
sudo openssh-server \
nfs-common \
avahi-daemon libnss-mdns
# Création utilisateur rhinov
useradd -m -s /bin/bash rhinov
usermod -aG sudo rhinov
# Montage NFS
mkdir -p /mnt/rhinov-stack
echo "$NFS_SERVER:$NFS_PATH /mnt/rhinov-stack nfs defaults,_netdev 0 0" >> /etc/fstab
mount -a
# Installation Docker
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Configuration Docker data-root sur NFS
mkdir -p $DOCKER_DATA_ROOT
cat > /etc/docker/daemon.json << EOF
{
"data-root": "$DOCKER_DATA_ROOT",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
EOF
systemctl restart docker
# Ajout rhinov au groupe docker
usermod -aG docker rhinov
# Installation Portainer
docker volume create portainer_data
docker run -d \
-p 8000:8000 -p 9443:9443 \
--name portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
# mDNS
hostnamectl set-hostname "${ENV_TYPE}-01"
systemctl enable avahi-daemon docker
systemctl start avahi-daemon
echo "VM ${ENV_TYPE}-01 configurée."
echo "Portainer accessible sur https://$(hostname -I | awk '{print $1}'):9443"
echo "Ajouter la clé publique Claude dans /home/rhinov/.ssh/authorized_keys"
Un fichier CLAUDE.md complet sera généré avec :
rhinov-stack-setup.zip
├── README.md # Guide d'installation rapide
├── CLAUDE.md # Instructions Claude (template)
│
├── env/
│ └── template.env # Variables à remplir
│
├── scripts/
│ ├── setup/
│ │ ├── 01-proxmox-init.sh
│ │ ├── 02-vm-claude.sh
│ │ ├── 03-vm-nginx.sh
│ │ └── 04-vm-portainer.sh
│ │
│ ├── nginx/
│ │ ├── generate-config.sh # Génère config Nginx
│ │ ├── deploy-config.sh # Déploie sur VM Nginx
│ │ └── templates/
│ │ ├── reverse-proxy.conf.tpl
│ │ └── ssl-redirect.conf.tpl
│ │
│ ├── dns/
│ │ ├── gandi-add-record.sh
│ │ ├── ovh-add-record.sh
│ │ └── check-propagation.sh
│ │
│ ├── docker/
│ │ ├── health-check.sh
│ │ ├── logs.sh
│ │ └── cleanup.sh
│ │
│ ├── deploy/
│ │ ├── deploy-service.sh
│ │ └── rollback.sh
│ │
│ └── lib/
│ ├── colors.sh # Couleurs terminal
│ ├── logging.sh # Fonctions de log
│ ├── notifications.sh # Ntfy/Telegram
│ └── utils.sh # Fonctions utilitaires
│
├── shortcuts/
│ ├── node/
│ │ └── Dockerfile
│ ├── bun/
│ │ └── Dockerfile
│ └── qwik/
│ └── Dockerfile
│
└── docs/
├── architecture.md
├── scaling.md # Guide ajout instances
└── troubleshooting.md
┌─────────────────────────────────────────────────────────────────┐
│ VMs WINDOWS RENDU 3D │
│ (H24 / GPU passthrough) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ WIN-3D-01 │ │ WIN-3D-02 │ │ WIN-3D-0N │ │
│ │ 192.168.X.50│ │ 192.168.X.51│ │ 192.168.X.5N│ │
│ │ │ │ │ │ │ │
│ │ - Windows │ │ - Windows │ │ - Windows │ │
│ │ - OpenSSH │ │ - OpenSSH │ │ - OpenSSH │ │
│ │ - Bonjour │ │ - Bonjour │ │ - Bonjour │ │
│ │ - GPU NVIDIA│ │ - GPU NVIDIA│ │ - GPU NVIDIA│ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┴────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ VM-CLAUDE │ │
│ │ Monitoring & Ctrl │ │
│ └───────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
| VM | Hostname | IP suggérée | GPU | Rôle |
|---|---|---|---|---|
| WIN-3D-01 | win3d-01 | 192.168.X.50 | NVIDIA RTX | Rendu 3D #1 |
| WIN-3D-02 | win3d-02 | 192.168.X.51 | NVIDIA RTX | Rendu 3D #2 |
| WIN-3D-03 | win3d-03 | 192.168.X.52 | NVIDIA RTX | Rendu 3D #3 |
| ... | ... | ... | ... | ... |
# install-ssh-windows.ps1
# À exécuter en tant qu'Administrateur sur chaque VM Windows
# Installation OpenSSH Server
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
# Démarrage et activation automatique
Start-Service sshd
Set-Service -Name sshd -StartupType 'Automatic'
# Firewall - autoriser SSH
New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
# Configuration sshd_config pour authentification par clé
$sshdConfig = @"
# Configuration OpenSSH pour Rhinov
Port 22
ListenAddress 0.0.0.0
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
# Shell par défaut : PowerShell
Subsystem sftp sftp-server.exe
"@
$sshdConfig | Out-File -Encoding utf8 "C:\ProgramData\ssh\sshd_config" -Force
# Création utilisateur rhinov (compte de service pour Claude)
$Password = ConvertTo-SecureString "MotDePasseTemporaire123!" -AsPlainText -Force
New-LocalUser -Name "rhinov" -Password $Password -PasswordNeverExpires -UserMayNotChangePassword
Add-LocalGroupMember -Group "Administrators" -Member "rhinov"
# Création dossier .ssh pour rhinov
$sshPath = "C:\Users\rhinov\.ssh"
New-Item -ItemType Directory -Path $sshPath -Force
# Placeholder pour la clé publique Claude (à remplacer)
$authorizedKeys = @"
"@
$authorizedKeys | Out-File -Encoding utf8 "$sshPath\authorized_keys" -Force
# Permissions sur authorized_keys (important pour Windows)
icacls "$sshPath\authorized_keys" /inheritance:r /grant "rhinov:F" /grant "SYSTEM:F"
# Redémarrage du service
Restart-Service sshd
Write-Host "OpenSSH Server installé et configuré."
Write-Host "Ajouter la clé publique Claude dans C:\Users\rhinov\.ssh\authorized_keys"
# install-bonjour.ps1
# Permet la résolution .local (mDNS)
# Télécharger Bonjour Print Services (contient mDNS)
$bonjourUrl = "https://support.apple.com/kb/DL999"
# Ou utiliser le lien direct si disponible
# Alternative : installer via winget si disponible
winget install Apple.Bonjour
# Vérification
Test-NetConnection -ComputerName "claude.local" -Port 22
#!/bin/bash
# setup-windows-ssh.sh
# À exécuter depuis VM-CLAUDE pour configurer l'accès aux VMs Windows
set -e
WINDOWS_VMS=("192.168.X.50" "192.168.X.51" "192.168.X.52")
SSH_KEY="/home/rhinov/.ssh/id_ed25519.pub"
WIN_USER="rhinov"
WIN_PASS="MotDePasseTemporaire123!" # À changer après setup
for VM_IP in "${WINDOWS_VMS[@]}"; do
echo "Configuration de $VM_IP..."
# Copie de la clé publique
sshpass -p "$WIN_PASS" ssh -o StrictHostKeyChecking=no $WIN_USER@$VM_IP "mkdir -p C:\Users\$WIN_USER\.ssh"
sshpass -p "$WIN_PASS" scp $SSH_KEY $WIN_USER@$VM_IP:"C:\Users\\$WIN_USER\\.ssh\\authorized_keys"
# Test de connexion sans mot de passe
ssh -o PasswordAuthentication=no $WIN_USER@$VM_IP "hostname"
echo "$VM_IP configuré avec succès."
done
echo "Toutes les VMs Windows sont accessibles via SSH."
# Connexion SSH à une VM Windows
ssh rhinov@win3d-01.local
# Exécuter une commande PowerShell
ssh rhinov@win3d-01.local "powershell -Command Get-Process"
# Vérifier l'état GPU
ssh rhinov@win3d-01.local "nvidia-smi"
# Lister les jobs de rendu en cours
ssh rhinov@win3d-01.local "powershell -Command Get-Process | Where-Object {$_.Name -like '*3dsmax*' -or $_.Name -like '*blender*'}"
# Redémarrer une VM (avec précaution)
ssh rhinov@win3d-01.local "shutdown /r /t 60 /c 'Redémarrage planifié par Claude'"
# Annuler un redémarrage
ssh rhinov@win3d-01.local "shutdown /a"
# Copier un fichier vers Windows
scp /path/to/file rhinov@win3d-01.local:"C:\Rendu\input\"
# Récupérer un rendu
scp rhinov@win3d-01.local:"C:\Rendu\output\scene.png" /local/path/
#!/bin/bash
# monitor-windows-3d.sh
# Collecte les métriques des VMs de rendu
WINDOWS_VMS=("win3d-01.local" "win3d-02.local" "win3d-03.local")
OUTPUT_FILE="/mnt/rhinov-stack/monitoring/windows-3d.json"
echo "[" > $OUTPUT_FILE
for i in "${!WINDOWS_VMS[@]}"; do
VM="${WINDOWS_VMS[$i]}"
# Collecte des infos
HOSTNAME=$(ssh rhinov@$VM "hostname" 2>/dev/null || echo "offline")
GPU_USAGE=$(ssh rhinov@$VM "nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits" 2>/dev/null || echo "N/A")
GPU_MEM=$(ssh rhinov@$VM "nvidia-smi --query-gpu=memory.used,memory.total --format=csv,noheader,nounits" 2>/dev/null || echo "N/A")
CPU_USAGE=$(ssh rhinov@$VM "powershell -Command \"(Get-Counter '\\Processor(_Total)\\% Processor Time').CounterSamples.CookedValue\"" 2>/dev/null || echo "N/A")
# JSON
COMMA=""
[[ $i -lt $((${#WINDOWS_VMS[@]}-1)) ]] && COMMA=","
cat >> $OUTPUT_FILE << EOF
{
"hostname": "$HOSTNAME",
"vm": "$VM",
"gpu_usage": "$GPU_USAGE",
"gpu_memory": "$GPU_MEM",
"cpu_usage": "$CPU_USAGE",
"timestamp": "$(date -Iseconds)"
}$COMMA
EOF
done
echo "]" >> $OUTPUT_FILE
# Ajouter dans template.env
# -----------------------------
# VMs WINDOWS RENDU 3D
# -----------------------------
WIN3D_USER=rhinov
WIN3D_PASSWORD= # À définir (ou vide si clé SSH)
WIN3D_01_IP=192.168.X.50
WIN3D_01_HOSTNAME=win3d-01
WIN3D_01_GPU=RTX3090
WIN3D_02_IP=192.168.X.51
WIN3D_02_HOSTNAME=win3d-02
WIN3D_02_GPU=RTX3090
# Ajouter autant de VMs que nécessaire...
En attente de validation pour générer le ZIP final.