Skip to content

Commit

Permalink
add remote connection script and remote node task runner
Browse files Browse the repository at this point in the history
  • Loading branch information
aayushmau5 committed Mar 24, 2024
1 parent 8d742e6 commit 6e9c60c
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 0 deletions.
80 changes: 80 additions & 0 deletions elixir_cluster_with_remote_flyio
Original file line number Diff line number Diff line change
@@ -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
41 changes: 41 additions & 0 deletions lib/battleship/task_runner.ex
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 6e9c60c

Please sign in to comment.