Flake: HM module
This commit is contained in:
4
.envrc
4
.envrc
@@ -1 +1,5 @@
|
|||||||
use flake
|
use flake
|
||||||
|
|
||||||
|
export RELEASE_NODE=frajtano-test@nomos
|
||||||
|
export FRAJTANO_DIR=$PWD/.frajtano_state
|
||||||
|
export SSH_AUTH_SOCK=$FRAJTANO_DIR/agent.sock
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -29,3 +29,5 @@ frajtano-*.tar
|
|||||||
/.nix-hex
|
/.nix-hex
|
||||||
/.nix-mix
|
/.nix-mix
|
||||||
/result
|
/result
|
||||||
|
/.frajtano_state
|
||||||
|
/todo
|
||||||
|
|||||||
@@ -1,2 +1,6 @@
|
|||||||
import Config
|
import Config
|
||||||
config :frajtano, listen_path: Path.expand(System.get_env("FRAJTANO_DIR")) |> Path.join("agent.sock")
|
config :frajtano,
|
||||||
|
listen_path: Path.expand(System.get_env("FRAJTANO_DIR")) |> Path.join("agent.sock"),
|
||||||
|
initial_peers: System.get_env("FRAJTANO_PEERS", "") |> String.split(":", trim: true) |> Enum.map(&Path.expand/1)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
};
|
};
|
||||||
script = pkgs.writeShellScriptBin pname ''
|
script = pkgs.writeShellScriptBin pname ''
|
||||||
set -eu
|
set -eu
|
||||||
|
${pkgs.coreutils}/bin/mkdir -p $FRAJTANO_DIR
|
||||||
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")
|
||||||
@@ -28,10 +29,13 @@
|
|||||||
exec ${lib.getExe pkg} "$@"
|
exec ${lib.getExe pkg} "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
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.add_peer()"
|
||||||
;;
|
;;
|
||||||
|
socket)
|
||||||
|
echo $FRAJTANO_DIR/agent.sock
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
run "$@"
|
run "$@"
|
||||||
;;
|
;;
|
||||||
|
|||||||
47
flake.nix
47
flake.nix
@@ -15,7 +15,11 @@
|
|||||||
ownPkgs = self.packages.${system};
|
ownPkgs = self.packages.${system};
|
||||||
});
|
});
|
||||||
in {
|
in {
|
||||||
devShells = eachSystem ({pkgs, ownPkgs, ...}: {
|
devShells = eachSystem ({
|
||||||
|
pkgs,
|
||||||
|
ownPkgs,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
default = pkgs.beam.packages.erlang_26.callPackage ./shell.nix {
|
default = pkgs.beam.packages.erlang_26.callPackage ./shell.nix {
|
||||||
inherit ownPkgs;
|
inherit ownPkgs;
|
||||||
};
|
};
|
||||||
@@ -23,5 +27,46 @@
|
|||||||
packages = eachSystem ({pkgs, ...}: {
|
packages = eachSystem ({pkgs, ...}: {
|
||||||
default = pkgs.beam.packages.erlang_26.callPackage ./default.nix {};
|
default = pkgs.beam.packages.erlang_26.callPackage ./default.nix {};
|
||||||
});
|
});
|
||||||
|
homeModules.default = {
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.bluepython508.frajtano;
|
||||||
|
in {
|
||||||
|
options.bluepython508.frajtano = {
|
||||||
|
enable = lib.mkEnableOption "frajtano";
|
||||||
|
dir = lib.mkOption {
|
||||||
|
description = "directory in which to place the listening socket";
|
||||||
|
default = "${config.home.homeDirectory}/.ssh/frajtano";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
home.sessionVariables.FRAJTANO_DIR = cfg.dir;
|
||||||
|
home.packages = [self.packages.${pkgs.system}.default];
|
||||||
|
|
||||||
|
systemd.user.services.frajtano = {
|
||||||
|
Unit.Description = "frajtano";
|
||||||
|
Unit.After = ["default.target"];
|
||||||
|
Service.Environment = "'FRAJTANO_DIR=${cfg.dir}'";
|
||||||
|
Service.ExecStart = "${self.packages.${pkgs.system}.default}/bin/frajtano start";
|
||||||
|
Install.WantedBy = ["default.target"];
|
||||||
|
};
|
||||||
|
|
||||||
|
launchd.agents.frajtano = {
|
||||||
|
enable = true;
|
||||||
|
config = {
|
||||||
|
EnvironmentVariables = {
|
||||||
|
FRAJTANO_DIR = cfg.dir;
|
||||||
|
};
|
||||||
|
ProgramArguments = ["${lib.getExe self.packages.${pkgs.system}.default}/bin/frajtano" "start"];
|
||||||
|
RunAtLoad = true;
|
||||||
|
KeepAlive = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
19
lib/agent.ex
19
lib/agent.ex
@@ -8,10 +8,19 @@ defmodule Frajtano.Agent do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def init(_) do
|
def init(_) do
|
||||||
{:ok,
|
{
|
||||||
|
:ok,
|
||||||
%{
|
%{
|
||||||
keys: %{}
|
keys: %{}
|
||||||
}}
|
},
|
||||||
|
{:continue, :init_peers}
|
||||||
|
}
|
||||||
|
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
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@@ -27,9 +36,8 @@ defmodule Frajtano.Agent do
|
|||||||
do: {:ok, {idents, peer}}
|
do: {:ok, {idents, peer}}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
||||||
# Double :ok-wrapping because of Task.async_stream
|
# Double :ok-wrapping because of Task.async_stream
|
||||||
idents = (for {:ok, {:ok, {idents, peer}}} <- idents, do: {idents, peer})
|
idents = for {:ok, {:ok, {idents, peer}}} <- idents, do: {idents, peer}
|
||||||
|
|
||||||
{
|
{
|
||||||
:reply,
|
:reply,
|
||||||
@@ -61,7 +69,8 @@ defmodule Frajtano.Agent do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def sign(agent \\ __MODULE__, request) do
|
def sign(agent \\ __MODULE__, request) do
|
||||||
GenServer.call(agent, {:sign, request})
|
# Signing can take some time, as a password may need to be entered or similar
|
||||||
|
GenServer.call(agent, {:sign, request}, :infinity)
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_peer(agent \\ __MODULE__, path) do
|
def add_peer(agent \\ __MODULE__, path) do
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ defmodule Frajtano.Supervisor do
|
|||||||
@impl true
|
@impl true
|
||||||
def init(:ok) do
|
def init(:ok) do
|
||||||
children = [
|
children = [
|
||||||
|
{DynamicSupervisor, name: Frajtano.Peer},
|
||||||
Frajtano.Agent,
|
Frajtano.Agent,
|
||||||
{Frajtano.Listener, [Application.fetch_env!(:frajtano, :listen_path)]},
|
|
||||||
{Task.Supervisor, name: Frajtano.ClientSupervisor},
|
{Task.Supervisor, name: Frajtano.ClientSupervisor},
|
||||||
{DynamicSupervisor, name: Frajtano.Peer}
|
{Frajtano.Listener, [Application.fetch_env!(:frajtano, :listen_path)]},
|
||||||
]
|
]
|
||||||
|
|
||||||
Supervisor.init(children, strategy: :one_for_one)
|
Supervisor.init(children, strategy: :one_for_one)
|
||||||
|
|||||||
81
lib/peer.ex
81
lib/peer.ex
@@ -17,18 +17,8 @@ defmodule Frajtano.Peer do
|
|||||||
{:ok, %{conn: conn, clients: :queue.new(), buffer: <<>>}}
|
{:ok, %{conn: conn, clients: :queue.new(), buffer: <<>>}}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
def reply({client, ref}, msg) do
|
||||||
def handle_call(packet, from, %{conn: conn, clients: clients} = state) do
|
send(client, {ref, msg})
|
||||||
case :gen_tcp.send(conn, Proto.encode(packet)) do
|
|
||||||
:ok ->
|
|
||||||
{:noreply, %{state | clients: :queue.in(from, clients)}}
|
|
||||||
|
|
||||||
{:error, :closed} ->
|
|
||||||
{:noreply, state, {:continue, :closed}}
|
|
||||||
|
|
||||||
{:error, e} ->
|
|
||||||
raise(e)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp handle_messages(%{clients: clients, buffer: buffer} = state) do
|
defp handle_messages(%{clients: clients, buffer: buffer} = state) do
|
||||||
@@ -38,42 +28,85 @@ defmodule Frajtano.Peer do
|
|||||||
|
|
||||||
{msg, buffer} ->
|
{msg, buffer} ->
|
||||||
{{:value, client}, clients} = :queue.out(clients)
|
{{:value, client}, clients} = :queue.out(clients)
|
||||||
GenServer.reply(client, {:ok, msg})
|
reply(client, {:ok, msg})
|
||||||
handle_messages(%{state | clients: clients, buffer: buffer})
|
handle_messages(%{state | clients: clients, buffer: buffer})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info({:send, packet, from}, %{conn: conn, clients: clients} = state) do
|
||||||
|
case :gen_tcp.send(conn, Proto.encode(packet)) do
|
||||||
|
:ok ->
|
||||||
|
{:noreply, %{state | clients: :queue.in(from, clients)}}
|
||||||
|
|
||||||
|
{:error, e} ->
|
||||||
|
{:noreply, state, {:continue, {:error, e}}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info({:tcp, conn, msg}, %{conn: conn, buffer: buffer} = state) do
|
def handle_info({:tcp, conn, msg}, %{conn: conn, buffer: buffer} = state) do
|
||||||
:inet.setopts(conn, active: :once)
|
:inet.setopts(conn, active: :once)
|
||||||
buffer = buffer <> msg
|
buffer = buffer <> msg
|
||||||
|
|
||||||
handle_messages(%{ state | buffer: buffer })
|
handle_messages(%{state | buffer: buffer})
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info({:tcp_closed, _}, state) do
|
def handle_info({:tcp_closed, _}, state) do
|
||||||
{:noreply, state, {:continue, :closed}}
|
{:noreply, state, {:continue, {:error, :closed}}}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_continue(:closed, %{clients: clients}) do
|
def handle_info(:timeout, state) do
|
||||||
|
{:noreply, state, {:continue, {:error, :closed}}}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_continue({:error, e}, %{clients: clients}) do
|
||||||
clients
|
clients
|
||||||
|> :queue.to_list()
|
|> :queue.to_list()
|
||||||
|> Enum.each(&GenServer.reply(&1, {:error, :closed}))
|
|> Enum.each(&reply(&1, {:error, e}))
|
||||||
|
|
||||||
{:stop, :closed, %{}}
|
{:stop, {:error, e}, %{}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def identities(peer) do
|
def identities(peer) do
|
||||||
with {:ok, {:agent_identities_answer, identities}} <-
|
ref = make_ref()
|
||||||
GenServer.call(peer, {:agentc_request_identities, nil}),
|
send(peer, {:send, {:agentc_request_identities, nil}, {self(), ref}})
|
||||||
do: {:ok, identities}
|
# Needs to be less than the timeout in Frajtano.Agent.identities on the Task.async_stream call
|
||||||
|
# That's 5000 by default
|
||||||
|
timer = Process.send_after(peer, :timeout, 4500)
|
||||||
|
|
||||||
|
receive do
|
||||||
|
{^ref, msg} ->
|
||||||
|
Process.cancel_timer(timer)
|
||||||
|
msg
|
||||||
|
end
|
||||||
|
|> case do
|
||||||
|
{:ok, {:agent_identities_answer, identities}} -> {:ok, identities}
|
||||||
|
{:ok, {:agent_failure, nil}} -> {:error, :agent_failure}
|
||||||
|
{:ok, msg} -> raise("Unexpected message #{inspect(msg)}")
|
||||||
|
{:error, e} -> {:error, e}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def sign(peer, request) do
|
def sign(peer, request) do
|
||||||
with {:ok, {:agent_sign_response, signature}} <-
|
# Signing may take some time, as a password may need to be entered or similar
|
||||||
GenServer.call(peer, {:agentc_sign_request, request}),
|
# There is therefore no timeout
|
||||||
do: {:ok, signature}
|
# If something requests identities afterwards, it will timeout, which also kills this signature request
|
||||||
|
# The SSH agent protocol strict ordering leaves fun problems with timeouts, as it turns out
|
||||||
|
ref = make_ref()
|
||||||
|
send(peer, {:send, {:agentc_sign_request, request}, {self(), ref}})
|
||||||
|
|
||||||
|
receive do
|
||||||
|
{^ref, msg} -> msg
|
||||||
|
end
|
||||||
|
|> case do
|
||||||
|
{:ok, {:agent_sign_response, signature}} -> {:ok, signature}
|
||||||
|
{:ok, {:agent_failure, nil}} -> {:error, :agent_failure}
|
||||||
|
{:ok, msg} -> raise("Unexpected message #{inspect(msg)}")
|
||||||
|
{:error, e} -> {:error, e}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
1
mix.exs
1
mix.exs
@@ -14,7 +14,6 @@ defmodule Frajtano.MixProject do
|
|||||||
def application do
|
def application do
|
||||||
[
|
[
|
||||||
extra_applications: [:logger],
|
extra_applications: [:logger],
|
||||||
env: [listen_path: nil],
|
|
||||||
mod: {Frajtano, []}
|
mod: {Frajtano, []}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,13 +3,14 @@
|
|||||||
pkgs,
|
pkgs,
|
||||||
ownPkgs,
|
ownPkgs,
|
||||||
mkShell,
|
mkShell,
|
||||||
|
elixir,
|
||||||
elixir-ls,
|
elixir-ls,
|
||||||
inotify-tools,
|
inotify-tools,
|
||||||
}:
|
}:
|
||||||
mkShell {
|
mkShell {
|
||||||
inputsFrom = [ ownPkgs.default ];
|
inputsFrom = [ ownPkgs.default ];
|
||||||
packages =
|
packages =
|
||||||
[elixir-ls]
|
[elixir elixir-ls]
|
||||||
++ lib.lists.optional (pkgs.system == "x86_64-linux") inotify-tools
|
++ lib.lists.optional (pkgs.system == "x86_64-linux") inotify-tools
|
||||||
++ lib.lists.optionals (pkgs.system == "aarch64-darwin") (with pkgs.darwin.apple_sdk.frameworks; [
|
++ lib.lists.optionals (pkgs.system == "aarch64-darwin") (with pkgs.darwin.apple_sdk.frameworks; [
|
||||||
CoreFoundation
|
CoreFoundation
|
||||||
|
|||||||
Reference in New Issue
Block a user