sshm: simple SSH launcher
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
DravenTec 009bb6f03c
Some checks are pending
ShellCheck / shellcheck (push) Waiting to run
Merge pull request #1 from DravenTec/claude/sshm-connection-logging-7j5v7j
Verbindungs-Logging, Menü-Loop, scp-Dateibrowser mit sudo, --run/--check/--report und Shell-Completion (v1.7.0)
2026-07-11 19:07:51 +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 Create .gitignore 2026-07-09 20:14:01 +02:00
LICENSE Initial commit 2026-07-09 19:43:52 +02:00
README.md Report, Notizen, --check, --run, Menü-Info, CTRL-N und Shell-Completion 2026-07-11 16:56:24 +00:00
sshm Report, Notizen, --check, --run, Menü-Info, CTRL-N und Shell-Completion 2026-07-11 16:56:24 +00: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 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/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.
  • ~/.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 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