$ fondamentaux bash
Comprendre Bash
de zéro à partout
Un cours pratique sur le shell universel : macOS, Linux, VPS, CI/CD, conteneurs Docker, agents LLM. Mêmes commandes, tous les environnements.
Bash est un interpréteur de commandes — un programme qui lit des instructions en texte et les exécute sur le système d'exploitation Linux. Quand tu ouvres un terminal et tu tapes quelque chose, c'est bash qui lit, comprend et exécute.
Le nom signifie Bourne Again SHell — une réécriture du shell original Unix des années 70. Il est présent sur absolument tous les serveurs Linux sans aucune installation requise. C'est pourquoi tous les guides, scripts d'installation, Docker, Coolify, et les agents LLM s'appuient dessus.
Analogie simple : Bash est au serveur Linux ce que le terminal Windows est à Windows — mais beaucoup plus puissant, universel, et scriptable.
Sur ton PC portable sous macOS ou Linux, c'est bash (ou zsh, son cousin direct) que tu utilises dans le terminal. Sur ton VPS OVH, c'est bash que tu pilotes via SSH. Même commandes, même logique, partout.
C'est le point crucial que beaucoup ratent : bash ne vit pas que sur les serveurs. C'est le même shell, les mêmes commandes, dans cinq environnements complètement différents. Reconnaître le contexte dans lequel tu es, c'est comprendre ce que tu peux faire — et avec quels droits.
# 1 — Sur ton Mac ou Linux local
ulrich@macbook:~/projets$ ← Terminal.app, iTerm2, Warp, VS Code...
# 2 — Sur Windows via WSL (Windows Subsystem for Linux)
ulrich@DESKTOP:/mnt/c/Users/ulrich$ ← bash natif dans Windows 10/11
# 3 — Sur un VPS distant en SSH
ulrich@vps:~$ ← connexion SSH à un serveur OVH, Hetzner, AWS...
# 4 — Dans un pipeline CI/CD (GitHub Actions, GitLab CI...)
runner@fv-az756:~/work/mon-repo$ ← bash lancé automatiquement à chaque push
# 5 — À l'intérieur d'un conteneur Docker
root@a3f1b2c:/app# ← après docker exec -it mon-conteneur bash
L'essentiel : La commande ls -la fait exactement la même chose dans les 5 contextes. Ce qui change : qui a lancé bash, sur quelle machine, avec quels droits. Le prompt te dit tout ça en un coup d'œil.
Ta stack MultiBrasServices — bash en dessous de tout
Toi (SSH) ──→ Bash (shell) ──→ Linux kernel
↑ tu tapes ici ↑ traduit ↑ exécute vraiment
Bash ──→ Docker ──→ Coolify / n8n / Supabase
commandes docker tes conteneurs tournent ici
Claude (LLM) ──→ bash_tool ──→ Bash ──→ Linux
↑ outil connecté ↑ même shell, même puissance
Bash n'est pas "à côté" de Docker ou Coolify — il est en dessous. Quand Coolify installe un service, il exécute des commandes bash. Quand un agent LLM agit sur une machine, il passe par bash. La boîte noire devient transparente dès que tu comprends le shell.
Clé de compréhension : Quand Claude utilise bash_tool dans cette conversation, il fait exactement ce que tu ferais en tapant dans ton terminal SSH. Même niveau d'accès, même commandes.
Avant de taper quoi que ce soit, il faut savoir lire ce qu'on voit dans un terminal. Chaque partie a un sens précis.
Le prompt — ce qui s'affiche avant ta commande
ulrich@vps:~$ ma-commande
# ↑↑↑↑↑↑ ↑ ↑↑↑ ↑ ↑ ↑
# user @ host : ~ $
# ulrich → l'utilisateur connecté
# @vps → le nom de la machine (ton VPS OVH)
# :~ → le dossier courant (~ = ton home /home/ulrich)
# $ → tu es un utilisateur normal (# = tu es root/admin)
# Exemples de prompts selon où tu es :
ulrich@vps:~$ ← dans /home/ulrich
ulrich@vps:/opt/coolify$ ← dans /opt/coolify
root@vps:/# ← connecté en root, à la racine du système
La structure d'une commande
$ docker logs -f --tail 100 n8n
# ↑ ↑ ↑ ↑ ↑
# commande sous- option option longue argument
# cmd courte avec valeur (nom du conteneur)
# Options courtes : -f -r -la -n 50
# Options longues : --follow --recursive --tail=100
# On peut souvent combiner : -la = -l + -a
# Exemples réels :
$ ls -la /opt/coolify ← commande + options + argument
$ tail -f -n 50 app.log ← 2 options + argument
$ docker ps ← commande + sous-commande, pas d'argument
Les chemins : absolu vs relatif
# Chemin ABSOLU — commence toujours par /
# Même résultat peu importe où tu es
$ cd /opt/coolify ← va dans /opt/coolify depuis n'importe où
$ cat /home/ulrich/.env
# Chemin RELATIF — part de là où tu es actuellement
ulrich@vps:/opt$ cd coolify ← = cd /opt/coolify
ulrich@vps:/opt$ cd ../home ← remonte d'un niveau, va dans home
ulrich@vps:/opt$ ./script.sh ← exécute script.sh dans le dossier courant
Les caractères spéciaux
| Caractère | Signification |
| ~ | Ton dossier home (/home/ulrich). cd ~ te ramène toujours chez toi. |
| . | Le dossier courant. ./script.sh = exécute dans le dossier où tu es. |
| .. | Le dossier parent. cd .. remonte d'un niveau. |
| * | Wildcard — correspond à n'importe quoi. *.log = tous les fichiers .log. |
| $ | Préfixe d'une variable. $NOM = valeur de la variable NOM. |
| # | Commentaire dans un script. Tout ce qui suit est ignoré par bash. |
| / | Séparateur de chemin ET racine du système (/ seul = la racine absolue). |
| - | Préfixe d'une option courte (-f), ou chemin spécial : cd - retourne au dossier précédent. |
Le code de retour — bash sait si ça a marché
# Après chaque commande, bash stocke un code dans $?
# 0 = succès | tout autre nombre = erreur
$ docker ps
CONTAINER ID IMAGE ...
$ echo $?
0 ← succès
$ cat fichier-inexistant.txt
cat: fichier-inexistant.txt: No such file or directory
$ echo $?
1 ← erreur
# C'est pour ça que && fonctionne :
apt update && apt upgrade ← upgrade seulement si update renvoie 0
À retenir : Le prompt te dit qui tu es, où tu es et avec quels droits. Avant de taper une commande destructive, lis ton prompt — deux secondes qui évitent de supprimer le mauvais dossier.
Les commandes de base pour te déplacer et manipuler des fichiers. À maîtriser absolument — tu les utiliseras tous les jours, que tu sois dans ton terminal local, sur un VPS en SSH ou à l'intérieur d'un conteneur.
| Commande | Description |
| pwd | Affiche le dossier où tu es (Print Working Directory) |
| ls | Liste le contenu du dossier courant |
| ls -la | Liste détaillée avec fichiers cachés, tailles, droits |
| cd /chemin | Va dans un dossier (chemin absolu) |
| cd .. | Remonte d'un niveau |
| cd ~ | Va dans ton dossier personnel (home) |
| mkdir mon-dossier | Crée un dossier |
| mkdir -p a/b/c | Crée plusieurs niveaux de dossiers d'un coup |
| cp fichier copie | Copie un fichier |
| cp -r dossier/ dest/ | Copie un dossier entier (récursif) |
| mv ancien nouveau | Renomme ou déplace un fichier/dossier |
| touch fichier.txt | Crée un fichier vide |
bash — local (macOS / Linux)
# Où suis-je ? — même commande partout
ulrich@macbook:~$ pwd
/Users/ulrich ← macOS
ulrich@ubuntu:~$ pwd
/home/ulrich ← Linux / WSL
# Naviguer dans un projet local
ulrich@macbook:~$ ls -la ~/projets/zoomali
drwxr-xr-x src/
-rw-r--r-- package.json
-rw------- .env
# Créer une structure de projet — local ou VPS, c'est identique
ulrich@macbook:~$ mkdir -p projets/zoomali/backup
ulrich@macbook:~$ cd projets/zoomali && touch .env
# Sur VPS : exactement les mêmes commandes
ulrich@vps:~$ ls -la /opt/coolify
drwxr-xr-x coolify/
-rw-r--r-- docker-compose.yml
Indispensable partout : inspecter des logs en local pendant le dev, des configs sur un VPS, des fichiers à l'intérieur d'un conteneur. Même commandes, tous les contextes.
| Commande | Description |
| cat fichier | Affiche le contenu entier d'un fichier |
| less fichier | Lecture paginée (q pour quitter, / pour chercher) |
| head -n 20 fichier | Affiche les 20 premières lignes |
| tail -n 50 fichier | Affiche les 50 dernières lignes |
| tail -f fichier | Suit le fichier en temps réel — parfait pour les logs live |
| grep "mot" fichier | Cherche "mot" dans un fichier |
| grep -r "mot" dossier/ | Cherche récursivement dans tous les fichiers |
| find / -name "*.env" | Trouve tous les fichiers .env sur le système |
# Voir les logs de n8n en direct (Ctrl+C pour arrêter)
ulrich@vps:~$ docker logs -f n8n
[2026-05-11] INFO Workflow executed successfully
[2026-05-11] INFO Webhook received...
# Chercher une erreur dans les logs Coolify
ulrich@vps:~$ grep -r "ERROR" /var/log/coolify/
app.log: ERROR Connection refused on port 5432
# Inspecter un .env sans éditeur
ulrich@vps:~$ cat /opt/n8n/.env
N8N_PORT=5678
DB_TYPE=postgresdb
Astuce universelle : tail -f est ta meilleure arme pour déboguer. Lance-le sur un fichier de log local, les logs Docker, ou les logs d'un service système — les erreurs apparaissent en direct, peu importe l'environnement.
Comprendre ce qui tourne sur ta machine. Crucial quand un port est occupé, qu'un service ne répond plus, ou que la RAM est pleine.
| Commande | Description |
| ps aux | Liste tous les processus en cours |
| top | Vue temps réel CPU/RAM (q pour quitter) |
| htop | Version améliorée de top (à installer via apt) |
| kill PID | Arrête un processus par son numéro |
| kill -9 PID | Force l'arrêt immédiat (ne peut pas être ignoré) |
| lsof -i :8080 | Quel processus utilise le port 8080 ? |
| systemctl status nginx | État d'un service système |
| docker ps | Conteneurs Docker actifs |
| docker ps -a | Tous les conteneurs (même arrêtés) |
| df -h | Espace disque disponible |
| free -h | RAM utilisée / disponible |
bash — diagnostic port occupé
# Mon service ne démarre pas — port 3000 occupé ?
ulrich@vps:~$ lsof -i :3000
COMMAND PID USER FD TYPE NODE NAME
node 1423 ulrich 12u IPv4 TCP *:3000 (LISTEN)
# Je vois le PID 1423 — je vérifie ce que c'est
ulrich@vps:~$ ps aux | grep 1423
ulrich 1423 node /opt/old-app/server.js
# Vieux process oublié — je l'arrête proprement
ulrich@vps:~$ kill 1423
Sur Linux, chaque fichier a des droits de lecture (r), écriture (w) et exécution (x) pour son propriétaire, son groupe, et les autres. Beaucoup d'erreurs mystérieuses sur un VPS viennent de permissions incorrectes.
ulrich@vps:~$ ls -la
-rwxr-xr-- ulrich staff backup.sh
# ↑↑↑ propriétaire : peut lire, écrire, exécuter
# ↑↑↑ groupe : peut lire et exécuter
# ↑↑↑ autres : peuvent seulement lire
| Commande | Description |
| chmod +x script.sh | Rend un script exécutable |
| chmod 644 fichier | rw-r--r-- : lisible par tous, modifiable seulement par toi |
| chmod 600 .env | rw------- : uniquement toi (idéal pour les secrets) |
| chmod 755 dossier/ | rwxr-xr-x : standard pour les dossiers web |
| chown user:group fichier | Change le propriétaire d'un fichier |
| sudo commande | Exécute en tant que super-administrateur |
| whoami | Affiche l'utilisateur courant |
Bonne pratique : Tes fichiers .env contenant des clés API et mots de passe doivent toujours être en chmod 600. Jamais lisibles par d'autres utilisateurs sur le serveur.
Le vrai superpouvoir de bash : enchaîner des commandes. La sortie d'une commande devient l'entrée de la suivante. C'est ce qui permet d'écrire des one-liners très puissants.
| Symbole | Description |
| cmd1 | cmd2 | Pipe — envoie la sortie de cmd1 vers cmd2 |
| cmd > fichier | Redirige la sortie vers un fichier (écrase le contenu) |
| cmd >> fichier | Ajoute à la fin du fichier sans écraser |
| cmd 2> erreurs.log | Redirige uniquement les erreurs vers un fichier |
| cmd1 && cmd2 | Exécute cmd2 seulement si cmd1 réussit (code 0) |
| cmd1 || cmd2 | Exécute cmd2 seulement si cmd1 échoue |
# Compter les erreurs dans les logs Docker
ulrich@vps:~$ docker logs n8n | grep "error" | wc -l
7
# Archiver les logs du jour
ulrich@vps:~$ docker logs n8n >> /var/log/n8n-backup.log
# Mettre à jour ET redémarrer — seulement si la mise à jour réussit
ulrich@vps:~$ apt update && apt upgrade -y && systemctl restart nginx
# Filtrer les conteneurs actifs par nom
ulrich@vps:~$ docker ps | grep coolify
a3f1b coolify:latest Up 3 days 0.0.0.0:8000->8000/tcp coolify
# En CI/CD (GitHub Actions) — exactement la même logique
# Vérifier que les tests passent ET builder l'image seulement si OK
runner@fv-az756:~/work/mon-repo$ npm test && docker build -t mon-app .
# En local — trouver les fichiers modifiés récemment et les lister
ulrich@macbook:~/projets$ find . -name "*.js" -mtime -1 | grep -v node_modules
./src/api.js
./src/utils/format.js
Dès que tu répètes plus de 3 commandes, pense à un script. Un fichier .sh automatise des tâches récurrentes — backups, déploiements, maintenance du VPS.
# Assigner une variable (pas d'espaces autour du =)
ulrich@vps:~$ NOM="zoomali"
ulrich@vps:~$ echo "Projet : $NOM"
Projet : zoomali
# Variable calculée : résultat d'une commande
ulrich@vps:~$ DATE=$(date +%Y%m%d)
ulrich@vps:~$ echo $DATE
20260511
#!/bin/bash
# Script de backup automatique — MultiBrasServices
DB_NAME="zoomali_prod"
BACKUP_DIR="/opt/backups"
DATE=$(date +%Y%m%d_%H%M)
mkdir -p $BACKUP_DIR
# Supabase tourne dans Docker — on passe par docker exec
docker exec supabase-db pg_dump -U postgres $DB_NAME \
> $BACKUP_DIR/backup_$DATE.sql
echo "✅ Backup terminé : backup_$DATE.sql"
bash — exécuter le script
# Rendre le script exécutable (une seule fois)
ulrich@vps:~$ chmod +x backup-supabase.sh
# Lancer
ulrich@vps:~$ ./backup-supabase.sh
✅ Backup terminé : backup_20260511_2130.sql
Bash n'est pas qu'une suite de commandes — c'est un vrai langage de programmation. Il possède des conditions, des boucles et des fonctions. Maîtriser ces constructions, c'est passer du copier-coller à l'écriture de scripts robustes et automatisés.
Conditions — if / elif / else
# Structure de base
if [ condition ]; then
# commandes si vrai
elif [ autre_condition ]; then
# commandes si autre_condition
else
# commandes si faux
fi ← ferme le bloc (if à l'envers)
# Tester si le fichier .env de n8n existe
if [ -f /opt/n8n/.env ]; then
echo "✅ .env présent"
else
echo "❌ .env manquant — arrêt"
exit 1 ← quitte le script avec code d'erreur
fi
# Tester si le conteneur n8n tourne (-q = silencieux, pas de sortie)
if docker ps | grep -q "n8n"; then
echo "n8n est actif"
else
docker start n8n
fi
| Test | Signification |
| -f fichier | Le fichier existe (et est un fichier régulier) |
| -d dossier | Le dossier existe |
| -z "$VAR" | La variable est vide |
| -n "$VAR" | La variable n'est pas vide |
| cmd | grep -q "mot" | La commande produit une ligne contenant "mot" |
| ! condition | Inverse la condition (NOT) |
Boucles — for & while
# for — itérer sur une liste de fichiers
for fichier in /opt/backups/*.sql; do
echo "Trouvé : $fichier"
done
# for — vérifier plusieurs conteneurs d'un coup
for service in n8n coolify supabase-db; do
if ! docker ps | grep -q "$service"; then
echo "⚠ $service arrêté — relance"
docker start "$service"
fi
done
# while — retry logic : réessayer jusqu'à 3 fois
tentatives=0
while [ $tentatives -lt 3 ]; do
docker start n8n && break ← break = sort de la boucle si succès
tentatives=$((tentatives + 1))
sleep 5
done
Fonctions
bash — déclarer et appeler une fonction
# Déclaration — $1 = premier argument passé à la fonction
log() {
echo "$(date '+%Y-%m-%d %H:%M') — $1" >> /var/log/watchdog.log
}
# Appels
log "n8n relancé"
log "backup démarré"
# Résultat dans le fichier de log :
2026-05-12 02:00 — n8n relancé
2026-05-12 02:01 — backup démarré
La frontière bash / Python / n8n
Bash est puissant pour orchestrer le système. Mais dès que la logique se complexifie, il devient fragile. Dans ta stack, la frontière est claire :
Bash → orchestration système, lancement de services, glue entre outils
Python → traitement de données, manipulation JSON/CSV, appels API complexes
n8n → workflows métier, intégrations visuelles, logique conditionnelle avancée
Règle empirique : Si ton script bash dépasse ~50 lignes ou commence à parser du JSON avec grep, c'est le signal de basculer en Python. Si tu relies plusieurs services avec de la logique métier, c'est du n8n.
Exemple intégrateur — watchdog n8n complet
Ce script combine tout ce que tu viens d'apprendre : une fonction de log, un if, et une action de relance. Il vérifie si n8n tourne et le redémarre si besoin.
#!/bin/bash
# Watchdog n8n — relance automatique si le conteneur s'arrête
# Fonction de log horodaté
log() {
echo "$(date '+%Y-%m-%d %H:%M') — $1" >> /var/log/watchdog.log
}
# Si n8n ne tourne pas → relancer et loguer
if ! docker ps | grep -q "n8n"; then
docker start n8n
log "n8n arrêté — relancé automatiquement"
fi
bash — déployer le watchdog
# Placer le script dans le dossier dédié
ulrich@vps:~$ cp watchdog-n8n.sh /opt/scripts/watchdog-n8n.sh
ulrich@vps:~$ chmod +x /opt/scripts/watchdog-n8n.sh
# Tester manuellement avant de le planifier
ulrich@vps:~$ /opt/scripts/watchdog-n8n.sh
ulrich@vps:~$ cat /var/log/watchdog.log
2026-05-12 14:22 — n8n arrêté — relancé automatiquement
Planifier ce script : Pour que ce watchdog tourne automatiquement toutes les 5 minutes, il suffit d'une ligne crontab — voir Section 13 — Cron & automatisation.
Sur ta stack, tout tourne dans des conteneurs. docker exec te permet d'entrer dans un conteneur en cours d'exécution — indispensable pour inspecter, déboguer, ou lancer une commande directement à l'intérieur.
| Commande | Description |
| docker exec -it nom bash | Ouvre un shell interactif dans le conteneur |
| docker exec -it nom sh | Même chose avec sh (si bash n'est pas installé dans l'image) |
| docker exec nom commande | Exécute une commande unique sans ouvrir de shell |
| exit | Quitter le shell du conteneur et revenir sur le VPS |
-it expliqué : -i = mode interactif (garde stdin ouvert), -t = alloue un pseudo-terminal. Sans ces deux flags, le shell s'ouvre et se referme immédiatement.
bash — entrer dans les conteneurs de ta stack
# Entrer dans n8n pour inspecter les fichiers ou l'environnement
ulrich@vps:~$ docker exec -it n8n sh
/home/node $ ← tu es maintenant DANS le conteneur n8n
/home/node $ ls /home/node/.n8n/
database.sqlite config workflows/
/home/node $ exit ← retour sur le VPS
# Entrer dans le conteneur Postgres de Supabase
ulrich@vps:~$ docker exec -it supabase-db bash
root@supabase-db:/# psql -U postgres
postgres=# \dt ← liste les tables
postgres=# \q ← quitte psql
root@supabase-db:/# exit
# Exécuter une commande unique sans ouvrir de shell interactif
ulrich@vps:~$ docker exec supabase-db pg_dump -U postgres zoomali_prod \
> /opt/backups/dump_manuel.sql
# Inspecter les variables d'environnement d'un conteneur
ulrich@vps:~$ docker exec coolify env | grep PORT
PORT=8000
METRICS_PORT=9090
Astuce : Si tu ne connais pas le nom exact du conteneur, commence par docker ps — la colonne NAMES à droite te donne le nom à utiliser dans docker exec.
Sur un VPS en SSH, il n'y a pas d'interface graphique. Pour modifier un fichier de config, un script ou un .env, tu as besoin d'un éditeur en ligne de commande. nano est le plus simple — il affiche ses raccourcis en bas de l'écran.
# Ouvrir un fichier existant (ou le créer s'il n'existe pas)
ulrich@vps:~$ nano /opt/n8n/.env
# Tu es maintenant dans l'éditeur — les raccourcis s'affichent en bas
GNU nano 7.2 /opt/n8n/.env
N8N_PORT=5678
DB_TYPE=postgresdb
DB_HOST=supabase-db
^G Help ^O Write ^X Exit ^W Search ^K Cut ^U Paste
| Raccourci | Action |
| Ctrl + O | Sauvegarder le fichier (Write Out) — confirme avec Entrée |
| Ctrl + X | Quitter (propose de sauvegarder si modifications non enregistrées) |
| Ctrl + W | Chercher un mot dans le fichier |
| Ctrl + K | Couper la ligne entière |
| Ctrl + U | Coller la ligne coupée |
| Ctrl + G | Aide complète |
Le piège vim : Si tu tapes vi ou vim par accident et que tu ne sais pas en sortir — tape :q! puis Entrée. Le ! force la sortie sans sauvegarder. Pour sortir en sauvegardant : :wq puis Entrée.
flux typique — modifier un .env
# 1. Sauvegarder l'original avant de toucher quoi que ce soit
ulrich@vps:~$ cp /opt/n8n/.env /opt/n8n/.env.bak
# 2. Éditer
ulrich@vps:~$ nano /opt/n8n/.env
# → modifier la valeur, Ctrl+O pour sauver, Ctrl+X pour quitter
# 3. Redémarrer le service pour appliquer
ulrich@vps:~$ docker restart n8n
Cron est le planificateur de tâches de Linux. Il permet d'exécuter un script automatiquement à une heure précise, tous les jours, toutes les semaines, ou selon n'importe quelle fréquence. Indispensable pour les backups, la rotation de logs, et la maintenance du VPS.
La syntaxe crontab
* * * * * commande
│ │ │ │ │
│ │ │ │ └── jour de la semaine (0=dim, 6=sam)
│ │ │ └────── mois (1-12)
│ │ └────────── jour du mois (1-31)
│ └────────────── heure (0-23)
└────────────────── minute (0-59)
| Commande | Description |
| crontab -e | Éditer tes tâches cron (ouvre nano ou vim) |
| crontab -l | Lister tes tâches cron actives |
| crontab -r | Supprimer toutes tes tâches cron (dangereux — pas de confirmation) |
| systemctl status cron | Vérifier que le démon cron tourne bien |
crontab — exemples pratiques MultiBrasServices
# Éditer le crontab
ulrich@vps:~$ crontab -e
# Backup Supabase tous les jours à 2h du matin
0 2 * * * /opt/scripts/backup-supabase.sh >> /var/log/backup.log 2>&1
# Nettoyer les vieux backups (garder 30 jours) — tous les dimanches à 3h
0 3 * * 0 find /opt/backups -name "*.sql" -mtime +30 -delete
# Redémarrer n8n tous les lundis à 4h (maintenance hebdo)
0 4 * * 1 /usr/bin/docker restart n8n
# Vérifier les tâches actives
ulrich@vps:~$ crontab -l
0 2 * * * /opt/scripts/backup-supabase.sh >> /var/log/backup.log 2>&1
0 3 * * 0 find /opt/backups -name "*.sql" -mtime +30 -delete
Bonne pratique : Toujours rediriger la sortie d'une tâche cron vers un fichier de log avec >> /var/log/ma-tache.log 2>&1. Sans ça, tu ne sauras jamais si la tâche a réussi ou échoué.
Chemins absolus obligatoires : Dans un crontab, le $PATH est minimal — les commandes comme docker ne sont pas forcément trouvées. Utilise toujours le chemin complet : /usr/bin/docker ou /opt/scripts/backup.sh.
SSH (Secure Shell) est le protocole qui te connecte à ton VPS depuis ton PC. Par défaut on se connecte avec un mot de passe — mais les clés SSH sont plus sûres et bien plus confortables : plus de mot de passe à taper.
Générer et déployer une clé SSH
SSH — depuis ton PC local
# 1. Générer une paire de clés (à faire UNE FOIS sur ton PC)
ulrich@pc:~$ ssh-keygen -t ed25519 -C "ulrich@multibrasservices"
Generating public/private ed25519 key pair.
Enter file in which to save the key: ~/.ssh/id_ed25519
Enter passphrase (empty for no passphrase): ← optionnel mais recommandé
# Résultat : 2 fichiers créés
~/.ssh/id_ed25519 ← clé PRIVÉE — ne jamais partager
~/.ssh/id_ed25519.pub ← clé publique — à copier sur le serveur
# 2. Copier la clé publique sur le VPS OVH
ulrich@pc:~$ ssh-copy-id -i ~/.ssh/id_ed25519.pub ulrich@vps.ovh.net
/usr/bin/ssh-copy-id: 1 key(s) added
# 3. Connexion sans mot de passe désormais
ulrich@pc:~$ ssh ulrich@vps.ovh.net
Welcome to Ubuntu 24.04 LTS
ulrich@vps:~$
Le fichier ~/.ssh/config — simplifier les connexions
Plutôt que de taper ssh ulrich@xxx.xxx.xxx.xxx à chaque fois, configure des alias dans ~/.ssh/config sur ton PC :
~/.ssh/config — sur ton PC
# VPS OVH principal
Host ovh
HostName xxx.xxx.xxx.xxx ← IP du VPS
User ulrich
IdentityFile ~/.ssh/id_ed25519
Port 22
# Maintenant tu te connectes simplement avec :
ulrich@pc:~$ ssh ovh ← au lieu de ssh ulrich@xxx.xxx.xxx.xxx
| Commande | Description |
| ssh user@host | Connexion SSH basique |
| ssh -p 2222 user@host | Connexion sur un port non standard |
| scp fichier user@host:/dest/ | Copier un fichier local vers le VPS |
| scp user@host:/src/fichier ./ | Copier un fichier du VPS vers ton PC |
| ssh-keygen -t ed25519 | Générer une paire de clés moderne (ed25519 > rsa) |
| cat ~/.ssh/id_ed25519.pub | Afficher ta clé publique à coller dans un panneau de contrôle |
Migration vers OVH : Pour chaque nouveau VPS OVH, copie le contenu de ~/.ssh/id_ed25519.pub dans le panneau OVH lors de la création du VPS — la clé sera déjà en place dès la première connexion, sans passer par ssh-copy-id.
Sécurité : Une fois les clés en place, désactive l'authentification par mot de passe dans /etc/ssh/sshd_config (PasswordAuthentication no). Un VPS avec SSH par mot de passe est une cible constante de brute-force.
Hermes Agent est un agent IA auto-hébergé conçu par Nous Research. Il tourne sur ton VPS, apprend de vos échanges, planifie des tâches, pilote Docker, et se connecte à Telegram ou Discord. Pourquoi en parler ici ? Parce que tout ce que tu viens d'apprendre dans ce cours, c'est exactement ce qui le fait tourner en coulisses.
Toi (SSH) ──→ Bash ──→ Docker ──→ Hermes Agent
↓ ↓
cron scheduler Telegram / Discord
backups Supabase n8n · Coolify
Installation — un `curl | bash` décortiqué
bash — installer Hermes sur le VPS OVH
# La commande d'installation officielle
ulrich@vps:~$ curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
# Ce que cette ligne fait concrètement :
# curl -fsSL → télécharge le script d'installation (-f = échoue proprement,
# -s = silencieux, -S = affiche erreurs, -L = suit les redirections)
# | → pipe : envoie le script téléchargé directement à bash
# bash → exécute le script reçu
# L'installateur configure automatiquement : Python, Node.js, ripgrep, ffmpeg
Installing Hermes Agent...
✓ uv installed
✓ Python 3.11 ready
✓ hermes command available
# Recharger le shell (section 03 — variables d'environnement)
ulrich@vps:~$ source ~/.bashrc
ulrich@vps:~$ hermes
Opérer Hermes au quotidien — tu reconnais tout
bash — maintenance Hermes sur VPS
# Voir les logs en direct (section 05 — tail -f)
ulrich@vps:~$ tail -f ~/.hermes/logs/hermes.log
[2026-05-12] INFO Skill created: backup-supabase
[2026-05-12] INFO Cron job scheduled: 0 2 * * *
# Chercher une erreur dans les logs (section 05 — grep)
ulrich@vps:~$ grep "ERROR" ~/.hermes/logs/hermes.log
# Entrer dans le conteneur Hermes pour inspecter (section 11 — docker exec)
ulrich@vps:~$ docker exec -it hermes bash
root@hermes:~# ls ~/.hermes/skills/
backup-supabase.md deploy-coolify.md morning-report.md
root@hermes:~# exit
# Redémarrer Hermes sans perdre les sessions (section 06 — docker)
ulrich@vps:~$ docker restart hermes
# Vérifier les tâches cron créées par Hermes (section 13 — crontab)
ulrich@vps:~$ crontab -l
0 2 * * * /opt/scripts/backup-supabase.sh >> /var/log/backup.log 2>&1
0 7 * * * hermes run morning-report
# Mettre à jour Hermes (section 04 — commandes simples)
ulrich@vps:~$ hermes update
La boucle complète — ce que Hermes fait la nuit pendant que tu dors
02:00 → cron → backup-supabase.sh → docker exec supabase-db pg_dump
↓ fichier sauvegardé dans /opt/backups/
03:00 → cron → find /opt/backups -mtime +30 -delete → rotation automatique
07:00 → cron → hermes run morning-report → résumé Telegram
Ce que ce cours t'a donné : Quand Hermes crée un skill de backup ou planifie une tâche nocturne, tu sais maintenant exactement ce qui se passe sous le capot — le docker exec, le pg_dump, l'entrée crontab, le fichier de log. La boîte noire est ouverte.
Avant d'installer : Hermes a besoin d'accès à ton VPS via SSH (section 14) et tourne idéalement dans Docker (section 11). Maîtrise d'abord ces deux sections — tu auras besoin de déboguer si quelque chose ne démarre pas.
Bash n'est pas qu'un outil local — c'est aussi un client HTTP de premier plan. curl + jq permettent d'interroger n'importe quelle API REST directement depuis le terminal, sans Python ni Node.
Cas réel : Lors d'un audit de vérifications périodiques, les dates des derniers rapports ont été extraites d'une API SharePoint via fetch() dans le navigateur — même principe qu'un curl authentifié, même logique REST.
curl — l'essentiel
# GET simple
ulrich@vps:~$ curl https://api.example.com/data
# -s = silencieux (pas de barre de progression)
# -i = inclure les headers HTTP de réponse
ulrich@vps:~$ curl -si https://api.example.com/data
# POST avec corps JSON
ulrich@vps:~$ curl -X POST \
-H "Content-Type: application/json" \
-d '{"name":"ulrich","role":"admin"}' \
https://api.example.com/users
# -w affiche le code HTTP à la fin — utile dans les scripts
ulrich@vps:~$ curl -s -o /dev/null -w "%{http_code}" https://api.example.com/health
200
| Flag | Rôle |
| -s | Silencieux — supprime la barre de progression |
| -i | Affiche les headers de réponse |
| -X POST | Change la méthode HTTP (GET par défaut) |
| -H "..." | Ajoute un header (Content-Type, Authorization…) |
| -d "..." | Corps de la requête (POST/PUT/PATCH) |
| -o fichier | Écrit la réponse dans un fichier plutôt que stdout |
| -L | Suit les redirections (301, 302) |
| -w "%{http_code}" | Affiche le code HTTP de réponse |
jq — lire du JSON en bash
jq est le couteau suisse du JSON en ligne de commande. Il filtre, transforme et extrait les valeurs des réponses API.
jq — extraire des valeurs
# Installer jq
ulrich@vps:~$ apt install jq -y
# Réponse brute d'une API
ulrich@vps:~$ curl -s https://api.example.com/user
{"id":42,"name":"Ulrich","role":"admin","active":true}
# Extraire une valeur
ulrich@vps:~$ curl -s https://api.example.com/user | jq '.name'
"Ulrich"
# -r = raw (sans guillemets) — utile pour stocker dans une variable
ulrich@vps:~$ NAME=$(curl -s https://api.example.com/user | jq -r '.name')
ulrich@vps:~$ echo "Bonjour $NAME"
Bonjour Ulrich
# Tableau — extraire le premier élément
ulrich@vps:~$ echo '[{"nom":"RIA"},{"nom":"Extincteurs"}]' | jq '.[0].nom'
"RIA"
# Tableau — extraire une clé sur tous les éléments
ulrich@vps:~$ echo '[{"nom":"RIA"},{"nom":"Extincteurs"}]' | jq '.[].nom'
"RIA"
"Extincteurs"
# Filtrer + reformater
ulrich@vps:~$ curl -s https://api.example.com/items | jq '[.[] | select(.active==true) | .name]'
Authentification Bearer (JWT)
La majorité des APIs modernes utilisent un token Bearer dans le header Authorization. C'est le cas de Supabase, n8n, et de nombreuses APIs d'entreprise.
requête authentifiée par token
# Token stocké dans une variable (jamais en dur dans le script)
ulrich@vps:~$ TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
# Appel API avec Bearer token
ulrich@vps:~$ curl -s \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
https://api.example.com/protected/data | jq '.'
# Pattern Supabase — anon key en header
ulrich@vps:~$ curl -s \
-H "apikey: $SUPABASE_ANON_KEY" \
-H "Authorization: Bearer $SUPABASE_ANON_KEY" \
"$SUPABASE_URL/rest/v1/verifications?select=*" | jq '.[0]'
Cas concret — API SharePoint REST
SharePoint expose une API REST complète accessible via son URL de site. Voici le pattern utilisé pour lire les dossiers d'un bibliothèque de documents :
lire les dossiers d'une bibliothèque SharePoint
# Définir les variables
ulrich@vps:~$ SITE="https://tenant.sharepoint.com/sites/MonSite"
ulrich@vps:~$ FOLDER="Documents partages/QSE/L/Verifications Periodiques"
ulrich@vps:~$ TOKEN="$(cat ~/.sharepoint_token)"
# Lister les sous-dossiers avec leur date de modification
ulrich@vps:~$ curl -s \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json;odata=verbose" \
"$SITE/_api/web/GetFolderByServerRelativeUrl('$FOLDER')/Folders?\$select=Name,TimeLastModified&\$orderby=TimeLastModified desc" \
| jq -r '.d.results[] | "\(.TimeLastModified[:10]) \(.Name)"'
2026-03-30 Portes Automatiques
2026-03-10 Presse à carton
2026-01-20 Aérotherme
2025-12-08 Engins de Manutention
2025-11-21 Scie à panneau
# Récupérer seulement la date du dossier le plus récent
ulrich@vps:~$ ... | jq -r '.d.results[0].TimeLastModified[:10]'
2026-03-30
Note : SharePoint utilise le format OData (odata=verbose) — les résultats sont dans .d.results[] et non à la racine. Chaque API a ses propres conventions, lis toujours la doc ou inspecte la réponse brute avec jq '.' avant de filtrer.
Pattern complet — script de vérification
Un script bash réaliste qui interroge une API, vérifie le code HTTP, et traite la réponse :
#!/bin/bash
set -e
API_URL="https://api.example.com"
TOKEN="${API_TOKEN:?'Variable API_TOKEN manquante'}"
# Appel — code HTTP séparé du corps
HTTP_CODE=$(curl -s -o /tmp/api_response.json -w "%{http_code}" \
-H "Authorization: Bearer $TOKEN" \
"$API_URL/items")
# Vérifier le code HTTP
if [[ "$HTTP_CODE" -ne 200 ]]; then
echo "Erreur API : HTTP $HTTP_CODE" >&2
exit 1
fi
# Traiter la réponse
COUNT=$(jq 'length' /tmp/api_response.json)
echo "$COUNT éléments récupérés"
# Boucler sur les résultats
jq -r '.[] | "\(.id) - \(.name)"' /tmp/api_response.json | while read -r ligne; do
echo " › $ligne"
done
Lien avec Hermes : Quand Hermes déclenche un workflow n8n via webhook ou interroge Supabase depuis un script, c'est exactement ce pattern — curl -s -H "Authorization: Bearer $TOKEN" ... | jq '.field'. Bash devient le chef d'orchestre de toute ta stack.
💀 Pas de corbeille. Pas d'annulation. Immédiat.
Sur Linux, rm supprime définitivement. Il n'y a aucune corbeille, aucun Ctrl+Z possible. La suppression est instantanée et irréversible — même en production sur un VPS avec des clients.
Les différentes formes de rm et leurs niveaux de risque :
rm — du moins au plus dangereux
# Supprime un fichier unique — relativement sûr si tu vises bien
ulrich@vps:~$ rm fichier.txt
# -r = récursif, supprime un dossier et TOUT son contenu
ulrich@vps:~$ rm -r mon-dossier/
# -f = force, passe outre les protections, aucune confirmation
ulrich@vps:~$ rm -rf mon-dossier/
⚠ DANGER ABSOLU — supprime le système entier en quelques secondes
ulrich@vps:~$ rm -rf / ← NE JAMAIS TAPER ÇA
# Détruit complètement le serveur. Irrécupérable.
⚠ Variante avec espace accidentel — aussi catastrophique
ulrich@vps:~$ rm -rf /opt /coolify ← espace = supprime /opt ENTIER d'abord
Le scénario classique de catastrophe — le wildcard :
# Objectif : supprimer les fichiers .log dans /opt/logs
# Problème : tu t'es trompé de dossier courant
ulrich@vps:/opt/coolify$ rm -rf *.log
# Si aucun .log n'existe ici mais que le shell expand * différemment
# tu peux supprimer des fichiers non voulus
✅ La bonne méthode : vérifier avec ls AVANT de supprimer
ulrich@vps:~$ ls *.log ← je vois exactement ce qui sera supprimé
app.log debug.log access.log
ulrich@vps:~$ rm *.log ← maintenant je supprime en confiance
Les protections disponibles :
# -i = interactif, demande confirmation pour chaque fichier
ulrich@vps:~$ rm -i fichier.txt
rm: remove 'fichier.txt'? y
# Simuler sans supprimer — affiche la commande sans l'exécuter
ulrich@vps:~$ echo rm -rf /opt/vieux-projet/
rm -rf /opt/vieux-projet/ ← vérifie avant d'enlever le "echo"
# Alias de sécurité à ajouter dans ~/.bashrc
alias rm='rm -i' ← toujours demander confirmation
Règle d'or : Sur un VPS en production — avant tout rm -rf, fais d'abord un ls sur la cible pour voir exactement ce qui sera supprimé. Deux secondes qui peuvent sauver ton serveur et tes clients.
Ce ne sont pas des commandes à éviter — ce sont des commandes à comprendre avant d'utiliser.
🔥 dd — le "destroyer de données"
La commande dd copie des données brutes octet par octet. Utilisée pour flasher des disques, créer des images système. Une erreur dans if= (source) ou of= (destination) et tu écrases un disque entier silencieusement.
dd if=/dev/sda of=/dev/sdb ← écrase sdb avec sda. Irrécupérable.
# Toujours vérifier if= (source) et of= (destination) deux fois
⚡ chmod -R 777 — la fausse bonne idée
Quand un service refuse de lire un fichier, la tentation est d'ouvrir les droits à tout le monde avec 777. Sur un VPS, c'est ouvrir la porte : n'importe qui peut lire tes clés API, modifier tes configs, exécuter du code.
chmod -R 777 /var/www/ ← tout le monde peut tout lire ET modifier
chmod -R 755 /var/www/ ← ✅ lecture pour tous, écriture seulement toi
chmod 600 .env ← ✅ secrets : toi seulement
🧨 > fichier seul — le vidage silencieux
Le symbole > seul, sans commande devant, vide instantanément un fichier. Une faute de frappe peut détruire un fichier de config essentiel.
> /etc/nginx/nginx.conf ← vide la config nginx. Service cassé.
cp /etc/nginx/nginx.conf nginx.conf.bak ← ✅ toujours sauvegarder avant
📋 Coller une commande inconnue avec sudo
Ne jamais copier-coller une commande depuis internet sans la comprendre, surtout avec sudo. C'est le vecteur d'attaque numéro un sur les VPS. Si tu ne comprends pas une ligne, demande avant d'exécuter — à moi, ou sur un forum de confiance.
Par défaut, Bash continue d'exécuter un script même si une commande échoue. C'est silencieux, dangereux, et la source de la plupart des bugs en production. Une seule ligne placée en tête de script change tout.
#!/usr/bin/env bash
set -euo pipefail
# La suite de ton script...
Ce sont trois options distinctes, combinées en une seule ligne :
| Option | Ce qu'elle fait | Sans elle |
| -e | Arrête le script à la première erreur (code de retour ≠ 0) | Le script continue silencieusement après un échec |
| -u | Traite les variables non définies comme une erreur | $TYPO vaut "" — aucun avertissement |
| -o pipefail | Un pipe échoue si n'importe quelle commande du pipe échoue | cmd_qui_echoue | grep foo retourne 0 |
Exemple concret — sans et avec :
sans set -e — comportement par défaut
#!/usr/bin/env bash
cp /fichier-inexistant /tmp/dest ← échoue (code 1)
echo "Backup terminé" ← s'exécute quand même !
rm -rf /tmp/dest ← continue sur un backup raté
#!/usr/bin/env bash
set -euo pipefail
cp /fichier-inexistant /tmp/dest ← échoue → script s'arrête ici
# echo et rm ne s'exécutent jamais → comportement sûr
Cas spécial — commande qui peut échouer intentionnellement : si tu attends qu'une commande puisse retourner un code non-zéro sans que ce soit une erreur, utilise || true pour l'exempter explicitement.
exemption explicite avec || true
grep -q "pattern" fichier.log || true ← pas d'erreur si grep ne trouve rien
docker stop mon_conteneur || true ← pas d'erreur si déjà arrêté
Règle : tout script qui tourne en prod, en cron, ou dans un pipeline CI/CD doit commencer par set -euo pipefail. C'est la première ligne après le shebang, sans exception.
Ces réflexes s'appliquent partout — en local, sur un VPS, dans un conteneur, en CI/CD. À acquérir une fois, à garder pour toujours.
| Commande | Pourquoi c'est important |
| pwd | Toujours vérifier où tu es avant une suppression ou modification |
| ls | Vérifier ce qui existe avant d'utiliser rm ou > |
| cp fichier fichier.bak | Copier avant de modifier un fichier de config important |
| history | Voir les dernières commandes — utile pour retrouver ce qui a planté |
| man commande | Lire le manuel d'une commande avant de l'utiliser |
| commande --help | Aide rapide sur les options disponibles |
| which commande | Savoir où est installée une commande |
Alias de sécurité : Ajoute ces lignes dans ton ~/.bashrc sur le VPS et recharge avec source ~/.bashrc :
~/.bashrc — alias recommandés
# Sécurité — demande toujours confirmation
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
# Raccourcis pratiques
alias ll='ls -la'
alias dps='docker ps'
alias dlogs='docker logs -f'
alias disk='df -h'
alias mem='free -h'
# Appliquer les changements
ulrich@vps:~$ source ~/.bashrc
La règle des 3 secondes : Avant toute commande destructive — que tu sois en local, sur un VPS en prod, ou dans un conteneur — prends 3 secondes pour relire ce que tu as tapé. La plupart des catastrophes arrivent dans la précipitation ou le copier-coller rapide. Le contexte change, le risque reste le même.