Files
pyssh-client/README.md
Florian f5d99584fd
Some checks failed
CI / lint (push) Has been cancelled
CI / tests (push) Has been cancelled
CI / typecheck (push) Has been cancelled
CI / build (push) Has been cancelled
Assistant IA : liste deroulante de fournisseurs (dont Ollama, LM Studio)
Retour utilisateur : le champ "Fournisseur" en simple texte libre ne donnait
aucune indication de ce qui est possible, notamment pour l'IA locale
(Ollama, LM Studio), alors que d'autres preferent un service cloud.

Remplace par une QComboBox editable : une liste de raccourcis usuels (OpenAI,
OpenRouter, Mistral AI, Ollama local, LM Studio local, Personnalise...) qui
pre-remplit l'URL de base au choix - sans jamais empecher de saisir librement
un autre fournisseur/proxy compatible OpenAI, conformement a l'exigence
d'origine de ne rien figer en dur. Le modele reste toujours en saisie libre,
chaque installation Ollama/LM Studio ayant ses propres modeles telecharges.

Bonus cible : choisir un prereglage local (Ollama/LM Studio, detecte via
localhost/127.0.0.1 dans l'URL) met a jour l'indication du champ cle API pour
signaler qu'elle n'est generalement pas necessaire.

tests/test_ai_settings_dialog.py (8 verifications, nouveau fichier - aucun
test ne couvrait encore AISettingsDialog elle-meme) : presence d'Ollama/LM
Studio dans la liste, pre-remplissage de l'URL par prereglage, "Personnalise"
qui ne touche pas une URL deja saisie, persistance d'un nom de fournisseur
personnalise a travers un enregistrement puis une reouverture. Suite complete
revalidee (34/34) et ruff toujours propre.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 08:00:25 +02:00

303 lines
19 KiB
Markdown

# PySSH Client
Client SSH/SFTP personnel (type MobaXterm/Termius/Bitvise), **100% local**.
**Aucun compte requis, aucun service cloud, aucune telemetrie.** Sessions, cles, mots de passe
memorises et snippets restent dans des fichiers locaux (ou le trousseau securise de Windows) —
rien n'est jamais envoye a un serveur tiers. La seule fonctionnalite qui communique vers
l'exterieur est l'**assistant IA**, entierement optionnel, desactive par defaut, et qui n'envoie
quoi que ce soit qu'au fournisseur que *vous* configurez avec *votre propre* cle API (voir plus bas).
## Fonctionnalites
### Terminal SSH
- Sessions SSH multiples dans des onglets, avec terminal ANSI colore et trois themes
- Copier-coller **a la souris, comme dans un terminal Unix** : selectionner copie aussitot
(double-clic pour un mot), et le clic droit colle directement — ni menu, ni raccourci.
Le clic molette colle aussi. `Ctrl+Shift+C` / `Ctrl+Shift+V` / `Maj+Inser` restent
disponibles au clavier, et `Ctrl+C` reste l'interruption comme dans tout terminal
- Confort d'affichage : zoom `Ctrl+molette` ou `Ctrl+ +/-` (`Ctrl+0` remet a zero, la taille est
memorisee), barre de defilement de l'historique, `Maj+PagePrec/Suiv`, `Ctrl+Fin` pour revenir
en direct, et recherche dans l'historique avec `Ctrl+F`
- Onglets renommables (double-clic, ou clic droit) et deplaçables ; le clic droit permet
aussi de les diviser en **2 ou 4 panneaux** (cote a cote, empiles, ou grille), de deplacer un
autre onglet ouvert dans un panneau, ou de fermer les autres onglets
- **Keepalive et reconnexion automatique** configurables par session (intervalle, nombre de
tentatives, delai avec recul progressif) : une coupure reseau n'oblige plus a rouvrir
la session et ressaisir les identifiants
- Journalisation de session dans un fichier (bouton "Journal"), sequences ANSI retirees pour
rester lisible
- Reconnexion manuelle en un clic (bouton dans la barre de l'onglet)
### Connexion et authentification
- **Authentification automatique** (mode par defaut) : comme la commande `ssh`, l'application
utilise l'agent SSH et les cles de `~/.ssh` sans que vous ayez a designer quoi que ce soit, et
ne demande un mot de passe qu'en dernier recours
- **Import de votre `~/.ssh/config`** : les `Host` deja definis (HostName, User, Port,
IdentityFile, ProxyJump, ForwardAgent...) apparaissent directement dans le panneau lateral
(section "⚙ Configuration OpenSSH"), utilisables sans les ressaisir, ou importables comme
session PySSH en un clic
- **Bastion / ProxyJump** : une session peut passer par un rebond SSH, choisi parmi vos sessions
existantes, un `Host` de `~/.ssh/config`, ou une configuration manuelle. Un groupe de sessions
peut aussi heriter d'un bastion commun (voir Groupes ci-dessous)
- **Tunnels SSH** (bouton "🔀 Tunnels" dans la barre d'un onglet SSH) : redirection locale
(`-L`, atteindre depuis votre poste un service qui n'ecoute que sur la boucle locale du
serveur), distante (`-R`), et **proxy SOCKS dynamique** (`-D`, pour faire transiter tout le
trafic d'une application par le tunnel)
- Verification des empreintes de cles d'hote (TOFU) avant premiere connexion, comme un client
SSH classique. Les hotes deja approuves par votre commande `ssh` (`~/.ssh/known_hosts`) sont
reconnus : pas de double confirmation d'empreinte
- Gestionnaire des hotes connus (menu "Hotes connus") : si une adresse en DHCP change de
machine, la cle d'hote presentee ne correspond plus a celle enregistree — l'application le
detecte et propose de remplacer l'ancienne entree directement au moment de la connexion
### Organisation
- Carnet d'adresses de serveurs (JSON local, `data/sessions.json`) organise par dossiers,
connexion en un clic
- **Tags** (plusieurs par session) avec filtre/recherche dans le panneau lateral
- **Groupes** : un dossier de sessions peut porter des parametres par defaut (utilisateur, port,
cle SSH, bastion, keepalive) via clic droit > "Parametres du groupe...". Le bouton
"↓ Heriter du groupe" du formulaire de session les reprend en pre-remplissage, sans empecher
de les modifier au cas par cas pour une session donnee
- **Workspaces** : sauvegardent l'etat complet d'une disposition de travail — onglets ouverts,
ordre, divisions et tailles de panneaux, sessions associees — et la restaurent en un clic
(menu "Workspaces", `Ctrl+Shift+W`). Une case optionnelle restaure le dernier workspace au
demarrage. **Aucun mot de passe n'est jamais stocke dans un workspace**
- **Recherche rapide** (`Ctrl+K`) : palette façon lanceur d'applications, recherche floue parmi
sessions, hotes OpenSSH, workspaces, snippets et actions, entierement au clavier
- **Snippets** : commandes enregistrees par categorie (des exemples Linux/Docker/Proxmox sont
fournis au premier lancement), gerables (creer/modifier/supprimer/rechercher) depuis le menu
"Snippets" ou le bouton "📋 Snippets" de chaque onglet. Un snippet peut etre **insere** (le
texte est ecrit, jamais valide automatiquement), **execute**, ou **execute sur tous les
onglets a la fois** via Multi-Send
- **Envoi simultane** : le bouton "⇉ Multi" du panneau lateral diffuse ce que vous tapez vers
tous les onglets terminal ouverts (y compris dans les panneaux divises), pour piloter
plusieurs serveurs d'un coup
### SFTP
- Client SFTP integre facon WinSCP : double panneau local/distant avec colonnes triables (nom,
taille, date, droits), glisser-deposer entre les panneaux, renommage (F2), suppression
(Suppr), creation de dossier, permissions distantes (chmod), et file de transferts avec
progression — le tout en arriere-plan, sans jamais geler l'interface
- **Aucun favori n'est necessaire pour l'utiliser** — trois façons d'y arriver :
- *Vous avez tape `ssh user@hote` dans un terminal* : bouton "📁 SFTP sur cette connexion"
dans la barre de l'onglet. L'hote et l'utilisateur sont deduits de la commande tapee
- *Connexion ponctuelle* : bouton "📁 SFTP" du panneau lateral, ou `Ctrl+Shift+F`
- *Session enregistree* : le panneau de fichiers est deja affiche a cote du terminal des
l'ouverture, comme dans MobaXterm ; ou clic droit > "Ouvrir en SFTP" pour un onglet dedie
### Cles et secrets
- Gestion des cles SSH : generation **Ed25519, ECDSA (courbes NIST P-256/P-384/P-521) et RSA**,
import de cles existantes, export, protection par passphrase. Sous Windows, les cles privees
generees ou importees reçoivent des permissions NTFS restreintes (ACL), equivalent du
`chmod 600` Unix
- **Coffre-fort de mots de passe optionnel** : a la creation d'une session par mot de passe, une
case "Memoriser de maniere securisee" permet de ne plus le ressaisir a chaque connexion. Le
mot de passe est alors stocke dans le **Gestionnaire d'identifiants Windows** (chiffre par
DPAPI), **jamais en clair dans un fichier de PySSH**. Sans cette case cochee (comportement par
defaut), rien n'est memorise : le mot de passe est redemande a chaque connexion, comme
aujourd'hui
### Assistant IA (optionnel, desactive par defaut)
Une aide ponctuelle pour l'administration systeme, pas un pilote automatique : **aucune commande
n'est jamais executee automatiquement**.
- **Desactive par defaut**, et sans le moindre fournisseur impose : compatible avec toute API de
type OpenAI. Se configure depuis le menu "Assistant IA > Parametres...", avec une liste
deroulante de raccourcis usuels — OpenAI, OpenRouter, Mistral AI, mais aussi **Ollama** et
**LM Studio** pour faire tourner un modele **entierement en local** — qui pre-remplit l'URL de
base, et un champ "Personnalise..." pour tout autre service compatible OpenAI (proxy interne,
autre LLM local...). Le modele, lui, reste toujours en saisie libre (pas de liste figee, chaque
installation Ollama/LM Studio ayant ses propres modeles telecharges). Bouton "Tester la
connexion" pour verifier la configuration avant de l'utiliser
- Depuis un onglet terminal, le bouton "🤖 Assistant IA" permet, sur la selection courante :
**Expliquer cette sortie**, **Diagnostiquer cette erreur**, **Proposer une solution**, ou
**Poser une question** en y ajoutant la selection comme contexte. Les reponses s'affichent
dans un panneau lateral (conversation en memoire uniquement, jamais journalisee ni ecrite sur
le disque)
- **Generer une commande** (a partir d'une description en langage naturel, sans selection
necessaire) affiche toujours la commande proposee avant tout usage, avec trois choix : la
**copier**, l'**inserer dans le terminal** (le texte est ecrit, jamais valide automatiquement),
ou **annuler**. Une commande correspondant a un motif reconnu comme destructeur (`rm -rf`,
`mkfs`, `dd`, un `DROP DATABASE`...) est signalee par un avertissement explicite
- **Avant tout envoi** au fournisseur configure : un avis de principe s'affiche une seule fois,
et chaque envoi est verifie localement a la recherche de secrets evidents (mot de passe, cle
privee, jeton d'authentification) — si quelque chose est detecte, vous choisissez de le
masquer automatiquement, de l'envoyer tel quel, ou d'annuler l'envoi
- **La cle API** passe exclusivement par le meme coffre-fort securise que les mots de passe de
session (Gestionnaire d'identifiants Windows) : jamais dans `settings.json`, jamais journalisee
## Installation (Windows)
Necessite Python 3.10+ installe sur le poste (verifiable avec `py -3 --version`).
```bash
setup.bat
```
Ce script cree un environnement virtuel local (`venv/`) et y installe les dependances (PySide6,
paramiko, pyte, cryptography, pywinpty) — rien n'est installe globalement sur le poste.
Pour contribuer au code (tests, lint, verification de types), installez plutot les dependances
de developpement :
```bash
venv\Scripts\pip install -r requirements-dev.txt
```
## Lancement
Trois facons, au choix :
- **Raccourci Windows** (recommande) : double-cliquez sur `PySSH Client.lnk` a la racine du projet.
Il lance l'application via `pythonw.exe`, donc **sans fenetre de console noire**. Vous pouvez le
copier sur le Bureau, ou l'epingler a la barre des taches.
Pour le (re)creer — apres un deplacement du projet, ou sur un autre poste :
```bash
powershell -ExecutionPolicy Bypass -File tools\create_shortcut.ps1 -Desktop
```
(`-Desktop` et `-StartMenu` sont optionnels et ajoutent une copie a ces emplacements.)
- **En ligne de commande**, sans console residuelle :
```bash
venv\Scripts\pythonw.exe main.py
```
- **`run.bat`** : garde une fenetre de console ouverte, ce qui est pratique pour voir les erreurs
si l'application refuse de demarrer.
## Version portable (sans Python installe)
```bash
build_exe.bat
```
Produit un dossier autonome `dist\PySSH Client\` contenant l'executable et tout ce dont il a besoin,
interpreteur Python compris. Copiez ce dossier ou vous voulez (y compris sur une cle USB) : il
fonctionne sur n'importe quel poste Windows, **meme sans Python installe**.
Le format retenu est un dossier plutot qu'un fichier `.exe` unique : avec PySide6, un executable
unique doit decompresser environ 200 Mo a chaque lancement, ce qui ajoute plusieurs secondes
d'attente a chaque demarrage. Le dossier demarre immediatement.
> **Note pour qui modifie la recette de build** (`PySSHClient.spec`) : `pywinpty` a besoin de deux
> executables auxiliaires, `OpenConsole.exe` (backend ConPTY) et `winpty-agent.exe` (repli WinPTY).
> PyInstaller ne suit que les DLL liees et les oublie : ils sont donc ajoutes explicitement dans
> `datas`. Sans eux, l'application se lance normalement mais **tous les terminaux se ferment
> instantanement** ("Deconnecte (Terminal ferme)").
**Ou sont stockees les donnees ?** A cote de l'executable, dans son propre sous-dossier `data\`
(mode portable : vos sessions et cles suivent la cle USB). Si cet emplacement n'est pas accessible
en ecriture — installation dans `Program Files`, support en lecture seule — l'application bascule
automatiquement sur `%APPDATA%\PySSHClient\`.
## Premiers pas
1. **Terminal local immediat** : bouton "💻 Terminal" dans le panneau lateral (ou `Ctrl+Shift+T`), ou double-clic sur "PowerShell"/"Invite de commandes" dans la section "💻 Terminaux locaux" de l'arborescence — aucune session a creer.
2. **Nouvelle session SSH** (bouton "+ Session" ou `Ctrl+N`) : renseignez l'hote, le port, l'utilisateur et le mode d'authentification. Vous avez deja un `~/.ssh/config` ? Ses `Host` apparaissent tout seuls dans la section "⚙ Configuration OpenSSH", inutile de les ressaisir.
3. **Cles SSH** (menu "Cles SSH > Gestionnaire de cles") : generez une nouvelle paire de cles ou importez-en une existante, puis selectionnez-la dans le formulaire de session.
4. Double-cliquez sur une session dans l'arborescence pour ouvrir un onglet terminal SSH.
5. **SFTP — trois facons d'y arriver**, selon la situation :
- *Vous avez tape `ssh user@hote` dans un terminal* : bouton **"📁 SFTP sur cette connexion"** dans la barre de
l'onglet. L'hote et l'utilisateur sont deduits de la commande, le formulaire est pre-rempli. Rien a enregistrer.
- *Connexion ponctuelle* : bouton **"📁 SFTP"** du panneau lateral, ou `Ctrl+Shift+F`. La case "Enregistrer dans mes
sessions" est decochee par defaut.
- *Session enregistree* : clic droit > **"Ouvrir en SFTP"** (onglet dedie). Dans une session SSH ouverte par
l'application, le panneau de fichiers est deja affiche a cote du terminal, comme dans MobaXterm.
Dans tous les cas : glisser-deposer entre les deux panneaux, F2 renomme, Suppr supprime, F5 actualise, et le clic
droit sur le panneau distant donne acces aux permissions (chmod).
6. **Aller plus vite** : `Ctrl+K` ouvre une recherche rapide parmi tout ce que vous avez deja ouvert ou enregistre.
7. **Retrouver une disposition de travail** : une fois plusieurs onglets/panneaux organises a votre gout, menu
"Workspaces > Gestionnaire de workspaces..." (`Ctrl+Shift+W`) pour l'enregistrer et la rouvrir en un clic.
8. **Assistant IA** (facultatif) : menu "Assistant IA > Parametres..." pour indiquer l'URL, le modele et la cle API
d'un fournisseur compatible OpenAI. Tant que cette etape n'est pas faite, l'assistant reste inactif et
n'envoie jamais rien nulle part.
## Structure du projet
```
main.py point d'entree
app/
main_window.py fenetre principale : assemble panneau lateral, onglets, menus
session_manager.py base de sessions (JSON), tags, parametres de bastion/keepalive/reconnexion
key_manager.py generation/import/export de cles SSH, empreintes
known_hosts_manager.py gestion du fichier known_hosts (lister/retirer/remplacer une cle d'hote)
ssh_connection.py connexion SSH en arriere-plan (paramiko + QThread), bastion, keepalive
local_terminal.py terminal local Windows (PowerShell/cmd via pywinpty)
terminal_widget.py emulateur de terminal ANSI (pyte + QPainter)
sftp_panel.py client SFTP a deux panneaux, transferts en arriere-plan
tunnels.py tunnels SSH : redirection -L / -R et proxy SOCKS dynamique -D
bastion.py resolution d'un ProxyJump (session / Host OpenSSH / manuel)
credentials.py coffre-fort optionnel (Gestionnaire d'identifiants Windows)
workspace.py, snippets.py, persistance des workspaces, snippets et parametres de groupe
groups.py, app_settings.py
theme.py feuille de style "bleu nuit" + icone d'application
ai/ assistant IA : fournisseur, redaction de secrets, detection de
commandes dangereuses, parametres, execution en arriere-plan
controllers/ logique d'assemblage : cycle de vie des onglets, panneau des
sessions, diffusion Multi-Send, restauration des workspaces
ui/ widgets d'onglet (SSH/local/SFTP), terminal divise, panneau IA
dialogs/ boites de dialogue (session, cles, hotes, tunnels, snippets,
workspaces, groupes, recherche rapide, parametres IA...)
openssh/ lecture de ~/.ssh/config
security/ protection des cles privees (ACL NTFS sous Windows)
storage/ ecriture atomique de fichiers JSON, migration de schema
services/ logique pure sans dependance Qt (detection ~cible SSH, recherche
floue, calcul du delai de reconnexion)
resources/
theme.qss feuille de style Qt "bleu nuit"
data/
sessions.json, keys.json, vos donnees locales (non versionnees) - voir .gitignore
workspaces.json, snippets.json,
groups.json, known_hosts, keys/
tests/ suite de tests (voir "Tests" ci-dessous)
```
## Tests
```bash
pytest
```
(necessite `pip install -r requirements-dev.txt`). Chaque fichier `tests/test_*.py` reste aussi
executable individuellement (`python tests/test_xxx.py`), pratique pour rejouer un seul scenario
pendant le developpement.
Les tests construisent de vraies fenetres Qt sans les afficher (`QT_QPA_PLATFORM=offscreen`) et
couvrent, entre autres : le copier-coller et les raccourcis du terminal, la detection de l'hote
dans une commande `ssh` tapee a la main, la logique SFTP (transferts, renommage, tri, suppression
recursive) avec un serveur simule, le stockage atomique et la migration de schema, la lecture de
`~/.ssh/config`, la resolution de ProxyJump, le coffre-fort d'identifiants, la detection de
secrets et de commandes dangereuses, et l'ensemble de l'assistant IA (fournisseur HTTP, panneau,
integration dans les onglets) — sans jamais faire d'appel reseau reel.
## Qualite
```bash
ruff check app main.py tests # style et erreurs courantes
pyright # verification de types (mode progressif, voir pyproject.toml)
```
Une CI Gitea Actions (`.gitea/workflows/ci.yml`) execute le lint et les tests a chaque push, et
construit la version portable sur la branche principale.
## Limites connues / pistes d'amelioration
- L'emulation de terminal couvre les usages courants (bash, vim, htop...) mais n'est pas un xterm parfaitement complet.
- Le terminal local repose sur `pywinpty` (pseudo-console Windows). Si un onglet local reste bloque sur un ecran vide, tapez une touche : le rafraichissement de la console Windows peut etre paresseux dans certains contextes (notamment les sessions Windows non interactives) et se declenche a la premiere frappe.
- Le SFTP ne propose pas encore l'edition de fichier distant en place (telecharger / modifier / renvoyer a la main).
- Le ProxyJump ne gere qu'un seul rebond pour l'instant (pas de chainage de plusieurs bastions).
- Le coffre-fort d'identifiants (mots de passe de session, cle API IA) repose sur le Gestionnaire d'identifiants Windows : pas d'equivalent sur Linux/macOS pour l'instant, l'application fonctionne mais sans memorisation securisee sur ces systemes.
- L'application est concue et testee pour Windows uniquement (pywinpty, ACL NTFS, Gestionnaire d'identifiants).
- La verification de types (`pyright`) est volontairement en mode progressif : un socle de signalements existe encore dans `app/ui/tabs.py` et `app/ssh_connection.py` (voir `pyproject.toml`), a resorber au fil du temps plutot que d'un bloc.