Skip to content

Commit 4d08e7b

Browse files
committed
Initial commit
0 parents  commit 4d08e7b

File tree

16 files changed

+497
-0
lines changed

16 files changed

+497
-0
lines changed

.github/workflows/ci.yaml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Set the workflow name.
2+
name: CI
3+
4+
# Execute the workflow on pushes and pull requests.
5+
on: [push, pull_request]
6+
7+
jobs:
8+
linux:
9+
name: Linux
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v2
13+
- uses: actions/setup-go@v2
14+
with:
15+
go-version: '^1.17'
16+
- run: docker version
17+
- run: go version
18+
- run: ./build.sh
19+
- uses: actions/upload-artifact@v2
20+
with:
21+
name: binaries
22+
path: mutagen-ssh-wrapper.tar.gz
23+
versioning:
24+
name: Versioning
25+
runs-on: ubuntu-latest
26+
steps:
27+
- uses: actions/checkout@v2
28+
- uses: actions/setup-go@v2
29+
with:
30+
go-version: '^1.17'
31+
- run: go version
32+
- name: "Analyze version information and release status"
33+
id: analyze
34+
run: |
35+
# Determine whether or not this is a release build.
36+
RELEASE="${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}"
37+
# Determine version target information for Go. If this is a release,
38+
# then we'll use the tag, otherwise we'll use the raw commit identifier.
39+
if [ "${RELEASE}" = "true" ]; then
40+
TARGET="${GITHUB_REF#refs/tags/}"
41+
else
42+
TARGET="${GITHUB_SHA}"
43+
fi
44+
# Set outputs.
45+
echo ::set-output name=release::${RELEASE}
46+
echo ::set-output name=target::${TARGET}
47+
outputs:
48+
release: ${{ steps.analyze.outputs.release }}
49+
target: ${{ steps.analyze.outputs.target }}
50+
release:
51+
name: Release
52+
runs-on: ubuntu-latest
53+
needs: [linux, versioning]
54+
if: ${{ needs.versioning.outputs.release == 'true' }}
55+
steps:
56+
- uses: actions/download-artifact@v2
57+
with:
58+
name: binaries
59+
path: .
60+
- uses: softprops/action-gh-release@v1
61+
with:
62+
files: |
63+
mutagen-ssh-wrapper.tar.gz

.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Binaries for programs and plugins
2+
*.exe
3+
*.exe~
4+
*.dll
5+
*.so
6+
*.dylib
7+
8+
# Test binary, built with `go test -c`
9+
*.test
10+
11+
# Output of the go coverage tool, specifically when used with LiteIDE
12+
*.out
13+
14+
# Dependency directories (remove the comment below to include it)
15+
# vendor/
16+
17+
# IDE specific
18+
/.vscode
19+
20+
# Output dir
21+
/out
22+
23+
# Bundle
24+
*.tar.gz

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Duckly
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# mutagen-ssh-wrapper
2+
Mutagen SSH wrapper for Windows

build.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/sh
2+
set -e
3+
4+
GOOS=windows GOARCH=386 go build -o ./out/ ./cmd/ssh
5+
GOOS=windows GOARCH=386 go build -o ./out/ ./cmd/scp
6+
GOOS=windows GOARCH=386 go build -o ./out/ ./cmd/grun
7+
tar czf mutagen-ssh-wrapper.tar.gz -C ./out scp.exe ssh.exe grun.exe

cmd/grun/grun.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"strconv"
9+
10+
"golang.org/x/sync/errgroup"
11+
"ssh-wrapper/cmd"
12+
)
13+
14+
func main() {
15+
if len(os.Args) < 2 {
16+
if len(os.Args) == 1 {
17+
fmt.Fprintln(os.Stderr, "Error: missing arguments: parent PID and command")
18+
} else {
19+
fmt.Fprintln(os.Stderr, "Error: missing command argument")
20+
}
21+
os.Exit(1)
22+
}
23+
24+
commandName, args := os.Args[2], os.Args[3:]
25+
26+
parentPid, err := strconv.Atoi(os.Args[1])
27+
if err != nil {
28+
fmt.Fprintln(os.Stderr, "Error: parent PID is not a number:", parentPid)
29+
os.Exit(1)
30+
}
31+
32+
parentProcess, err := os.FindProcess(parentPid)
33+
if err != nil {
34+
fmt.Fprintf(os.Stderr, "Error: cannot find process PID %d: %v", parentPid, err)
35+
os.Exit(1)
36+
}
37+
38+
g, ctx := errgroup.WithContext(context.Background())
39+
40+
command := exec.CommandContext(ctx, commandName, args...)
41+
command.Stdin = os.Stdin
42+
command.Stdout = os.Stdout
43+
command.Stderr = os.Stderr
44+
45+
// Wait for the child process to finish
46+
g.Go(func() error {
47+
cmd.WrapRunAndExit(command)
48+
return nil
49+
})
50+
51+
// Wait for the parent process to finish. If so, kill the child process
52+
g.Go(func() error {
53+
_, err := parentProcess.Wait()
54+
command.Process.Kill()
55+
return err
56+
})
57+
58+
g.Wait()
59+
os.Exit(1)
60+
}

cmd/main.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"path"
9+
"path/filepath"
10+
"runtime"
11+
"strconv"
12+
"strings"
13+
"syscall"
14+
15+
"ssh-wrapper/pkg/process"
16+
)
17+
18+
const (
19+
MUTAGEN_SSH_CONFIG = "MUTAGEN_SSH_CONFIG"
20+
MUTAGEN_CONNECT_TIMEOUT_IN_SECONDS = 20
21+
)
22+
23+
func ProcessArgs() []string {
24+
sshConfig := os.Getenv(MUTAGEN_SSH_CONFIG)
25+
if sshConfig == "" {
26+
fmt.Fprintln(os.Stderr, "Error: MUTAGEN_SSH_CONFIG environment variable is not set")
27+
os.Exit(1)
28+
}
29+
30+
// Increase the default timeout
31+
args := os.Args[1:]
32+
for i, arg := range args {
33+
if strings.HasPrefix(arg, "-oConnectTimeout=") {
34+
args[i] = fmt.Sprintf("-oConnectTimeout=%d", MUTAGEN_CONNECT_TIMEOUT_IN_SECONDS)
35+
break
36+
}
37+
}
38+
39+
return append([]string{fmt.Sprintf("-F%s", sshConfig)}, args...)
40+
}
41+
42+
func WrapRunAndExit(command *exec.Cmd) {
43+
command.Stdin = os.Stdin
44+
command.Stdout = os.Stdout
45+
command.Stderr = os.Stderr
46+
47+
if err := command.Run(); err != nil {
48+
if exiterr, ok := err.(*exec.ExitError); ok {
49+
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
50+
os.Exit(status.ExitStatus())
51+
}
52+
}
53+
os.Exit(1)
54+
}
55+
os.Exit(0)
56+
}
57+
58+
func Grun(commandName string, args ...string) {
59+
args = append([]string{strconv.Itoa(os.Getpid()), commandName}, args...)
60+
61+
self, err := os.Executable()
62+
if err != nil {
63+
fmt.Fprintln(os.Stderr, "Error: cannot get current executable path")
64+
os.Exit(1)
65+
}
66+
67+
grun := path.Join(filepath.Dir(self), process.ExecutableName("grun", runtime.GOOS))
68+
command := exec.CommandContext(context.Background(), grun, args...)
69+
WrapRunAndExit(command)
70+
}

cmd/scp/scp.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"ssh-wrapper/pkg/ssh"
8+
"ssh-wrapper/cmd"
9+
)
10+
11+
func main() {
12+
args := cmd.ProcessArgs()
13+
14+
commandName, err := ssh.ScpCommandPath()
15+
if err != nil {
16+
fmt.Fprintln(os.Stderr, "Error: unable to set up SCP invocation:", err)
17+
os.Exit(1)
18+
}
19+
20+
cmd.Grun(commandName, args...)
21+
}

cmd/ssh/ssh.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"ssh-wrapper/cmd"
8+
"ssh-wrapper/pkg/ssh"
9+
)
10+
11+
func main() {
12+
args := cmd.ProcessArgs()
13+
14+
commandName, err := ssh.SshCommandPath()
15+
if err != nil {
16+
fmt.Fprintln(os.Stderr, "Error: unable to set up SSH invocation:", err)
17+
os.Exit(1)
18+
}
19+
20+
cmd.Grun(commandName, args...)
21+
}

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module ssh-wrapper
2+
3+
go 1.17
4+
5+
require golang.org/x/sync v0.0.0-20210220032951-036812b2e83c

0 commit comments

Comments
 (0)