repos / zmx

session persistence for terminal processes
git clone https://github.com/neurosnap/zmx.git

commit
b5b525c
parent
b5e97df
author
Eric Bower
date
2025-12-05 16:09:02 -0500 EST
refactor!: postfix uid for socket dir

- first we look for TMPDIR env var or else we default to `/tmp`
- then we look for ZMX_DIR env var or else we create a `/tmp/zmx-{uid}` folder

Since this is a breaking change, you can kill all of your previous
sessions by using `ZMX_DIR=/tmp/zmx zmx kill {sesh}`

Closes: https://github.com/neurosnap/zmx/issues/15

BREAKING CHANGE: this moves sockets to a new location which means none
of your previous sessions will be available
3 files changed,  +45, -12
M README.md
+7, -4
 1@@ -146,15 +146,18 @@ Wow! Now you can setup all your os tiling windows how you like them for your pro
 2 
 3 ## socket file location
 4 
 5-Each session gets its own unix socket file. Right now, the default location is `/tmp/zmx`. At the moment this is not configurable.
 6+Each session gets its own unix socket file. Right now, the default location is `/tmp/zmx-{uid}`.  You can configure this using environment variables:
 7+
 8+- `TMPDIR` => overrides `/tmp`
 9+- `ZMX_DIR` => overrides `/tmp/zmx-{uid}`
10 
11 ## debugging
12 
13-We store global logs for cli commands in `/tmp/zmx/logs/zmx.log`. We store session-specific logs in `/tmp/zmx/logs/{session_name}.log`. These logs rotate to `.old` after 5MB. At the moment this is not configurable.
14+We 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.
15 
16 ## a note on configuration
17 
18-At this point, nothing is configurable. We 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.
19+We 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.
20 
21 ## a smol contract
22 
23@@ -175,7 +178,7 @@ At this point, nothing is configurable. We are evaluating what should be configu
24 
25 - The `daemon` and client processes communicate via a unix socket
26 - Both `daemon` and `client` loops leverage `poll()`
27-- Each session creates its own unix socket file `/tmp/zmx/*`
28+- Each session creates its own unix socket file
29 - We restore terminal state and output using `libghostty-vt`
30 
31 ### libghostty-vt
M src/log.zig
+2, -2
 1@@ -13,7 +13,7 @@ pub const LogSystem = struct {
 2         self.path = try alloc.dupe(u8, path);
 3 
 4         const file = std.fs.openFileAbsolute(path, .{ .mode = .read_write }) catch |err| switch (err) {
 5-            error.FileNotFound => try std.fs.createFileAbsolute(path, .{ .read = true }),
 6+            error.FileNotFound => try std.fs.createFileAbsolute(path, .{ .read = true, .mode = 0o640 }),
 7             else => return err,
 8         };
 9 
10@@ -82,7 +82,7 @@ pub const LogSystem = struct {
11             else => return err,
12         };
13 
14-        self.file = try std.fs.createFileAbsolute(self.path, .{ .truncate = true, .read = true });
15+        self.file = try std.fs.createFileAbsolute(self.path, .{ .truncate = true, .read = true, .mode = 0o640 });
16         self.current_size = 0;
17     }
18 };
M src/main.zig
+36, -6
 1@@ -67,17 +67,47 @@ const Client = struct {
 2 };
 3 
 4 const Cfg = struct {
 5-    socket_dir: []const u8 = "/tmp/zmx",
 6-    log_dir: []const u8 = "/tmp/zmx/logs",
 7+    socket_dir: []const u8,
 8+    log_dir: []const u8,
 9     max_scrollback: usize = 10_000_000,
10 
11+    pub fn init(alloc: std.mem.Allocator) !Cfg {
12+        const tmpdir = posix.getenv("TMPDIR") orelse "/tmp";
13+        const uid = posix.getuid();
14+
15+        var socket_dir: []const u8 = "";
16+        if (posix.getenv("ZMX_DIR")) |zmxdir| {
17+            socket_dir = try alloc.dupe(u8, zmxdir);
18+        } else {
19+            socket_dir = try std.fmt.allocPrint(alloc, "{s}/zmx-{d}", .{ tmpdir, uid });
20+        }
21+        errdefer alloc.free(socket_dir);
22+
23+        const log_dir = try std.fmt.allocPrint(alloc, "{s}/logs", .{socket_dir});
24+        errdefer alloc.free(log_dir);
25+
26+        var cfg = Cfg{
27+            .socket_dir = socket_dir,
28+            .log_dir = log_dir,
29+        };
30+
31+        try cfg.mkdir();
32+
33+        return cfg;
34+    }
35+
36+    pub fn deinit(self: *Cfg, alloc: std.mem.Allocator) void {
37+        if (self.socket_dir.len > 0) alloc.free(self.socket_dir);
38+        if (self.log_dir.len > 0) alloc.free(self.log_dir);
39+    }
40+
41     pub fn mkdir(self: *Cfg) !void {
42-        std.fs.makeDirAbsolute(self.socket_dir) catch |err| switch (err) {
43+        posix.mkdirat(posix.AT.FDCWD, self.socket_dir, 0o750) catch |err| switch (err) {
44             error.PathAlreadyExists => {},
45             else => return err,
46         };
47 
48-        std.fs.makeDirAbsolute(self.log_dir) catch |err| switch (err) {
49+        posix.mkdirat(posix.AT.FDCWD, self.log_dir, 0o750) catch |err| switch (err) {
50             error.PathAlreadyExists => {},
51             else => return err,
52         };
53@@ -133,8 +163,8 @@ pub fn main() !void {
54     defer args.deinit();
55     _ = args.skip(); // skip program name
56 
57-    var cfg = Cfg{};
58-    try cfg.mkdir();
59+    var cfg = try Cfg.init(alloc);
60+    defer cfg.deinit(alloc);
61 
62     const log_path = try std.fs.path.join(alloc, &.{ cfg.log_dir, "zmx.log" });
63     defer alloc.free(log_path);