- Shell 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks are pending
ShellCheck / shellcheck (push) Waiting to run
Verbindungs-Logging, Menü-Loop, scp-Dateibrowser mit sudo, --run/--check/--report und Shell-Completion (v1.7.0) |
||
| .github/workflows | ||
| .gitattributes | ||
| .gitignore | ||
| LICENSE | ||
| README.md | ||
| sshm | ||
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 |
empfohlen | Interaktives Menü mit Fuzzy-Suche; Pflicht für den Dateibrowser (-f) |
nc (netcat) |
optional | Erreichbarkeits-Check (SSHM_PROBE=1) — ohne nc wird timeout + Bash /dev/tcp genutzt; fehlt beides, wird der Check übersprungen |
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/config—Host *mitAddKeysToAgent yesund automatisch erkannten Keys: sshm scannt~/.sshnach privaten Keys (unabhängig vom Dateinamen — erkannt über passende.pub-Datei oder denPRIVATE KEY-Header) und trägt alle alsIdentityFile-Zeilen ein. Pfade mit Leerzeichen werden automatisch gequotet. Wird kein Key gefunden, landet ein auskommentierter Platzhalter samt Hinweis aufssh-keygen -t ed25519im Block.~/.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.
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.
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 — 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
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").
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. Exit-Code 0 nur, wenn alle erreichbar sind — auch für Skripte oder Monitoring-Einzeiler nutzbar.
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.
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 Kopfzeile zeigt das aktuelle Kopierziel (das aktuelle Verzeichnis der Gegenseite):
| 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) |
ESC |
Browser beenden |
Die rechte Seite zeigt immer das aktuelle Kopierziel — das Verzeichnis der Gegenseite samt Inhalt. So ist jederzeit sichtbar, wo ENTER die Dateien ablegt (und was dort schon liegt).
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. Die Statusleiste zeigt den aktiven Modus mit ⚡ / sudo aktiv an.
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 (ohne 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 mitssh-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