Something went wrong. Try again.
Deployment and lifecycle management for Nix
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191defmodule PeerNode do require Logger use TypedStruct
typedstruct do field :name, String.t() field :pid, String.t() field :instance, String.t() end
def start(instance) do instance_ip = instance_ip(instance)
allow_boot(instance_ip) setup_erl(instance)
case :peer.start_link( %{ exec: {:os.find_executable(~c"incus"), Enum.map( [ "exec", instance, "--", "/root/erl" ], &to_charlist/1 )}, connection: :standardio, name: to_charlist(instance), host: to_charlist(instance_ip), args: Enum.map( [ "-hosts", :inet.ntoa(central_node_ip()), # TODO move cookie to a file "-setcookie", "#{:erlang.get_cookie()}", "-loader", "inet" ], &to_charlist/1 ) } # this dbg silences dialyzer... ) do {_, peer_pid, peer_name} -> node = %__MODULE__{ pid: peer_pid, name: peer_name, instance: instance }
load_paths(node)
node
{:ok, _} -> nil
{:error, _} = err -> err end end
def stop(%__MODULE__{} = node) do :peer.stop(node.pid) end
def restart(node) do stop(node) start(node.instance) end
def start_garden(%__MODULE__{} = node) do set_config(node)
call(node, Application, :ensure_all_started, [:garden]) end
def get_env(%__MODULE__{} = node, name) do call(node, Application, :get_env, [ :garden, name ]) end
def put_env(%__MODULE__{} = node, name, value) do call(node, Application, :put_env, [ :garden, name, value ]) end
def set_config(%__MODULE__{} = node) do Logger.debug(msg: "Setting configuration")
put_env( node, :config, Application.get_env(:garden, :config) )
storage_dir = "/tmp/#{node.name}"
call(node, File, :mkdir_p, [storage_dir])
put_env( node, Garden.Storage, file: "#{storage_dir}/storage.etf" )
put_env( node, Garden.Socket, uri: "ws://#{:inet.ntoa(central_node_ip())}:7150/garden/websocket", reconnect_after_msec: [200, 500, 1000, 2000] ) end
def call(%__MODULE__{} = node, module, method, args) do :rpc.block_call(node.name, module, method, args) end
def instance_ip(instance) do {:ok, state} = IncusClient.ApiClient.get("/1.0/instances/{name}/state", name: instance, project: "sowerdev" )
state.metadata.network |> Enum.reject(fn {n, _} -> String.starts_with?(n, "lo") end) |> List.first() |> elem(1) |> Map.get(:addresses) # grab the first ipv4 |> Enum.find(fn a -> a.family == "inet" end) |> Map.get(:address) end
defp allow_boot(host) do host |> to_charlist() |> :inet.parse_ipv4_address() |> then(fn {:ok, ipv4} -> ipv4 end) |> :erl_boot_server.add_slave() end
defp load_paths(node) do call(node, :code, :add_paths, [:code.get_path()]) end
def reload_paths(node) do call(node, :code, :del_paths, [:code.get_path()]) load_paths(node) end
def central_node_ip() do # alternative using interface :inet.getifaddrs() |> elem(1) |> Enum.find(fn {name, _} -> name == ~c"incusbr0" end) |> elem(1) |> Keyword.get(:addr)
# node() # |> to_string # |> String.split("@") # |> Enum.at(1) # |> to_charlist end
def setup_erl(instance) do {_, 0} = System.shell( ~s{incus exec #{instance} -- bash -i -c "which erl || nix --extra-experimental-features 'flakes nix-command' profile install nixpkgs#erlang"} )
# TODO, gcroot this? {erl_path, 0} = System.shell(~s{incus exec #{instance} -- bash -i -c 'readlink -f $(which erl)'})
Nix.Store.realize(erl_path) endend