Skip to main content

lib/systemd/install.ex

defmodule Systemd.Install do
  @moduledoc """
  Helpers for installing generated unit files.

  File writes are intentionally local filesystem operations. Reloading systemd is
  still performed through the D-Bus manager API, not through `systemctl`.
  """

  alias Systemd.{Error, Manager, UnitFile}

  @system_unit_dir "/etc/systemd/system"
  @user_unit_dir Path.join(System.user_home!(), ".config/systemd/user")

  @type target :: :system | :user | {:directory, Path.t()}

  @doc """
  Returns the target path for a unit name.
  """
  @spec unit_path(String.t(), target()) :: Path.t()
  def unit_path(name, target \\ :system)
  def unit_path(name, :system), do: Path.join(@system_unit_dir, name)
  def unit_path(name, :user), do: Path.join(@user_unit_dir, name)
  def unit_path(name, {:directory, directory}), do: Path.join(directory, name)

  @doc """
  Writes a unit file to the selected target directory.
  """
  @spec write_unit(String.t(), UnitFile.t() | String.t(), keyword()) ::
          {:ok, Path.t()} | {:error, Error.t()}
  def write_unit(name, unit_file_or_text, opts \\ []) do
    target = Keyword.get(opts, :target, :system)
    path = unit_path(name, target)
    text = render(unit_file_or_text)

    with :ok <- File.mkdir_p(Path.dirname(path)),
         :ok <- File.write(path, text) do
      {:ok, path}
    else
      {:error, reason} -> {:error, Error.connection_error(reason)}
    end
  end

  @doc """
  Writes a unit file and reloads systemd manager configuration over D-Bus.
  """
  @spec install_unit(String.t(), UnitFile.t() | String.t(), keyword()) ::
          {:ok, Path.t()} | {:error, Error.t()}
  def install_unit(name, unit_file_or_text, opts \\ []) do
    with {:ok, path} <- write_unit(name, unit_file_or_text, opts),
         {:ok, conn} <- Manager.connect(bus: Keyword.get(opts, :bus, :system)),
         :ok <- Manager.reload(conn) do
      {:ok, path}
    end
  end

  defp render(%UnitFile{} = unit_file), do: UnitFile.to_string(unit_file)
  defp render(text) when is_binary(text), do: text
end