mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-18 17:56:09 +03:00
linux: switch systemd user service to type notify-reload (#7791)
This enables `systemd` to send SIGUSR2 to Ghostty to signal it to reload the configuration. This is much easier and reliable than using a script to search process listings for Ghostty's main PID to send the signal to. The command to do so is: `systemctl reload --user com.mitchellh.ghostty.service`
This commit is contained in:
5
dist/linux/systemd.service.in
vendored
5
dist/linux/systemd.service.in
vendored
@ -1,9 +1,12 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=@NAME@
|
Description=@NAME@
|
||||||
After=graphical-session.target
|
After=graphical-session.target
|
||||||
|
After=dbus.socket
|
||||||
|
Requires=dbus.socket
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=dbus
|
Type=notify-reload
|
||||||
|
ReloadSignal=SIGUSR2
|
||||||
BusName=@APPID@
|
BusName=@APPID@
|
||||||
ExecStart=@GHOSTTY@ --launched-from=systemd
|
ExecStart=@GHOSTTY@ --launched-from=systemd
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ const apprt = @import("../../apprt.zig");
|
|||||||
const configpkg = @import("../../config.zig");
|
const configpkg = @import("../../config.zig");
|
||||||
const input = @import("../../input.zig");
|
const input = @import("../../input.zig");
|
||||||
const internal_os = @import("../../os/main.zig");
|
const internal_os = @import("../../os/main.zig");
|
||||||
|
const systemd = @import("../../os/systemd.zig");
|
||||||
const terminal = @import("../../terminal/main.zig");
|
const terminal = @import("../../terminal/main.zig");
|
||||||
const Config = configpkg.Config;
|
const Config = configpkg.Config;
|
||||||
const CoreApp = @import("../../App.zig");
|
const CoreApp = @import("../../App.zig");
|
||||||
@ -1035,6 +1036,12 @@ pub fn reloadConfig(
|
|||||||
target: apprt.action.Target,
|
target: apprt.action.Target,
|
||||||
opts: apprt.action.ReloadConfig,
|
opts: apprt.action.ReloadConfig,
|
||||||
) !void {
|
) !void {
|
||||||
|
// Tell systemd that reloading has started.
|
||||||
|
systemd.notify.reloading();
|
||||||
|
|
||||||
|
// When we exit this function tell systemd that reloading has finished.
|
||||||
|
defer systemd.notify.ready();
|
||||||
|
|
||||||
if (opts.soft) {
|
if (opts.soft) {
|
||||||
switch (target) {
|
switch (target) {
|
||||||
.app => try self.core_app.updateConfig(self, &self.config),
|
.app => try self.core_app.updateConfig(self, &self.config),
|
||||||
@ -1367,6 +1374,9 @@ pub fn run(self: *App) !void {
|
|||||||
log.warn("error handling configuration changes err={}", .{err});
|
log.warn("error handling configuration changes err={}", .{err});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Tell systemd that we are ready.
|
||||||
|
systemd.notify.ready();
|
||||||
|
|
||||||
while (self.running) {
|
while (self.running) {
|
||||||
_ = glib.MainContext.iteration(self.ctx, 1);
|
_ = glib.MainContext.iteration(self.ctx, 1);
|
||||||
|
|
||||||
|
@ -2333,6 +2333,7 @@ pub fn defaultTermioEnv(self: *Surface) !std.process.EnvMap {
|
|||||||
env.remove("DBUS_STARTER_BUS_TYPE");
|
env.remove("DBUS_STARTER_BUS_TYPE");
|
||||||
env.remove("INVOCATION_ID");
|
env.remove("INVOCATION_ID");
|
||||||
env.remove("JOURNAL_STREAM");
|
env.remove("JOURNAL_STREAM");
|
||||||
|
env.remove("NOTIFY_SOCKET");
|
||||||
|
|
||||||
// Unset environment varies set by snaps if we're running in a snap.
|
// Unset environment varies set by snaps if we're running in a snap.
|
||||||
// This allows Ghostty to further launch additional snaps.
|
// This allows Ghostty to further launch additional snaps.
|
||||||
|
@ -63,3 +63,136 @@ pub fn launchedBySystemd() bool {
|
|||||||
else => false,
|
else => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// systemd notifications. Used by Ghostty to inform systemd of the state of the
|
||||||
|
/// process. Currently only used to notify systemd that we are ready and that
|
||||||
|
/// configuration reloading has started.
|
||||||
|
///
|
||||||
|
/// See: https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html
|
||||||
|
///
|
||||||
|
/// These functions were re-implemented in Zig instead of using the `libsystemd`
|
||||||
|
/// library to avoid the complexity of another external dependency, as well as
|
||||||
|
/// to take advantage of Zig features like `comptime` to ensure minimal impact
|
||||||
|
/// on non-Linux systems (like FreeBSD) that will never support `systemd`.
|
||||||
|
///
|
||||||
|
/// Linux systems that do not use `systemd` should not be impacted as they
|
||||||
|
/// should never start Ghostty with the `NOTIFY_SOCKET` environment variable set
|
||||||
|
/// and these functions essentially become a no-op.
|
||||||
|
///
|
||||||
|
/// See `systemd`'s [Interface Portability and Stability Promise](https://systemd.io/PORTABILITY_AND_STABILITY/)
|
||||||
|
/// for assurances that the interfaces used here will be supported and stable for
|
||||||
|
/// the long term.
|
||||||
|
pub const notify = struct {
|
||||||
|
/// Send the given message to the UNIX socket specified in the NOTIFY_SOCKET
|
||||||
|
/// environment variable. If there NOTIFY_SOCKET environment variable does
|
||||||
|
/// not exist then no message is sent.
|
||||||
|
fn send(message: []const u8) void {
|
||||||
|
// systemd is Linux-only so this is a no-op anywhere else
|
||||||
|
if (comptime builtin.os.tag != .linux) return;
|
||||||
|
|
||||||
|
// Get the socket address that should receive notifications.
|
||||||
|
const socket_path = std.posix.getenv("NOTIFY_SOCKET") orelse return;
|
||||||
|
|
||||||
|
// If the socket address is an empty string return.
|
||||||
|
if (socket_path.len == 0) return;
|
||||||
|
|
||||||
|
// The socket address must be a path or an abstract socket.
|
||||||
|
if (socket_path[0] != '/' and socket_path[0] != '@') {
|
||||||
|
log.warn("only AF_UNIX sockets with path or abstract namespace addresses are supported!", .{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var socket_address: std.os.linux.sockaddr.un = undefined;
|
||||||
|
|
||||||
|
// Error out if the supplied socket path is too long.
|
||||||
|
if (socket_address.path.len < socket_path.len) {
|
||||||
|
log.warn("NOTIFY_SOCKET path is too long!", .{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_address.family = std.os.linux.AF.UNIX;
|
||||||
|
|
||||||
|
@memcpy(socket_address.path[0..socket_path.len], socket_path);
|
||||||
|
socket_address.path[socket_path.len] = 0;
|
||||||
|
|
||||||
|
const socket: std.os.linux.socket_t = socket: {
|
||||||
|
const rc = std.os.linux.socket(
|
||||||
|
std.os.linux.AF.UNIX,
|
||||||
|
std.os.linux.SOCK.DGRAM | std.os.linux.SOCK.CLOEXEC,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
switch (std.os.linux.E.init(rc)) {
|
||||||
|
.SUCCESS => break :socket @intCast(rc),
|
||||||
|
else => |e| {
|
||||||
|
log.warn("creating socket failed: {s}", .{@tagName(e)});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
defer _ = std.os.linux.close(socket);
|
||||||
|
|
||||||
|
connect: {
|
||||||
|
const rc = std.os.linux.connect(
|
||||||
|
socket,
|
||||||
|
&socket_address,
|
||||||
|
@offsetOf(std.os.linux.sockaddr.un, "path") + socket_address.path.len,
|
||||||
|
);
|
||||||
|
switch (std.os.linux.E.init(rc)) {
|
||||||
|
.SUCCESS => break :connect,
|
||||||
|
else => |e| {
|
||||||
|
log.warn("unable to connect to notify socket: {s}", .{@tagName(e)});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write: {
|
||||||
|
const rc = std.os.linux.write(socket, message.ptr, message.len);
|
||||||
|
switch (std.os.linux.E.init(rc)) {
|
||||||
|
.SUCCESS => {
|
||||||
|
const written = rc;
|
||||||
|
if (written < message.len) {
|
||||||
|
log.warn("short write to notify socket: {d} < {d}", .{ rc, message.len });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break :write;
|
||||||
|
},
|
||||||
|
else => |e| {
|
||||||
|
log.warn("unable to write to notify socket: {s}", .{@tagName(e)});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tell systemd that we are ready or that we are finished reloading.
|
||||||
|
/// See: https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html#READY=1
|
||||||
|
pub fn ready() void {
|
||||||
|
if (comptime builtin.os.tag != .linux) return;
|
||||||
|
|
||||||
|
send("READY=1");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tell systemd that we have started reloading our configuration.
|
||||||
|
/// See: https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html#RELOADING=1
|
||||||
|
/// and: https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html#MONOTONIC_USEC=%E2%80%A6
|
||||||
|
pub fn reloading() void {
|
||||||
|
if (comptime builtin.os.tag != .linux) return;
|
||||||
|
|
||||||
|
const ts = std.posix.clock_gettime(.MONOTONIC) catch |err| {
|
||||||
|
log.err("unable to get MONOTONIC clock: {}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const now = ts.sec * std.time.us_per_s + @divFloor(ts.nsec, std.time.ns_per_us);
|
||||||
|
|
||||||
|
var buffer: [64]u8 = undefined;
|
||||||
|
const message = std.fmt.bufPrint(&buffer, "RELOADING=1\nMONOTONIC_USEC={d}", .{now}) catch |err| {
|
||||||
|
log.err("unable to format reloading message: {}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
send(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user