diff --git a/README.md b/README.md index c7278f3..d838ee5 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ # XS ![last build status](https://bacillus.blitter.com/onPush-xs-build/lastStatusIcon) +![terminal screenshot on MSYS64](https://blitter.com/~russtopia/files/xs-msys64.png) -- XS (**X**perimental **S**hell) is a simple alternative to ssh (<5% total SLOCC) written from scratch in Go. @@ -210,6 +211,12 @@ If no leading / is specified in src-or-dest-path, it is assumed to be relative t remote user. File operations are all performed as the remote user, so account permissions apply as expected. +When running under MSYS2, one must set the MINGW_ROOT environment variable to assist in +determining how to convert Windows paths to UNIX-style paths. This should be the installation path +of one's MSYS2 environment (eg., _C:/msys2_). Go's stdlib, under the hood, still uses Windows +style paths (drive letters and all) to locate other executables and _xc_ uses _tar_ as part of the copy +functionality. + Local (client) to remote (server) copy: ``` $ xc fileA /some/where/fileB /some/where/else/dirC joebloggs@host-or-ip:remoteDir diff --git a/auth.go b/auth.go index f10ecf8..f5bb1f0 100644 --- a/auth.go +++ b/auth.go @@ -215,18 +215,39 @@ func AuthUserByToken(ctx *AuthCtx, username string, connhostname string, auth st return } +func GroomFsPath(path string) (ret string) { + pathRoot := os.Getenv("MINGW_ROOT") + if pathRoot != "" { + ret = path[len(pathRoot):] + ret = strings.ReplaceAll(ret, "\\", "/") + } else { + ret = path + } + //fmt.Printf("groomed fspath:%v\n", ret) + return +} + func GetTool(tool string) (ret string) { - ret = "/bin/" + tool + cmdSuffix := "" + pathRoot := os.Getenv("MINGW_ROOT") + + if pathRoot != "" { + cmdSuffix = ".exe" + } + + //fmt.Printf("pathRoot:%v cmdSuffix:%v\n", pathRoot, cmdSuffix) + + ret = pathRoot + "/bin/" + tool + cmdSuffix _, err := os.Stat(ret) if err == nil { return ret } - ret = "/usr/bin/" + tool + ret = pathRoot + "/usr/bin/" + tool + cmdSuffix _, err = os.Stat(ret) if err == nil { return ret } - ret = "/usr/local/bin/" + tool + ret = pathRoot + "/usr/local/bin/" + tool + cmdSuffix _, err = os.Stat(ret) if err == nil { return ret diff --git a/bacillus/ci_pushbuild.sh b/bacillus/ci_pushbuild.sh index 8cfe45f..aa4bc7e 100755 --- a/bacillus/ci_pushbuild.sh +++ b/bacillus/ci_pushbuild.sh @@ -25,8 +25,15 @@ echo "Building most recent push on branch $branch" git checkout "$branch" ls -go mod init -go mod tidy +#!############ +#!stage "GoMod" +#!############ +#!go clean -modcache +#! +#!rm -f go.{mod,sum} +#!go mod init blitter.com/go/xs +#!go mod tidy +#!echo "---" ############ stage "Build" @@ -35,16 +42,19 @@ echo "Invoking 'make clean' ..." make clean echo "Invoking 'make all' ..." make all +echo "---" ############ stage "Lint" ############ make lint +echo "---" ############ stage "UnitTests" ############ go test -v . +echo "---" ############ stage "Test(Authtoken)" @@ -64,6 +74,7 @@ else echo "client cmd performed OK." unset tokentest fi +echo "---" ############ stage "Test(xc S->C)" @@ -88,6 +99,7 @@ else echo "FAILED!" exit $stat fi +echo "---" ############ stage "Test(xc C->S)" @@ -98,12 +110,14 @@ if [ -f ~/.config/xs/.xs_id.bak ]; then echo "Restoring test user $USER .xs_id file ..." mv ~/.config/xs/.xs_id.bak ~/.config/xs/.xs_id fi +echo "---" ############ stage "Artifacts" ############ echo -n "Creating tarfile ..." tar -cz --exclude=.git --exclude=cptest -f ${BACILLUS_ARTFDIR}/xs.tgz . +echo "---" ############ stage "Cleanup" diff --git a/go.mod b/go.mod index 60cff15..85f6613 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module blitter.com/go/xs -go 1.25.3 +go 1.24.0 + +toolchain go1.24.11 require ( blitter.com/go/cryptmt v1.0.3 diff --git a/xs/xs.go b/xs/xs.go index 9b5a658..e882a20 100755 --- a/xs/xs.go +++ b/xs/xs.go @@ -294,7 +294,14 @@ func buildCmdLocalToRemote(copyQuiet bool, copyLimitBPS uint, files string) (cap captureStderr = true cmd = xs.GetTool("tar") - args = []string{"-cz", "-f", "/dev/stdout"} + //fmt.Printf("GetTool found cmd:%v\n", cmd) + /* Explicit -f /dev/stdout doesn't work in MINGW/MSYS64 + * as '/dev/stdout' doesn't actually appear in the /dev/ filesystem...? + * And it appears not to actually be required as without -f stdout is + * implied. -rlm 2025-12-07 + */ + //args = []string{"-cz", "-f", "/dev/stdout"} + args = []string{"-cz"} files = strings.TrimSpace(files) // Awesome fact: tar actually can take multiple -C args, and // changes to the dest dir *as it sees each one*. This enables @@ -310,6 +317,7 @@ func buildCmdLocalToRemote(copyQuiet bool, copyLimitBPS uint, files string) (cap // remote destDir. for _, v := range strings.Split(files, " ") { v, _ = filepath.Abs(v) // #nosec + v = xs.GroomFsPath(v) dirTmp, fileTmp := path.Split(v) if dirTmp == "" { args = append(args, fileTmp) @@ -322,7 +330,8 @@ func buildCmdLocalToRemote(copyQuiet bool, copyLimitBPS uint, files string) (cap bandwidthInBytesPerSec := " -L " + fmt.Sprintf("%d", copyLimitBPS) displayOpts := " -pre " //nolint:goconst,nolintlint cmd = xs.GetTool("bash") - args = []string{"-c", xs.GetTool("tar") + " -cz -f /dev/stdout "} + //args = []string{"-c", xs.GetTool("tar") + " -cz -f /dev/stdout "} + args = []string{"-c", xs.GetTool("tar") + " -cz "} files = strings.TrimSpace(files) // Awesome fact: tar actually can take multiple -C args, and // changes to the dest dir *as it sees each one*. This enables @@ -338,6 +347,7 @@ func buildCmdLocalToRemote(copyQuiet bool, copyLimitBPS uint, files string) (cap // remote destDir. for _, v := range strings.Split(files, " ") { v, _ = filepath.Abs(v) // #nosec + v = xs.GroomFsPath(v) dirTmp, fileTmp := path.Split(v) if dirTmp == "" { args[1] = args[1] + fileTmp + " " @@ -386,6 +396,8 @@ func doCopyMode(conn *xsnet.Conn, remoteDest bool, files string, copyQuiet bool, c.Stderr = os.Stderr } + //fmt.Printf("cmd:%v args:%v\n", cmdName, cmdArgs) + // Start the command (no pty) err = c.Start() // returns immediately ///////////// @@ -614,6 +626,12 @@ func parseNonSwitchArgs(a []string) (user, host, path string, isDest bool, other // Whether fancyArg is src or dst file depends on flag.Args() index; // fancyArg as last flag.Args() element denotes dstFile // fancyArg as not-last flag.Args() element denotes srcFile + + /* rlm:2025-12-10 This breaks if srcPath is outside of MSYS2 tree, as srcPath + appears to silently be converted to an absolute winpath eg., + /c/users/RM/... -> C:/users/RM/... + and the colon (:) in this breaks the logic below. + */ var fancyUser, fancyHost, fancyPath string for i, arg := range a { if strings.Contains(arg, ":") || strings.Contains(arg, "@") {