Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ecdffbbbd |
@@ -106,7 +106,7 @@ that ships in `%WINDIR%\System32\OpenSSH`.
|
|||||||
|
|
||||||
### Build from source instead
|
### Build from source instead
|
||||||
|
|
||||||
Requires Go 1.23+.
|
Requires Go 1.25+.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make install # builds and installs into $GOBIN (or $GOPATH/bin, ~/go/bin)
|
make install # builds and installs into $GOBIN (or $GOPATH/bin, ~/go/bin)
|
||||||
|
|||||||
+28
-80
@@ -11,10 +11,10 @@ added and where it stands.)
|
|||||||
interactive host picker: it reads `~/.ssh/config`, shows a clean, static,
|
interactive host picker: it reads `~/.ssh/config`, shows a clean, static,
|
||||||
high-contrast, low-light-friendly list, and on selection `exec`s the real system
|
high-contrast, low-light-friendly list, and on selection `exec`s the real system
|
||||||
`ssh` client into the host (so tty/ProxyJump/agent forwarding just work). It's a
|
`ssh` client into the host (so tty/ProxyJump/agent forwarding just work). It's a
|
||||||
single Go binary. v2 added sorting/grouping, an SSH key generator, and an in-app
|
single Go binary. v2 added richer sorting/grouping and a native SSH key
|
||||||
SFTP file browser. Optional features include a YubiKey launch gate. App data
|
generator. Optional features include a YubiKey launch gate. App data (recents,
|
||||||
(recents, favourites, settings) lives under `~/.ssh/.dial/` so it syncs via the
|
favourites, settings) lives under `~/.ssh/.dial/` so it syncs via the user's
|
||||||
user's existing Syncthing.
|
existing Syncthing.
|
||||||
|
|
||||||
## What v2 shipped
|
## What v2 shipped
|
||||||
|
|
||||||
@@ -40,21 +40,6 @@ user's existing Syncthing.
|
|||||||
duplicate an existing alias. It's a rebuild of an old `create_ssh_keypair.sh`,
|
duplicate an existing alias. It's a rebuild of an old `create_ssh_keypair.sh`,
|
||||||
fixing that script's macOS-only `sed`, key-deletion, and unquoted-append bugs.
|
fixing that script's macOS-only `sed`, key-deletion, and unquoted-append bugs.
|
||||||
|
|
||||||
5. **In-app SFTP file browser.** Pressing `t` on a host opens a dual-pane
|
|
||||||
browser (local left, remote right) that transfers files without leaving dial:
|
|
||||||
- Side-by-side panes, `tab` to switch, arrow/`jk` to move, `enter` to descend,
|
|
||||||
`backspace` up, `space` to batch-select.
|
|
||||||
- Directional transfers: `→`/`p` push local→remote, `←`/`g` pull remote→local,
|
|
||||||
into the other pane's current directory.
|
|
||||||
- Recursive directory transfers, aggregate progress bar, and overwrite prompts
|
|
||||||
(`[o]verwrite`/`[s]kip`/`[r]ename`, uppercase = apply to all remaining).
|
|
||||||
- **Same-session resume**: an interrupted transfer retried in the same session
|
|
||||||
resumes partial files instead of restarting.
|
|
||||||
- Connects over SFTP in-process (`pkg/sftp` + `golang.org/x/crypto/ssh`),
|
|
||||||
reusing the host's real `~/.ssh/config` (resolved via `ssh -G`), including
|
|
||||||
ProxyJump, authenticating via ssh-agent or unencrypted key files.
|
|
||||||
- When the YubiKey gate is enabled it re-challenges before opening the browser.
|
|
||||||
|
|
||||||
## Architecture (packages)
|
## Architecture (packages)
|
||||||
|
|
||||||
Existing v1 packages: `sshconf` (read-only config parser), `store`
|
Existing v1 packages: `sshconf` (read-only config parser), `store`
|
||||||
@@ -63,87 +48,50 @@ Existing v1 packages: `sshconf` (read-only config parser), `store`
|
|||||||
|
|
||||||
- `internal/keygen` — native key generation (crypto + `x/crypto/ssh`), key-file
|
- `internal/keygen` — native key generation (crypto + `x/crypto/ssh`), key-file
|
||||||
writing with correct perms, and safe `~/.ssh/config` appends.
|
writing with correct perms, and safe `~/.ssh/config` appends.
|
||||||
- `internal/xfer` — the transfer engine. A small `fs` abstraction lets push and
|
|
||||||
pull share one recursive copy routine; handles progress, conflict policy, and
|
|
||||||
resume. **Fully unit-tested against an in-memory SFTP server** (no sshd needed).
|
|
||||||
- `internal/sshx` — the connection layer. Resolves effective config via
|
|
||||||
`ssh -G <alias>`, builds the `ssh.ClientConfig` (agent + key auth, known_hosts
|
|
||||||
verification), and dials directly or through a ProxyJump chain.
|
|
||||||
- `internal/browser` — the dual-pane Bubble Tea file browser, using `xfer` for
|
|
||||||
transfers and coordinating the overwrite prompt across goroutines.
|
|
||||||
|
|
||||||
The picker's `t` key returns an `ActionSFTP` result; `main` then (re-)runs the
|
Host *selection* stayed decoupled from the *action*, which keeps room to add new
|
||||||
gate, connects via `sshx`, opens an `sftp.Client`, and runs the browser. The
|
per-host actions (e.g. the deferred SFTP browser) without disturbing the picker.
|
||||||
default flow is now a loop: pick → connect (exec, ends) or sftp (returns to the
|
|
||||||
picker). Host *selection* stayed decoupled from the *action*, which is what let
|
|
||||||
SFTP reuse the picker cleanly.
|
|
||||||
|
|
||||||
## Current state
|
## Current state
|
||||||
|
|
||||||
- **Complete and green.** All packages build; `go vet` clean; unit tests pass
|
- **Complete and green.** All packages build; `go vet` clean; unit tests pass
|
||||||
for `sshconf`, `store`, `yubikey`, `picker`, `keygen`, `xfer`, `sshx`.
|
for `sshconf`, `store`, `yubikey`, `picker`, `keygen`.
|
||||||
- **Cross-compiles** to all five targets (macOS arm64/amd64, Linux amd64/arm64,
|
- **Cross-compiles** to all five targets (macOS arm64/amd64, Linux amd64/arm64,
|
||||||
Windows amd64). Binary grew from ~3.5 MB to ~6.1 MB — the cost of the
|
Windows amd64).
|
||||||
in-process SFTP stack (`x/crypto/ssh` + `pkg/sftp`), a deliberate trade chosen
|
- **Live-verified via tmux**: keygen verified end-to-end (valid ed25519, correct
|
||||||
over shelling out to the system `sftp`.
|
perms, config append with backup); picker navigation, sort cycling, tag
|
||||||
- **Live-verified via tmux** (against an in-memory SFTP server): dual-pane
|
grouping, and favourites toggling all exercised.
|
||||||
render, navigation, batch selection, single-file push, recursive directory
|
|
||||||
push (contents verified on the far side), and the overwrite-conflict prompt
|
|
||||||
(goroutine↔UI coordination). keygen verified end-to-end (valid ed25519,
|
|
||||||
correct perms, config append with backup).
|
|
||||||
|
|
||||||
## Known limitations / not yet exercised
|
## Known limitations / not yet exercised
|
||||||
|
|
||||||
- **The SFTP connection layer is now live-verified against a real sshd.**
|
- **YubiKey gate is hardware-untested** — the challenge-response and recovery-code
|
||||||
Against a test host (key-based auth, no agent, unencrypted key loaded directly
|
logic have unit coverage, but the gate has not been exercised against a
|
||||||
from `IdentityFile`): `sshx.Connect` + SFTP listing, an SFTP write/read/remove
|
physical YubiKey + `ykman` on real hardware.
|
||||||
round-trip, and a full-TUI pull *and* push both passed with byte-for-byte
|
|
||||||
sha256 matches. An env-gated live test (`DIAL_LIVE_HOST=<host> go test
|
|
||||||
./internal/sshx -run TestLiveConnect`) is kept for re-running this. Still
|
|
||||||
unverified against a real host: **ProxyJump chaining** and **agent-backed /
|
|
||||||
encrypted-key auth** (the test host used a plain key file, no agent). (Same
|
|
||||||
hardware-untested posture remains for the YubiKey gate.)
|
|
||||||
- **ssh-agent on Windows** uses named pipes, not the `SSH_AUTH_SOCK` unix socket
|
|
||||||
the code dials; it compiles, but agent auth on Windows likely needs follow-up.
|
|
||||||
- **Encrypted key files** are skipped by the SFTP auth (it relies on the agent
|
|
||||||
for those) — dial never prompts for a key passphrase when connecting.
|
|
||||||
- **No mid-transfer cancel** — resume covers interruptions, but there's no
|
|
||||||
in-flight cancel key; a stuck transfer would need Ctrl-C out of the browser.
|
|
||||||
- **known_hosts is verify-only** — an unknown host errors out ("ssh to it once
|
|
||||||
first") rather than offering trust-on-first-use; no known_hosts management.
|
|
||||||
- Still unsigned binaries (Gatekeeper/SmartScreen warnings); no CI/releases.
|
- Still unsigned binaries (Gatekeeper/SmartScreen warnings); no CI/releases.
|
||||||
|
|
||||||
## Deferred (explicitly out of this pass)
|
## Deferred (explicitly out of this pass)
|
||||||
|
|
||||||
Idle-timeout gate option; crash-persistent resume (surviving a dial restart);
|
- **In-app SFTP file browser.** Planned for v2 (see `dial-v2-plan.md` §3) — a
|
||||||
distribution/signing/package managers; multi-select/batch actions *across hosts*
|
dual-pane push/pull browser opened with `t`, built on an in-process SFTP stack
|
||||||
(distinct from the in-browser batch file selection, which shipped); per-host
|
(`pkg/sftp` + `golang.org/x/crypto/ssh`). **Not built.** No `t` action, no
|
||||||
notes; fuzzy search.
|
transfer/connection packages, and `pkg/sftp` is not a dependency. This is the
|
||||||
|
largest deferred item and would be the natural centrepiece of a v3.
|
||||||
|
- Idle-timeout gate option; distribution/signing/package managers; multi-select
|
||||||
|
actions across hosts; per-host notes; fuzzy search.
|
||||||
|
|
||||||
## Open questions & directions to brainstorm
|
## Open questions & directions to brainstorm
|
||||||
|
|
||||||
1. **Real-host validation plan for SFTP** — what's the cheapest way to smoke-test
|
1. **SFTP browser — build it next?** It was the headline v2 plan but slipped.
|
||||||
the connection layer (a throwaway VM? a container running sshd? a known host)?
|
Worth scoping as the v3 centrepiece, and if so, what's the cheapest way to
|
||||||
2. **Windows agent support** — is Windows a real target for the SFTP browser, or
|
smoke-test the connection layer against a real sshd (a throwaway VM? a
|
||||||
is it macOS/Linux-first? Determines whether named-pipe agent support is worth
|
container running sshd? a known host)?
|
||||||
building.
|
2. **Grouping refinements** — should multi-tagged hosts optionally appear under
|
||||||
3. **Cancel & crash-persistent resume** — how often do transfers of very large
|
|
||||||
files over flaky links actually happen? That's the trigger for investing in
|
|
||||||
disk-persisted transfer state.
|
|
||||||
4. **known_hosts UX** — is verify-only acceptable, or should the browser offer to
|
|
||||||
record a new host key (with a clear prompt)?
|
|
||||||
5. **Grouping refinements** — should multi-tagged hosts optionally appear under
|
|
||||||
*every* tag? Should there be a per-tag filter shortcut or colour legend?
|
*every* tag? Should there be a per-tag filter shortcut or colour legend?
|
||||||
6. **Distribution** — Homebrew tap / Scoop / `install.sh` / signed releases via
|
3. **Distribution** — Homebrew tap / Scoop / `install.sh` / signed releases via
|
||||||
CI: worth doing now that the feature set is broad?
|
CI: worth doing now that the feature set is broad?
|
||||||
7. **SFTP polish** — a file preview? delete/rename/mkdir on either side? a "sync
|
|
||||||
this directory" one-shot? These edge toward a full file manager — where's the
|
|
||||||
line for a tool that's fundamentally a host picker?
|
|
||||||
|
|
||||||
## How to build / try
|
## How to build / try
|
||||||
|
|
||||||
- Build: `make build` (native) or `make dist` (all platforms). Requires Go 1.23+.
|
- Build: `make build` (native) or `make dist` (all platforms). Requires Go 1.25+.
|
||||||
- Keys: `dial keygen` (no external deps).
|
- Keys: `dial keygen` (no external deps).
|
||||||
- SFTP browser: `dial`, pick a host, press `t` (needs the host in known_hosts and
|
|
||||||
agent/key auth working — i.e. a host you can already `ssh` to).
|
|
||||||
- YubiKey gate: `dial yubikey enable` (requires Yubico's `ykman`).
|
- YubiKey gate: `dial yubikey enable` (requires Yubico's `ykman`).
|
||||||
|
|||||||
Reference in New Issue
Block a user