-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
187 lines (160 loc) · 4.19 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
)
// Config represents the JSON configuration structure.
type Config struct {
Distro string `json:"distro"`
SSHPath string `json:"ssh_path"`
WSLPath string `json:"wsl_path"`
}
// DefaultConfig is the default value for BypaSSH config.
var DefaultConfig = Config{
Distro: "Ubuntu",
SSHPath: "/usr/bin/ssh",
WSLPath: "C:\\Windows\\system32\\wsl.exe",
}
// parseConfig returns parsed config from file and any error encountered during
// parse.
func parseConfig() (conf Config, err error) {
conf = DefaultConfig
dir, err := os.Executable()
if err != nil {
return
}
dir = filepath.Dir(dir)
fpath := filepath.Join(dir, "bypassh.json")
f, err := os.Open(fpath)
if err != nil {
return
}
defer f.Close()
var fconf Config
if err = json.NewDecoder(f).Decode(&fconf); err != nil {
return
}
if fconf.Distro != "" {
conf.Distro = fconf.Distro
}
if fconf.SSHPath != "" {
conf.Distro = fconf.SSHPath
}
if fconf.WSLPath != "" {
conf.WSLPath = fconf.WSLPath
}
return
}
// replaceWindowsPaths replaces in with WSL-compatible path. If there's any
// changes between in and out, ok will return true.
func replaceWindowsPaths(in string) (out string, ok bool) {
var builder strings.Builder
var pos int
for {
rpos := strings.Index(in[pos:], `:\`)
if rpos <= 0 {
break
}
npos := pos + rpos
builder.WriteString(in[pos : npos-1])
builder.WriteString("/mnt/")
builder.WriteString(strings.ToLower(in[npos-1 : npos]))
builder.WriteString("/")
pos = npos + 2 // Skip the :\ part
}
builder.WriteString(in[pos:])
return builder.String(), pos != 0
}
// translatePaths translates any Windows-styled paths and return them as
// WSL-styled paths.
func translatePaths(input []string, distro string) (output []string) {
for _, in := range input {
var isPath bool
if strings.Contains(in, `\\wsl$\`) {
in = strings.ReplaceAll(in, `\\wsl$\`+distro, "")
isPath = true
}
var ok bool
if in, ok = replaceWindowsPaths(in); ok {
isPath = true
}
if isPath {
in = strings.ReplaceAll(in, `\`, "/")
}
output = append(output, in)
}
return output
}
const helpMessage = `Usage: %s [-options...] destination [command]
Refer to ` + "`man ssh`" + ` for more information about the ssh arguments.
For BypaSSH configuration, create "bypassh.json" file in the same location as
the binary with the following fields and its default value:
{
// The target WSL2 distro
"distro": "Ubuntu",
// Path to the SSH binary inside WSL
"ssh_path": "/usr/bin/ssh",
// Path to the WSL binary in Windows
"wsl_path": "C:\\Windows\\system32\\wsl.exe"
}
For most cases, if you are using the default Ubuntu distro you can leave the
configuration as-is. Use ` + "`-P`" + ` to print the JSON config and parse any
error message to debug your configuration.
`
func main() {
conf, err := parseConfig()
if len(os.Args) > 1 {
switch os.Args[1] {
// Fast path for OpenSSH binary detection by Visual Studio Code.
//
// Using naive `ssh -V` from WSL will resulting in slower start up time
// causing VSCode to give up and fallback to the default SSH binary.
case "-V":
fmt.Printf("wsl-ssh-helper 1.0.0 WSL2 OpenSSH-compatible proxy binary\n")
return
case "-h", "--help":
fmt.Printf(helpMessage, os.Args[0])
return
case "-P":
fmt.Printf("config: ")
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", "\t")
enc.Encode(conf)
if err != nil {
fmt.Printf("parse error: %s\n", err.Error())
}
return
}
}
args := translatePaths(os.Args[1:], conf.Distro)
execArgs := append([]string{"-d", conf.Distro, conf.SSHPath}, args...)
cmd := exec.Command(conf.WSLPath, execArgs...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
fmt.Printf("error exec start: %s\n", err.Error())
os.Exit(1)
}
// Interrupt signal forwarder
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt)
go func() {
for {
<-sig
interruptCmd(cmd)
}
}()
err = cmd.Wait()
if exErr, ok := err.(*exec.ExitError); ok {
os.Exit(exErr.ExitCode())
} else if err != nil {
fmt.Printf("error exec wait: %s\n", err.Error())
os.Exit(1)
}
}