Commit fbf0171

Adrian  ·  2026-04-13 11:58:12 -0400 EDT
parent 5f30ba8
feat: allow configuring socket and log permissions via environment variables (#130)
3 files changed,  +67, -7
+9, -0
......@@ -360,6 +360,15 @@ Each session gets its own unix socket file. The default location depends on your
360360 1. `TMPDIR` => uses `{TMPDIR}/zmx-{uid}` (appends uid for multi-user safety)
361361 1. `/tmp` => uses `/tmp/zmx-{uid}` (default fallback, appends uid for multi-user safety)
362362
363+## permissions
364+
365+You can configure the permissions for the socket directory and log files using the following environment variables:
366+
367+- `ZMX_DIR_MODE` => sets the mode for the socket and log directories (octal, defaults to `0750`)
368+- `ZMX_LOG_MODE` => sets the mode for the log files (octal, defaults to `0640`)
369+
370+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.
371+
363372 ## debugging
364373
365374 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
......@@ -7,15 +7,17 @@ pub const LogSystem = struct {
77 max_size: u64 = 5 * 1024 * 1024, // 5MB
88 path: []const u8 = "",
99 alloc: std.mem.Allocator = undefined,
10+ mode: u32 = 0o640,
1011
11- pub fn init(self: *LogSystem, alloc: std.mem.Allocator, path: []const u8) !void {
12+ pub fn init(self: *LogSystem, alloc: std.mem.Allocator, path: []const u8, mode: u32) !void {
1213 self.alloc = alloc;
1314 self.path = try alloc.dupe(u8, path);
15+ self.mode = mode;
1416
1517 const file = std.fs.openFileAbsolute(path, .{ .mode = .read_write }) catch |err| switch (err) {
1618 error.FileNotFound => try std.fs.createFileAbsolute(
1719 path,
18- .{ .read = true, .mode = 0o640 },
20+ .{ .read = true, .mode = @intCast(self.mode) },
1921 ),
2022 else => return err,
2123 };
......@@ -93,7 +95,7 @@ pub const LogSystem = struct {
9395
9496 self.file = try std.fs.createFileAbsolute(
9597 self.path,
96- .{ .truncate = true, .read = true, .mode = 0o640 },
98+ .{ .truncate = true, .read = true, .mode = @intCast(self.mode) },
9799 );
98100 self.current_size = 0;
99101 }
+53, -4
......@@ -55,7 +55,7 @@ pub fn main() !void {
5555
5656 const log_path = try std.fs.path.join(alloc, &.{ cfg.log_dir, "zmx.log" });
5757 defer alloc.free(log_path);
58- try log_system.init(alloc, log_path);
58+ try log_system.init(alloc, log_path, cfg.log_mode);
5959 defer log_system.deinit();
6060
6161 const cmd = args.next() orelse {
......@@ -270,15 +270,29 @@ const Cfg = struct {
270270 socket_dir: []const u8,
271271 log_dir: []const u8,
272272 max_scrollback: usize = 10_000_000,
273+ dir_mode: u32 = 0o750,
274+ log_mode: u32 = 0o640,
273275
274276 pub fn init(alloc: std.mem.Allocator) !Cfg {
275277 const socket_dir = try socketDir(alloc);
276278 const log_dir = try std.fmt.allocPrint(alloc, "{s}/logs", .{socket_dir});
277279 errdefer alloc.free(log_dir);
278280
281+ const dir_mode = if (std.posix.getenv("ZMX_DIR_MODE")) |m|
282+ std.fmt.parseInt(u32, m, 8) catch 0o750
283+ else
284+ 0o750;
285+
286+ const log_mode = if (std.posix.getenv("ZMX_LOG_MODE")) |m|
287+ std.fmt.parseInt(u32, m, 8) catch 0o640
288+ else
289+ 0o640;
290+
279291 var cfg = Cfg{
280292 .socket_dir = socket_dir,
281293 .log_dir = log_dir,
294+ .dir_mode = dir_mode,
295+ .log_mode = log_mode,
282296 };
283297
284298 try cfg.mkdir();
......@@ -307,18 +321,51 @@ const Cfg = struct {
307321 }
308322
309323 pub fn mkdir(self: *Cfg) !void {
310- posix.mkdirat(posix.AT.FDCWD, self.socket_dir, 0o750) catch |err| switch (err) {
324+ posix.mkdirat(posix.AT.FDCWD, self.socket_dir, @intCast(self.dir_mode)) catch |err| switch (err) {
311325 error.PathAlreadyExists => {},
312326 else => return err,
313327 };
314328
315- posix.mkdirat(posix.AT.FDCWD, self.log_dir, 0o750) catch |err| switch (err) {
329+ posix.mkdirat(posix.AT.FDCWD, self.log_dir, @intCast(self.dir_mode)) catch |err| switch (err) {
316330 error.PathAlreadyExists => {},
317331 else => return err,
318332 };
319333 }
320334 };
321335
336+test "Cfg.init uses default modes when env vars are not set" {
337+ const alloc = std.testing.allocator;
338+
339+ // Ensure they are not set
340+ _ = cross.c.unsetenv("ZMX_DIR_MODE");
341+ _ = cross.c.unsetenv("ZMX_LOG_MODE");
342+
343+ var cfg = try Cfg.init(alloc);
344+ defer cfg.deinit(alloc);
345+
346+ try std.testing.expectEqual(@as(u32, 0o750), cfg.dir_mode);
347+ try std.testing.expectEqual(@as(u32, 0o640), cfg.log_mode);
348+}
349+
350+test "Cfg.init uses custom modes from env vars" {
351+ const alloc = std.testing.allocator;
352+
353+ // Set custom octal values
354+ _ = cross.c.setenv("ZMX_DIR_MODE", "770", 1);
355+ _ = cross.c.setenv("ZMX_LOG_MODE", "660", 1);
356+ defer {
357+ _ = cross.c.unsetenv("ZMX_DIR_MODE");
358+ _ = cross.c.unsetenv("ZMX_LOG_MODE");
359+ }
360+
361+ var cfg = try Cfg.init(alloc);
362+ defer cfg.deinit(alloc);
363+
364+ try std.testing.expectEqual(@as(u32, 0o770), cfg.dir_mode);
365+ try std.testing.expectEqual(@as(u32, 0o660), cfg.log_mode);
366+}
367+
368+
322369 /// Daemon is responsible for managing a zmx session.
323370 ///
324371 /// It holds all the state for a running session. Instead of a single daemon for all sessions, we
......@@ -535,7 +582,7 @@ const Daemon = struct {
535582 &.{ self.cfg.log_dir, session_log_name },
536583 );
537584 defer self.alloc.free(session_log_path);
538- try log_system.init(self.alloc, session_log_path);
585+ try log_system.init(self.alloc, session_log_path, self.cfg.log_mode);
539586
540587 // If spawnPty fails, clean up here. Once it succeeds,
541588 // the inner block's defer takes ownership of cleanup to
......@@ -891,6 +938,8 @@ fn help() !void {
891938 \\ - TMPDIR Controls which folder is used to store unix socket files (prio: 3)
892939 \\ - ZMX_SESSION The session name we inject into every zmx session automatically
893940 \\ - ZMX_SESSION_PREFIX Adds this value to the start of every session name for all commands
941+ \\ - ZMX_DIR_MODE Sets the mode for the socket and log directories (octal, defaults to 0750)
942+ \\ - ZMX_LOG_MODE Sets the mode for the log files (octal, defaults to 0640)
894943 \\
895944 ;
896945 var buf: [4096]u8 = undefined;