Repository navigation
V2? Indeed #597
Description
I’ve finally gotten into the muck and got the work done to push out a v2 API.
This is right now just a preview, so we can look at things in a potentially breaking change manner, to see where we need to improve things, and invite discussion on the design.
The client copies much closer the os package, particularly with the OpenFile call, and reduces a lot of the repetitious code. It also uses generics, so we’re setting a new minimum version that can use our package. 🙈
The server on the other side is mimicking the gRPC framing for general instructions, but still remains the sort of os.File mimickry for Files. It does split Files and Directories, even though I’m not sure any implementation actually truly cares about this. But the design should allow one to force this distinction, if they want to make such a strict implementation. It is right now, strictly single threaded, but designed with an eye for how things could be spun off into concurrent tasks.
A local filesystem server is implemented as a subpackage, which is also useful as a demonstration of how to implement the new server interface. This removes the need for an in-memory filesystem, but that’s also something we could add back in, if there is significant demand.
Also, I took the time to read up every single supported Statfs implementation and figure out how that converts to that OS’s Statvfs. 🙃 That was fun. Also, we’re pulling in as much of the Stat_t as possible, which requires a pretty interesting array of OS-specific packages due to subtle naming differences.
Uh… yeah. Pile the bug tickets on, I want to know where I missed the mark, or messed up the implementation.
PS: Oh, I also want to add that all this work was possible because of the incredible work the people ahead of me did with this package. While I might not keep much of any of the original code, it taught me what works, and what was more kludgy than it was worth. So many decisions are made when we cannot know the ramifications later, like when the language puts out its own fs abstraction, and now we’re holding onto a deprecated design. But knowing now to not make those decisions now is worth more than gold! And I probably would have made the same choice, because it looked right at the time. We just cannot see the future. 🤷♀️
Activity
christhomas commentedon Oct 17, 2026
I've spent the last few days working through the v2 code (#683, #684, #685), and wanted to put together a picture of where the API stands and what still seems to be missing for a v2.0.0, as a starting point for discussion. It tries to follow the direction already in dev-v2 and in #407: model the client on
osandio/fs, keep the server gRPC-shaped with optional extension interfaces, keep the protocol inencoding/ssh/filexfer, and keep the public surface small. None of it is meant as final.Where it stands
Already in dev-v2 (or #649):
encoding/ssh/filexferandopensshas the wire protocol packages, which Proposal: io/fs presents us an opportunity to rework the whole api into a v2 #407 suggested building first.os:OpenFile(name, flag, perm),Mkdir(name, perm),ReadDirreturning[]fs.DirEntry, aDirtype withReadDir(n), errors as*fs.PathError/*fs.LinkErroraround ansshfx.StatuswhoseIsmatchesfs.ErrNotExist,fs.ErrPermissionandio.EOF(greatroar's suggestion in Proposal: io/fs presents us an opportunity to rework the whole api into a v2 #407),DirFS(dir) fs.FS, and a genericNewClientover smallSSHClient/SSHSessioninterfaces, so the package doesn't import x/crypto.ServerHandler,UnimplementedServerHandlerto embed, extension interfaces (HardlinkServerHandler,POSIXRenameServerHandler,StatVFSServerHandler),FileHandler/DirHandlerwith optionalSetStat/Truncate/Chmod/Chown/Chtimes/StatVFSinterfaces,Hijackfor raw packets, andlocalfsas a separate package.Client
Walk,GlobandMatch. In v2,fs.WalkDir(cl.DirFS("/"), …)andfs.Globalready work, but only for paths below theDirFSroot, so paths relative to the login directory (what v1'sWalk("dir")does) need a wrapper. Twofilepath-style helpers would cover it:func (cl *Client) WalkDir(root string, fn fs.WalkDirFunc) errorandfunc (cl *Client) Glob(pattern string) ([]string, error). Or we just document theDirFSroute and leave them out.ReadDirContextandReaddirContexttake a context today, though everything is context-aware internally. Either add…Contextvariants for all the request methods (theDialContextpattern), or only for the ones that can take a long time:ReadDir,RemoveAll,ReadFile/WriteFile, andWalkDirif it's added. I'd lean towards the second, to keep the surface small.NewClientalways requests thesftpsubsystem.WithSubsystem(name string)andWithCommand(cmd string)options would cover Execute SUDO prior to transfer #12 (sudo /usr/lib/openssh/sftp-server, the same assftp -s).WithCommandneedsStart(cmd string) errorinSSHSession, which*ssh.Sessionalready has.Lchmod,LchownandLchtimesthroughlsetstat@openssh.com([v2] add client and server support for lsetstat@openssh.com #684).limits@openssh.com(lets the client size requests to what the server accepts instead of assuming 32 KiB),expand-path@openssh.com(~expansion),copy-data(server-side copy),home-directoryandusers-groups-by-id@openssh.com(names forls -lstyle listings). Each one is small and independent.Server
fs.FS. A read-onlyfunc FSHandler(fsys fs.FS) ServerHandlerwould make servingos.DirFS, anembed.FSor atesting/fstest.MapFSa one-liner. It also covers most of what v1's in-memory handler was used for in tests, without bringing a memfs back into the package.os.Root. dev-v2 already requires go1.25, solocalfscould resolve paths through anos.Rootopened on a configured directory. The OS then keeps every operation inside that directory, including through symlinks that point outside it, whichWorkDirandpath.Joincan't do. That would give the chroot use case drakkan mentioned in Proposal: io/fs presents us an opportunity to rework the whole api into a v2 #407 without extra code in the server, and makelocalfsa safer starting point for people who copy it.Debug io.Writercould become a*slog.Logger, so it fits whatever logging the application already uses.Testing support
An
sftptestpackage along the lines ofhttptest(#274):With
FSHandlerandfstest.MapFS, a test against a fake remote filesystem would be about three lines. A second helper that starts a real SSH server on 127.0.0.1 with a generated host key would cover code that takes an*ssh.Client, but it needs x/crypto, so it would have to live in its own module, likeexamplesdoes.A possible order
If this looks like the right direction, each of these could be its own small issue and PR, which would also suit the project board idea from #648:
FSHandler, thensftptest.NewClienton top of itWithSubsystem/WithCommandlocalfsonos.RootWalkDir/Glob, or documenting theDirFSrouteI'm happy to take on any of these. Let me know which you'd want, and what you'd change.
puellanivis commentedon Oct 19, 2026
Client
Walk,Glob,Match...Contextsuffix.WithSubsystemandWithCommandare reasonable features, but perhaps after release ofv2.0.0properlsetstatalreadfy done and merged, (I think.) So, obviously, I agree with those.x/cryptoit would be unfortunate to return it… while we could likely also just expand theSSH...interfaces, it doesn’t present something that people can see how it works. And they’ll probably want to add their own logging on that.Server
FSHandlerisn’t a bad idea.*os.Rootfor handling theWorkDirsftp-serveris also single-threaded. The safest way to ensure compatibility with expectations, and coherence are to stay single-threaded. But yeah, like I said, this is something we should be able to do later. Probably through a mux of receivers based on filename/handle. 🤔 Better to leave for afterv2.0.0release.slog.Loggeris also a good idea.Intended path:
I want to ensure that we’re basically feature complete enough that we can port
v1to usingv2under the hood. And that we have addressed security, stability concerns, and API design choices. We need to consider that there are things that can be implemented after a proper release. Features and nice-to-haves are great, but we don’t need to block release ofv2.0.0behind implementing additional OpenSSH extensions.So, I will have main interests in focusing on reviewing and developing release blockers, rather than extra features.
christhomas commentedon Oct 19, 2026
Well, I'll see what I can do and you can tell me what you like or don't like and we'll go from there, I'll try to make the PRs not so crazy :P
puellanivis commentedon Oct 19, 2026
You’re doing great so far!