Signale par l'utilisateur : "Expliquer cette sortie" n'avait aucun effet
utilisable, le menu restait grise. Cause reelle : ces actions exigeaient une
selection prealable dans le terminal, alors que l'attente naturelle ("explique
ce que j'ai a l'ecran") est de pouvoir les utiliser directement sur ce qui est
affiche - confirme avec l'utilisateur avant d'implementer (deux options
proposees : rabattre sur l'ecran visible, ou juste clarifier le blocage).
app/terminal_widget.py : nouvelle methode visible_text() (l'ecran actuellement
affiche, sans l'historique de scroll - au contraire de buffer_text() qui sert
a la detection de commande ssh et remonte l'historique proche).
app/ui/tabs.py : Expliquer/Diagnostiquer/Proposer une solution/Poser une
question utilisent desormais _ai_source_text() = la selection si elle existe
(pour cibler precisement), sinon l'ecran visible. Les 3 premieres actions du
menu ne sont plus desactivees des qu'il n'y a pas de selection, mais
seulement si vraiment rien n'est affiche ; "Poser une question" reste
utilisable meme un terminal vide (contexte optionnel, comme le bouton
"Nouvelle question" du panneau).
tests/test_ai_tab_integration.py : reecriture des tests 1 et 2 (etats
d'activation du menu) + 2 nouveaux tests verifiant explicitement le repli
(sans selection, le texte envoye est bien celui de l'ecran) et sa priorite
inverse (une selection existante prime toujours sur l'ecran visible).
tests/test_clipboard.py : couverture directe de visible_text() (ecran complet,
lignes vides de fin retirees). Suite complete revalidee (34/34), ruff propre.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
305 lines
19 KiB
Markdown
305 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 : **Expliquer cette sortie**,
|
|
**Diagnostiquer cette erreur**, **Proposer une solution**, ou **Poser une question**. Ces
|
|
actions portent sur le texte **selectionne** s'il y en a un (pour cibler precisement une
|
|
partie d'une sortie longue), et se rabattent sinon automatiquement sur **tout l'ecran visible
|
|
du terminal** — inutile de selectionner quoi que ce soit pour un simple "explique-moi ce que
|
|
j'ai sous les yeux". 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.
|