Project structure

A tour of the repository for contributors and curious readers.

Directory overview

NeoSSHWinManager/
├── main.py                      # GUI entry point, single-instance, theme bootstrap
├── cli_main.py                  # CLI companion — talks to GUI via Named Pipe
├── build_dual.ps1               # PyInstaller build for GUI + CLI
├── NeoSSHWinManager.spec        # PyInstaller spec (GUI)
├── NeoSSHWinManager-cli.spec    # PyInstaller spec (CLI)
├── file_version_info.txt        # Windows VERSIONINFO block
├── requirements.txt
├── LICENSE
├── README.md
├── assets/
│   ├── app_icon.ico             # Multi-resolution Windows icon
│   ├── app_icon.png
│   └── screenshots/
├── src/
│   ├── config.py                # AppSettings + JSON load/save
│   ├── database.py              # SQLite schema + migrations
│   ├── auth_manager.py          # Login, key derivation, multi-user
│   ├── sshfs_controller.py      # Mount / unmount / status
│   ├── ssh_launcher.py          # SSH sessions (OpenSSH / PuTTY)
│   ├── single_instance.py       # Mutex + window activation
│   ├── ipc_server.py            # Named-pipe server for CLI companion
│   ├── i18n.py                  # Load / resolve translations
│   ├── translations/
│   │   ├── en.json
│   │   └── de.json
│   ├── models/
│   │   ├── connection.py        # Connection data model
│   │   └── settings.py          # AppSettings data model
│   └── ui/
│       ├── main_window.py
│       ├── login_window.py
│       ├── widgets/             # ConnectionCard, MountToggle, …
│       └── dialogs/             # add_edit, settings, ask_password, debug
└── tests/
    ├── test_config.py
    ├── test_auth_manager.py
    └── test_sshfs_controller.py

Main modules

main.py

Entry-Point der GUI. Tut der Reihe nach:

  1. Lädt die Konfig (config.AppSettings.load()).
  2. Erzwingt Single-Instance (SingleInstance("SSHWinManager_Mutex_v1")).
  3. Initialisiert QApplication mit dem gespeicherten Theme.
  4. Zeigt Login-Window (außer Auto-Login greift) → Hauptfenster.
  5. Startet IPCServer in einem Worker-Thread.

cli_main.py

Argparse-CLI. Verbindet sich mit \\.\pipe\SSHWinManager_IPC_v1, sendet einen JSON-Request {op:"connect", key:"…", exec:"…"}, erhält Credentials zurück und ruft dann ssh_launcher.spawn_session() auf.

src/config.py

  • AppSettings — Dataclass mit Defaults (Theme, Sprache, PuTTY-Pfad, Auto-Reconnect, Check-Intervall, Tray, Debug, …).
  • load()/save() — JSON in %APPDATA%\SSHWinManager\config.json.
  • Migrations: fehlende Keys werden mit Defaults aufgefüllt.

src/database.py

  • SQLite, Pfad: %APPDATA%\SSHWinManager\app.db.
  • Tabellen: users, connections, settings_per_user.
  • Schema-Version in PRAGMA user_version; migrate() fügt neue Spalten idempotent hinzu.

src/auth_manager.py

  • UserManager — anlegen, einloggen, Passwort ändern, löschen.
  • Passwort-Hash: PBKDF2-HMAC-SHA256, per-User Salt, 200k Iterations.
  • Session-Key wird beim Login abgeleitet und hält nur im RAM.
  • UserConnectionManager — CRUD für Verbindungen, automatische En/Decryption pro User.

src/sshfs_controller.py

  • SSHFSController.mount(connection, password=None) — baut die Argumentliste, startet sshfs.exe als detached Subprocess.
  • unmount(letter) — drei Strategien (siehe Verbindungen).
  • list_mounts() — parst fsutil-Output.
  • status_for(connection) — gibt "mounted", "unmounted" oder "stale" zurück.

src/ssh_launcher.py

Startet eine interaktive SSH-Session. Wählt OpenSSH oder PuTTY je nach AppSettings.use_putty. Übergibt Key oder Passwort sicher.

src/ipc_server.py

Worker-Thread mit Named-Pipe-Server. Akzeptiert JSON-Requests, validiert Access-Key gegen die DB, gibt entschlüsselte Credentials zurück.

UI modules

File Responsibility
login_window.pyLogin-Form, Setup-Wizard für Erstprofil.
main_window.pyHauptfenster: Sidebar + Verbindungsliste + Statusbar.
tray.pySystem-Tray-Icon mit Schnellzugriff-Menü.
widgets/connection_card.pyKachel pro Verbindung mit Mount-Toggle.
widgets/mount_toggle.pyCustom Switch mit Cyan/Grün-States + Spinner.
dialogs/add_edit_dialog.pyVerbindung anlegen / bearbeiten.
dialogs/settings_dialog.pyApp-weite Einstellungen.
dialogs/ask_password_dialog.pyPasswort-Abfrage für „Jedes Mal fragen”.
dialogs/debug_dialog.pyLive-Log-Viewer mit Filter.

Data models

Connection

@dataclass
class Connection:
    id: int | None
    user_id: int
    name: str
    host: str
    port: int = 22
    user: str = ""
    remote_path: str = "/"
    drive_letter: str = "Z:"
    auth_method: str = "password"   # password | key | ask
    password: str | None = None     # plaintext only in RAM
    key_path: str | None = None
    cli_access_enabled: bool = False
    access_key: str | None = None
    auto_mount: bool = False
    created_at: datetime
    updated_at: datetime

AppSettings

@dataclass
class AppSettings:
    theme: str = "dark"             # dark | light
    language: str = "en"            # en | de
    start_with_windows: bool = False
    minimize_to_tray: bool = True
    auto_login_with_windows: bool = False
    auto_reconnect: bool = False
    auto_reconnect_mounts_on_start: bool = True
    check_interval_seconds: int = 30
    debug_mode: bool = False
    use_putty: bool = False
    putty_path: str = r"C:\Program Files\PuTTY\putty.exe"

Build & release process

  1. Version in file_version_info.txt, README und changelog.html erhöhen.
  2. .\build_dual.ps1 ausführen — baut GUI- und CLI-EXE per PyInstaller in dist/.
  3. Beide EXEs lokal testen (Login, ein Mount, ein CLI-Aufruf).
  4. Tag setzen: git tag v1.x.0 && git push --tags.
  5. Auf GitHub neues Release anlegen, beide EXEs anhängen, Changelog einfügen.