Tech 4 min read

Can Tauri build a multi-pane SFTP client?

IkesanContents

Why I looked into this

Most Mac SFTP clients are annoying to use. I wanted to open multiple directories in separate panes at the same time, but many tools just do not support that.

On the same server, I often jump between fixed deep paths such as /var/www/production/assets/2024/01/ and /var/www/staging/assets/. Re-navigating every time is a pain.

People kept saying “build it yourself” and “Tauri can do it”, so I checked whether that was actually true.

Conclusion

Yes, Tauri can absolutely build this.

  • There are already real-world examples such as AeroFTP, Jexpe, and T-Shell
  • Rust has solid SFTP crates
  • It can be built as a native macOS app

Useful Rust crates

CrateNotes
russh-sftpPure Rust, async/tokio support. My pick
ssh2libssh2 wrapper. Stable
remotefs-sshHigher-level API. Simple to use

russh-sftp feels the most modern and fits Tauri’s tokio-based architecture well.

Design approach

Let existing file managers handle local files

This app should be remote-only. Local file operations can stay in Finder or QSpace.

  • No local file browser
  • Upload via drag-and-drop from Finder
  • Download by dragging from the app to Finder

Simple division of responsibility.

Multiple panes, multiple paths

You can open multiple panes on the same server. Share the connection and vary only the path.

┌─────────────────────────────────────────┐
│  [+] New pane   [Connection settings]   │
├──────────────┬──────────────┬───────────┤
│ Server A     │ Server A     │ Server B  │
│ /var/www     │ /home/user   │ /opt/app  │
├──────────────┼──────────────┼───────────┤
│ css/         │ .bashrc      │ logs/     │
│ js/          │ .zshrc       │ conf/     │
│ index.html   │              │           │
└──────────────┴──────────────┴───────────┘

With drag-and-drop between panes, same-server copy/move operations should work without going through the local machine.

Technical points

Tauri IPC

Expose Rust functions to the frontend with #[tauri::command].

#[tauri::command(async)]
async fn list_directory(
    state: State<'_, SftpManager>,
    pane_id: String,
    path: String,
) -> Result<Vec<FileEntry>, String> {
    // Fetch directory entries
}

Call it from the frontend with invoke:

const files = await invoke('list_directory', {
  paneId: 'left',
  path: '/var/www'
});

Drag-and-drop support

Tauri supports file drops out of the box:

tauri::Builder::default()
    .on_file_drop_event(|event| {
        if let tauri::FileDropEvent::Dropped(paths) = event.event {
            // File paths dropped from Finder
        }
    })

State management

Keep per-pane SFTP sessions in a HashMap:

struct SftpManager {
    sessions: HashMap<String, SftpSession>,  // pane_id -> session
}

If multiple panes connect to the same server, you can also keep one SSH connection and open multiple SFTP channels on top of it.

Async processing

russh-sftp is tokio-based, so large directory listings and transfers will not block the UI. Progress updates can also be emitted in real time with window.emit().

Caveats

IPC overhead

Tauri IPC is JSON serialization. If there are too many files, it becomes expensive.

Mitigation: page results in chunks of 50 to 100 entries.

macOS sandbox

If you enable the App Sandbox, network permissions need to be configured. Tauri handles most of it, but Entitlements.plist still needs to be checked.

SSH key auth

  • ssh-agent integration: use ssh2::Agent to talk to the system ssh-agent
  • Direct key-file loading: ssh_key can read PEM/OpenSSH formats

Estimated development time

  • If you already know Rust: 2 to 4 weeks for the core features
  • If you are learning Rust at the same time: 4 to 8 weeks

The frontend can be React or Svelte, whichever you prefer.

Projects worth looking at

Wrap-up

A multi-pane SFTP client in Tauri is technically very doable. There are existing implementations and the crates are already there.

If I get some time, I want to build it.

CyberDuck that can open multiple windows would be fine too, honestly.