From 6e9c60c610963a1bf18626749a17f914ab318de5 Mon Sep 17 00:00:00 2001 From: Aayush Kumar Sahu Date: Sun, 24 Mar 2024 21:29:04 +0530 Subject: [PATCH] add remote connection script and remote node task runner --- elixir_cluster_with_remote_flyio | 80 ++++++++++++++++++++++++++++++++ lib/battleship/task_runner.ex | 41 ++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100755 elixir_cluster_with_remote_flyio create mode 100644 lib/battleship/task_runner.ex diff --git a/elixir_cluster_with_remote_flyio b/elixir_cluster_with_remote_flyio new file mode 100755 index 0000000..7bf2f1b --- /dev/null +++ b/elixir_cluster_with_remote_flyio @@ -0,0 +1,80 @@ +#!/bin/bash + +# After opening a WireGuard connection to your Fly.io network, copy this file +# into the directory for your Elixir application. Run the script to start the +# local Elixir project and cluster it to an application running on Fly.io. + +# In order for this to work: +# - Your wireguard connection must be up. +# - When run from a directory with a `fly.toml` file, `flyctl` command is used +# to access information about the application. +# - Set the ENV `CLUSTER_APP_NAME` to specify a different hosted app name. +# - Set the ENV `RELEASE_COOKIE` to override the Erlang cookie used for +# clustering. It uses the the value from the deployed app if it is set there. +# - Run the script. + +set -e + +if ! command -v jq &> /dev/null; then + echo "jq is not installed. Please install it before running this script. It is a command-line JSON processor." + exit 1 +fi + +# Check if CLUSTER_APP_NAME is set and use it if found +if [[ -n $CLUSTER_APP_NAME ]]; then + # Use the override app_name from the ENV + json_data=$(fly status --app ${CLUSTER_APP_NAME} --json) +else + # Use the app_name for the current app + json_data=$(fly status --json) +fi + +# Use an explicit RELEASE_COOKIE value if provided +if [ -n "$RELEASE_COOKIE" ]; then + release_cookie=$RELEASE_COOKIE +else + # Extract the RELEASE_COOKIE value from the deployed app + release_cookie=$(echo "$json_data" | jq -r '.Machines[] | select(.state == "started") | .config | .env | .RELEASE_COOKIExxx' | head -n 1) + + if [ $release_cookie == "null" ]; then + echo "The deployed application did not set RELEASE_COOKIE ENV. If the cookie is static on the server, provide it locally through RELEASE_COOKIE." + exit 1 + fi +fi + +# Extract the app_name +app_name=$(echo "$json_data" | jq -r '.Name') + +# Extract private_ip for the first started machine +private_ip=$(echo "$json_data" | jq -r '.Machines[] | select(.state == "started") | .private_ip' | head -n 1) + +# Extract image_ref tag hash for the first started machine +image_tags=$(echo "$json_data" | jq -r '.Machines[] | select(.state == "started") | .image_ref.tag | sub("deployment-"; "")' | head -n 1) + +if [ -z "$private_ip" ]; then + echo "No instances appear to be running at this time." + exit 1 +fi + +# Assemble the full node name +full_node_name="${app_name}@${private_ip}" +echo Attempting to connect to $full_node_name + +# IMPORTANT: +# ========== +# Fly.io uses an IPv6 network internally for private IPs. The BEAM needs IPv6 +# support to be enabled explicitly. +# +# The issue is, if it's enabled globally like in a `.bashrc` file, then setting +# it here essentially flips it OFF. If not set globally, then it should be set +# here. Choose the version that fits your situation. +# +# It's the `--erl "-proto_dist inet6_tcp"` portion. + +# export ERL_AFLAGS="-kernel shell_history enabled -proto_dist inet6_tcp" + +# Toggles on IPv6 support for the local node being started. +iex --erl "-proto_dist inet6_tcp" --sname local-battleship --cookie ${release_cookie} -e "IO.inspect(Node.connect(:'${full_node_name}'), label: \"Node Connected?\"); IO.inspect(Node.list(), label: \"Connected Nodes\")" + +# Does NOT toggle on IPv6 support, assuming it is enabled some other way. +# iex --sname local --cookie ${release_cookie} --hidden -e "IO.inspect(Node.connect(:'${full_node_name}'), label: \"Node Connected?\"); IO.inspect(Node.list(:hidden), label: \"Connected Nodes\")" -S mix phx.server diff --git a/lib/battleship/task_runner.ex b/lib/battleship/task_runner.ex new file mode 100644 index 0000000..8a05f0b --- /dev/null +++ b/lib/battleship/task_runner.ex @@ -0,0 +1,41 @@ +defmodule Battleship.TaskRunner do + require Logger + + @remote_node_name "phoenix-aayushsahu-com" + + def get_task_runner_node() do + Node.list() + |> Enum.find(nil, fn node_name -> + node_name + |> Atom.to_string() + |> String.contains?(@remote_node_name) + end) + end + + def run(task_info, remote_node) do + %{module: module, function: function, args: args} = task_info + + Task.async(fn -> + Process.flag(:trap_exit, true) + + _t2 = + Task.Supervisor.async_nolink( + # Accumulator.TaskRunner is a task supervisor that's running on remote node + {Accumulator.TaskRunner, remote_node}, + module, + function, + args + ) + + receive do + {:DOWN, _, _, pid, _reason} -> + Logger.error("Task execution failed with PID: #{pid}") + nil + + {_ref, result} -> + result + end + end) + |> Task.await() + end +end