diff --git a/Vagrantfile b/Vagrantfile index c370d3d..aefb65c 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -8,4 +8,5 @@ Vagrant.configure("2") do |config| config.vm.box = "debian/bullseye64" config.vm.network "forwarded_port", guest: 50012, host: 50012, host_ip: "127.0.0.1" + config.vm.network "forwarded_port", guest: 80, host: 8000, host_ip: "127.0.0.1" end diff --git a/agent/src/server/agent.rs b/agent/src/server/agent.rs index 05d49f7..24e4538 100644 --- a/agent/src/server/agent.rs +++ b/agent/src/server/agent.rs @@ -1,5 +1,6 @@ use std::{pin::Pin, process::Stdio, sync::Mutex}; +use tokio::process::Command; use tokio_stream::{ wrappers::{ReceiverStream, WatchStream}, Stream, StreamExt, @@ -116,29 +117,47 @@ impl agent_server::Agent for AgentService<'static> { async fn exec(&self, req: Request) -> AgentResult { use exec_response::Out; - let ExecRequest { program, args } = req.get_ref(); + let ExecRequest { + user, + program, + args, + } = req.get_ref(); - let mut command = tokio::process::Command::new(program) + if user.is_empty() { + return Err(Status::invalid_argument("you must specify a user")); + } + + if program.is_empty() { + return Err(Status::invalid_argument("you must specify a program")); + } + + let mut command = if user != "root" { + let mut cmd = Command::new("sudo"); + cmd.arg("-iu").arg(user).arg("--").arg(program); + cmd + } else { + Command::new(program) + }; + + let mut io = command .args(args) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()?; - let stdout = - FramedRead::new(command.stdout.take().unwrap(), BytesCodec::new()).map(|stdout| { - let stdout = stdout.unwrap(); - Out::Stdout(String::from_utf8_lossy(&stdout[..]).to_string()) - }); + let stdout = FramedRead::new(io.stdout.take().unwrap(), BytesCodec::new()).map(|stdout| { + let stdout = stdout.unwrap(); + Out::Stdout(String::from_utf8_lossy(&stdout[..]).to_string()) + }); - let stderr = - FramedRead::new(command.stderr.take().unwrap(), BytesCodec::new()).map(|stderr| { - let stderr = stderr.unwrap(); - Out::Stderr(String::from_utf8_lossy(&stderr[..]).to_string()) - }); + let stderr = FramedRead::new(io.stderr.take().unwrap(), BytesCodec::new()).map(|stderr| { + let stderr = stderr.unwrap(); + Out::Stderr(String::from_utf8_lossy(&stderr[..]).to_string()) + }); let exit = TaskBuilder::new(format!("exec {program}")) .health_monitor(self.health.clone()) - .add_step(async move { command.wait().await.unwrap() }) + .add_step(async move { io.wait().await.unwrap() }) .build() .into_stream(); diff --git a/app/config/config.exs b/app/config/config.exs index 159b49d..c7ecf19 100644 --- a/app/config/config.exs +++ b/app/config/config.exs @@ -31,6 +31,10 @@ config :prymn, PrymnWeb.Endpoint, # at the `config/runtime.exs`. config :prymn, Prymn.Mailer, adapter: Swoosh.Adapters.Local +config :prymn, Oban, + repo: Prymn.Repo, + queues: [default: 10] + # Configure esbuild (the version is required) config :esbuild, version: "0.17.11", diff --git a/app/lib/prymn/agents.ex b/app/lib/prymn/agents.ex index 4556f6c..4a28eae 100644 --- a/app/lib/prymn/agents.ex +++ b/app/lib/prymn/agents.ex @@ -5,31 +5,82 @@ defmodule Prymn.Agents do the Prymn.Agents.ConnectionSupervisor and are book-kept using the Prymn.Agents.Registry. + Agents are only valid when a `Prymn.Servers.Server` is considered registered. + """ - alias Prymn.Agents.Connection - alias Prymn.Agents.Health + require Logger + alias Prymn.Agents.{Connection, Health, Agent} + alias PrymnProto.Prymn.Agent.Stub + alias PrymnProto.Prymn.{ExecRequest, SysUpdateRequest} + @doc """ + Establish a connection with a Server if one does not already exist, and + return an Agent that interfaces with the rest of the system. + + ## Examples + iex> Prymn.Servers.get_server_by_ip!("127.0.0.1") |> Prymn.Agents.from_server() + %Prymn.Agents.Agent{} + """ + def from_server(%Prymn.Servers.Server{status: :registered} = server) do + case start_connection(server.public_ip) do + {:ok, _pid} -> Agent.new(server.public_ip) + {:error, error} -> {:error, error} + end + end + + def from_server(%Prymn.Servers.Server{}) do + Logger.error("Tried to establish a connection with an unregistered server.") + {:error, :unauthorized_action} + end + + @doc """ + Establish a connection with a Server if one does not already exist for a + given App. Returns an [Agent] that interfaces with the rest of the system. + """ + def from_app(%Prymn.Apps.App{} = app) do + app = Prymn.Repo.preload(app, :server) + from_server(app.server) + end + + @doc """ + Starts a new connection with `host_address` if one does not exist. + + ## Examples + iex> Prymn.Agents.start_connection("127.0.0.1") + {:ok, } + iex> Prymn.Agents.start_connection("127.0.0.1") + {:ok, } + """ def start_connection(host_address) do spec = {Connection, host_address} case DynamicSupervisor.start_child(Prymn.Agents.ConnectionSupervisor, spec) do - {:ok, _pid} -> :ok - {:error, {:already_started, _pid}} -> :ok + {:ok, pid} -> {:ok, pid} + {:error, {:already_started, pid}} -> {:ok, pid} {:error, error} -> {:error, error} end end @doc """ - Subscribe to the host's Health using Phoenix.PubSub Broadcasted messages are - the Health struct: + Subscribe to the host's Health using Phoenix.PubSub. + Broadcasted messages are the Health struct: %Prymn.Agents.Health{} """ + def subscribe_to_health(%Agent{} = agent) do + :ok = Health.subscribe(agent.host_address) + agent + end + def subscribe_to_health(host_address) do :ok = Health.subscribe(host_address) end + # TODO + # def alive?(host_address) do + # end + @doc """ Return the last known health status of the Agent, or `nil` if it doesn't exist. @@ -41,30 +92,49 @@ defmodule Prymn.Agents do @doc """ Get the system's information (CPU, Memory usage, etc.). """ - def get_sys_info(host_address) do - case lookup_connection(host_address) do - nil -> nil - pid -> Connection.get_sys_info(pid) + def get_sys_info(%Agent{} = agent) do + with {:ok, channel} <- get_channel(agent), + {:ok, result} <- Stub.get_sys_info(channel, %Google.Protobuf.Empty{}) do + result + else + {:error, error} -> {:error, error} end end @doc """ - Perform a system update. - - ## Asynchronous call - Messages are sent to the caller in the form of the struct: - - %PrymnProto.Prymn.SysUpdateResponse{} + Run a command. """ - def sys_update(host_address, dry_run) when is_boolean(dry_run) do - lookup_connection(host_address) - |> Connection.sys_update(dry_run) + def exec(%Agent{} = agent, %ExecRequest{} = request) do + with {:ok, channel} <- get_channel(agent), + {:ok, result} <- Stub.exec(channel, request) do + result + else + {:error, error} -> {:error, error} + end end - defp lookup_connection(host_address) when is_binary(host_address) do - case Registry.lookup(Prymn.Agents.Registry, host_address) do - [{pid, _}] -> pid - [] -> nil + def exec(%Agent{} = agent, request) when is_map(request), + do: exec(agent, struct(ExecRequest, request)) + + @doc """ + Perform a system update. + """ + def sys_update(%Agent{} = agent, %SysUpdateRequest{} = request) do + with {:ok, channel} <- get_channel(agent), + {:ok, result} <- Stub.sys_update(channel, request) do + result + else + {:error, error} -> {:error, error} + end + end + + def sys_update(%Agent{} = agent, request) when is_map(request), + do: sys_update(agent, struct(SysUpdateRequest, request)) + + defp get_channel(%Agent{} = agent) do + case start_connection(agent.host_address) do + {:ok, pid} -> {:ok, Connection.get_channel(pid)} + {:error, error} -> {:error, error} end end end diff --git a/app/lib/prymn/agents/agent.ex b/app/lib/prymn/agents/agent.ex new file mode 100644 index 0000000..a2594e5 --- /dev/null +++ b/app/lib/prymn/agents/agent.ex @@ -0,0 +1,13 @@ +defmodule Prymn.Agents.Agent do + @moduledoc false + + defstruct [:host_address] + + @type t :: %__MODULE__{ + host_address: String.t() + } + + def new(host_address) when is_binary(host_address) do + %__MODULE__{host_address: host_address} + end +end diff --git a/app/lib/prymn/agents/connection.ex b/app/lib/prymn/agents/connection.ex index 9f5ec9b..0bbdee7 100644 --- a/app/lib/prymn/agents/connection.ex +++ b/app/lib/prymn/agents/connection.ex @@ -13,18 +13,10 @@ defmodule Prymn.Agents.Connection do GenServer.start_link(__MODULE__, host_address, name: via(host_address)) end - def get_channel(server) do + def get_channel(server) when is_pid(server) do GenServer.call(server, :get_channel) end - def get_sys_info(server) when is_pid(server) do - GenServer.call(server, :get_sys_info) - end - - def sys_update(server, dry_run) when is_pid(server) and is_boolean(dry_run) do - GenServer.call(server, {:sys_update, dry_run}) - end - ## ## Server callbacks ## @@ -69,21 +61,10 @@ defmodule Prymn.Agents.Connection do end @impl true - def handle_call(:get_channel, _from, {_, channel} = state) do + def handle_call(:get_channel, _, {_, channel} = state) do {:reply, channel, state, @timeout} end - def handle_call(:get_sys_info, _from, {_, channel} = state) do - reply = Stub.get_sys_info(channel, %Google.Protobuf.Empty{}) - {:reply, reply, state, @timeout} - end - - def handle_call({:sys_update, dry_run}, {from, _}, {_, channel} = state) do - request = %PrymnProto.Prymn.SysUpdateRequest{dry_run: dry_run} - streaming_call(fn -> Stub.sys_update(channel, request) end, from) - {:reply, :ok, state, @timeout} - end - @impl true def handle_info(%GRPC.Channel{} = channel, {host, _}) do {:noreply, {host, channel}, {:continue, :health}} @@ -156,18 +137,4 @@ defmodule Prymn.Agents.Connection do receive_loop(pid) end - - defp streaming_call(fun, from) do - Task.start_link(fn -> - case fun.() do - {:ok, stream} -> - stream - |> Stream.each(fn {:ok, data} -> send(from, data) end) - |> Enum.to_list() - - {:error, _error} -> - :todo - end - end) - end end diff --git a/app/lib/prymn/application.ex b/app/lib/prymn/application.ex index ffecde8..f698871 100644 --- a/app/lib/prymn/application.ex +++ b/app/lib/prymn/application.ex @@ -8,20 +8,13 @@ defmodule Prymn.Application do @impl true def start(_type, _args) do children = [ - # Start the Telemetry supervisor PrymnWeb.Telemetry, - # Start the Ecto repository Prymn.Repo, - # Start the PubSub system {DNSCluster, query: Application.get_env(:prymn, :dns_cluster_query) || :ignore}, {Phoenix.PubSub, name: Prymn.PubSub}, - # Start the Finch HTTP client for sending emails {Finch, name: Prymn.Finch}, - # Start the Agents (dynamic GRPC connections) supervisor + {Oban, Application.fetch_env!(:prymn, Oban)}, Prymn.Agents.Supervisor, - # Start a worker by calling: SampleApp.Worker.start_link(arg) - # {SampleApp.Worker, arg}, - # Start to serve requests, typically the last entry PrymnWeb.Endpoint ] diff --git a/app/lib/prymn/apps.ex b/app/lib/prymn/apps.ex index db51b1c..b7f22e5 100644 --- a/app/lib/prymn/apps.ex +++ b/app/lib/prymn/apps.ex @@ -18,40 +18,36 @@ defmodule Prymn.Apps do """ def list_apps do Repo.all(App) + |> Repo.preload(:server) end @doc """ Gets a single app. Raises `Ecto.NoResultsError` if the App does not exist. - - ## Examples - - iex> get_app!(123) - %App{} - - iex> get_app!(456) - ** (Ecto.NoResultsError) - """ def get_app!(id), do: Repo.get!(App, id) @doc """ - Creates a app. - - ## Examples - - iex> create_app(%{field: value}) - {:ok, %App{}} - - iex> create_app(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - + Creates an app and prepares it for deployment. """ - def create_app(attrs \\ %{}) do - %App{} - |> App.changeset(attrs) + def create_app(%Prymn.Servers.Server{} = server, attrs \\ %{}) do + # TODO: Any app.. + server + |> Ecto.build_assoc(:apps) + |> App.change_wordpress(attrs) |> Repo.insert() + |> case do + {:ok, %App{} = app} -> + %{app_id: app.id} + |> Prymn.Worker.new() + |> Oban.insert() + + app + + {:error, changeset} -> + changeset + end end @doc """ diff --git a/app/lib/prymn/apps/app.ex b/app/lib/prymn/apps/app.ex index 77a9943..de35c49 100644 --- a/app/lib/prymn/apps/app.ex +++ b/app/lib/prymn/apps/app.ex @@ -1,9 +1,15 @@ defmodule Prymn.Apps.App do use Ecto.Schema + import Ecto.Changeset schema "apps" do + belongs_to :server, Prymn.Servers.Server field :name, :string + field :type, :string + field :status, Ecto.Enum, values: [:initialized, :deployed], default: :initialized + embeds_one :wordpress, Prymn.Apps.Wordpress, source: :metadata, on_replace: :update + # embeds_one :html, Prymn.Apps.Html, source: :metadata, on_replace: :update timestamps() end @@ -12,6 +18,16 @@ defmodule Prymn.Apps.App do def changeset(app, attrs) do app |> cast(attrs, [:name]) - |> validate_required([:name]) + |> validate_required([:name, :server_id]) + end + + @doc false + def change_wordpress(app, attrs \\ %{}) do + app + |> changeset(attrs) + |> cast_embed(:wordpress) + |> validate_required(:wordpress) + |> put_change(:type, "wordpress") + |> put_change(:status, :initialized) end end diff --git a/app/lib/prymn/apps/wordpress.ex b/app/lib/prymn/apps/wordpress.ex new file mode 100644 index 0000000..30cd2e7 --- /dev/null +++ b/app/lib/prymn/apps/wordpress.ex @@ -0,0 +1,199 @@ +defmodule Prymn.Apps.Wordpress do + use Ecto.Schema + + import Ecto.Changeset + alias Prymn.Apps.App + alias Prymn.Agents + alias PrymnProto.Prymn.{ExecRequest, ExecResponse} + + @primary_key false + embedded_schema do + field :path, :string + field :db_host, :string + field :db_name, :string + field :db_user, :string + field :db_pass, :string + field :admin_username, :string + field :admin_email, :string + end + + def changeset(app, attrs) do + app + |> cast(attrs, [ + :path, + :db_host, + :db_name, + :db_user, + :db_pass, + :admin_username, + :admin_email + ]) + |> validate_required([ + :path, + :db_user, + :db_host, + :db_user, + :db_pass, + :admin_email, + :admin_username + ]) + end + + @max_steps 6 + + # TODO: We need a mechanism to drive some messages to the pubsub, maybe using Health: + # Health.change_task(%Health.Task{ + # key: "create_db_user", + # message: "Creating db user...", + # curr_step: 0, + # max_steps: 5 + # }) + # |> Health.update_and_broadcast() + + def deploy(%App{type: "wordpress"} = app, agent, notify_fun) do + # TODO: Run sanity checks + # e.g Database does not exist, domain does not exist, etc. + deploy(app, agent, notify_fun, 1) + end + + defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 1 = step) do + # TODO: We need a mechanism to wait for the agent to connect before proceeding, + # this is executed faster than the connection (which happens on deploy_app in Worker) + + Agents.exec(agent, %ExecRequest{ + user: "root", + program: "mysql", + args: [ + "-e", + # TODO: Sanitize the string to protect from injection + "create user '#{wp.db_user}'@'#{wp.db_host}' identified by '#{wp.db_pass}';" + ] + }) + |> then(&get_results/1) + |> case do + {_, _, exit_code} = data when exit_code != 0 -> + notify_fun.(:error, data, 1 / 5 * 100) + + data -> + notify_fun.(:progress, data, step / @max_steps * 100) + deploy(app, agent, notify_fun, step + 1) + end + end + + defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 2 = step) do + Agents.exec(agent, %ExecRequest{ + user: "root", + program: "mysql", + args: [ + "-e", + # TODO: Sanitize the string to protect from injection + "grant all privileges on #{wp.db_name}.* to '#{wp.db_user}'@'#{wp.db_host}';" + ] + }) + |> then(&get_results/1) + |> case do + {_, _, exit_code} = data when exit_code != 0 -> + notify_fun.(:error, data, step / @max_steps * 100) + + data -> + notify_fun.(:progress, data, step / @max_steps * 100) + deploy(app, agent, notify_fun, step + 1) + end + end + + defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 3 = step) do + Agents.exec(agent, %ExecRequest{ + user: "vagrant", + program: "wp", + args: ["core", "download", "--path=#{wp.path}"] + }) + |> then(&get_results/1) + |> case do + {_, _, exit_code} = data when exit_code != 0 -> + notify_fun.(:error, data, step / @max_steps * 100) + + data -> + notify_fun.(:progress, data, step / @max_steps * 100) + deploy(app, agent, notify_fun, step + 1) + end + end + + defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 4 = step) do + Agents.exec(agent, %ExecRequest{ + user: "vagrant", + program: "wp", + args: [ + "config", + "create", + "--path=#{wp.path}", + "--dbhost=#{wp.db_host}", + "--dbname=#{wp.db_name}", + "--dbuser=#{wp.db_user}", + "--dbpass=#{wp.db_pass}" + ] + }) + |> then(&get_results/1) + |> case do + {_, _, exit_code} = data when exit_code != 0 -> + notify_fun.(:error, data, step / @max_steps * 100) + + data -> + notify_fun.(:progress, data, step / @max_steps * 100) + deploy(app, agent, notify_fun, step + 1) + end + end + + defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 5 = step) do + Agents.exec(agent, %ExecRequest{ + user: "vagrant", + program: "wp", + args: ["db", "create", "--path=#{wp.path}"] + }) + |> then(&get_results/1) + |> case do + {_, _, exit_code} = data when exit_code != 0 -> + notify_fun.(:error, data, step / @max_steps * 100) + + data -> + notify_fun.(:progress, data, step / @max_steps * 100) + deploy(app, agent, notify_fun, step + 1) + end + end + + defp deploy(%App{name: name, wordpress: %__MODULE__{} = wp}, agent, notify_fun, 6 = step) do + Agents.exec(agent, %ExecRequest{ + user: "vagrant", + program: "wp", + args: [ + "core", + "install", + "--path=#{wp.path}", + "--url=http://site.test", + "--title=#{name}", + "--admin_user=#{wp.admin_username}", + "--admin_email=#{wp.admin_email}" + ] + }) + |> then(&get_results/1) + |> case do + {_, _, exit_code} = data when exit_code != 0 -> + notify_fun.(:error, data, step / @max_steps * 100) + + data -> + notify_fun.(:complete, data, step / @max_steps * 100) + end + end + + defp get_results(stream) do + Enum.reduce_while(stream, {"", "", nil}, fn + {:ok, %ExecResponse{out: {:exit_code, exit_code}}}, {stdout, stderr, _} -> + {:halt, {stdout, stderr, exit_code}} + + {:ok, %ExecResponse{out: {:stdout, stdout}}}, {acc_stdout, stderr, exit_code} -> + {:cont, {acc_stdout <> stdout, stderr, exit_code}} + + {:ok, %ExecResponse{out: {:stderr, stderr}}}, {stdout, acc_stderr, exit_code} -> + {:cont, {stdout, acc_stderr <> stderr, exit_code}} + end) + end +end diff --git a/app/lib/prymn/servers.ex b/app/lib/prymn/servers.ex index 196b0cb..e4e4d98 100644 --- a/app/lib/prymn/servers.ex +++ b/app/lib/prymn/servers.ex @@ -21,6 +21,11 @@ defmodule Prymn.Servers do Repo.all(Server |> order_by(desc: :inserted_at)) end + def list_registered_servers() do + query = from s in Server, select: s, where: s.status == :registered + Repo.all(query) + end + @doc """ Gets a single server. diff --git a/app/lib/prymn/servers/server.ex b/app/lib/prymn/servers/server.ex index 66426c3..e99a0a4 100644 --- a/app/lib/prymn/servers/server.ex +++ b/app/lib/prymn/servers/server.ex @@ -33,6 +33,8 @@ defmodule Prymn.Servers.Server do values: [:unregistered, :registered], default: :unregistered + has_many :apps, Prymn.Apps.App + timestamps() end diff --git a/app/lib/prymn/worker.ex b/app/lib/prymn/worker.ex new file mode 100644 index 0000000..a30c5ef --- /dev/null +++ b/app/lib/prymn/worker.ex @@ -0,0 +1,46 @@ +defmodule Prymn.Worker do + use Oban.Worker + + alias Prymn.{Agents, Apps} + + @impl true + def perform(%Oban.Job{args: %{"app_id" => app_id}}) do + deploy_app(app_id) + await_deploy("app:#{app_id}") + :ok + end + + defp deploy_app(app_id) do + pid = self() + app = Apps.get_app!(app_id) + agent = Agents.from_app(app) + + Task.start_link(fn -> + notify_fun = fn + :progress, data, progress -> send(pid, {:progress, data, progress}) + :complete, data, _progress -> send(pid, {:complete, data}) + :error, data, _progress -> send(pid, {:error, data}) + end + + Apps.Wordpress.deploy(app, agent, notify_fun) + end) + end + + defp await_deploy(channel) do + receive do + {:progress, data, progress} -> + Phoenix.PubSub.broadcast!(Prymn.PubSub, channel, {:progress, data, progress}) + await_deploy(channel) + + {:complete, data} -> + Phoenix.PubSub.broadcast!(Prymn.PubSub, channel, {:complete, data}) + + {:error, data} -> + Phoenix.PubSub.broadcast!(Prymn.PubSub, channel, {:error, data}) + # raise "Error occured during deployment: #{inspect(data)}" + after + 60_000 -> + raise RuntimeError, "no progress after 1 minute" + end + end +end diff --git a/app/lib/prymn_web/components/create_app.ex b/app/lib/prymn_web/components/create_app.ex index 2b84949..938ae54 100644 --- a/app/lib/prymn_web/components/create_app.ex +++ b/app/lib/prymn_web/components/create_app.ex @@ -1,6 +1,24 @@ defmodule PrymnWeb.CreateApp do use PrymnWeb, :live_component + alias Prymn.Apps + + @impl true + def mount(socket) do + servers = Prymn.Servers.list_registered_servers() + {:ok, assign(socket, :servers, servers)} + end + + @impl true + def update(assigns, socket) do + changeset = Apps.change_app(%Apps.App{server: nil}) + + {:ok, + socket + |> assign(assigns) + |> assign(:form, to_form(changeset))} + end + @impl true def render(assigns) do ~H""" @@ -26,52 +44,107 @@ defmodule PrymnWeb.CreateApp do - <.wordpress_app_form :if={assigns.app_type == "wordpress"} servers={assigns[:servers]} /> + <.wordpress_app_form + :if={@app_type == "wordpress"} + form={@form} + servers={@servers} + myself={@myself} + /> + <.plain_app_form :if={@app_type == "plain"} form={@form} servers={@servers} myself={@myself} /> """ end + @impl true + def handle_event("create_wordpress_app", %{"app" => params}, socket) do + server = + Enum.find(socket.assigns.servers, %Prymn.Servers.Server{}, fn server -> + Integer.to_string(server.id) == params["server_id"] + end) + + socket = + case Apps.create_app(server, params) do + %Ecto.Changeset{valid?: false} = changeset -> assign(socket, :form, to_form(changeset)) + %Apps.App{} -> push_navigate(socket, to: ~p"/apps") + end + + {:noreply, socket} + end + + def handle_event("create_plain_app", _params, socket) do + {:noreply, socket} + end + defp wordpress_app_form(assigns) do ~H""" - <.simple_form id="test" for={nil}> + <.simple_form method="POST" for={@form} phx-submit="create_wordpress_app" phx-target={@myself}> <.input - :if={assigns.servers != nil} - id="server" type="select" - name="server" - prompt="Select a server to host this app..." - options={[]} - value={nil} label="Hosting Server" + prompt="Select a server" + field={@form[:server_id]} + options={Enum.map(@servers, &{&1.name, &1.id})} /> - <.input id="name" type="text" name="app_name" value={nil} label="WordPress Site Name" required /> - <.input id="domain" type="text" name="domain" value={nil} label="Domain Name" required /> - <.input type="checkbox" name="create_database?" label="Create a new database?" /> - <.input type="text" name="db_name" value={nil} label="Database name" /> + <.input type="text" label="App Name" field={@form[:name]} /> + <.input type="text" label="Domain Name" field={@form[:domain]} /> +
+ + WordPress Settings + +
+ <.inputs_for :let={wordpress} field={@form[:wordpress]}> + <.input type="email" label="Admin Email" field={wordpress[:admin_email]} /> + <.input type="text" label="Admin Username" field={wordpress[:admin_username]} /> + <.input type="text" label="Installation Path" field={wordpress[:path]} /> + <.input + type="text" + label="Database Host" + field={wordpress[:db_host]} + value="127.0.0.1" + readonly + /> + <.input type="text" label="Database Name" field={wordpress[:db_name]} /> + <.input type="text" label="Database User" field={wordpress[:db_user]} /> + <.input type="password" label="Database Password" field={wordpress[:db_pass]} /> + +
+
+ <:actions> + Create + + + """ + end - <.input type="select" name="db_name" value={nil} options={["db1", "db2"]} label="Database" /> - - <.input type="text" name="admin_username" value={nil} label="Admin Username" /> - <.input type="email" name="admin_email" value={nil} label="Admin Email" /> - <.input type="password" name="admin_password" value={nil} label="Admin Password" /> - - Create + defp plain_app_form(assigns) do + ~H""" + <.simple_form method="POST" for={@form} phx-submit="create_plain_app" phx-target={@myself}> + <.input + type="select" + label="Hosting Server" + prompt="Select a server" + field={@form[:server_id]} + options={Enum.map(@servers, &{&1.name, &1.id})} + /> + <:actions> + Create + """ end diff --git a/app/lib/prymn_web/components/layouts/app.html.heex b/app/lib/prymn_web/components/layouts/app.html.heex index c56ed79..2bf2ff9 100644 --- a/app/lib/prymn_web/components/layouts/app.html.heex +++ b/app/lib/prymn_web/components/layouts/app.html.heex @@ -5,37 +5,29 @@ > <%= gettext("Skip to content") %> -
-
-

codename

- +
+
+

codename

+
Prymn

v<%= Application.spec(:prymn, :vsn) %>

-
- <.dropdown position="right"> - <:button variant="tertiary"> - <.icon name="hero-user-solid" /> - - <:item href={~p"/users/settings"}>Settings - <:item method="DELETE" href={~p"/auth/log_out"}>Log out - -
+ <.dropdown position="right"> + <:button variant="tertiary"> + <.icon name="hero-user-solid" /> + + <:item href={~p"/users/settings"}>Settings + <:item method="DELETE" href={~p"/auth/log_out"}>Log out +
-
-
-
    -
  • Projects
  • -
  • Usage
  • -
  • Activities
  • -
  • Limits
  • -
  • Support
  • -
-
+
<.flash_group flash={@flash} /> <%= @inner_content %> diff --git a/app/lib/prymn_web/components/system_info.ex b/app/lib/prymn_web/components/system_info.ex index 87a11a1..d78a5fb 100644 --- a/app/lib/prymn_web/components/system_info.ex +++ b/app/lib/prymn_web/components/system_info.ex @@ -8,11 +8,10 @@ defmodule PrymnWeb.SystemInfo do def update(assigns, socket) do {:ok, socket - |> assign(:ip, assigns.ip) + |> assign(:agent, assigns.agent) |> assign(:sys_info, AsyncResult.loading()) |> start_async(:get_sys_info, fn -> - Logger.debug("getting initial system info for #{assigns.ip}...") - Prymn.Agents.get_sys_info(assigns.ip) + Prymn.Agents.get_sys_info(assigns.agent) end)} end @@ -61,17 +60,16 @@ defmodule PrymnWeb.SystemInfo do end def handle_async(:get_sys_info, {:ok, {:ok, sys_info}}, socket) do - %{sys_info: sys_info_result, ip: host_address} = socket.assigns + %{sys_info: sys_info_result, agent: agent} = socket.assigns {:noreply, socket |> assign(:sys_info, AsyncResult.ok(sys_info_result, sys_info)) |> start_async(:get_sys_info, fn -> - Logger.debug("getting more system info for #{host_address}...") # 10 seconds is >5 which is gun's timeout duration (which might have a race # condition if they are equal) Process.sleep(:timer.seconds(10)) - Prymn.Agents.get_sys_info(host_address) + Prymn.Agents.get_sys_info(agent) end)} end diff --git a/app/lib/prymn_web/live/app_index_live.ex b/app/lib/prymn_web/live/app_index_live.ex index 8c05669..9e89e7d 100644 --- a/app/lib/prymn_web/live/app_index_live.ex +++ b/app/lib/prymn_web/live/app_index_live.ex @@ -1,15 +1,17 @@ defmodule PrymnWeb.AppIndexLive do use PrymnWeb, :live_view + alias Prymn.Apps + @impl true def mount(_, _, socket) do - apps = Prymn.Apps.list_apps() - servers = Prymn.Servers.list_servers() + apps = Apps.list_apps() - {:ok, - socket - |> assign(:servers, servers) - |> assign(:apps, apps)} + for %Apps.App{} = app <- apps do + Phoenix.PubSub.subscribe(Prymn.PubSub, "app:#{app.id}") + end + + {:ok, assign(socket, :apps, apps)} end @impl true @@ -18,12 +20,7 @@ defmodule PrymnWeb.AppIndexLive do <%= cond do %> <% assigns.live_action == :new -> %> <.back navigate={~p"/apps"}>Go back - <.live_component - id={:new} - module={PrymnWeb.CreateApp} - app_type={assigns[:app_type]} - servers={@servers} - /> + <.live_component id={:new} module={PrymnWeb.CreateApp} app_type={assigns[:app_type]} /> <% assigns.apps == [] -> %> <.onboarding /> <% true -> %> @@ -33,12 +30,25 @@ defmodule PrymnWeb.AppIndexLive do <:subtitle> All of your apps accross all projects. + <:actions> + Create app + +
+

App: <%= app.name %>

+

Server: <%= app.server.name %>

+
<% end %> """ end + @impl true + def handle_info(msg, socket) do + dbg(msg, label: "Incoming message from pubsub") + {:noreply, socket} + end + @impl true def handle_params(%{"app_type" => app_type}, _, socket) do {:noreply, assign(socket, app_type: app_type)} @@ -52,12 +62,14 @@ defmodule PrymnWeb.AppIndexLive do defp onboarding(assigns) do ~H""" -
-

You have no Apps.

-

Create your first App here!

- - Create a new App - +
+
+

You have no Apps.

+

Create your first App here!

+ + Create a new App + +
""" end diff --git a/app/lib/prymn_web/live/dashboard_live.ex b/app/lib/prymn_web/live/dashboard_live.ex index 78a9a6e..15f1665 100644 --- a/app/lib/prymn_web/live/dashboard_live.ex +++ b/app/lib/prymn_web/live/dashboard_live.ex @@ -9,7 +9,7 @@ defmodule PrymnWeb.DashboardLive do

Good morning, <%= @current_user.email %>!

Your overview

-
+
0

Projects

@@ -24,7 +24,7 @@ defmodule PrymnWeb.DashboardLive do <.icon class="h-3 w-4" name="hero-arrow-right" /> View your servers
-
+
0

Apps

<.link class="text-sm text-blue-600" navigate={~p"/apps"}> diff --git a/app/lib/prymn_web/live/server_live/index.ex b/app/lib/prymn_web/live/server_live/index.ex index b19ef77..5e84abd 100644 --- a/app/lib/prymn_web/live/server_live/index.ex +++ b/app/lib/prymn_web/live/server_live/index.ex @@ -11,9 +11,10 @@ defmodule PrymnWeb.ServerLive.Index do healths = if connected?(socket) do - for %Servers.Server{status: :registered, public_ip: ip} <- servers, into: %{} do - Agents.subscribe_to_health(ip) - Agents.start_connection(ip) + for %Servers.Server{status: :registered, public_ip: ip} = server <- servers, into: %{} do + Agents.from_server(server) + |> Agents.subscribe_to_health() + {ip, Agents.get_health(ip)} end else @@ -51,15 +52,15 @@ defmodule PrymnWeb.ServerLive.Index do
IP: <%= server.public_ip || "N/A" %> - - <%= for {name, task} <- Enum.take(@healths[server.public_ip].tasks, 1) do %> + <%= if @healths[server.public_ip] do %> +
In progress: <%= name %>
<%= task.progress %>
- <% end %> -
+
+ <% end %>
diff --git a/app/lib/prymn_web/live/server_live/show.ex b/app/lib/prymn_web/live/server_live/show.ex index 392055a..4e5aa1b 100644 --- a/app/lib/prymn_web/live/server_live/show.ex +++ b/app/lib/prymn_web/live/server_live/show.ex @@ -75,7 +75,7 @@ defmodule PrymnWeb.ServerLive.Show do <.live_component id={"system_info-#{@server.name}"} module={PrymnWeb.SystemInfo} - ip={@server.public_ip} + agent={assigns[:agent]} />
@@ -131,10 +131,14 @@ defmodule PrymnWeb.ServerLive.Show do def handle_params(%{"id" => id}, _, socket) do server = Servers.get_server!(id) - if connected?(socket) and server.status == :registered do - Agents.subscribe_to_health(server.public_ip) - Agents.start_connection(server.public_ip) - end + socket = + if connected?(socket) and server.status == :registered do + agent = Agents.from_server(server) + Agents.subscribe_to_health(agent) + assign(socket, :agent, agent) + else + socket + end health = Agents.get_health(server.public_ip) @@ -167,20 +171,24 @@ defmodule PrymnWeb.ServerLive.Show do @impl true def handle_event("system_update", _params, socket) do - host_address = get_in(socket.assigns, [:server, Access.key(:public_ip)]) server_name = get_in(socket.assigns, [:server, Access.key(:name)]) + pid = self() - socket = - if host_address do - Agents.sys_update(host_address, socket.assigns.dry_run) - put_flash(socket, :info, "Started a system update on server #{server_name}.") - else - put_flash( - socket, - :error, - "Could not perform the update. Your server does not seem to have an address" - ) - end + if agent = socket.assigns[:agent] do + # TODO: This is ugly + Task.start_link(fn -> + Agents.sys_update(agent, %{dry_run: socket.assigns.dry_run}) + |> Stream.each(fn + {:ok, msg} -> send(pid, msg) + {:error, error} -> Logger.error("error during system update call: #{inspect(error)}") + end) + |> Enum.to_list() + end) + + put_flash(socket, :info, "Started a system update on server #{server_name}.") + else + put_flash(socket, :error, "Could not perform the update.") + end {:noreply, socket} end diff --git a/mix.exs b/mix.exs index 0b2214a..c3e4d6e 100644 --- a/mix.exs +++ b/mix.exs @@ -54,6 +54,7 @@ defmodule Prymn.MixProject do {:grpc, "~> 0.7"}, {:protobuf, "~> 0.12.0"}, {:google_protos, "~> 0.3.0"}, + {:oban, "~> 2.17"}, # Test {:floki, ">= 0.30.0", only: :test}, diff --git a/mix.lock b/mix.lock index ad7232b..5bf615e 100644 --- a/mix.lock +++ b/mix.lock @@ -11,8 +11,8 @@ "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "dialyxir": {:hex, :dialyxir, "1.4.2", "764a6e8e7a354f0ba95d58418178d486065ead1f69ad89782817c296d0d746a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "516603d8067b2fd585319e4b13d3674ad4f314a5902ba8130cd97dc902ce6bbd"}, "dns_cluster": {:hex, :dns_cluster, "0.1.1", "73b4b2c3ec692f8a64276c43f8c929733a9ab9ac48c34e4c0b3d9d1b5cd69155", [:mix], [], "hexpm", "03a3f6ff16dcbb53e219b99c7af6aab29eb6b88acf80164b4bd76ac18dc890b3"}, - "ecto": {:hex, :ecto, "3.11.0", "ff8614b4e70a774f9d39af809c426def80852048440e8785d93a6e91f48fec00", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7769dad267ef967310d6e988e92d772659b11b09a0c015f101ce0fff81ce1f81"}, - "ecto_sql": {:hex, :ecto_sql, "3.11.0", "c787b24b224942b69c9ff7ab9107f258ecdc68326be04815c6cce2941b6fad1c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "77aa3677169f55c2714dda7352d563002d180eb33c0dc29cd36d39c0a1a971f5"}, + "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, + "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"}, @@ -30,6 +30,7 @@ "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, + "oban": {:hex, :oban, "2.17.1", "42d6221a1c17b63d81c19e3bad9ea82b59e39c47c1f9b7670ee33628569a449b", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c02686ada7979b00e259c0efbafeae2749f8209747b3460001fe695c5bdbeee6"}, "phoenix": {:hex, :phoenix, "1.7.10", "02189140a61b2ce85bb633a9b6fd02dff705a5f1596869547aeb2b2b95edd729", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "cf784932e010fd736d656d7fead6a584a4498efefe5b8227e9f383bf15bb79d0"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.3", "86e9878f833829c3f66da03d75254c155d91d72a201eb56ae83482328dc7ca93", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d36c401206f3011fefd63d04e8ef626ec8791975d9d107f9a0817d426f61ac07"}, "phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"}, @@ -41,7 +42,7 @@ "plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"}, "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, - "postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, + "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, "protobuf": {:hex, :protobuf, "0.12.0", "58c0dfea5f929b96b5aa54ec02b7130688f09d2de5ddc521d696eec2a015b223", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "75fa6cbf262062073dd51be44dd0ab940500e18386a6c4e87d5819a58964dc45"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "swoosh": {:hex, :swoosh, "1.14.1", "d8813699ba410509008dd3dfdb2df057e3fce367d45d5e6d76b146a7c9d559cd", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87da72260b4351678f96aec61db5c2acc8a88cda2cf2c4f534eb4c9c461350c7"}, diff --git a/priv/repo/migrations/20231119141943_create_apps.exs b/priv/repo/migrations/20231119141943_create_apps.exs index 7b1e392..6e08aa3 100644 --- a/priv/repo/migrations/20231119141943_create_apps.exs +++ b/priv/repo/migrations/20231119141943_create_apps.exs @@ -3,7 +3,11 @@ defmodule Prymn.Repo.Migrations.CreateApps do def change do create table(:apps) do + add :server_id, references("servers") add :name, :string + add :type, :string + add :status, :string + add :metadata, :map timestamps() end diff --git a/priv/repo/migrations/20231130171151_add_oban_jobs_table.exs b/priv/repo/migrations/20231130171151_add_oban_jobs_table.exs new file mode 100644 index 0000000..5218964 --- /dev/null +++ b/priv/repo/migrations/20231130171151_add_oban_jobs_table.exs @@ -0,0 +1,11 @@ +defmodule Prymn.Repo.Migrations.AddObanJobsTable do + use Ecto.Migration + + def up do + Oban.Migration.up() + end + + def down do + Oban.Migration.down(version: 1) + end +end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index dd002e9..faabe7b 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -1,11 +1,13 @@ # Script for populating the database. You can run it as: # # mix run priv/repo/seeds.exs -# -# Inside the script, you can read and write to any of your -# repositories directly: -# -# Prymn.Repo.insert!(%Prymn.SomeSchema{}) -# -# We recommend using the bang functions (`insert!`, `update!` -# and so on) as they will fail if something goes wrong. + +Prymn.Accounts.register_user(%{email: "dev@test", password: "password"}) + +Prymn.Repo.insert!(%Prymn.Servers.Server{ + status: :registered, + public_ip: "127.0.0.1", + name: "local server", + provider: :Custom, + registration_token: "" +}) diff --git a/proto/agent.proto b/proto/agent.proto index 6c1e998..bc745eb 100644 --- a/proto/agent.proto +++ b/proto/agent.proto @@ -46,8 +46,9 @@ message SysInfoResponse { } message ExecRequest { - string program = 1; - repeated string args = 2; + string user = 1; + string program = 2; + repeated string args = 3; } message ExecResponse {