20 KiB
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 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.
# 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
./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.