diff --git a/lib/git_sync/connection.ex b/lib/git_sync/connection.ex index 95a36b1..3de7161 100644 --- a/lib/git_sync/connection.ex +++ b/lib/git_sync/connection.ex @@ -35,6 +35,17 @@ defmodule GitSync.Connection do |> unique_constraint(:kind) end + @doc """ + Changeset for a knot, whose credential is an SSH keypair the operator pastes + in. The private key is never rendered back, so both secrets are re-entered on + every save. + """ + def ssh_changeset(connection, attrs) do + connection + |> changeset(attrs) + |> validate_required([:ssh_key, :host_key]) + end + @doc """ Changeset for a forge whose credential is obtained over OAuth. """ diff --git a/lib/git_sync/connections.ex b/lib/git_sync/connections.ex index 9a7e06d..1d0eae8 100644 --- a/lib/git_sync/connections.ex +++ b/lib/git_sync/connections.ex @@ -19,6 +19,8 @@ defmodule GitSync.Connections do def github, do: get(:github) + def tangled, do: get(:tangled) + @doc """ Whether the first-run wizard has been completed. """ @@ -61,6 +63,17 @@ defmodule GitSync.Connections do Repo.insert_or_update(changeset) end + @doc """ + Saves the knot's base URL and the keypair used to push to it. + """ + def configure_tangled(attrs) do + changeset = + (tangled() || %Connection{}) + |> Connection.ssh_changeset(Map.put(attrs, "kind", :tangled)) + + Repo.insert_or_update(changeset) + end + @doc """ The stored credential, or `nil` for a connection nobody has signed in to. """ diff --git a/lib/git_sync_web/controllers/connection_controller.ex b/lib/git_sync_web/controllers/connection_controller.ex index a35f7c0..a3389ed 100644 --- a/lib/git_sync_web/controllers/connection_controller.ex +++ b/lib/git_sync_web/controllers/connection_controller.ex @@ -7,30 +7,38 @@ defmodule GitSyncWeb.ConnectionController do alias GitSync.Connections alias GitSync.Forge - def index(conn, _params) do - render_index(conn, github_form(Connection.oauth_changeset(%Connection{}, %{}))) - end + def index(conn, _params), do: render_index(conn) @doc """ - Renders the connections page. GitHub's own actions land back here, so the - form they were submitted from has to be rebuildable from outside this module. + Renders the connections page. Each forge's own actions land back here, so the + forms they were submitted from have to be rebuildable from outside this + module; a failed submission passes its changeset in as an override. """ - def render_index(conn, github_form) do + def render_index(conn, overrides \\ []) do forgejo = Connections.forgejo() github = Connections.github() + tangled = Connections.tangled() + + assigns = + Keyword.merge( + [ + connection: forgejo, + repos: Forge.list_repos(forgejo), + github: github, + github_repos: github_repos(github), + github_form: form(Connection.oauth_changeset(%Connection{}, %{})), + tangled: tangled, + tangled_form: form(Connection.changeset(tangled || %Connection{}, %{})) + ], + overrides + ) conn |> put_view(html: GitSyncWeb.ConnectionHTML) - |> render(:index, - connection: forgejo, - repos: Forge.list_repos(forgejo), - github: github, - github_repos: github_repos(github), - github_form: github_form - ) + |> render(:index, assigns) end - def github_form(changeset), do: to_form(changeset) + def form(changeset), do: to_form(changeset) defp github_repos(%Connection{token: token} = github) when is_binary(token), do: Forge.list_repos(github) diff --git a/lib/git_sync_web/controllers/connection_html/index.html.heex b/lib/git_sync_web/controllers/connection_html/index.html.heex index f839461..e7737d9 100644 --- a/lib/git_sync_web/controllers/connection_html/index.html.heex +++ b/lib/git_sync_web/controllers/connection_html/index.html.heex @@ -46,6 +46,31 @@ <% end %> <% end %> + <.header> + Tangled + <:subtitle> + A knot speaks plain git. Paste the SSH key git-sync should push with and the + knot's host key line, as it appears in known_hosts. + + + + <.form for={@tangled_form} id="tangled-form" action={~p"/connections/tangled"}> + <.input + field={@tangled_form[:base_url]} + label="Knot URL" + placeholder="https://knot.example.com" + /> + <.input field={@tangled_form[:ssh_key]} type="textarea" label="Private key" /> + <.input + field={@tangled_form[:host_key]} + type="textarea" + label="Host key" + placeholder="knot.example.com ssh-ed25519 AAAA..." + /> + + <.button id="save-tangled" type="submit">Save knot + + <%= case @github_repos do %> <% {:ok, repos} -> %>