166 lines
20 KiB
Markdown
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.*
|