bsncubed 47ad62bb84 docs: add a proper Windows build section
Windows previously got one line pointing vaguely at MSYS2, while every other
platform got real commands — and the README's main instruction is `make build`,
which stock Windows cannot run at all.

Covers the three ways Windows actually differs: cgo needs a GCC-style compiler
(MSVC will not do), there is no make, and a GUI build needs -H=windowsgui or
Windows opens a console window behind the app on every launch. The Makefile
does not pass that flag since it targets Unix, so the section gives the direct
go build command and names the .exe output explicitly.

Also notes that Windows, unlike Linux, needs no graphics dev packages because
OpenGL ships with the OS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 20:22:30 +10:00

haul

A desktop SFTP client — a two-pane local/remote file browser over SSH, in the spirit of WinSCP but quite a bit simpler. haul reads your ~/.ssh/config for its host list, or connects ad-hoc to anything that isn't in it.

  • Native desktop GUI, not a terminal app — built on Fyne, so it's a single compiled binary with no runtime.
  • Two panes: local disk on the left, the remote host on the right, with the same navigation, marking, and file operations on both sides.
  • Your existing SSH setup just works — saved hosts come from ~/.ssh/config (Include directives followed), and connecting resolves the effective config through ssh -G, so ProxyJump chains, IdentityFile, User, and Port behave exactly as the real ssh client would.
  • Auth the usual ways: ssh-agent, identity files (with passphrase prompt), password, and keyboard-interactive.
  • Host keys are verified, not blindly accepted. An unknown key is shown with its SHA256 fingerprint for confirmation before it's recorded in known_hosts.
  • Recursive transfers in both directions with aggregate progress, a stop button, per-file conflict prompts (overwrite / skip / rename, with "apply to all"), and resume of partial files.
  • Never blocks on the network — every SFTP call runs off the UI thread, so listing a slow directory doesn't freeze the window.
  • High-contrast dark theme with tightened padding, so more rows fit on screen.

haul was split out of dial (an interactive SSH host picker) so that file transfer lives in its own tool. The two share ~/.ssh/config as their common base and are otherwise independent.

Requirements

To run haul:

Needs Why
The system ssh client Saved-host connections call ssh -G <alias> to resolve the effective config, so ssh_config semantics are never re-implemented
A graphical desktop haul is a GUI application; it needs a display server
An SFTP-capable server Transfers use the SFTP subsystem — see Scope

Building

haul is written in Go and uses cgo (Fyne renders through OpenGL), so a C compiler and the platform's graphics/windowing headers are needed as well as the Go toolchain.

Prerequisites

There are two prerequisites — the Go toolchain, and a C toolchain for cgo. Both are needed on every platform, macOS included.

1. Go 1.24 or newer. Verify with go version; if that says command not found, install it:

# macOS
brew install go

# Debian / Ubuntu
sudo apt install golang-go

# Fedora
sudo dnf install golang

# Arch
sudo pacman -S go

Or download it from go.dev/dl. If your distro's package is older than 1.24, use the tarball from there instead.

2. A C toolchain and the platform's graphics headers, because Fyne renders through OpenGL:

# macOS — the Xcode command line tools
xcode-select --install

# Debian / Ubuntu
sudo apt install gcc libgl1-mesa-dev xorg-dev

# Fedora
sudo dnf install gcc libX11-devel libXcursor-devel libXrandr-devel \
                 libXinerama-devel mesa-libGL-devel libXi-devel libXxf86vm-devel

# Arch
sudo pacman -S gcc libx11 libxcursor libxrandr libxinerama mesa libxi

Windows needs a MinGW-w64 toolchain and has no make — it gets its own section below.

On macOS the command line tools cover the C side entirely — there is no Homebrew formula to add for graphics. That makes it easy to run xcode-select --install, see "already installed", and assume you're ready when Go itself is still missing. Both steps are required.

Build it

make build          # -> bin/haul

That's the whole thing. Run it with ./bin/haul, or install it onto your PATH:

make install        # -> $GOBIN, or $GOPATH/bin (usually ~/go/bin)

Make sure that directory is on PATH:

echo 'export PATH="$(go env GOPATH)/bin:$PATH"' >> ~/.bashrc   # or ~/.zshrc

Or just copy bin/haul wherever you like:

install -D -m 0755 bin/haul ~/.local/bin/haul

Expect the first build to take a few minutes. Compiling Fyne's cgo dependencies from scratch is the slow part; afterwards Go's build cache makes rebuilds close to instant.

Without make, the equivalent is:

go build -trimpath -ldflags "-s -w" -o bin/haul ./cmd/haul

Building on Windows

Windows differs from the instructions above in three ways: cgo needs a MinGW-w64 compiler, there is no make, and a GUI build needs an extra linker flag to suppress the console window. All three are covered here.

1. Install Go. Either

winget install GoLang.Go

or the .msi from go.dev/dl, which puts Go on PATH for you. Open a new terminal afterwards, then check with go version.

2. Install a C compiler. Go's cgo needs a GCC-style compiler — MSVC/cl.exe will not work. MSYS2 is the usual source:

winget install MSYS2.MSYS2

Then open the "MSYS2 UCRT64" shell from the Start menu and install GCC:

pacman -S mingw-w64-ucrt-x86_64-gcc

Add its bin directory to your Windows PATH so the normal Go toolchain can find it — in PowerShell, as your user:

[Environment]::SetEnvironmentVariable("Path",
  [Environment]::GetEnvironmentVariable("Path","User") + ";C:\msys64\ucrt64\bin",
  "User")

Open a new terminal and confirm both are visible:

go version
gcc --version

3. Build. Windows has no make, so invoke the compiler directly from PowerShell in the repo root:

go build -trimpath -ldflags "-s -w -H=windowsgui" -o bin\haul.exe .\cmd\haul

Then run .\bin\haul.exe.

Two details in that command matter:

  • -H=windowsgui marks the binary as a GUI application. Without it Windows opens a console window behind haul every time it launches. The Makefile does not pass this, because it targets Unix — so on Windows prefer the command above even if you do have make.
  • -o bin\haul.exe names the output explicitly. Windows needs the .exe extension to execute a file.

If you would rather use the Makefile, install make inside the MSYS2 shell (pacman -S make) and build from there — but you will still want to add -H=windowsgui yourself, for example make build LDFLAGS="-s -w -H=windowsgui".

Unlike Linux, Windows needs no graphics development packages: OpenGL is part of the operating system.

Make targets

Target Does
make build Native binary into ./bin/haul
make install Build and install into $GOBIN / $GOPATH/bin
make test Run the test suite
make vet go vet ./...
make check Typecheck every package without needing the cgo toolchain
make run Build, then run (needs a DISPLAY)
make run-xvfb Build, then run against a virtual X display
make dist Release binary into ./dist
make clean Remove bin/ and dist/

The Makefile picks up a Go toolchain at ~/.local/go/bin/go if one is there, falling back to whatever go is on PATH — so it works on machines where Go is deliberately kept off PATH. Override it explicitly with make GO=/path/to/go build.

make check is the useful one when the C dependencies aren't installed: it builds for GOOS=js GOARCH=wasm, which typechecks the entire tree including all Fyne API usage without any cgo toolchain. It proves nothing about layout or runtime behaviour, but it catches every compile error.

make run-xvfb is for headless machines — it starts an Xvfb virtual display, runs haul against it with software GL, and tears the display down again when you quit (including on Ctrl-C). Requires xvfb installed.

Cross-compiling

This does not work the way it does for dial. dial is pure Go and cross-compiles to five platforms with nothing but GOOS/GOARCH. haul is cgo, so GOOS=windows go build will fail — each target needs a matching C toolchain:

Target Needs
linux/arm64 gcc-aarch64-linux-gnu
windows/amd64 gcc-mingw-w64
darwin/* a Mac, or osxcross

For that reason make dist builds only the native binary. The practical route to the others is fyne-cross, which does each build inside a Docker image carrying the right toolchain:

go install github.com/fyne-io/fyne-cross@latest
fyne-cross windows -arch=amd64

Usage

haul

Connecting

The connect window has two tabs:

  • Saved — every Host block from ~/.ssh/config, with a search box to filter them. Connecting resolves the alias through ssh -G, so it honours everything your config says about that host, ProxyJump chains included.
  • Quick connect — host, port, user, and optionally a password or a private key file, for anything not in your config. It doesn't touch ~/.ssh/config.

If the server's host key isn't in known_hosts, haul shows the SHA256 fingerprint and asks before recording it. Passphrase-protected keys and password prompts appear as dialogs during the connection attempt.

Browsing

Each pane has a path bar (type a path and press Enter to jump straight there), up / home / refresh buttons, and New folder, Rename, and Delete actions. In the file table:

Click Action
the left column mark / unmark a file for transfer
a directory name descend into it
.. go up one level
anywhere else move the highlight

Marks apply to the directory you made them in and are cleared when you navigate away, since they refer to names in the directory you left.

Transferring

Upload → copies from the local pane to the remote one, ← Download the other way. Either takes whatever is marked in the source pane, falling back to the highlighted row if nothing is marked, and writes into the other pane's current directory. Directories are copied recursively.

While a transfer runs, the progress bar shows aggregate bytes and file counts, and Stop cancels it. Completed files stay put and the interrupted file is left on disk — running the same transfer again resumes it from where it stopped, for as long as the session stays open.

If a destination file already existed beforehand, haul asks whether to overwrite, skip, or write under a non-colliding name, with an "apply to all" option so you're asked once rather than per file.

Closing the window or disconnecting mid-transfer asks for confirmation first.

Keyboard shortcuts

Key Action
Ctrl+R reload the active pane
Ctrl+Up go up a directory in the active pane
Ctrl+T transfer the active pane's selection to the other side

Ctrl+T uploads or downloads depending on which pane you last touched, so one key covers both directions.

Scope

haul speaks SFTP only. There is no SCP or TFTP support: SCP is a copy-stream protocol with no directory listing, stat, rename, or delete, and TFTP has neither listing nor authentication — neither can back a file browser without falling back to parsing shell command output. Servers whose sshd has no sftp-server subsystem are therefore out of reach.

Resume works within a session, because the engine tracks which destination files it created; restarting haul starts those files over.

~/.ssh/config is treated as read-only — haul never writes to it. The only file it writes outside a transfer is known_hosts, when you confirm a new host key.

S
Description
No description provided
Readme 103 MiB
0.1.0 Latest
2026-07-31 22:44:16 +10:00
Languages
Go 90.5%
Makefile 5.5%
Inno Setup 4%