The systemd service manager protocol, without libsystemd: readiness and
status notifications (sd_notify(3)), the watchdog (sd_watchdog_enabled(3),
see Sovite.Systemd.Watchdog), and socket activation (sd_listen_fds(3)).
:ok = Sovite.Systemd.ready(status: "Accepting mail")Everything is read from the environment systemd sets for the service.
Outside systemd these variables are missing, and every function is a
no-op: notifications return {:error, :no_socket}, listen_fds/1
returns [], and watchdog_interval/1 returns :disabled. Callers can
ignore the results.
Notifications are only accepted from the service's main process unless
the unit sets NotifyAccess=all, so the BEAM must be the process
systemd started (a release's start script execs it, which is enough).
Options
Every function takes:
:env- a map of environment variables to read instead of the OS environment, for tests. Defaults to readingSystem.get_env/1.
Summary
Types
Why a notification was not sent: :no_socket outside systemd,
:invalid_socket for a NOTIFY_SOCKET that is not an absolute path or
an abstract @name, :unsupported for a VSOCK address, or a
:socket error.
A notification: a string such as "READY=1", or a list of {key, value} pairs, sent as KEY=value lines. Atom keys are upcased, so
[ready: 1, status: "Started"] sends "READY=1\nSTATUS=Started\n".
Newlines in values are replaced by spaces.
Functions
Sends EXTEND_TIMEOUT_USEC=usec: the start, reload, or stop in progress
needs usec more microseconds.
Returns the sockets systemd passed for socket activation, as {name, fd} pairs in order, or [] when there are none or they are meant for
another process (LISTEN_PID).
Opens a :gen_tcp listen socket on fd, an inherited listening socket
(from listen_fds/1). opts are :gen_tcp.listen/2 options, such as
:binary and active: false. The socket is already bound, so leave
out :ip and :port.
Returns the current time of CLOCK_MONOTONIC in microseconds, the clock
of MONOTONIC_USEC, or :error when the runtime does not read that
clock.
Sends a notification to the service manager, through the unix datagram
socket in NOTIFY_SOCKET. A socket name starting with @ is in the
abstract namespace.
Returns whether NOTIFY_SOCKET is set, so notifications can be sent.
Sends READY=1: the service has started. Takes the :status option, a
text sent with it (see status/2).
The address an inherited socket is bound to, as {ip, port} for a TCP
or UDP socket, or {:local, path}. The socket is not changed: this
looks at a duplicate of fd. Returns :error for a closed fd or
another kind of socket.
Sends STATUS=text, shown by systemctl status. Newlines in text
are replaced by spaces.
Sends STOPPING=1: the service is shutting down. Takes the :status
option, as ready/1.
Sends WATCHDOG=1, the watchdog keep-alive ping. See
Sovite.Systemd.Watchdog to send it on a schedule.
Returns the watchdog interval in milliseconds, from WATCHDOG_USEC, or
:disabled. The watchdog is disabled when WATCHDOG_USEC is not a
positive integer, or when WATCHDOG_PID is set to another process.
Types
@type notify_error() :: :no_socket | :invalid_socket | :unsupported | term()
Why a notification was not sent: :no_socket outside systemd,
:invalid_socket for a NOTIFY_SOCKET that is not an absolute path or
an abstract @name, :unsupported for a VSOCK address, or a
:socket error.
@type state() :: String.t() | [{atom() | String.t(), String.Chars.t()}]
A notification: a string such as "READY=1", or a list of {key, value} pairs, sent as KEY=value lines. Atom keys are upcased, so
[ready: 1, status: "Started"] sends "READY=1\nSTATUS=Started\n".
Newlines in values are replaced by spaces.
Functions
@spec extend_timeout(non_neg_integer(), keyword()) :: :ok | {:error, notify_error()}
Sends EXTEND_TIMEOUT_USEC=usec: the start, reload, or stop in progress
needs usec more microseconds.
@spec listen_fds(keyword()) :: [{String.t(), non_neg_integer()}]
Returns the sockets systemd passed for socket activation, as {name, fd} pairs in order, or [] when there are none or they are meant for
another process (LISTEN_PID).
Names come from FileDescriptorName= in the socket units
(LISTEN_FDNAMES). When they are missing, or their number does not
match, every name is "unknown".
Turn a listening TCP socket into a :gen_tcp socket with
listen_socket/2.
Options
:env- as in the module docs.:unset- removeLISTEN_PID,LISTEN_FDS, andLISTEN_FDNAMESfrom the OS environment, so processes started later do not see them. Ignored with:env. Defaults tofalse.
@spec listen_socket(non_neg_integer(), [:gen_tcp.listen_option()]) :: {:ok, :gen_tcp.socket()} | {:error, term()}
Opens a :gen_tcp listen socket on fd, an inherited listening socket
(from listen_fds/1). opts are :gen_tcp.listen/2 options, such as
:binary and active: false. The socket is already bound, so leave
out :ip and :port.
:gen_tcp must be told the socket's family. Without :inet, :inet6,
or :local in opts, each is tried in turn, since opening a socket as
the wrong family fails without closing it.
@spec monotonic_usec() :: {:ok, non_neg_integer()} | :error
Returns the current time of CLOCK_MONOTONIC in microseconds, the clock
of MONOTONIC_USEC, or :error when the runtime does not read that
clock.
Erlang's own monotonic time has an arbitrary offset, and its rate can be
corrected, so this reads the runtime's OS monotonic time source instead,
when it is clock_gettime(CLOCK_MONOTONIC) (as on Linux).
@spec notify(state(), keyword()) :: :ok | {:error, notify_error()}
Sends a notification to the service manager, through the unix datagram
socket in NOTIFY_SOCKET. A socket name starting with @ is in the
abstract namespace.
Returns :ok once the datagram is sent, or {:error, reason} (see
notify_error/0). It never raises.
Returns whether NOTIFY_SOCKET is set, so notifications can be sent.
@spec ready(keyword()) :: :ok | {:error, notify_error()}
Sends READY=1: the service has started. Takes the :status option, a
text sent with it (see status/2).
@spec reloading(keyword()) :: :ok | {:error, notify_error()}
Sends RELOADING=1: the service is reloading its configuration. Send
ready/1 when done. Takes the :status option, as ready/1.
MONOTONIC_USEC is sent with it when monotonic_usec/0 can read the
clock, as Type=notify-reload units need. Where it cannot, use
Type=notify.
@spec socket_address(non_neg_integer()) :: {:ok, {:inet.ip_address(), :inet.port_number()} | {:local, binary()}} | :error
The address an inherited socket is bound to, as {ip, port} for a TCP
or UDP socket, or {:local, path}. The socket is not changed: this
looks at a duplicate of fd. Returns :error for a closed fd or
another kind of socket.
@spec status(String.t(), keyword()) :: :ok | {:error, notify_error()}
Sends STATUS=text, shown by systemctl status. Newlines in text
are replaced by spaces.
@spec stopping(keyword()) :: :ok | {:error, notify_error()}
Sends STOPPING=1: the service is shutting down. Takes the :status
option, as ready/1.
@spec watchdog(keyword()) :: :ok | {:error, notify_error()}
Sends WATCHDOG=1, the watchdog keep-alive ping. See
Sovite.Systemd.Watchdog to send it on a schedule.
@spec watchdog_interval(keyword()) :: {:ok, pos_integer()} | :disabled
Returns the watchdog interval in milliseconds, from WATCHDOG_USEC, or
:disabled. The watchdog is disabled when WATCHDOG_USEC is not a
positive integer, or when WATCHDOG_PID is set to another process.
The service must send watchdog/1 within every interval; sending it
every half interval is the usual choice.