1<img src="./logo.png" width="50px" height="50px">
2
3# zmx
4
5session persistence for terminal processes
6
7Reason for this tool: [You might not need `tmux`](https://bower.sh/you-might-not-need-tmux)
8
9## features
10
11- Persist terminal shell sessions (pty processes)
12- Ability to attach and detach from a shell session without killing it
13- Native terminal scrollback
14- Multiple clients can connect to the same session
15- Re-attaching to a session restores previous terminal state and output
16- Works on mac and linux
17- This project does **NOT** provide windows, tabs, or splits
18
19## install
20
21### binaries
22
23- https://zmx.sh/a/zmx-0.1.0-linux-aarch64.tar.gz
24- https://zmx.sh/a/zmx-0.1.0-linux-x86_64.tar.gz
25- https://zmx.sh/a/zmx-0.1.0-macos-aarch64.tar.gz
26- https://zmx.sh/a/zmx-0.1.0-macos-x86_64.tar.gz
27
28### homebrew
29
30```bash
31brew tap neurosnap/tap
32brew install zmx
33```
34
35### src
36
37- Requires zig `v0.15`
38- Clone the repo
39- Run build cmd
40
41```bash
42zig build -Doptimize=ReleaseSafe --prefix ~/.local
43# be sure to add ~/.local/bin to your PATH
44```
45
46## usage
47
48> [!IMPORTANT]
49> Press `ctrl+\` to detach from the session.
50
51```
52Usage: zmx <command> [args]
53
54Commands:
55 [a]ttach <name> [command...] Create or attach to a session
56 [d]etach Detach all clients from current session (ctrl+\ for current client)
57 [l]ist List active sessions
58 [k]ill <name> Kill a session and all attached clients
59 [v]ersion Show version information
60 [h]elp Show this help message
61```
62
63### examples
64
65```bash
66zmx attach dev # start a shell session
67zmx attach dev nvim . # start nvim in a persistent session
68zmx attach build make -j8 # run a build, reattach to check progress
69zmx attach mux dvtm # run a multiplexer inside zmx
70```
71
72## shell prompt
73
74When you attach to a zmx session, we don't provide any indication that you are inside `zmx`. We do provide an environment variable `ZMX_SESSION` which contains the session name.
75
76We recommend checking for that env var inside your prompt and displaying some indication there.
77
78### fish
79
80Place this file in `~/.config/fish/config.fish`:
81
82```fish
83functions -c fish_prompt _original_fish_prompt 2>/dev/null
84
85function fish_prompt --description 'Write out the prompt'
86 if set -q ZMX_SESSION
87 echo -n "[$ZMX_SESSION] "
88 end
89 _original_fish_prompt
90end
91```
92
93### bash
94
95todo.
96
97### zsh
98
99Place this in `.zshrc`, update current `$PROMPT/$PS1` to `BASE_PROMPT`
100
101```zsh
102BASE_PROMPT=$PS1/$PROMPT
103PROMPT="${ZMX_SESSION:+[$ZMX_SESSION]} $BASE_PROMPT"
104```
105
106## philosophy
107
108The entire argument for `zmx` instead of something like `tmux` that has windows, panes, splits, etc. is that job should be handled by your os window manager. By using something like `tmux` you now have redundent functionality in your dev stack: a window manager for your os and a window manager for your terminal. Further, in order to use modern terminal features, your terminal emulator **and** `tmux` need to have support for them. This holds back the terminal enthusiast community and feature development.
109
110Instead, this tool specifically focuses on session persistence and defers window management to your os wm.
111
112## ssh workflow
113
114Using `zmx` with `ssh` is a first-class citizen. Instead of `ssh`ing into your remote system with a single terminal and `n` tmux panes, you open `n` terminals and run `ssh` for all of them. This might sound tedious, but there are tools to make this a delightful workflow.
115
116First, create an `ssh` config entry for your remote dev server:
117
118```bash
119Host = d.*
120 HostName 192.168.1.xxx
121
122 RemoteCommand zmx attach %k
123 RequestTTY yes
124 ControlPath ~/.ssh/cm-%r@%h:%p
125 ControlMaster auto
126 ControlPersist 10m
127```
128
129Now you can spawn as many terminal sessions as you'd like:
130
131```bash
132ssh d.term
133ssh d.irc
134ssh d.pico
135ssh d.dotfiles
136```
137
138This will create or attach to each session and since we are using `ControlMaster` the same `ssh` connection is reused for every call to `ssh` for near-instant connection times.
139
140Now you can use the [`autossh`](https://linux.die.net/man/1/autossh) tool to make your ssh connections auto-reconnect. For example, if you have a laptop and close/open your laptop lid it will automatically reconnect all your ssh connections:
141
142```bash
143autossh -M 0 -q d.term
144```
145
146Or create an `alias`/`abbr`:
147
148```fish
149abbr -a ash "autossh -M 0 -q"
150```
151
152```bash
153ash d.term
154ash d.irc
155ash d.pico
156ash d.dotifles
157```
158
159Wow! Now you can setup all your os tiling windows how you like them for your project and have as many windows as you'd like, almost replicating exactly what `tmux` does but with native windows, tabs, splits, and scrollback! It also has the added benefit of supporting all the terminal features your emulator supports, no longer restricted by what `tmux` supports.
160
161## socket file location
162
163Each session gets its own unix socket file. Right now, the default location is `/tmp/zmx-{uid}`. You can configure this using environment variables:
164
165- `TMPDIR` => overrides `/tmp`
166- `ZMX_DIR` => overrides `/tmp/zmx-{uid}`
167
168## debugging
169
170We store global logs for cli commands in `/tmp/zmx-{uid}/logs/zmx.log`. We store session-specific logs in `/tmp/zmx-{uid}/logs/{session_name}.log`. These logs rotate to `.old` after 5MB.
171
172## a note on configuration
173
174We are evaluating what should be configurable and what should not. Every configuration option is a burden for us maintainers. For example, being able to change the default detach shortcut is difficult in a terminal environment.
175
176## a smol contract
177
178- Write programs that solve a well defined problem.
179- Write programs that behave the way most users expect them to behave.
180- Write programs that a single person can maintain.
181- Write programs that compose with other smol tools.
182- Write programs that can be finished.
183
184## impl
185
186- The `daemon` and client processes communicate via a unix socket
187- Both `daemon` and `client` loops leverage `poll()`
188- Each session creates its own unix socket file
189- We restore terminal state and output using `libghostty-vt`
190
191### libghostty-vt
192
193We use `libghostty-vt` to restore the previous state of the terminal when a client re-attaches to a session.
194
195How it works:
196
197- user creates session `zmx attach term`
198- user interacts with terminal stdin
199- stdin gets sent to pty via daemon
200- daemon sends pty output to client *and* `ghostty-vt`
201- `ghostty-vt` holds terminal state and scrollback
202- user disconnects
203- user re-attaches to session
204- `ghostty-vt` sends terminal snapshot to client stdout
205
206In this way, `ghostty-vt` doesn't sit in the middle of an active terminal session, it simply receives all the same data the client receives so it can re-hydrate clients that connect to the session. This enables users to pick up where they left off as if they didn't disconnect from the terminal session at all. It also has the added benefit of being very fast, the only thing sitting in-between you and your PTY is a unix socket.
207
208## prior art
209
210Below is a list of projects that inspired me to build this project.
211
212### shpool
213
214You can find the source code at this repo: https://github.com/shell-pool/shpool
215
216`shpool` is a service that enables session persistence by allowing the creation of named shell sessions owned by `shpool` so that the session is not lost if the connection drops.
217
218`shpool` can be thought of as a lighter weight alternative to tmux or GNU screen. While tmux and screen take over the whole terminal and provide window splitting and tiling features, `shpool` only provides persistent sessions.
219
220The biggest advantage of this approach is that `shpool` does not break native scrollback or copy-paste.
221
222### abduco
223
224You can find the source code at this repo: https://github.com/martanne/abduco
225
226abduco provides session management i.e. it allows programs to be run independently from its controlling terminal. That is programs can be detached - run in the background - and then later reattached. Together with dvtm it provides a simpler and cleaner alternative to tmux or screen.
227
228### dtach
229
230You can find the source code at this repo: https://github.com/crigler/dtach
231
232A simple program that emulates the detach feature of screen.
233
234dtach is a program written in C that emulates the detach feature of screen, which allows a program to be executed in an environment that is protected from the controlling terminal. For instance, the program under the control of dtach would not be affected by the terminal being disconnected for some reason.
235
236## comparison
237
238| Feature | zmx | shpool | abduco | dtach | tmux |
239| ------------------------------ | --- | ------ | ------ | ----- | ---- |
240| 1:1 Terminal emulator features | ✓ | ✓ | ✓ | ✓ | ✗ |
241| Terminal state restore | ✓ | ✓ | ✗ | ✗ | ✓ |
242| Window management | ✗ | ✗ | ✗ | ✗ | ✓ |
243| Multiple clients per session | ✓ | ✗ | ✓ | ✓ | ✓ |
244| Native scrollback | ✓ | ✓ | ✓ | ✓ | ✗ |
245| Configurable detach key | ✗ | ✓ | ✓ | ✓ | ✓ |
246| Auto-daemonize | ✓ | ✓ | ✓ | ✓ | ✓ |
247| Daemon per session | ✓ | ✗ | ✓ | ✓ | ✗ |
248| Session listing | ✓ | ✓ | ✓ | ✗ | ✓ |