Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Project structure

Keep this in sync with the actual tree (the docs-in-sync rule).

All modules present: main, app, askpass, config, forwards, import, model, paths, search, secrets, ssh, state, store, transfer/{mod,worker,pane,screen,e2e}, testsupport (test-only), vault, ui/{mod,list,help,widgets,wizard,browse,settings,sites,transfer,forward_popup,forwards,two_factor}. (error.rs was removed — the codebase uses anyhow throughout.)

Repository

ssh-tui/                 (crate/binary name: `sshelf`)
├── Cargo.toml
├── CONTRIBUTING.md      contributor guide + docs-in-sync rule
├── README.md           (M8) user-facing intro + positioning
├── SECURITY.md         (M8) threat model for OSS users (mirrors docs/security.md)
├── LICENSE-MIT         (M1)
├── LICENSE-APACHE      (M1)
├── docs/               living documentation (this directory)
└── src/                see below

src/ modules

FileResponsibility
main.rsEntry/dispatch. If SSHELF_ASKPASS is set → askpass mode (read argv[1]). Else clap parses: default TUI, or subcommands (import, list, add).
app.rsApp state + synchronous event loop + screen routing (component orchestration).
model.rsHost + Site structs (+ AuthMethod); Host::with_site_defaults/find_site (site inheritance); serde derives.
store.rsLoad/save hosts.toml with atomic write (temp + rename); load config.toml.
state.rsFrecency state (use_count, last_used) load/save (state.json); score computation.
forwards.rsBackground port-forwards: the ForwardSpec/ForwardEntry model, the -L/-R/-D argv builder, spawn (detached ssh -N + readiness/error mapping), PID liveness/kill via ps/kill, reconcile, and forwards.json load/save.
secrets.rsSecretStore trait → keyring backend + age-vault fallback; zeroize on secrets.
ssh.rsBuild ssh argv from a Host; terminal teardown + exec() handoff; askpass env wiring.
askpass.rsHeadless askpass entry: inspect argv[1]; answer password prompts via secrets, or a queued 2FA code (SSHELF_2FA_CODE) for the verification prompt; else decline.
search.rsFuzzy filter (nucleo-matcher) + frecency ranking + per-row match indices for highlight.
import.rsssh2-config parse of ~/.ssh/configHost mapping; warn on unsupported Match/Include.
paths.rsetcetera path resolution (config/data dirs); file paths; dir/file perms (0700/0600).
config.rsPreferences: decay_rate, default_sort, accent color; writes a commented default on first run.
transfer/mod.rsFile-transfer core: ssh-ControlMaster + sftp argv builders, the worker↔UI message protocol (WorkerCmd/WorkerEvent), and progress math.
transfer/worker.rsBackground worker thread: owns the ControlMaster (open/readiness/teardown), lists remote dirs (sftp ls -l), runs sftp get/put transfers with progress + cancel.
transfer/pane.rsOne side’s browsing state (fuzzy filter + selection + nav, reusing search); read_local_dir for the local side; RemoteEntryPaneEntry.
transfer/screen.rsThe dual-pane TransferScreen: two panes over one session, key handling, draining worker events.
ui/list.rsHost list rendering + match highlighting + selection.
ui/transfer.rsRenders the transfer screen (two panes + progress/status + hint bar) from a borrowed view.
ui/wizard.rsAuth-aware add/edit form: fields, validation, key picker, opens the file browser.
ui/browse.rsFile-browser modal (fuzzy-filtered) for picking a key file anywhere on disk.
ui/settings.rsSettings screen (F2): config-file display + editable hosts-file location.
ui/sites.rsSites manager (F3): list + add/edit/delete sites and their optional defaults; emits renames for the app to cascade.
ui/forward_popup.rsNew-port-forward popup (Ctrl-f): kind chooser (Local/Remote/Dynamic) + ports/host fields + validation; emits a ForwardSpec for the app to spawn.
ui/forwards.rsPort-forwards manager (F4): lists all active forwards from a live snapshot; emits a kill request for the app to act on.
ui/two_factor.rs2FA code popup shown before connecting to a requires_2fa host; emits the entered code for the app to queue + supply via askpass.
ui/help.rsHelp overlay.
ui/widgets.rsShared widgets: single-line text input (hand-rolled), keybind hint bar, confirm modal.

Data flow at a glance

paths ──▶ store ──▶ model(Host[])  ┐
paths ──▶ state ──▶ frecency       ├─▶ search ──▶ ui ──▶ (Enter) ──▶ ssh ──▶ exec
                                   ┘                                  │
secrets ◀── ui (store on add/edit)                     askpass ◀── ssh (via SSH_ASKPASS)
   └────────────────────────────────────────────────────▶ secrets (retrieve)

Conventions

  • One responsibility per module; ssh.rs is the only place that calls exec(); secrets.rs is the only place that touches the keyring/vault.
  • No unwrap()/expect() on fallible I/O in non-test code — return errors, surface them in the UI.
  • Every new/moved module updates this file.