sshm: simple SSH launcher
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Draven 2fc8469787
Some checks failed
ShellCheck / shellcheck (push) Failing after 3s
Neun Review-Findings behoben (v1.8.3)
Behoben:
- pipefail: ein ls-Fehler im Listing-Teil der Browser-Pipeline beendete
  den Dateibrowser kommentarlos — jetzt zählt nur der fzf-Status
  (PIPESTATUS)
- fzf-Mindestversion 0.35 wird geprüft; zu altes fzf fällt auf den
  plain-read-Pfad zurück statt sich still zu beenden
- --ansi im Dateibrowser entfernt: Escape-Sequenzen in Remote-Dateinamen
  konnten die Anzeige verfälschen (Display-Spoofing)
- README: entfernte Kopfzeile und "sudo aktiv"-Statusleiste aus dem
  Browser-Kapitel getilgt, sudo-Indikator ist das sudo im Prompt

Geändert:
- Kopierziel-Fenster des Browsers unter 72 Spalten ausgeblendet (wie das
  Menü); CTRL-/ blendet Info/Kopierziel jederzeit ein und aus
- Tiefe Pfade im Browser-Prompt links gekürzt — die Filter-Query blieb
  sonst unsichtbar
- Kopierziel-Ansicht einmal pro Runde gerendert statt pro Cursorzeile
  (spart Skript-Start + ssh-Roundtrip je Cursorbewegung)
- --run-Hostauswahl im gemeinsamen fzf-Stil; Grundoptionen im geteilten
  FZF_STYLE-Array statt dreifach dupliziert

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018z6nekj1nhQ91SHXqFvKum
2026-09-11 15:44:45 +02:00
.github/workflows Create shellcheck.yml 2026-07-09 20:17:11 +02:00
.gitattributes Create .gitattributes 2026-07-09 20:14:31 +02:00
.gitignore Erreichbarkeits-Check, Report/Notizen und Doku aktualisiert 2026-08-05 22:38:09 +02:00
CHANGELOG.md Neun Review-Findings behoben (v1.8.3) 2026-09-11 15:44:45 +02:00
LICENSE Initial commit 2026-07-09 19:43:52 +02:00
README.md Neun Review-Findings behoben (v1.8.3) 2026-09-11 15:44:45 +02:00
sshm Neun Review-Findings behoben (v1.8.3) 2026-09-11 15:44:45 +02:00

sshm: simple SSH launcher

Einfaches, tastaturgesteuertes SSH-Menü für die Kommandozeile.
Mit fzf-Integration, Farbe, Fuzzy-Suche, direktem Verbindungsaufbau per Nummer oder Name — und automatischer Key-Erkennung (Lernmodus).


Voraussetzungen

Paket Pflicht Zweck
bash ≥ 4.0 Shell — sshm prüft das beim Start und bricht mit klarer Meldung ab
ssh Verbindungsaufbau
fzf ≥ 0.35 empfohlen Interaktives Menü mit Fuzzy-Suche; Pflicht für den Dateibrowser (-f). Ältere fzf-Versionen werden erkannt und wie „kein fzf" behandelt (plain-read-Fallback)
nc (netcat) optional Erreichbarkeits-Check (--check, SSHM_PROBE=1) — ohne nc wird timeout + Bash /dev/tcp genutzt; fehlt beides, meldet --check das ehrlich mit Exit-Code 2 statt lauter ✓

macOS: Die System-Bash ist 3.2 und zu alt — zuerst brew install bash, danach läuft sshm normal (der Shebang findet die neue Version automatisch).

fzf installieren

CachyOS:

sudo pacman -S fzf

Debian:

sudo apt install fzf

Schritt 1 — sshm installieren

# Nur für den aktuellen User (empfohlen)
install -m 755 sshm ~/.local/bin/sshm
# Oder systemweit für alle User
sudo install -m 755 sshm /usr/local/bin/sshm

PATH prüfen (nur bei ~/.local/bin)

CachyOS/Fish — standardmäßig bereits im PATH, prüfen mit:

echo $PATH

Debian/Bash — falls ~/.local/bin nicht enthalten ist, in ~/.bashrc oder ~/.profile ergänzen:

export PATH="$HOME/.local/bin:$PATH"

Hinweis bei Bearbeitung unter Windows: Das Script braucht LF-Zeilenenden. Im Repo sichert das eine .gitattributes mit sshm text eol=lf ab — sonst macht ein Windows-Editor oder Git-Autocrlf die Datei für Linux unbrauchbar.


Schritt 2 — Setup ausführen

sshm --setup

Legt idempotent an bzw. hängt an:

  • ~/.ssh/configHost * mit AddKeysToAgent yes und automatisch erkannten Keys: sshm scannt ~/.ssh nach privaten Keys (unabhängig vom Dateinamen — erkannt über passende .pub-Datei oder den PRIVATE KEY-Header) und trägt alle als IdentityFile-Zeilen ein. Pfade mit Leerzeichen werden automatisch gequotet. Wird kein Key gefunden, landet ein auskommentierter Platzhalter samt Hinweis auf ssh-keygen -t ed25519 im Block.
  • ~/.config/systemd/user/sshm-agent.service — systemd-User-Service für den ssh-agent (falls systemd läuft), siehe unten
  • ~/.bashrc — ssh-agent-Block (falls Bash installiert)
  • ~/.config/fish/config.fish — ssh-agent-Block (falls Fish installiert)

Der Agent-Socket liegt unter $XDG_RUNTIME_DIR (privat pro User, wird beim Logout aufgeräumt); fehlt das Verzeichnis, ist der Fallback ~/.ssh/agent.sock — bewusst nicht /tmp, wo der Pfad auf Mehrbenutzersystemen (Jumphosts) vorhersagbar wäre.

Die Shell-Blöcke laufen nur in interaktiven Shells. Das ist wichtig, weil Bash die ~/.bashrc auch bei nicht-interaktiven Aufrufen über sshd liest (ssh host 'kommando') — dort soll weder ein Agent gestartet noch aufgeräumt werden.

Vor jeder Änderung wird ein Backup als <datei>.sshm-bak.<timestamp> angelegt.
Blöcke sind mit # >>> sshm setup >>> / # <<< sshm setup <<< markiert — mehrfaches sshm --setup fügt nichts doppelt hinzu.

Der ssh-agent als systemd-User-Service

Läuft systemd mit einer aktiven User-Session, richtet --setup zusätzlich sshm-agent.service ein und aktiviert ihn (systemctl --user enable --now):

[Service]
ExecStart=/usr/bin/ssh-agent -D -t 2h -a %t/ssh-agent.sock
Restart=always

Der Vorteil gegenüber einem von der Shell gestarteten Agent: Der Service endet automatisch mit der letzten Session des Users (ohne Lingering) — beim Logout werden entsperrte Keys also zuverlässig aus dem Speicher freigegeben. -t 2h ist das zweite Netz für vergessene oder abgerissene Sessions: danach fragt ssh wieder nach der Passphrase.

  • TTL ändern: in der Unit-Datei ~/.config/systemd/user/sshm-agent.service, danach systemctl --user daemon-reload && systemctl --user restart sshm-agent. (Nur ohne systemd ist die TTL im Shell-Block hinterlegt.)
  • Status prüfen: systemctl --user status sshm-agent
  • Kein systemd? Kein Problem — dann startet der Shell-Block den Agent selbst, gleicher Socket-Pfad, gleiches Verhalten. --setup meldet, welcher Weg greift.
  • Entfernen: sshm --uninstall deaktiviert und löscht die Unit mit.

Keys nachträglich erstellt?

--setup fasst vorhandene Blöcke nicht mehr an. Wenn Keys nach dem Setup dazugekommen sind (z. B. via ssh-keygen), schreibt

sshm --rescan

den ssh_config-Block mit den aktuell gefundenen Keys neu (mit Backup).


Schritt 3 — Erster Start

Neues Terminal öffnen (damit der ssh-agent aus Schritt 2 aktiv wird), dann:

sshm

Beim allerersten Aufruf wird ~/.config/sshm/hosts automatisch angelegt (Rechte 600) und der Editor geöffnet ($EDITOR, Fallback: nano).
Die Datei enthält einen kommentierten Header mit Formatbeschreibung und Beispielen. Nach dem Speichern und Schließen startet das Menü direkt.


Konfigurationsdatei

Pfad: ~/.config/sshm/hosts

Update von einer Version ≤ 1.3.0? Die Konfig lag früher unter ~/.config/ssh-menu — einfach umbenennen: mv ~/.config/ssh-menu ~/.config/sshm

Format: NAME | USER@HOST[:PORT] | SSH-OPTIONEN | BESCHREIBUNG

# Kommentare und deaktivierte Einträge beginnen mit #
# Felder 3 und 4 (Optionen, Beschreibung) können leer bleiben

# Produktiv
Webserver  | admin@192.168.1.10        |                                          | Nginx
Datenbank  | deploy@db.example.com:222 | -o IdentitiesOnly=yes -i ~/.ssh/id_db    | PostgreSQL

# Key-Pfade mit Leerzeichen in Quotes setzen
Sonderfall | user@10.0.0.9             | -i "~/.ssh/mein key"                     |

# IPv6 mit Port: Adresse in eckige Klammern setzen
V6Server   | user@[2001:db8::10]:2222  |                                          | IPv6-Beispiel

# Testumgebung
# TestServer | user@test | | deaktiviert — mit # auskommentiert
Test1      | user@10.0.0.5             |                                          |

Das OPTS-Feld kann leer bleiben — dann kümmert sich der Lernmodus (siehe unten) beim ersten erfolgreichen Connect automatisch um den passenden Key.

Bestehende ~/.ssh/config übernehmen

sshm --import

Übernimmt konkrete Host-Aliasse (samt HostName, User, Port) einmalig als Einträge — Wildcards (Host *), Match-Blöcke und Include-Dateien werden bewusst ignoriert, bereits vorhandene Namen übersprungen. Der Import ist einseitig: die sshm-Konfig bleibt danach die eigene Wahrheit und wird nicht mit der ~/.ssh/config synchron gehalten.


Verwendung

sshm                               # Interaktives Menü (fzf oder plain read)
sshm 3                             # Eintrag Nr. 3 direkt starten
sshm web                           # Substring-Match auf Name (case-insensitiv)
sshm -f | --files [num|name]       # Dateibrowser (scp) — siehe unten
sshm --run [-s] 'CMD' [host ...]   # Kommando auf mehreren Hosts — siehe unten
sshm --check                       # Erreichbarkeit aller Einträge prüfen
sshm --last                        # Letzten Eintrag erneut verbinden
sshm --history [N]                 # Verbindungslog anzeigen (letzte N Zeilen, Default 10)
sshm --report [woche|monat|alle|TAGE] [--csv]   # Verbindungszeiten aufsummieren
sshm --completion bash|fish        # Shell-Completion ausgeben
sshm -l | --list                   # Liste anzeigen ohne Connect
sshm -e | --edit                   # Konfig im Editor öffnen
sshm -a 'Name|user@host||Beschr.'  # Eintrag schnell anhängen
sshm --import                      # Host-Aliasse aus ~/.ssh/config übernehmen
sshm --setup                       # ssh_config + ssh-agent einrichten
sshm --rescan                      # ssh_config-Block mit aktuellen Keys neu schreiben
sshm --uninstall                   # --setup-Blöcke wieder entfernen
sshm -V | --version                # Version anzeigen
sshm -h | --help                   # Hilfe anzeigen

Jede Verbindung wird in ~/.local/state/sshm/history protokolliert (Rechte 600, das Verzeichnis 700) — mit je einer start- und einer end-Zeile. Die end-Zeile enthält als Meta-Informationen die Verbindungsdauer, den Exit-Code und die übertragenen Datenmengen (aus der Transfer-Statistik von ssh -v):

2026-07-10 14:02:11  start  Webserver  admin@192.168.1.10
2026-07-10 14:31:47  end    Webserver  admin@192.168.1.10  duration=29m36s exit=0 sent=48.3KiB received=1.2MiB

Fehlversuche werden getrennt geführt. Kam die Verbindung nie zustande — Host nicht erreichbar, Auth abgelehnt, Hostkey-Prüfung gescheitert — steht dafür genau eine fail-Zeile im Log statt eines start/end-Paars mit 0 Sekunden Dauer:

2026-07-10 09:14:02  fail   Jumphost   admin@jump.example  reason=unreachable exit=255

reason ist eines von unreachable, auth, hostkey, unknown. In --history sind fail-Zeilen rot hervorgehoben, das Info-Fenster im Menü zeigt sie mit , und --report ignoriert sie — Fehlversuche verfälschen die Zeitauswertung also nicht.

Erkannt wird das an Authenticated to … in der Debug-Ausgabe von ssh -v. Mit SSHM_STATS=0 (also ohne -v) bleibt als Signal der Exit-Code 255, den ssh ausschließlich für eigene Verbindungs- und Auth-Fehler vergibt; reason ist dann immer unknown. Die übertragenen Datenmengen taugen übrigens nicht als Kriterium — ssh meldet auch für einen abgebrochenen Handshake ein paar hundert Byte.

Weil erst nach Sessionende feststeht, ob die Verbindung stand, werden beide Zeilen erst dann geschrieben; die start-Zeile trägt ihren echten Zeitpunkt nach. Eine laufende Session taucht dadurch noch nicht im Log auf.

Das Log wird automatisch auf die letzten 2000 Zeilen begrenzt. --last nutzt es für schnelles Wiederverbinden, --history zeigt die Liste. Einträge aus Versionen vor 1.4.0 (nur drei Felder) bleiben lesbar und werden als start angezeigt.

Nach dem Ende jeder Verbindung erscheint dieselbe Zusammenfassung direkt im Terminal — praktisch zum Nachhalten von Zeiten:

Verbindung beendet: Webserver  admin@192.168.1.10
  2026-07-10 14:02:11  →  2026-07-10 14:31:47   duration=29m36s exit=0 sent=48.3KiB received=1.2MiB

Wurde die Verbindung aus dem interaktiven Menü gestartet, kehrt sshm danach automatisch ins Menü zurück (die Zusammenfassung bleibt darüber sichtbar) — Beenden über den Exit-Eintrag, leere Eingabe oder CTRL-C. Bei Direktaufrufen (sshm 3, sshm web, sshm --last) geht es wie gewohnt zurück zur Shell. SSHM_LOOP=0 schaltet die Menü-Rückkehr ab.

Das fzf-Menü zeigt rechts ein Info-Fenster zum markierten Eintrag: Beschreibung, Optionen und die letzten Verbindungen zu diesem Host („da war ich zuletzt Dienstag, 40 Minuten"). In schmalen Terminals (unter 72 Spalten) ist das Fenster automatisch ausgeblendet — CTRL-/ blendet es jederzeit ein und aus.

Zeiten auswerten und Notizen

Mit SSHM_NOTE=1 fragt sshm nach jedem Sessionende kurz nach einer Notiz („Backup geprüft", „Ticket #1234") — sie landet mit im Verbindungslog. sshm --report summiert die protokollierten Zeiten je Tag und Host auf:

$ sshm --report woche
Zeitraum: 2026-07-07  2026-07-11

  2026-07-09  Webserver              1×  45m10s
  2026-07-09  Datenbank              1×  12m03s
  2026-07-10  Webserver              2×  1h31m

Summen:
  Webserver                          3×  2h16m
  Datenbank                          1×  12m03s
  Gesamt                             4×  2h28m

--csv gibt die Tageszeilen maschinenlesbar aus (datum,host,verbindungen,sekunden) — für die Übernahme in eine Zeiterfassung.

Erreichbarkeit prüfen

sshm --check pingt alle Einträge per TCP an (wie SSHM_PROBE, ohne zu verbinden) und zeigt ✓/✗ pro Host. Einträge mit -J/ProxyJump in den Optionen werden als übersprungen gekennzeichnet statt geprüft — das Ziel ist meist absichtlich nur über den Jumphost erreichbar, ein direkter Check wäre falsch-rot. Übersprungene zählen nicht als „nicht erreichbar".

Exit-Code Bedeutung
0 alle Einträge erreichbar
1 mindestens einer nicht erreichbar
2 Check nicht möglich — weder nc noch timeout installiert

Der Fall 2 ist bewusst ein eigener Code: ohne Werkzeug ein stilles ✓ zu melden wäre für Monitoring die schlechteste Variante.

Shell-Completion

sshm --setup richtet die Tab-Vervollständigung für Bash und Fish gleich mit ein (Eintragsnamen + Flags, immer aktuell aus der Konfig). Bestehende Setups (sshm-Block schon vorhanden) tragen eine Zeile manuell nach — Bash: eval "$(sshm --completion bash)" in die ~/.bashrc, Fish: sshm --completion fish | source in die config.fish.

Eine Nummer außerhalb des gültigen Bereichs (z. B. sshm 42 bei 3 Einträgen) gibt eine klare Fehlermeldung statt eines stillen Fallbacks.

Umgebungsvariablen:

SSHM_PROBE=1 sshm    # TCP-Check vor Connect — bei nicht erreichbarem Host
                     # erscheint eine j/N-Abfrage
                     # (nutzt nc falls installiert, sonst timeout + /dev/tcp)

SSHM_LEARN=0 sshm    # Key-Lernmodus abschalten (Default: an)

SSHM_STATS=0 sshm    # Transfer-Statistik abschalten — ssh läuft dann ohne -v
                     # (sofern der Lernmodus -v nicht ohnehin braucht);
                     # Start/Ende/Dauer/Exit-Code werden trotzdem geloggt

SSHM_LOOP=0 sshm     # Nach Verbindungsende nicht ins Menü zurückkehren
                     # (altes Verhalten — sshm endet mit dem Exit-Code von ssh)

SSHM_NOTE=1 sshm     # Nach Sessionende eine Notiz abfragen — landet im
                     # Verbindungslog (--history) und hilft beim Nachhalten

Kommando auf mehreren Hosts (--run)

sshm --run 'uptime'                    # Hosts per fzf-Mehrfachauswahl (TAB/EINFG)
sshm --run 'systemctl status nginx' web db 3
sshm --run -s 'apt update && apt upgrade -y'   # remote per sudo

Das Kommando läuft nacheinander auf allen gewählten Hosts, die Ausgabe ist pro Host mit einer Überschrift getrennt, am Ende steht je Host der Exit-Code. Der Gesamt-Exit-Code ist nur 0, wenn alle Hosts erfolgreich waren. Jeder Lauf wird im Verbindungslog protokolliert.

-s (sudo): Das Kommando läuft remote mit erhöhten Rechten. Das sudo-Passwort wird einmal lokal abgefragt und jedem Host über die verschlüsselte Verbindung auf stdin vorangestellt — ideal bei zentraler Authentifizierung (LDAP/AD), wo ein Passwort für alle betreuten Server gilt. Hosts mit NOPASSWD-sudo werden automatisch erkannt und ohne Passwort bedient (dann landet auch keine Passwortzeile im stdin des Kommandos). Wie im Dateibrowser gilt: Das Passwort bleibt nur im Speicher des Laufs, nie in Argumenten, Umgebungsvariablen oder Dateien.

Bewusst konservativ: sequenziell statt parallel — die Ausgabe bleibt lesbar, und ein Fehler fällt sofort auf, bevor er alle Hosts trifft.

Das Kommando wird für die Gegenseite POSIX-konform in Single Quotes verpackt, damit es auch dort ankommt, wo /bin/sh ein dash ist (Debian/Ubuntu) — mehrzeilige Kommandos und Sonderzeichen inklusive.


Dateibrowser (scp)

Zum schnellen Dateiaustausch mit einem Host gibt es einen fzf-basierten Dateibrowser — CTRL-F auf einem Eintrag im Menü öffnet ihn, alternativ direkt:

sshm -f web        # Dateibrowser für Eintrag "web"
sshm -f            # Menü im Dateibrowser-Modus (ENTER öffnet den Browser)

Angezeigt wird immer eine Seite — beim Start das Home-Verzeichnis des Remote-Hosts. Die Eingabezeile (Prompt) zeigt die aktive Seite und deren aktuelles Verzeichnis; das Kopierziel — das Verzeichnis der Gegenseite — steht im Fenster „Kopierziel" rechts daneben:

Taste Aktion
ENTER Verzeichnis öffnen bzw. Datei(en) zur Gegenseite kopieren
TAB oder EINFG Mehrfachauswahl markieren (Einfg wie im Midnight Commander)
CTRL-T Seite wechseln (remote ⇄ lokal) — beide Seiten sind frei navigierbar, damit sind Quell- und Zielordner frei wählbar
CTRL-Y Auswahl kopieren, auch ganze Ordner (rekursiv)
CTRL-N Neuen Ordner auf der aktuellen Seite anlegen (im sudo-Modus mit erhöhten Rechten)
CTRL-R sudo-Modus an/aus (siehe unten)
CTRL-/ Kopierziel-Fenster ein-/ausblenden
ESC Browser beenden

Rechts neben der Liste zeigt das Fenster „Kopierziel" immer das Verzeichnis der Gegenseite samt Inhalt. So ist jederzeit sichtbar, wo ENTER die Dateien ablegt (und was dort schon liegt). In schmalen Terminals (unter 72 Spalten) ist es automatisch ausgeblendet — CTRL-/ blendet es ein und aus. Die Tastenkommandos liegen im unteren Fensterrahmen und laufen damit durchgängig über die volle Breite — unter Liste und Kopierziel hindurch (^T steht für CTRL-T usw.).

Maus: Ein Klick wählt einen Eintrag, Doppelklick entspricht ENTER (Ordner öffnen bzw. Datei kopieren). Die Befehlszeile im Rahmen selbst ist nicht klickbar — fzf bietet Klick-Events nur für Header/Footer, und die laufen beide nicht über die volle Breite.

Rechte erhöhen (sudo-Modus)

Für Dateien, die dem eigenen Remote-User nicht gehören — z. B. Audit-Logs unter /var/log/audit — schaltet CTRL-R den sudo-Modus ein: Verzeichnislisten, Navigation und Kopien laufen dann remote mit sudo. Erlaubt der Server passwortloses sudo (NOPASSWD oder gecachter Timestamp), passiert das direkt; sonst fragt sshm das sudo-Passwort einmal ab. Es bleibt ausschließlich im Speicher der laufenden Browser-Sitzung (nie in Argumenten, Umgebungsvariablen oder Dateien) und wird jedem sudo-Aufruf über die verschlüsselte SSH-Verbindung auf stdin vorangestellt. CTRL-R erneut (oder das Ende der Sitzung) verwirft es.

Im sudo-Modus laufen Übertragungen als tar-Stream durch die SSH-Verbindung statt über scp (scp kann auf der Gegenseite kein sudo). Angenehmer Nebeneffekt beim Holen: Die Dateien werden lokal als eigener User entpackt und gehören damit sofort dem eigenen Account — das manuelle chown nach dem Kopieren entfällt. Der aktive Modus ist am ⚡sudo in der Eingabezeile erkennbar.

Technik: Beim Öffnen wird einmal authentifiziert und eine SSH-Multiplex-Verbindung aufgebaut (ControlMaster) — alle weiteren Verzeichniswechsel und Kopiervorgänge laufen über diesen Socket und sind dadurch praktisch verzögerungsfrei, ohne erneute Passwort-/Passphrase-Abfrage. Beim Beenden wird die Master-Verbindung geschlossen. Browser-Sitzungen erscheinen im Verbindungslog (--history) mit files=N (Anzahl der Übertragungen).

Grenzen: Der Browser braucht fzf ab Version 0.35 (ohne bzw. mit zu altem fzf gibt es eine klare Meldung). Er ist bewusst ein Holen/Bringen-Werkzeug, kein Dateimanager — Umbenennen/Löschen gibt es nicht. Dateinamen mit Zeilenumbrüchen werden nicht unterstützt; Leerzeichen und Sonderzeichen funktionieren.


Key-Handling bei vielen Keys

Das Problem: Server erlauben standardmäßig nur 6 Auth-Versuche pro Verbindung (MaxAuthTries) — und jeder angebotene Key zählt als Versuch. Wer viele Keys im Agent oder in der ssh_config hat, läuft in „Too many authentication failures", bevor der richtige Key an der Reihe ist.

sshm begegnet dem auf drei Ebenen:

1. Lernmodus (automatisch)

Bei Einträgen ohne -i liest sshm beim Connect per ssh -v mit, welchen Key der Server akzeptiert, und trägt ihn nach der Session automatisch in die Hosts-Datei nach:

✓ Key erkannt: Webserver → ~/.ssh/firmenkey
  In der Konfig nachgetragen (mit IdentitiesOnly=yes).

Ab dem zweiten Connect wird ausschließlich dieser Key angeboten. Die Debug-Ausgabe wird dabei vor dem Terminal weggefiltert, echte Meldungen (Banner, Fehler) bleiben sichtbar. Bei Passwort-Logins oder fehlgeschlagener Auth wird nichts eingetragen. Abschalten: SSHM_LEARN=0.

2. Key-Suche (Fallback bei Fehlschlag)

Scheitert der Connect an „Too many authentication failures", bietet sshm an, alle erkannten Keys einzeln zu testen — eine eigene Verbindung pro Key mit IdentitiesOnly, damit greift MaxAuthTries nie:

Key-Suche: 8 Key(s) einzeln testen? [j/N] j
  → ~/.ssh/key1   ✗
  → ~/.ssh/key7   ✓ passt
✓ Key erkannt: Jumphost → ~/.ssh/key7

Der Treffer wird eingetragen und die Verbindung direkt aufgebaut.

Beachten:

  • Jeder Einzeltest zählt serverseitig als Login-Versuch — bei fail2ban o. ä. kann das zu einer temporären Sperre führen (deshalb die j/N-Abfrage vorab).
  • Getestet wird ohne Prompts (BatchMode): erkennbar sind nur Keys ohne Passphrase oder solche, die bereits im Agent entsperrt sind. Passphrasegeschützte, noch nie benutzte Keys vorher mit ssh-add <keyfile> entsperren.

3. Warnung im Menü

Bei 6 oder mehr Keys und Einträgen ohne festes -i erscheint über dem Menü ein Hinweis mit der Empfehlung, pro Eintrag -o IdentitiesOnly=yes -i "PFAD_ZUM_KEY" zu setzen (bzw. den Lernmodus machen zu lassen).

Warum nicht IdentitiesOnly=yes global?

IdentitiesOnly=yes bedeutet nicht „biete weniger Keys an", sondern „nutze nur Keys aus ssh_config/-i, ignoriere reine Agent-Keys". Global gesetzt würde es das Problem nicht lösen (die config listet ja alle Keys) — und Agent-Forwarding auf Jumphosts kaputtmachen, wo Keys nur im weitergereichten Agent existieren. Sinnvoll ist die Option nur gepaart mit einem konkreten -i pro Host — genau so trägt sshm sie ein.


Deinstallieren

# sshm-Blöcke aus dotfiles entfernen (mit Backup)
sshm --uninstall

# Script entfernen
rm ~/.local/bin/sshm
# oder
sudo rm /usr/local/bin/sshm

# Hosts-Konfiguration entfernen (optional)
rm -rf ~/.config/sshm