defmodule NodeActivator do @moduledoc """ This module provides functionality to turn a non-distributed Erlang node into a distributed node. It handles the entire process of node activation, including: * Ensuring the EPMD (Erlang Port Mapper Daemon) is running * Generating unique node names * Starting the distributed node * Providing proper error handling and logging ## Features * Automatic EPMD activation * Unique node name generation with customizable prefixes * Idempotent operation (safe to call multiple times) * Comprehensive error handling * Detailed logging of node operations ## Usage The main function `run/1` takes a prefix string and returns either: * `{:ok, node()}` - The node was successfully started or was already running * `{:error, reason}` - An error occurred during node activation ## Dependencies * `epmd_up` - For EPMD management * `get_host` - For hostname resolution ## Examples # Start a new distributed node with prefix "myapp" {:ok, node_name} = NodeActivator.run("myapp") # => {:ok, :"myapp_abc123@hostname"} # Safe to call multiple times {:ok, same_node} = NodeActivator.run("myapp") # => {:ok, :"myapp_abc123@hostname"} """ require Logger @doc """ Turns a non-distributed node into a distributed node after ensuring that the `epmd` operation system process is running. Returns the name of the started distributed node. The node name will be generated by appending a random string to the provided `node_name_prefix` string. This function does nothing when the distribution has already been started. For more info, see `Node`. ## Examples {:ok, node_name} = NodeActivator.run("foo") """ @spec run(binary()) :: {:ok, node()} | {:error, any()} def run(node_name_prefix) do if Node.alive?() do {:ok, Node.self()} else start_distributed_node(node_name_prefix) end end defp start_distributed_node(node_name_prefix) do EpmdUp.activate() case NodeActivator.Util.generate_node_name(node_name_prefix) do {:ok, name} -> Logger.info("starting node #{name}") case Node.start(name) do {:ok, _node} -> Logger.info("Node #{name} has been launched.") {:ok, Node.self()} {:error, reason} -> Logger.error("Node #{name} cannot be launched by #{inspect(reason)}.") {:error, reason} end {:error, reason} -> {:error, reason} end end end