- commit
- fbf0171
- parent
- 5f30ba8
- author
- Adrian
- date
- 2026-04-13 11:58:12 -0400 EDT
feat: allow configuring socket and log permissions via environment variables (#130)
3 files changed,
+67,
-7
+9,
-0
1@@ -360,6 +360,15 @@ Each session gets its own unix socket file. The default location depends on your
2 1. `TMPDIR` => uses `{TMPDIR}/zmx-{uid}` (appends uid for multi-user safety)
3 1. `/tmp` => uses `/tmp/zmx-{uid}` (default fallback, appends uid for multi-user safety)
4
5+## permissions
6+
7+You can configure the permissions for the socket directory and log files using the following environment variables:
8+
9+- `ZMX_DIR_MODE` => sets the mode for the socket and log directories (octal, defaults to `0750`)
10+- `ZMX_LOG_MODE` => sets the mode for the log files (octal, defaults to `0640`)
11+
12+This is particularly useful when running `zmx` as a system service with a shared group. For example, setting `ZMX_DIR_MODE=0770` and `ZMX_LOG_MODE=0660` allows group members to attach to the session.
13+
14 ## debugging
15
16 We store global logs for cli commands in `{socket_dir}/logs/zmx.log`. We store session-specific logs in `{socket_dir}/logs/{session_name}.log`. Right now they are enabled by default and cannot be disabled. The idea here is to help with initial development until we reach a stable state.
+5,
-3
1@@ -7,15 +7,17 @@ pub const LogSystem = struct {
2 max_size: u64 = 5 * 1024 * 1024, // 5MB
3 path: []const u8 = "",
4 alloc: std.mem.Allocator = undefined,
5+ mode: u32 = 0o640,
6
7- pub fn init(self: *LogSystem, alloc: std.mem.Allocator, path: []const u8) !void {
8+ pub fn init(self: *LogSystem, alloc: std.mem.Allocator, path: []const u8, mode: u32) !void {
9 self.alloc = alloc;
10 self.path = try alloc.dupe(u8, path);
11+ self.mode = mode;
12
13 const file = std.fs.openFileAbsolute(path, .{ .mode = .read_write }) catch |err| switch (err) {
14 error.FileNotFound => try std.fs.createFileAbsolute(
15 path,
16- .{ .read = true, .mode = 0o640 },
17+ .{ .read = true, .mode = @intCast(self.mode) },
18 ),
19 else => return err,
20 };
21@@ -93,7 +95,7 @@ pub const LogSystem = struct {
22
23 self.file = try std.fs.createFileAbsolute(
24 self.path,
25- .{ .truncate = true, .read = true, .mode = 0o640 },
26+ .{ .truncate = true, .read = true, .mode = @intCast(self.mode) },
27 );
28 self.current_size = 0;
29 }
+53,
-4
1@@ -55,7 +55,7 @@ pub fn main() !void {
2
3 const log_path = try std.fs.path.join(alloc, &.{ cfg.log_dir, "zmx.log" });
4 defer alloc.free(log_path);
5- try log_system.init(alloc, log_path);
6+ try log_system.init(alloc, log_path, cfg.log_mode);
7 defer log_system.deinit();
8
9 const cmd = args.next() orelse {
10@@ -270,15 +270,29 @@ const Cfg = struct {
11 socket_dir: []const u8,
12 log_dir: []const u8,
13 max_scrollback: usize = 10_000_000,
14+ dir_mode: u32 = 0o750,
15+ log_mode: u32 = 0o640,
16
17 pub fn init(alloc: std.mem.Allocator) !Cfg {
18 const socket_dir = try socketDir(alloc);
19 const log_dir = try std.fmt.allocPrint(alloc, "{s}/logs", .{socket_dir});
20 errdefer alloc.free(log_dir);
21
22+ const dir_mode = if (std.posix.getenv("ZMX_DIR_MODE")) |m|
23+ std.fmt.parseInt(u32, m, 8) catch 0o750
24+ else
25+ 0o750;
26+
27+ const log_mode = if (std.posix.getenv("ZMX_LOG_MODE")) |m|
28+ std.fmt.parseInt(u32, m, 8) catch 0o640
29+ else
30+ 0o640;
31+
32 var cfg = Cfg{
33 .socket_dir = socket_dir,
34 .log_dir = log_dir,
35+ .dir_mode = dir_mode,
36+ .log_mode = log_mode,
37 };
38
39 try cfg.mkdir();
40@@ -307,18 +321,51 @@ const Cfg = struct {
41 }
42
43 pub fn mkdir(self: *Cfg) !void {
44- posix.mkdirat(posix.AT.FDCWD, self.socket_dir, 0o750) catch |err| switch (err) {
45+ posix.mkdirat(posix.AT.FDCWD, self.socket_dir, @intCast(self.dir_mode)) catch |err| switch (err) {
46 error.PathAlreadyExists => {},
47 else => return err,
48 };
49
50- posix.mkdirat(posix.AT.FDCWD, self.log_dir, 0o750) catch |err| switch (err) {
51+ posix.mkdirat(posix.AT.FDCWD, self.log_dir, @intCast(self.dir_mode)) catch |err| switch (err) {
52 error.PathAlreadyExists => {},
53 else => return err,
54 };
55 }
56 };
57
58+test "Cfg.init uses default modes when env vars are not set" {
59+ const alloc = std.testing.allocator;
60+
61+ // Ensure they are not set
62+ _ = cross.c.unsetenv("ZMX_DIR_MODE");
63+ _ = cross.c.unsetenv("ZMX_LOG_MODE");
64+
65+ var cfg = try Cfg.init(alloc);
66+ defer cfg.deinit(alloc);
67+
68+ try std.testing.expectEqual(@as(u32, 0o750), cfg.dir_mode);
69+ try std.testing.expectEqual(@as(u32, 0o640), cfg.log_mode);
70+}
71+
72+test "Cfg.init uses custom modes from env vars" {
73+ const alloc = std.testing.allocator;
74+
75+ // Set custom octal values
76+ _ = cross.c.setenv("ZMX_DIR_MODE", "770", 1);
77+ _ = cross.c.setenv("ZMX_LOG_MODE", "660", 1);
78+ defer {
79+ _ = cross.c.unsetenv("ZMX_DIR_MODE");
80+ _ = cross.c.unsetenv("ZMX_LOG_MODE");
81+ }
82+
83+ var cfg = try Cfg.init(alloc);
84+ defer cfg.deinit(alloc);
85+
86+ try std.testing.expectEqual(@as(u32, 0o770), cfg.dir_mode);
87+ try std.testing.expectEqual(@as(u32, 0o660), cfg.log_mode);
88+}
89+
90+
91 /// Daemon is responsible for managing a zmx session.
92 ///
93 /// It holds all the state for a running session. Instead of a single daemon for all sessions, we
94@@ -535,7 +582,7 @@ const Daemon = struct {
95 &.{ self.cfg.log_dir, session_log_name },
96 );
97 defer self.alloc.free(session_log_path);
98- try log_system.init(self.alloc, session_log_path);
99+ try log_system.init(self.alloc, session_log_path, self.cfg.log_mode);
100
101 // If spawnPty fails, clean up here. Once it succeeds,
102 // the inner block's defer takes ownership of cleanup to
103@@ -891,6 +938,8 @@ fn help() !void {
104 \\ - TMPDIR Controls which folder is used to store unix socket files (prio: 3)
105 \\ - ZMX_SESSION The session name we inject into every zmx session automatically
106 \\ - ZMX_SESSION_PREFIX Adds this value to the start of every session name for all commands
107+ \\ - ZMX_DIR_MODE Sets the mode for the socket and log directories (octal, defaults to 0750)
108+ \\ - ZMX_LOG_MODE Sets the mode for the log files (octal, defaults to 0640)
109 \\
110 ;
111 var buf: [4096]u8 = undefined;