SSH & SCP — Commandes distantes : guide pratique¶
Résumé¶
Guide complet des commandes SSH et SCP utilisées pour administrer un serveur distant (VPS Hetzner). Couvre l'exécution de commandes, le transfert de fichiers, les pièges à éviter, et les patterns avancés (python3, docker, heredoc) découverts lors du déploiement de [[PsychoEnLigneV2]].
1. Anatomie d'une commande SSH¶
ssh -i ~/.ssh/kanmaber_hetzner kanmaber@178.105.46.57 "commande 2>&1"
| Partie | Rôle |
|---|---|
ssh |
Client SSH (Secure Shell) — ouvre un tunnel chiffré |
-i ~/.ssh/kanmaber_hetzner |
Clé privée à utiliser (sans -i, SSH cherche ~/.ssh/id_rsa ou id_ed25519) |
kanmaber@178.105.46.57 |
utilisateur@adresse-ip-ou-domaine |
"commande" |
Commande à exécuter sur le serveur. Sans guillemets = session interactive |
2>&1 |
Redirige stderr vers stdout — capture TOUTES les sorties (erreurs incluses) |
Avec vs sans commande¶
# Session interactive (terminal ouvert sur le serveur)
ssh -i ~/.ssh/kanmaber_hetzner kanmaber@178.105.46.57
# Exécution non-interactive (une commande, résultat retourné)
ssh -i ~/.ssh/kanmaber_hetzner kanmaber@178.105.46.57 "ls /home/kanmaber"
Chaîner plusieurs commandes¶
# && : s'arrête si une commande échoue
ssh ... "cd /mon/dossier && npm install && npm run build 2>&1"
# ; : exécute toutes les commandes même si une échoue
ssh ... "cmd1; cmd2; cmd3"
2. Anatomie d'une commande SCP¶
scp -i ~/.ssh/kanmaber_hetzner /local/fichier.ts \
kanmaber@178.105.46.57:/remote/chemin/fichier.ts
| Partie | Rôle |
|---|---|
scp |
Secure Copy — transfert de fichiers via SSH |
-i ~/.ssh/kanmaber_hetzner |
Clé privée (même logique que SSH) |
/local/fichier.ts |
Source locale |
kanmaber@ip:/chemin |
Destination distante |
Sens du transfert¶
# LOCAL → DISTANT (upload)
scp -i clé fichier_local.ts utilisateur@ip:/chemin/distant/
# DISTANT → LOCAL (download)
scp -i clé utilisateur@ip:/chemin/distant/fichier.ts ./local/
# Dossier entier (-r pour récursif)
scp -r -i clé ./dossier/ utilisateur@ip:/chemin/distant/
Plusieurs fichiers en une fois¶
scp -i clé \
fichier1.ts \
fichier2.ts \
fichier3.ts \
utilisateur@ip:/même/dossier/distant/
3. Utilisateurs et permissions¶
Le problème des fichiers Docker (UID 10000)¶
Quand Docker crée des fichiers via un bind mount, ils appartiennent à l'UID interne du container
(souvent 10000 pour des images comme Hermes). L'utilisateur kanmaber ne peut pas les écrire.
-rw-r--r-- 1 10000 hermes-docker fichier.ts ← kanmaber ne peut PAS écrire
-rw-r--r-- 1 kanmaber kanmaber fichier.ts ← kanmaber PEUT écrire
Solutions par ordre de préférence¶
1. SCP en root (si root a une clé SSH configurée ou accepte la connexion)
scp fichier.ts root@ip:/chemin/fichier.ts
2. SSH + sudo cp (interactif, nécessite le mot de passe sudo)
# Copier d'abord dans /tmp (toujours accessible)
scp -i clé fichier.ts kanmaber@ip:/tmp/fichier.ts
# Puis en SSH interactif :
ssh -i clé kanmaber@ip
sudo cp /tmp/fichier.ts /chemin/protégé/fichier.ts
3. docker run pour écrire dans le volume (sans sudo)
ssh kanmaber@ip "
docker run --rm \
-v /home/kanmaber/projets:/project \
-v /tmp/fichier.ts:/src/fichier.ts:ro \
busybox cp /src/fichier.ts /project/chemin/fichier.ts
"
4. Fix permanent — docker-compose avec l'UID hôte
services:
monservice:
user: "${HOST_UID:-1000}:${HOST_GID:-1000}"
HOST_UID=$(id -u) HOST_GID=$(id -g) docker compose up --build
4. Édition de fichiers distants¶
python3 pour les fichiers UTF-8 (avec accents)¶
sed échoue sur les caractères accentués. Toujours utiliser python3 :
ssh kanmaber@ip "python3 -c \"
path = '/chemin/fichier.ts'
with open(path, 'r') as f:
content = f.read()
content = content.replace('ancien texte', 'nouveau texte')
with open(path, 'w') as f:
f.write(content)
print('OK')
\""
Suppression de lignes spécifiques¶
ssh kanmaber@ip "python3 -c \"
path = '/chemin/fichier.ts'
with open(path, 'r') as f:
lines = f.readlines()
# Supprimer les lignes 295 et 296 (index 0-based)
lines = [l for i, l in enumerate(lines) if i not in (295, 296)]
with open(path, 'w') as f:
f.writelines(lines)
print(f'{len(lines)} lignes restantes')
\""
Modifier un JSON (package.json, etc.)¶
ssh kanmaber@ip "python3 -c \"
import json
path = '/chemin/package.json'
with open(path) as f:
pkg = json.load(f)
pkg['scripts']['build'] = 'tsc && tsc-alias'
with open(path, 'w') as f:
json.dump(pkg, f, indent=2, ensure_ascii=False)
print('scripts.build:', pkg['scripts']['build'])
\""
Heredoc pour écrire un fichier complet¶
ssh kanmaber@ip "cat > /tmp/mon_fichier.ts << 'ENDOFFILE'
// Contenu du fichier
export const value = 42;
ENDOFFILE
echo 'fichier écrit'"
⚠️ Le
'ENDOFFILE'entre guillemets simples empêche l'interprétation des variables shell dans le contenu. Sans guillemets,$variableserait interprété.
5. Docker via SSH¶
Lister les containers en cours¶
ssh kanmaber@ip "docker ps --format '{{.Names}} {{.Status}} {{.Image}}'"
Lire les logs d'un container¶
# 20 dernières lignes
ssh kanmaber@ip "docker logs psycho-backend --tail 20 2>&1"
# Filtrer avec grep
ssh kanmaber@ip "docker logs psycho-backend 2>&1 | grep -E 'error|warn|info' | tail -15"
Build d'un service docker-compose¶
# Avec cache (rapide)
ssh kanmaber@ip "cd /home/kanmaber/projets/PsychoEnLigne && docker compose build backend 2>&1"
# Sans cache (force tout reconstruire)
ssh kanmaber@ip "cd /home/kanmaber/projets/PsychoEnLigne && docker compose build --no-cache backend 2>&1"
Démarrer / redémarrer un service¶
# Démarrer avec ses dépendances (postgres, redis)
ssh kanmaber@ip "cd /projet && docker compose up -d backend"
# Démarrer SANS recréer les dépendances déjà actives
ssh kanmaber@ip "cd /projet && docker compose up -d --no-deps backend"
# Redémarrer proprement
ssh kanmaber@ip "docker rm -f psycho-backend; cd /projet && docker compose up -d --no-deps backend"
Copier un fichier dans un container actif¶
# Hôte → Container
ssh kanmaber@ip "docker cp /tmp/fichier.ts psycho-backend:/app/src/fichier.ts"
# Container → Hôte
ssh kanmaber@ip "docker cp psycho-backend:/app/dist/server.js /tmp/server.js"
6. Diagnostics courants¶
Vérifier qu'un fichier existe et ses permissions¶
ssh kanmaber@ip "ls -la /chemin/fichier.ts"
Compter les lignes d'un fichier¶
ssh kanmaber@ip "wc -l /chemin/fichier.ts"
Lire les lignes N à M d'un fichier (avec numéros)¶
# Lignes 270 à 300, avec numéros
ssh kanmaber@ip "cat -n /chemin/fichier.ts | sed -n '270,300p'"
Chercher un pattern dans les sources¶
ssh kanmaber@ip "grep -rn 'REDIS_HOST\|localhost' /projet/BackEnd/src --include='*.ts' | head -10"
7. Pièges & règles de survie¶
| Piège | Solution |
|---|---|
sed casse les accents |
Utiliser python3 open() |
Docker cache le COPY . . |
docker compose build --no-cache |
Container utilise localhost au lieu du hostname Docker |
Passer REDIS_HOST=redis dans les env vars |
| Fichier appartenant à UID 10000 | SCP en root ou docker run busybox cp |
scp avec /dev/stdin échoue |
Écrire d'abord dans /tmp, puis scp depuis /tmp |
| sudo sans terminal (non-interactif) | Passer par une session SSH interactive |
npm run build passe mais module introuvable au runtime |
Les alias @shared ne sont pas résolus → ajouter tsc-alias |
Prisma : linux-musl introuvable dans Alpine |
Ajouter binaryTargets = ["native", "linux-musl-openssl-3.0.x"] + prisma generate dans Dockerfile |
Points clés¶
2>&1est presque toujours nécessaire pour capturer les erreurs- Préférer
python3àseddès qu'il y a des accents ou des caractères spéciaux - Le
--no-depsde docker-compose évite de recréer des containers déjà actifs - Pour les fichiers protégés : tenter
root@ipsans clé (scp fichier root@ip:/chemin) - Un container Docker qui redémarre en boucle → lire ses logs AVANT de modifier quoi que ce soit
Liens connexes¶
- hermes-acces-vps-hetzner — accès au VPS Hetzner, ports, services actifs
- PsychoEnLigneV2/architecture/stack-technique — stack du projet