Initial commit: haul — desktop SFTP client
A two-pane local/remote file browser over SSH, split out of dial so file transfer lives in its own tool. The two share ~/.ssh/config as their base. - internal/sshconf: ~/.ssh/config parsing, for the saved-host list. - internal/sshx: dialling and auth — agent, keys, password, host-key verification, ProxyJump. - internal/vfs: the FS interface both sides are driven through, implemented over the local disk and over SFTP. - internal/xfer: recursive copy engine with progress reporting, cancellation, conflict policy, and resume of partial transfers. - internal/ui: the Fyne GUI — connect window (saved hosts plus quick connect) and the two-pane browser session. Build with `make build`; the Makefile locates the Go toolchain at ~/.local/go, which is deliberately kept off PATH. Cross-compiling needs a per-target C toolchain because Fyne is cgo, so `make dist` builds the native binary only — the alternatives are documented in the Makefile. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
package sshx
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseSSHG(t *testing.T) {
|
||||
// Representative `ssh -G` output (abridged, real keys/casing).
|
||||
out := `host web1
|
||||
hostname 10.0.0.5
|
||||
user deploy
|
||||
port 2222
|
||||
proxyjump bastion
|
||||
identityfile ~/.ssh/id_ed25519
|
||||
identityfile ~/.ssh/id_rsa
|
||||
userknownhostsfile ~/.ssh/known_hosts ~/.ssh/known_hosts2
|
||||
stricthostkeychecking accept-new
|
||||
identityagent SSH_AUTH_SOCK
|
||||
`
|
||||
p := parseSSHG("web1", out)
|
||||
if p.HostName != "10.0.0.5" {
|
||||
t.Errorf("hostname = %q", p.HostName)
|
||||
}
|
||||
if p.User != "deploy" || p.Port != "2222" {
|
||||
t.Errorf("user/port = %q/%q", p.User, p.Port)
|
||||
}
|
||||
if p.ProxyJump != "bastion" {
|
||||
t.Errorf("proxyjump = %q", p.ProxyJump)
|
||||
}
|
||||
if len(p.IdentityFiles) != 2 {
|
||||
t.Errorf("identityfiles = %v", p.IdentityFiles)
|
||||
}
|
||||
if len(p.KnownHostsFile) != 2 {
|
||||
t.Errorf("knownhosts = %v", p.KnownHostsFile)
|
||||
}
|
||||
if p.StrictHostKey != "accept-new" {
|
||||
t.Errorf("stricthostkey = %q", p.StrictHostKey)
|
||||
}
|
||||
if p.Addr() != "10.0.0.5:2222" {
|
||||
t.Errorf("addr = %q", p.Addr())
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSSHGIdentitiesOnly(t *testing.T) {
|
||||
p := parseSSHG("h", "hostname h\nidentitiesonly yes\nidentityfile ~/.ssh/special\n")
|
||||
if !p.IdentitiesOnly {
|
||||
t.Error("identitiesonly yes should parse true")
|
||||
}
|
||||
q := parseSSHG("h", "hostname h\nidentitiesonly no\n")
|
||||
if q.IdentitiesOnly {
|
||||
t.Error("identitiesonly no should parse false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSSHGProxyJumpNone(t *testing.T) {
|
||||
p := parseSSHG("h", "hostname h.example.com\nproxyjump none\nport 22\n")
|
||||
if p.ProxyJump != "" {
|
||||
t.Errorf("proxyjump 'none' should be empty, got %q", p.ProxyJump)
|
||||
}
|
||||
if p.Addr() != "h.example.com:22" {
|
||||
t.Errorf("addr = %q", p.Addr())
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSSHGFallbackHostname(t *testing.T) {
|
||||
p := parseSSHG("myhost", "user bob\n")
|
||||
if p.HostName != "myhost" {
|
||||
t.Errorf("hostname should fall back to alias, got %q", p.HostName)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user