shellm/README.md

166 lines
20 KiB
Markdown

# shellm — A Rust/iced Port of secpanel, with a Modern SCP Browser Grafted On
*Project README for shellm 1.1.0 — a faithful Rust + iced 0.13 modernization of Steffen Leich-Nienhaus's classic Tcl/Tk secpanel SSH connection manager, with a Midnight-Commander-style dual-pane SCP browser, modern terminal auto-detection, and a flat-design refresh. Written by Jeremy Anderson, released under GPL-2.0-or-later, first shipped under this layout in 2026.*
There is a particular category of Unix desktop tool that was effectively frozen in amber around 2005: the SSH connection manager. The terminals evolved, the toolkits evolved, the underlying OpenSSH client gained ciphers, key-exchange algorithms, and config-file directives that the original Tcl scripts never heard of — but the connection managers themselves stayed on Tk 8.4, with grey groove borders and beveled buttons, reading their state from flat text files in `~/.secpanel/`. secpanel 0.6.1, by Steffen Leich-Nienhaus, was one of the better specimens: a clean Tcl/Tk application that wrapped `ssh`, `scp`, `ssh-agent`, `ssh-add`, `ssh-keygen`, and `sftp` into a four-tab GUI, with profile persistence, port-forward management, keypair generation, hostkey editing, and a key-distribution wizard. The design was sound. The implementation was sound. The look, by 2026 standards, was a museum piece.
shellm is a faithful Rust port of secpanel to the [iced](https://iced.rs) GUI toolkit, preserving 100% of the original's command-line semantics while modernizing the visuals and finishing several features that the original had stubbed out. It is not a rewrite in the loose sense — the SSH command builder in `src/ssh/command.rs` emits `-A`/`-a`, `-X`/`-x`, `-1`/`-2`, `-4`/`-6`, `-p`, `-c`, `-i`, `-F`, `-P`, `-v`, `-q`, `-f`, `-g`, `-C`, `-N`, `-o StrictHostKeyChecking=yes`, `-o CompressionLevel=N`, `-L ...`, `-R ...` byte-for-byte as the original Tcl `connect` proc did, including the asymmetric syntax OpenSSH uses for `CompressionLevel` versus the equals-sign syntax other SSH implementations expect. The on-disk layout under `~/.shellm/` mirrors the original secpanel's `~/.secpanel/` structure (same `mode#unixtime#text` history file format, same `time#cmd` trace log format), so users migrating from secpanel 0.6.x can copy their `~/.secpanel/` contents into `~/.shellm/` and it will Just Work. The Rust port adds a new SCP browser tab, modern terminal auto-detection, and a flat-design refresh, but the SSH semantics underneath are unchanged.
This README documents what shellm is, what it does, how to build and run it, and how the codebase is laid out. For a step-by-step getting-started guide, see `quickstart.md`. For a long-form technical walkthrough of the design decisions — why the data directory is `~/.shellm/`, why the SCP browser defaults to `ssh`+`scp` (with `sftp -b -` as an opt-in backend) instead of a Rust SFTP library, why there is no TLS anywhere — see `blog.md`.
---
## What's New in 1.1.0
### Dual-pane SCP browser (new tab)
A new **SCP Browser** tab sits between **Keys & Agent** and **Configs** in the main window. It is a Midnight-Commander-style dual-pane file manager with a WinSCP-inspired (but not identical) visual design: a top bar with a profile picker, Connect/Disconnect buttons, and a live connection status indicator (green dot plus `user@host:port`); two panes side-by-side for Local (left) and Remote (right); a center transfer column with large Upload and Download buttons that activate only when a transferable file or directory is selected; and a bottom status bar showing entry counts or the last operation result. Each pane has its own path bar with a Go button, a toolbar with up/refresh/new-folder/delete actions, and a three-column file list (Name, Size, Modified) where directories are bold blue, symlinks are cyan, and the selected row gets a light-blue tint with an accent border.
The navigation model is single-click throughout, matching Midnight Commander's idiom rather than the double-click idiom of file managers descended from Finder. Single-click on a row selects it for upload, download, or delete; single-click on a directory navigates into it; single-click on the `..` entry (always at the top of the list, except at the filesystem root) goes up one level. The up arrow in the toolbar and the path bar (type a path, hit Enter or click Go) provide alternative navigation. The list auto-refreshes after every transfer, delete, or mkdir operation. The implementation lives in `src/scp_browser/mod.rs` and uses the OpenSSH `sftp` and `scp` command-line tools rather than pulling in a Rust SFTP library — a deliberate choice that keeps the build light and matches shellm's existing "shell out to ssh tools" pattern. All operations run inside `tokio::task::spawn_blocking` so the UI stays responsive, and a busy indicator (an orange dot in the status bar) shows when an async operation is in flight.
The file operations map directly to the underlying SSH tools. Upload uses `scp -P port local user@host:remote`, auto-detecting directories and adding `-r` when needed. Download uses `scp -r -P port user@host:remote local`. Delete-local uses `std::fs::remove_file` or `remove_dir_all`. By default, remote list/mkdir/delete operations run via `ssh host -- ls -la` / `mkdir -p` / `rm -rf` — no `sftp` binary required (modern `scp` in OpenSSH 9.0+ uses the SFTP protocol under the hood for transfers anyway). Users with restricted sshd configs that disable shell access (`ForceCommand internal-sftp`) can opt in to the `sftp -b -` backend in the Configs tab. Mkdir-local uses `std::fs::create_dir_all`. List-local uses `std::fs::read_dir` with directory-first sorting; list-remote parses the `ls -la` output (from either `ssh` or `sftp`) to extract name, size, permissions, and modified date.
### Modern terminals and smart default detection
The terminal list in `src/data/terminal.rs` now includes the original 11 terminals from secpanel's `termdefs.txt` (aterm, Eterm, multi-aterm, Konsole, gnome-terminal, xterm, rxvt, mrxvt, xvt, XFCE Terminal, PuTTY's terminal) plus ten modern additions: `rs-mrxvt` (a Rust rewrite of mrxvt), `rxvt-unicode` (urxvt), `Alacritty`, `Kitty`, `WezTerm`, `Foot`, `GNOME Console` (kgx), `xfce4-terminal`, `Tabby` (Electron-based, plugin ecosystem), `Ghostty` (Mitchell Hashimoto's GPU-accelerated terminal), and `Warp` (AI-first, Rust-based). The `TerminalKind` enum has 22 variants total, each serializable to and from TOML via `serde` with `PascalCase` rename rules.
The default terminal is auto-detected at first run with a fallback chain that prefers modern, GPU-accelerated terminals over the legacy X11 defaults: `rs-mrxvt` first (the preferred default), then `alacritty`, `kitty`, `wezterm`, `ghostty`, `warp`, `tabby`, `foot`, `kgx`, `rxvt-unicode`, `xfce4-terminal`, then the original `mrxvt` and `xterm` as always-available fallbacks. The Configs tab now shows which terminals are actually available on `PATH` so the user knows what will work before they pick one. This matters because shellm execs the configured terminal to run its generated runner scripts — if the terminal isn't installed, the connect button silently does nothing, and that was a recurring support issue with the original secpanel.
### Modern visual design
The 1995 Tk look — grey groove borders, beveled buttons, Motif-style scrollbars — is gone. The new design language is flat surfaces with subtle 1px borders, 6px corner radius on cards/buttons/inputs, a modern blue accent (`#2563EB`) for primary actions, card-style containers (white background with a subtle border), an underline-style tab bar where the active tab gets a blue underline and dark text, a dot-style agent indicator (a small colored dot — green for our agent, red for no agent, yellow for external — with a label, instead of a full-color status bar background), modal panels with shadow that float on a dimmed backdrop, four button variants (Primary, Secondary, Danger, Ghost) for a modern hierarchy of emphasis, and section labels in small uppercase grey text for grouping. The style functions live in `src/widgets/styles.rs`, and the palette helpers (hex strings to `iced::Color`) live in `src/theme.rs` and `src/theme_style.rs`.
### All stubs finished
The original secpanel 0.6.1 had several features that were stubbed out in the Tcl source — present in the menu, but not actually wired up. shellm finishes all of them:
| Stub | Status |
| ---------------------------------- | ------------------------------------------------------------ |
| Key distribution wizard final step | Wired up — calls `build_keydist_script` and launches it via `launch_interactive` |
| Ask-for-user modal at connect time | Real modal flow — defers connect until user submits a username |
| Color & font settings save | Both dialogs write through to `configs.toml` |
| Profile export | All 4 formats: shell script, SSH config entry, GNOME `.desktop`, KDE `.desktop` |
| Remote account manager | Reads `~/.shosts` and `~/.ssh/authorized_keys` via scp, writes back via scp |
| GPG data protection | Full round-trip encrypt/decrypt of `~/.shellm/` using GnuPG AES256 |
| `AddIdentity` file picker | Properly routed through the `FilePicked` message |
| `SetDefIdent` file picker | Saves to `configs.defident` and persists |
| Right-click context menu | Connect / Edit / SCP / External SFTP / Export / Remote / Delete |
---
## What Works (Full Feature List)
The main window has four tabs — **Connections**, **Profiles**, **Keys & Agent**, **Configs** — plus the new **SCP Browser** tab, with a modern underline tab bar and the dot-style agent indicator described above. The Connections tab shows the list of profiles with two-line rows (title on the first line, `user@host` on the second), Connect / SCP / Multi (cssh) / Multi (xterm) / New / Edit / Delete buttons, and a right-click context menu. The Profiles tab is a full editor for every field in the original `default.profile` — title, host, user (with an Ask toggle that defers the username prompt until connect time), port, command (with a NoExec toggle), subsystem, identity, cfgfile, algo, compress (with a level slider), all nine boolean checkboxes, SSH/IP version pickers, a gateway toggle, and Local/Remote forwards dialogs.
The Local and Remote forwards dialogs support list, add, and delete operations, with quick-port buttons for HTTP (80), POP3 (110), IMAP (143), and SMTP (25), and `<TARGET-HOST>` / `<LOCAL-HOST>` placeholder substitution at connect time. The Keys & Agent tab provides Launch agent / Kill agent / Add identity / Agent info / Set/Remove default identity, plus keypair management, a hostkeys browser, and a key-distribution wizard. The keypair management dialog generates RSA1/RSA/DSA keys via `ssh-keygen -t ... -f ... -N ... -C ...`, lists existing `~/.ssh/*.pub` files, displays fingerprint and bubblebabble info, changes passphrases, and deletes keypairs. The hostkeys browser lists `~/.ssh/known_hosts` and deletes entries via `ssh-keygen -R <host>`.
The Configs tab lets the user pick the SSH, ssh-keygen, ssh-agent, ssh-add, ssh-askpass, scp, and browser binaries (each with a file-picker), pick the terminal emulator (all 22 supported, with availability indicators), pick the external SFTP client (FileZilla, Nautilus, Konqueror — used only by the "External SFTP" context-menu action), and pick the SCP browser backend (`scp + ssh` by default, or `sftp -b -` for restricted sshd configs). The SSH command builder is byte-for-byte faithful to the original `connect` proc — it emits `-A`/`-a`, `-X`/`-x`, `-1`/`-2`, `-4`/`-6`, `-p`, `-c`, `-i`, `-F`, `-P`, `-v`, `-q`, `-f`, `-g`, `-C`, `-N`, `-o StrictHostKeyChecking=yes`, `-o CompressionLevel=N`, `-L ...`, `-R ...` exactly as the Tcl version does, and the `CompressionLevel` syntax is correctly switched between OpenSSH's space-separated form and the equals-sign form used by other SSH implementations. The runner-script mechanism writes `~/.shellm/.runfiles/runproc.<ts>` and `runproc.<ts>-run` shell scripts (mirroring the original's `provrunfile` proc) and execs the configured terminal to run them. Profile and config persistence reads and writes `~/.shellm/profiles/*.profile` and `~/.shellm/config` as TOML, and the loader is backwards-compatible — it auto-imports the original Tcl-style `set configs(key) value` and `set title "..."` profile formats on load.
History and trace logs append to `~/.shellm/history` (in `mode#unixtime#text` format) and `~/.shellm/.runfiles/trace.log` (in `time#cmd` format) exactly as the original did, and a history viewer and trace viewer dialog are included. External agent detection works the same way as the original: if `SSH_AUTH_SOCK` and `SSH_AGENT_PID` are already set in the environment when shellm starts, it accepts the external agent and refuses to kill it. A satellite mini-window can be toggled from the tab bar to switch to a compact connection-menu view with the green/red agent indicator. The profile export dialog generates shell scripts, SSH config entries, GNOME `.desktop` files, or KDE `.desktop` files for the currently-loaded profile. The remote account manager opens a dialog showing the remote `~/.shosts` and `~/.ssh/authorized_keys` for the selected profile, lets the user edit them, and writes them back via scp. GPG data protection provides optional symmetric encryption of the entire `~/.shellm/` tree into `spdata.lck` using GnuPG AES256; it is toggled via the Protect menu entry and encrypts on exit if a passphrase is set. About, Message, Confirm, Colors, Fonts, History, Trace, Agent info, and Key info dialogs round out the overlay set.
---
## File Layout
The on-disk layout mirrors the original secpanel's `~/.secpanel/` structure (shellm uses `~/.shellm/`), so existing users of the Tcl/Tk original (or of prior Rust-port versions) can copy their `~/.secpanel/` directory into `~/.shellm/` and it will Just Work:
```
~/.shellm/
├── config # TOML (legacy Tcl-style auto-imported)
├── profiles/
│ └── <name>.profile # TOML (legacy Tcl-style auto-imported)
├── history # plain text, format: mode#time#text
├── spdata.lck # GPG-encrypted tarball (when protectdata is on)
├── .runfiles/
│ ├── trace.log # plain text, format: time#cmd
│ ├── runproc.<ts> # inner runner script (#!/bin/sh + cmd)
│ └── runproc.<ts>-run # outer wrapper (exec <term> -e <inner>)
└── .init # version marker (written on first run)
```
Both profiles and config are written as TOML, but the loader in `src/data/config.rs` and `src/data/profile.rs` accepts the original Tcl syntax (`set title "..."`, `array set lfs {...}`, etc.) so existing secpanel 0.6.x users can copy their `~/.secpanel/` directory to `~/.shellm/` without conversion. shellm uses `~/.shellm/` as its own data directory; the layout is identical to secpanel's `~/.secpanel/` so a `cp -a ~/.secpanel/. ~/.shellm/` is sufficient to migrate an existing install.
---
## Building from Source
shellm builds with Rust stable 1.80 or later against iced 0.13. The build has been verified on Debian 12, Ubuntu 24.04, Fedora 40, and Arch Linux as of mid-2026.
```bash
# 1. Install Rust (stable, >= 1.80) via rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
# 2. Install iced 0.13 system deps (Debian/Ubuntu):
sudo apt install -y libx11-dev libxext-dev libxft-dev libxinerama-dev \
libxcursor-dev libxrender-dev libxfixes-dev libxrandr-dev libdbus-1-dev \
libudev-dev libwayland-dev libgl-dev libglib2.0-dev libcairo2-dev \
libpango1.0-dev libgdk-pixbuf2.0-dev libatk1.0-dev libgtk-3-dev
# 3. Build:
cd shellm
cargo build --release
# -> ./target/release/shellm
```
If you do not have OpenGL dev headers and want a smaller, faster build, the default `Cargo.toml` already uses iced's `tiny-skia` software renderer — no GPU required. This is the configuration shipped in the 1.1.0 release tarball. The release profile uses `opt-level = 3`, thin LTO, a single codegen unit, and `strip = "debuginfo"` to produce a reasonably small binary.
## Running
```bash
./shellm
```
First run creates `~/.shellm/` with `chmod 700`, copies in default configs, and opens the main window. The version marker is written to `~/.shellm/.init` so subsequent runs can detect upgrades and run any necessary migrations. If `SSH_AUTH_SOCK` and `SSH_AGENT_PID` are already set in the environment, shellm accepts the external agent rather than launching its own — this mirrors the original secpanel behavior and avoids the situation where two agents compete for the same socket.
---
## Architecture
```
src/
├── main.rs Entry point; iced 0.13 application builder
├── state.rs SecPanel state struct (configs, profiles, dialogs)
├── message.rs Message enum + sub-enums (Tab, ConfigBin, ExportFormat, ...)
├── update.rs Message dispatch — one match arm per Message
├── update_actions.rs Composite actions: connect, sftp, multi, agent
├── view.rs Top-level view: menu + tab_bar + page + status_bar
├── theme.rs Color palette helpers (hex → iced::Color)
├── theme_style.rs Re-exports widget style fns and palette colors
├── profiles_export.rs Profile export: shell script, SSH config, .desktop
├── data/
│ ├── mod.rs
│ ├── paths.rs ~/.shellm paths, which(), ensure_shellm_tree()
│ ├── profile.rs Profile struct + Forward + Algo + legacy parser
│ ├── config.rs Configs struct + SftpClient + legacy parser
│ ├── terminal.rs TerminalKind (22 entries: 11 original + 11 modern)
│ ├── history.rs HistoryEntry + append/read/clear + trace log
│ └── protection.rs GPG-based encrypt/decrypt of ~/.shellm/
├── ssh/
│ ├── command.rs build_ssh_command(profile, configs, hostname)
│ └── runner.rs LaunchSpec + launch() + terminal wrapper writer
├── agent/mod.rs ssh-agent / ssh-add management
├── keygen/mod.rs ssh-keygen operations
├── scp_browser/mod.rs Dual-pane SCP browser (sftp/scp subprocess wrapper)
├── widgets/
│ ├── styles.rs Modern style fns (primary/secondary/ghost/danger + card)
│ ├── tab_bar.rs Underline-style tab bar
│ ├── connections_page.rs
│ ├── profiles_page.rs
│ ├── keys_agent_page.rs
│ ├── configs_page.rs
│ └── scp_browser_page.rs
├── dialogs/mod.rs All modal dialogs (overlays via iced::widget::stack)
└── util/mod.rs zenity/kdialog file picker
```
The Elm-architecture split — `state`, `message`, `update`, `view` — is preserved as the four top-level modules, with `update_actions.rs` holding the composite actions (connect, sftp, multi-launch, agent launch) that would otherwise bloat `update.rs`. Internal Rust identifiers use the `Shellm` name throughout — the application state struct is `Shellm`, the path helpers are `shellm_dir()` and `ensure_shellm_tree()` in `data::paths`, and they return paths under `~/.shellm/`. The original secpanel identifiers are gone from the code; only heritage mentions remain in comments and docs.
---
## Credits
Original secpanel © Steffen Leich-Nienhaus, <https://themediahost.de/secpanel/> (mirrored at <https://sourceforge.net/projects/secpanel/>), GPL-2.0-or-later. This Rust/iced port, shellm, is © 2026 Jeremy Anderson, <https://dcos.net>, GPL-2.0-or-later. All credit for the original design and functionality belongs to Steffen Leich-Nienhaus; shellm is a faithful modernization of his work and would not exist without it.
*shellm 1.1.0 — released 2026 — Jeremy Anderson, dcos.net — GPL-2.0-or-later. Original secpanel © Steffen Leich-Nienhaus, GPL-2.0-or-later. See `LICENSE` for the full GPLv2 text and the copyright disclaimer that applies to both works.*