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} -> %>