Compare commits
2 Commits
5e65fad33d
...
25c260c4a5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25c260c4a5 | ||
|
|
23d3a97643 |
2
.envrc
2
.envrc
@@ -1,4 +1,4 @@
|
|||||||
use flake
|
use flake
|
||||||
|
|
||||||
export RELEASE_NODE=frajtano-test@
|
export RELEASE_NODE=frajtano-test@$(cat /etc/hostname)
|
||||||
export FRAJTANO_DIR=$PWD/.frajtano_state
|
export FRAJTANO_DIR=$PWD/.frajtano_state
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import Config
|
import Config
|
||||||
config :frajtano,
|
|
||||||
listen_path: Path.expand(System.get_env("FRAJTANO_DIR")) |> Path.join("agent.sock"),
|
config :frajtano,
|
||||||
initial_peers: System.get_env("FRAJTANO_PEERS", "") |> String.split(":", trim: true) |> Enum.map(&Path.expand/1)
|
listen_path: Path.expand(System.get_env("FRAJTANO_DIR")) |> Path.join("agent.sock")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
36
default.nix
36
default.nix
@@ -3,9 +3,10 @@
|
|||||||
pkgs,
|
pkgs,
|
||||||
mixRelease,
|
mixRelease,
|
||||||
elixir,
|
elixir,
|
||||||
|
fetchMixDeps,
|
||||||
}: let
|
}: let
|
||||||
pname = "frajtano";
|
pname = "frajtano";
|
||||||
pkg = mixRelease {
|
pkg = mixRelease rec {
|
||||||
inherit pname;
|
inherit pname;
|
||||||
version = "0.0.1";
|
version = "0.0.1";
|
||||||
|
|
||||||
@@ -16,6 +17,18 @@
|
|||||||
filter = path: _type: baseNameOf path != "flake.nix" && baseNameOf path != "flake.lock";
|
filter = path: _type: baseNameOf path != "flake.nix" && baseNameOf path != "flake.lock";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Adapted from https://blog.eigenvalue.net/nix-rerunning-fixed-output-derivations/
|
||||||
|
# deps hash should change any time ./mix.lock changes, and not otherwise
|
||||||
|
mixFodDeps = let
|
||||||
|
deps = fetchMixDeps {
|
||||||
|
pname = "mix-deps-${pname}";
|
||||||
|
inherit version src;
|
||||||
|
sha256 = "sha256-4g5lUlr5+l+mNVp0InZkx6X31g2cckI8NNQp37QgBis=";
|
||||||
|
};
|
||||||
|
hash = builtins.substring 11 32 "${./mix.lock}";
|
||||||
|
in
|
||||||
|
deps.overrideAttrs (attrs: {name = "${deps.name}-${hash}";});
|
||||||
|
|
||||||
ELIXIR_MAKE_CACHE_DIR = "/tmp/.elixir-make-cache";
|
ELIXIR_MAKE_CACHE_DIR = "/tmp/.elixir-make-cache";
|
||||||
meta.mainProgram = pname;
|
meta.mainProgram = pname;
|
||||||
};
|
};
|
||||||
@@ -25,14 +38,24 @@
|
|||||||
file="$FRAJTANO_DIR/cookie"
|
file="$FRAJTANO_DIR/cookie"
|
||||||
(umask 077; [ -f "$file" ] || ${pkgs.coreutils}/bin/head -c 128 /dev/urandom | ${pkgs.coreutils}/bin/base64 -w0 > "$file")
|
(umask 077; [ -f "$file" ] || ${pkgs.coreutils}/bin/head -c 128 /dev/urandom | ${pkgs.coreutils}/bin/base64 -w0 > "$file")
|
||||||
export RELEASE_COOKIE=$(${pkgs.coreutils}/bin/cat "$file")
|
export RELEASE_COOKIE=$(${pkgs.coreutils}/bin/cat "$file")
|
||||||
export RELEASE_NODE="frajtano-$(${pkgs.coreutils}/bin/whoami)@$(${pkgs.coreutils}/bin/cat /etc/hostname)"
|
[ -n $RELEASE_NODE ] || export RELEASE_NODE="frajtano-$(${pkgs.coreutils}/bin/whoami)@$(${pkgs.coreutils}/bin/cat /etc/hostname)"
|
||||||
run() {
|
run() {
|
||||||
exec ${lib.getExe pkg} "$@"
|
exec ${lib.getExe pkg} "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list() {
|
||||||
|
for i in "$@"; do
|
||||||
|
echo "\"$i\", "
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
case "''${1:-}" in
|
case "''${1:-}" in
|
||||||
assimilate)
|
assimilate)
|
||||||
run rpc ":ok = \"$(echo -n "$2" | ${pkgs.coreutils}/bin/base64)\" |> Base.decode64!() |> Frajtano.Agent.add_peer()"
|
run rpc ":ok = \"$(echo -n "$2" | ${pkgs.coreutils}/bin/base64)\" |> Base.decode64!() |> Frajtano.Agent.assimilate()"
|
||||||
|
;;
|
||||||
|
spawn)
|
||||||
|
shift
|
||||||
|
run rpc ":ok = Frajtano.Agent.spawn_peer({\"/usr/bin/env\", [$(list "$@")]})"
|
||||||
;;
|
;;
|
||||||
socket)
|
socket)
|
||||||
echo $FRAJTANO_DIR/agent.sock
|
echo $FRAJTANO_DIR/agent.sock
|
||||||
@@ -42,4 +65,9 @@
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
'';
|
'';
|
||||||
in pkgs.symlinkJoin { name = pname; paths = [ script pkg ]; meta.mainProgram = pname; }
|
in
|
||||||
|
pkgs.symlinkJoin {
|
||||||
|
name = pname;
|
||||||
|
paths = [script pkg];
|
||||||
|
meta.mainProgram = pname;
|
||||||
|
}
|
||||||
|
|||||||
20
lib/agent.ex
20
lib/agent.ex
@@ -11,16 +11,9 @@ defmodule Frajtano.Agent do
|
|||||||
{
|
{
|
||||||
:ok,
|
:ok,
|
||||||
%{},
|
%{},
|
||||||
{:continue, :init_peers}
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_continue(:init_peers, state) do
|
|
||||||
for peer <- Application.fetch_env!(:frajtano, :initial_peers), do: {:ok, _} = Peer.start(peer)
|
|
||||||
{:noreply, state}
|
|
||||||
end
|
|
||||||
|
|
||||||
# select: list of specs, where specs are a tuple of match, guards, and outputs
|
# select: list of specs, where specs are a tuple of match, guards, and outputs
|
||||||
# match is {key, pid, value}, :"$1" is a match variable
|
# match is {key, pid, value}, :"$1" is a match variable
|
||||||
def peer_paths() do
|
def peer_paths() do
|
||||||
@@ -56,9 +49,8 @@ defmodule Frajtano.Agent do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_call({:add_peer, path}, _from, state) do
|
def handle_call({:add_peer, spec}, _from, state) do
|
||||||
# TODO: deduplicate peers by socket path
|
case Peer.start(spec) do
|
||||||
case Peer.start(path) do
|
|
||||||
{:ok, _} -> {:reply, :ok, state}
|
{:ok, _} -> {:reply, :ok, state}
|
||||||
{:error, error} -> {:reply, {:error, error}, state}
|
{:error, error} -> {:reply, {:error, error}, state}
|
||||||
end
|
end
|
||||||
@@ -73,7 +65,11 @@ defmodule Frajtano.Agent do
|
|||||||
GenServer.call(__MODULE__, {:sign, request}, :infinity)
|
GenServer.call(__MODULE__, {:sign, request}, :infinity)
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_peer(path) do
|
def assimilate(path) do
|
||||||
GenServer.call(__MODULE__, {:add_peer, path})
|
GenServer.call(__MODULE__, {:add_peer, {:socket, path}})
|
||||||
|
end
|
||||||
|
|
||||||
|
def spawn_peer(spec) do
|
||||||
|
GenServer.call(__MODULE__, {:add_peer, {:spawn, spec}})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
49
lib/peer.ex
49
lib/peer.ex
@@ -3,20 +3,59 @@ defmodule Frajtano.Peer do
|
|||||||
require Logger
|
require Logger
|
||||||
use GenServer, restart: :temporary
|
use GenServer, restart: :temporary
|
||||||
|
|
||||||
def start(path) do
|
defmodule Spawner do
|
||||||
DynamicSupervisor.start_child(Frajtano.PeerSupervisor, {__MODULE__, path})
|
use Supervisor, restart: :permanent
|
||||||
|
|
||||||
|
def start_link(spec) do
|
||||||
|
Supervisor.start_link(__MODULE__, spec, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def init({executable, args}) do
|
||||||
|
Temp.track!()
|
||||||
|
path = Path.join(Temp.mkdir!(), "agent.sock")
|
||||||
|
|
||||||
|
children = [
|
||||||
|
Supervisor.child_spec({MuonTrap.Daemon, [executable, args ++ [path]]}, restart: :temporary, significant: true),
|
||||||
|
Supervisor.child_spec({Frajtano.Peer, {path, :spawned, {executable, args}}}, restart: :permanent)
|
||||||
|
]
|
||||||
|
|
||||||
|
Supervisor.init(children, strategy: :one_for_all, auto_shutdown: :any_significant)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def start_link(path) do
|
def start({:socket, path}) do
|
||||||
GenServer.start_link(__MODULE__, path, name: {:via, Registry, {Frajtano.Peers, path}})
|
DynamicSupervisor.start_child(Frajtano.PeerSupervisor, {__MODULE__, {path}})
|
||||||
|
end
|
||||||
|
|
||||||
|
def start({:spawn, spec}) do
|
||||||
|
DynamicSupervisor.start_child(Frajtano.PeerSupervisor, {Spawner, spec})
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_link({path}) do
|
||||||
|
GenServer.start_link(__MODULE__, {path}, name: {:via, Registry, {Frajtano.Peers, path}})
|
||||||
|
end
|
||||||
|
def start_link({_, _, _} = spec) do
|
||||||
|
GenServer.start_link(__MODULE__, spec, name: {:via, Registry, {Frajtano.Peers, spec}})
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def init(path) do
|
def init({path}) do
|
||||||
{:ok, conn} = :gen_tcp.connect({:local, path}, 0, [:binary, active: :once])
|
{:ok, conn} = :gen_tcp.connect({:local, path}, 0, [:binary, active: :once])
|
||||||
{:ok, %{conn: conn, clients: :queue.new(), buffer: <<>>}}
|
{:ok, %{conn: conn, clients: :queue.new(), buffer: <<>>}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def init({path, :spawned, _}) do
|
||||||
|
if File.exists?(path) do
|
||||||
|
{:ok, conn} = :gen_tcp.connect({:local, path}, 0, [:binary, active: :once])
|
||||||
|
{:ok, %{conn: conn, clients: :queue.new(), buffer: <<>>}}
|
||||||
|
else
|
||||||
|
Process.sleep(100)
|
||||||
|
init({path, :spawned, nil})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def reply({client, ref}, msg) do
|
def reply({client, ref}, msg) do
|
||||||
send(client, {ref, msg})
|
send(client, {ref, msg})
|
||||||
end
|
end
|
||||||
|
|||||||
5
mix.exs
5
mix.exs
@@ -19,6 +19,9 @@ defmodule Frajtano.MixProject do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp deps do
|
defp deps do
|
||||||
[]
|
[
|
||||||
|
{:muontrap, "~> 1.0"},
|
||||||
|
{:temp, "~> 0.4"},
|
||||||
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
6
mix.lock
6
mix.lock
@@ -1 +1,5 @@
|
|||||||
|
%{
|
||||||
|
"elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"},
|
||||||
|
"muontrap": {:hex, :muontrap, "1.5.0", "bf5c273872379968615a39974458328209ac97fa1f588396192131ff973d1ca2", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "daf605e877f60b5be9215e3420d7971fc468677b29921e40915b15fd928273d4"},
|
||||||
|
"temp": {:hex, :temp, "0.4.9", "eb6355bfa7925a568b3d9eb3bb57e89aa6d2b78bfe8dfb6b698e090631b7f41f", [:mix], [], "hexpm", "bc8bf7b27d9105bef933ef4bf4ba37ac6b899dbeba329deaa88c60b62d6b4b6d"},
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user