$ 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.

Niveau
Débutant → Intermédiaire
Durée estimée
~2h lecture + pratique
Prérequis
Un terminal (local ou SSH)
01

C'est quoi bash ?

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.

02

Bash est partout — pas seulement sur un serveur

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.

les 5 contextes bash
# 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.

03

Lire une ligne de commande

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

anatomie du prompt
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

anatomie 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

chemins
# 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èreSignification
~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é

code de retour $?
# 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.
05

Lire des fichiers & chercher

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.

CommandeDescription
cat fichierAffiche le contenu entier d'un fichier
less fichierLecture paginée (q pour quitter, / pour chercher)
head -n 20 fichierAffiche les 20 premières lignes
tail -n 50 fichierAffiche les 50 dernières lignes
tail -f fichierSuit le fichier en temps réel — parfait pour les logs live
grep "mot" fichierCherche "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
bash — logs & recherche
# 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.
06

Processus & services

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.

CommandeDescription
ps auxListe tous les processus en cours
topVue temps réel CPU/RAM (q pour quitter)
htopVersion améliorée de top (à installer via apt)
kill PIDArrête un processus par son numéro
kill -9 PIDForce l'arrêt immédiat (ne peut pas être ignoré)
lsof -i :8080Quel processus utilise le port 8080 ?
systemctl status nginxÉtat d'un service système
docker psConteneurs Docker actifs
docker ps -aTous les conteneurs (même arrêtés)
df -hEspace disque disponible
free -hRAM 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
07

Droits & permissions

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.

bash — lire les droits
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
CommandeDescription
chmod +x script.shRend un script exécutable
chmod 644 fichierrw-r--r-- : lisible par tous, modifiable seulement par toi
chmod 600 .envrw------- : uniquement toi (idéal pour les secrets)
chmod 755 dossier/rwxr-xr-x : standard pour les dossiers web
chown user:group fichierChange le propriétaire d'un fichier
sudo commandeExécute en tant que super-administrateur
whoamiAffiche 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.
08

Pipes & redirections

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.

SymboleDescription
cmd1 | cmd2Pipe — envoie la sortie de cmd1 vers cmd2
cmd > fichierRedirige la sortie vers un fichier (écrase le contenu)
cmd >> fichierAjoute à la fin du fichier sans écraser
cmd 2> erreurs.logRedirige uniquement les erreurs vers un fichier
cmd1 && cmd2Exécute cmd2 seulement si cmd1 réussit (code 0)
cmd1 || cmd2Exécute cmd2 seulement si cmd1 échoue
bash — pipes en pratique
# 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
09

Variables & scripts bash

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.

bash — variables
# 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
backup-supabase.sh
#!/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
10

Bash comme langage : conditions, boucles & fonctions

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

bash — if/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
TestSignification
-f fichierLe fichier existe (et est un fichier régulier)
-d dossierLe 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"
! conditionInverse la condition (NOT)

Boucles — for & while

bash — boucles
# 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.

watchdog-n8n.sh
#!/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.
11

Entrer dans un conteneur Docker

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.

CommandeDescription
docker exec -it nom bashOuvre un shell interactif dans le conteneur
docker exec -it nom shMême chose avec sh (si bash n'est pas installé dans l'image)
docker exec nom commandeExécute une commande unique sans ouvrir de shell
exitQuitter 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.
12

Éditer des fichiers

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.

nano — ouvrir et éditer
# 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
RaccourciAction
Ctrl + OSauvegarder le fichier (Write Out) — confirme avec Entrée
Ctrl + XQuitter (propose de sauvegarder si modifications non enregistrées)
Ctrl + WChercher un mot dans le fichier
Ctrl + KCouper la ligne entière
Ctrl + UColler la ligne coupée
Ctrl + GAide 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
13

Cron & automatisation

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)
CommandeDescription
crontab -eÉditer tes tâches cron (ouvre nano ou vim)
crontab -lLister tes tâches cron actives
crontab -rSupprimer toutes tes tâches cron (dangereux — pas de confirmation)
systemctl status cronVé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.
14

SSH & clés

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
CommandeDescription
ssh user@hostConnexion SSH basique
ssh -p 2222 user@hostConnexion 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 ed25519Générer une paire de clés moderne (ed25519 > rsa)
cat ~/.ssh/id_ed25519.pubAfficher 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.
15

Hermes Agent — bash en action réelle

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.

16

API REST avec curl & jq

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

curl — requêtes de base
# 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
FlagRôle
-sSilencieux — supprime la barre de progression
-iAffiche les headers de réponse
-X POSTChange 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
-LSuit 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 :

check_api.sh
#!/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.

⚠ 17

rm — la commande la plus dangereuse

💀 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 :

le piège du * (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 :

rm — options de sécurité
# -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.
⚠ 18

Autres commandes à manier avec précaution

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
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.

permissions
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.

redirection accidentelle
> /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.

19

set -euo pipefail — écrire des scripts solides

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.

script-robuste.sh
#!/usr/bin/env bash set -euo pipefail   # La suite de ton script...

Ce sont trois options distinctes, combinées en une seule ligne :

OptionCe qu'elle faitSans elle
-eArrête le script à la première erreur (code de retour ≠ 0)Le script continue silencieusement après un échec
-uTraite les variables non définies comme une erreur$TYPO vaut "" — aucun avertissement
-o pipefailUn pipe échoue si n'importe quelle commande du pipe échouecmd_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é
avec set -euo pipefail
#!/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.
20

Bonnes pratiques — réflexes universels

Ces réflexes s'appliquent partout — en local, sur un VPS, dans un conteneur, en CI/CD. À acquérir une fois, à garder pour toujours.

CommandePourquoi c'est important
pwdToujours vérifier où tu es avant une suppression ou modification
lsVérifier ce qui existe avant d'utiliser rm ou >
cp fichier fichier.bakCopier avant de modifier un fichier de config important
historyVoir les dernières commandes — utile pour retrouver ce qui a planté
man commandeLire le manuel d'une commande avant de l'utiliser
commande --helpAide rapide sur les options disponibles
which commandeSavoir 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.