简体中文 · English · v3.3.4
Most remote terminals are packed with features I rarely need. They aren’t simple enough for me, so this project was born. ZauTerm is a cross-platform desktop terminal emulator built with Tauri 2, React, and xterm.js. It supports SSH, SFTP, Telnet, Serial, and Local (native shell) connections, with saved sessions, hierarchical grouping, encrypted credential storage, and a polished custom UI (overlay title bar, dark/light themes, bilingual interface).
Successor to the Electron-based ZenTerm app: same product features, Rust/Tauri backend.
- Features
- Keyboard Shortcuts
- Screenshots
- Tech Stack
- Project Structure
- Requirements
- Quick Start
- Development & Quality
- Build & Release
- Import / Export Format
- Security Model
- Data & Storage Locations
- Troubleshooting
- License
| Protocol | Description |
|---|---|
| SSH | Interactive shell via ssh2 (Rust), PTY resize, password or private-key auth (in-memory PEM; no temp key files), keepalive |
| SFTP | File browser in the sidebar: list, upload, download, mkdir, rename, delete; progress events; local paths restricted to safe user directories |
| Telnet | Raw TCP Telnet client |
| Serial | Local serial ports via serialport (baud rate, data/stop bits, parity); port list must be chosen from enumerated devices |
| Local | Native interactive shell via portable-pty (Unix PTY / Windows ConPTY); optional shell path and working directory (defaults: $SHELL / COMSPEC, home dir); PTY resize |
- Save sessions with label, group (hierarchical paths), and connection parameters
- Empty group placeholders — create folder-like groups before adding sessions
- Search saved sessions by name, host, serial path, or local shell (Ctrl/Cmd+F)
- Duplicate, rename, edit, delete sessions; optional confirm dialogs
- Export / import session lists (JSON envelope, v1); import from Settings or the sidebar
- Connect, Save & connect, or Save only from the connection dialog
- Credential prompt on connect when secrets are not stored; optional “Save & connect” to update vault
- xterm.js with Fit, Web Links, and Search addons; configurable scrollback (0–500,000 lines) and terminal font presets (Cascadia Code, JetBrains Mono, Fira Code, Menlo, Consolas, Source Code Pro, Courier New)
- Press R to reconnect after a session disconnects or an initial connection fails
- In-terminal search: incremental find with match highlighting; case sensitive, whole word, and regex modes; prev/next navigation; open via tab context menu or Ctrl/Cmd+Shift+F
- Character encodings: UTF-8, GBK, GB18030, GB2312, Big5, UTF-16 LE, Latin-1 (Rust
encoding_rson the backend;TextDecoderin the webview) - Backspace mode (per session): Auto (DEL for SSH/Local, BS for Telnet/Serial), or force DEL / BS
- Terminal interaction: select-to-copy and right-click paste (toggle in settings)
- Output highlighting: regex rules with colors (defaults for error/success/warning/IP)
- Tab bar: new connection, close tab/others/left/right/all, clear screen, save terminal output to file
- Session logging: off, or on (buffer delta append: screen-accurate plain text, append-only); log path validated against allowed directories
- Custom overlay title bar (minimize / maximize / close); click the ⚡ ZauTerm logo to open About
- Dark, light, or auto theme (follows OS); live preview in Settings before saving
- UI language: English, 简体中文, or auto (follows system)
- Resizable sidebar for saved sessions and SFTP tree
- Settings dialog organized into General, SSH & Terminal, and Data & Security tabs
- Webview uses Tauri isolation pattern + CSP; invoke surface allowlisted in
src-isolation/ - Capabilities use least privilege (no broad
fs:/opener:frontend permissions) - SSH host key verification (
zauterm-known-hosts.json); prompts on first connect and fingerprint change - Optional encrypted vault for passwords and keys (ChaCha20-Poly1305; master key in OS keyring)
- Local path policy for logs, SFTP, and Local session working directories: home, Documents, Downloads, Desktop, Music/Pictures/Videos, app data; on Windows, non-system drive roots (e.g.
D:\) are also allowed
Global shortcuts work when ZauTerm is focused. On macOS use Cmd; on Windows and Linux use Ctrl.
| Shortcut | Action |
|---|---|
| Ctrl/Cmd+F | Focus sidebar saved session search (expands the session list if collapsed) |
| Ctrl/Cmd+Shift+F | Open in-terminal search on the active tab |
| Ctrl/Cmd+0 | Reset zoom to default |
| Ctrl/Cmd+- | Zoom out |
| Ctrl/Cmd++ | Zoom in |
| Enter / Shift+Enter | Next / previous match (in terminal search bar) |
| Esc | Close terminal search bar |
| R | Reconnect the active terminal session (when disconnected or after a failed connect) |
| Layer | Technology |
|---|---|
| Desktop shell | Tauri 2 |
| Backend | Rust (ssh2, serialport, portable-pty, encoding_rs, …) |
| Frontend | TypeScript, React 19, Vite 6 |
| Terminal | @xterm/xterm 5, Fit / Web Links / Search addons |
| Crypto | ChaCha20-Poly1305 + OS keyring |
| Testing | Vitest 3 (frontend), cargo test (Rust) |
| Packaging | tauri build |
Source is organized as frontend / Rust backend / isolation hook:
| Directory | Role | Description |
|---|---|---|
src/ |
Frontend | React webview: UI, xterm, localStorage, window.zauterm bridge |
src-tauri/ |
Backend | Tauri/Rust: commands, SSH/SFTP/Telnet/Serial/Local, vault, path policy |
src-isolation/ |
IPC gate | Isolation hook allowlisting invoke commands |
zauterm/
├── src/ # Frontend (webview)
│ ├── main.tsx, App.tsx
│ ├── components/ # Title bar, sidebar, terminal, SFTP, connect/settings dialogs
│ ├── store/ # sessionStore, settingsStore, credentialsBridge
│ ├── lib/ # Import/export, IPC (`tauriZauterm`), session/terminal/settings
│ ├── hooks/, context/, i18n/, theme/, styles/, types/
│
├── src-tauri/ # Backend (Rust)
│ ├── src/
│ │ ├── lib.rs, main.rs # App entry, generate_handler!
│ │ ├── commands/ # window / app / log / credentials / ssh / sftp / telnet / serial / local
│ │ ├── ssh/, sftp/, telnet/, serial/, local/
│ │ ├── path_policy/, known_hosts/, vault/
│ │ ├── invoke_commands.rs # Canonical IPC command list (synced with isolation)
│ │ ├── ssh_key.rs # In-memory pubkey auth
│ │ └── encoding/, ipc.rs, dialogs/, …
│ ├── capabilities/default.json # Least-privilege permissions
│ ├── tauri.conf.json # + platform overlays
│ └── Cargo.toml
│
├── src-isolation/ # Isolation hook (ALLOWED_CMDS / ALLOWED_PLUGIN_CMDS)
├── tests/ # Vitest unit tests (tests/**/*.test.ts)
├── docs/images/ # README screenshots
├── build/, public/ # Icons / static assets
├── tsconfig.json # TS for app code: src/ + tests/ (DOM, JSX, @/*)
├── tsconfig.node.json # TS for Node tooling: vite.config.ts, vitest.config.ts
├── index.html, vite.config.ts, vitest.config.ts, eslint.config.js
└── package.json
TypeScript configs: tsconfig.json typechecks the React/webview app and unit tests (include: src, tests; enables DOM libs, react-jsx, and the @/* path alias). tsconfig.node.json typechecks Vite/Vitest config files that run in Node (no DOM/JSX). npm run typecheck runs both.
Runtime data flow (simplified):
Frontend src/ (React / xterm)
│ window.zauterm ← src/lib/ipc/tauriZauterm.ts (invoke / listen)
│ isolation hook (src-isolation/)
▼
Backend src-tauri/ (commands + protocol modules)
▼
Remote host / local serial / local shell (PTY) / OS keyring
- Node.js 18+ (LTS recommended)
- npm 9+
- Rust stable toolchain (
rustup; edition 2021) - Platform native deps (Tauri):
- macOS: Xcode Command Line Tools
- Windows: Visual Studio Build Tools (MSVC), WebView2
- Linux: see Tauri Linux prerequisites (e.g.
webkit2gtk,libudevfor Serial)
# GitHub
git clone https://github.com/zhuhezhang/zauterm.git
cd zauterm
# or Gitee
# git clone https://gitee.com/zhuhezhang/zauterm.git
# cd zauterm
npm install
npm run tauri:devThis starts Vite (port 5173) and the Tauri app (beforeDevCommand runs npm run dev). Frontend hot-reloads via Vite; Rust changes rebuild the host.
| Script | Description |
|---|---|
npm run dev |
Vite only (port 5173) |
npm run tauri:dev |
Full desktop app (Vite + Tauri) |
npm run tauri |
Pass-through to @tauri-apps/cli |
Before merging, run locally:
npm run typecheck
npm run lint
npm run test # Vitest, tests/**/*.test.ts
npm run cargo:check # cd src-tauri && cargo check
npm run cargo:test # Rust unit tests (path policy, vault, known_hosts, isolation sync, …)
# or all of the above:
npm run project:check| Script | Description |
|---|---|
npm run test:watch |
Vitest watch mode |
npm run cargo:test |
cd src-tauri && cargo test |
npm run project:check |
typecheck + lint + test + cargo:check + cargo:test |
npm run tauri:build # current platform → rename artifacts in bundle/Tauri writes packages under src-tauri/target/release/bundle/. After build, scripts/rename-tauri-artifacts.mjs inserts the OS name between version and arch in place (keeps _ and Tauri’s arch tokens):
| Platform | Example |
|---|---|
| Windows | ZauTerm_{version}_win_x64-setup.exe, ZauTerm_{version}_win_x64_en-US.msi, ZauTerm_{version}_win_x64-portable.exe (no-install copy of the main binary) |
| Linux | ZauTerm_{version}_linux_amd64.AppImage, .deb, … |
| macOS | ZauTerm_{version}_mac_aarch64.dmg |
On Windows, Tauri has no official “portable” target; the post-build script copies target/release/ZauTerm.exe to bundle/portable/. The portable build still requires WebView2 on the target PC (usually preinstalled on Windows 10/11).
The main binary / app bundle name is ZauTerm (mainBinaryName in tauri.conf.json).
Re-run renaming only (if needed): npm run rename:artifacts.
Keep the version in sync across package.json, src-tauri/Cargo.toml, src-tauri/tauri.conf.json, README.md, and README.zh-CN.md:
npm run mod:ver -- x.x.xExported sessions and settings use a versioned JSON envelope (max file size 8 MB):
{
"zautermExport": "sessions",
"version": 1,
"exportedAt": "Mon May 19 2026 ...",
"data": [ /* session objects or settings object */ ]
}zautermExportmust be"sessions"or"settings"(cross-import is rejected).versionmust be1.- Unknown settings keys are stripped on import; invalid sessions are skipped with a summary alert.
- Session imports are capped at 99999 entries per file (see
src/lib/import/constants.ts).
- Isolation + allowlist: Frontend IPC is filtered by
src-isolation/index.js; app commands must matchsrc-tauri/src/invoke_commands.rs(enforced by Rust tests). - Least-privilege capabilities:
capabilities/default.jsongrants only needed window/event/dialog permissions—not broad filesystem/opener defaults. - SSH MITM mitigation: Host keys are recorded; unknown or changed fingerprints require confirmation in a native dialog.
- Private keys in memory: SSH/SFTP pubkey auth uses
userauth_pubkey_memory(no temp PEM files under/tmp). - Path sandboxing: Session logs, SFTP local paths, and Local session working directories must resolve under allowed user folders or app data (plus Windows non-system drive roots).
- Serial safety: Connect only accepts paths returned by
listPorts(refreshed list). - Credential vault: Secrets encrypted with ChaCha20-Poly1305; master key stored via OS keyring (
keyringcrate).
This app is a convenience tool, not a full security audit. Review your threat model before storing production keys in the vault.
| Data | Location |
|---|---|
| Saved sessions (no secrets) | localStorage → zauterm_saved_sessions |
| Empty group placeholders | localStorage → __zauterm_group_placeholders__ |
| App settings | localStorage → zauterm_settings |
| SSH known hosts | {appData}/zauterm-known-hosts.json (pretty JSON, atomic rewrite) |
| Encrypted credentials | {appData}/zauterm-credentials-vault.json + OS keyring |
| Session logs | User-configured or Downloads / zauterm-session-log/ |
Typical app data paths:
- macOS:
~/Library/Application Support/zauterm/ - Windows:
%APPDATA%\zauterm\ - Linux:
~/.config/zauterm/
| Issue | Suggestions |
|---|---|
npm run tauri:dev fails on Rust deps |
Install Rust via rustup; ensure platform Tauri prerequisites |
| SSH algorithm / auth mismatch | Check username/key/passphrase; enable server-required options if exposed in settings |
| Garbled Chinese output | Set session encoding to GBK or GB18030 |
| SFTP “path not allowed” | Choose a directory under Downloads/Documents/home, not system paths |
| Serial port not listed | Click Refresh; on Linux ensure user is in dialout group |
| Local shell fails to start | Leave Shell empty for the system default ($SHELL / COMSPEC); ensure a custom path exists; pick a working directory under your home folder |
| Host key prompt every time | Check write permissions for app data; do not run from read-only profiles |
| Import fails / wrong file type | Use the correct export file (sessions vs settings); max 8 MB |
| Isolation / invoke blocked | New commands must be added to lib.rs, invoke_commands.rs, and src-isolation/index.js |
| Ctrl/Cmd+Shift+F doesn't open in-terminal search | IME may capture the shortcut; remap IME or use tab context menu → Search terminal |
MIT License — Copyright © 2026 zhuhezhang
中文文档: README.zh-CN.md


