| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 5bbc39f commit e646d57
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -34,11 +34,12 @@ var _ interface { | |||
| 34 | 34 | } = new(rootCmd) | |
| 35 | 35 | ||
| 36 | 36 | type rootCmd struct { | |
| 37 | - skipSync bool | ||
| 38 | - syncBack bool | ||
| 39 | - printVersion bool | ||
| 40 | - bindAddr string | ||
| 41 | - sshFlags string | ||
| 37 | + skipSync bool | ||
| 38 | + syncBack bool | ||
| 39 | + printVersion bool | ||
| 40 | + noReuseConnection bool | ||
| 41 | + bindAddr string | ||
| 42 | + sshFlags string | ||
| 42 | 43 | } | |
| 43 | 44 | ||
| 44 | 45 | func (c *rootCmd) Spec() cli.CommandSpec { | |
@@ -53,6 +54,7 @@ func (c *rootCmd) RegisterFlags(fl *flag.FlagSet) { | |||
| 53 | 54 | fl.BoolVar(&c.skipSync, "skipsync", false, "skip syncing local settings and extensions to remote host") | |
| 54 | 55 | fl.BoolVar(&c.syncBack, "b", false, "sync extensions back on termination") | |
| 55 | 56 | fl.BoolVar(&c.printVersion, "version", false, "print version information and exit") | |
| 57 | + fl.BoolVar(&c.noReuseConnection, "no-reuse-connection", false, "do not reuse SSH connection via control socket") | ||
| 56 | 58 | fl.StringVar(&c.bindAddr, "bind", "", "local bind address for ssh tunnel") | |
| 57 | 59 | fl.StringVar(&c.sshFlags, "ssh-flags", "", "custom SSH flags") | |
| 58 | 60 | } | |
@@ -76,10 +78,11 @@ func (c *rootCmd) Run(fl *flag.FlagSet) { | |||
| 76 | 78 | } | |
| 77 | 79 | ||
| 78 | 80 | err := sshCode(host, dir, options{ | |
| 79 | - skipSync: c.skipSync, | ||
| 80 | - sshFlags: c.sshFlags, | ||
| 81 | - bindAddr: c.bindAddr, | ||
| 82 | - syncBack: c.syncBack, | ||
| 81 | + skipSync: c.skipSync, | ||
| 82 | + sshFlags: c.sshFlags, | ||
| 83 | + bindAddr: c.bindAddr, | ||
| 84 | + syncBack: c.syncBack, | ||
| 85 | + noReuseConnection: c.noReuseConnection, | ||
| 83 | 86 | }) | |
| 84 | 87 | ||
| 85 | 88 | if err != nil { | |
@@ -101,7 +104,7 @@ Environment variables: | |||
| 101 | 104 | More info: https://github.com/cdr/sshcode | |
| 102 | 105 | ||
| 103 | 106 | Arguments: | |
| 104 | - %vHOST is passed into the ssh command. Valid formats are '<ip-address>' or 'gcp:<instance-name>'. | ||
| 107 | + %vHOST is passed into the ssh command. Valid formats are '<ip-address>' or 'gcp:<instance-name>'. | ||
| 105 | 108 | %vDIR is optional.`, | |
| 106 | 109 | helpTab, vsCodeConfigDirEnv, | |
| 107 | 110 | helpTab, vsCodeExtensionsDirEnv, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -20,14 +20,16 @@ import ( | |||
| 20 | 20 | ) | |
| 21 | 21 | ||
| 22 | 22 | const codeServerPath = "~/.cache/sshcode/sshcode-server" | |
| 23 | + const sshControlPath = "~/.ssh/control-%h-%p-%r" | ||
| 23 | 24 | ||
| 24 | 25 | type options struct { | |
| 25 | - skipSync bool | ||
| 26 | - syncBack bool | ||
| 27 | - noOpen bool | ||
| 28 | - bindAddr string | ||
| 29 | - remotePort string | ||
| 30 | - sshFlags string | ||
| 26 | + skipSync bool | ||
| 27 | + syncBack bool | ||
| 28 | + noOpen bool | ||
| 29 | + noReuseConnection bool | ||
| 30 | + bindAddr string | ||
| 31 | + remotePort string | ||
| 32 | + sshFlags string | ||
| 31 | 33 | } | |
| 32 | 34 | ||
| 33 | 35 | func sshCode(host, dir string, o options) error { | |
@@ -53,6 +55,41 @@ func sshCode(host, dir string, o options) error { | |||
| 53 | 55 | return xerrors.Errorf("failed to find available remote port: %w", err) | |
| 54 | 56 | } | |
| 55 | 57 | ||
| 58 | + // Start SSH master connection socket. This prevents multiple password prompts from appearing as authentication | ||
| 59 | + // only happens on the initial connection. | ||
| 60 | + var sshMasterCmd *exec.Cmd | ||
| 61 | + if !o.noReuseConnection { | ||
| 62 | + newSSHFlags := fmt.Sprintf(`%v -o "ControlPath=%v"`, o.sshFlags, sshControlPath) | ||
| 63 | + | ||
| 64 | + // -MN means "start a master socket and don't open a session, just connect". | ||
| 65 | + sshMasterCmdStr := fmt.Sprintf(`ssh %v -MN %v`, newSSHFlags, host) | ||
| 66 | + sshMasterCmd = exec.Command("sh", "-c", sshMasterCmdStr) | ||
| 67 | + sshMasterCmd.Stdin = os.Stdin | ||
| 68 | + sshMasterCmd.Stdout = os.Stdout | ||
| 69 | + sshMasterCmd.Stderr = os.Stderr | ||
| 70 | + err = sshMasterCmd.Start() | ||
| 71 | + if err != nil { | ||
| 72 | + flog.Error("failed to start SSH master connection, disabling connection reuse feature: %v", err) | ||
| 73 | + o.noReuseConnection = true | ||
| 74 | + } else { | ||
| 75 | + // Wait for master to be ready. | ||
| 76 | + err = checkSSHMaster(newSSHFlags, host) | ||
| 77 | + if err != nil { | ||
| 78 | + flog.Error("SSH master failed to start in time, disabling connection reuse feature: %v", err) | ||
| 79 | + o.noReuseConnection = true | ||
| 80 | + if sshMasterCmd.Process != nil { | ||
| 81 | + err = sshMasterCmd.Process.Kill() | ||
| 82 | + if err != nil { | ||
| 83 | + flog.Error("failed to kill SSH master connection, ignoring: %v", err) | ||
| 84 | + } | ||
| 85 | + } | ||
| 86 | + } else { | ||
| 87 | + sshMasterCmd.Stdin = nil | ||
| 88 | + o.sshFlags = newSSHFlags | ||
| 89 | + } | ||
| 90 | + } | ||
| 91 | + } | ||
| 92 | + | ||
| 56 | 93 | dlScript := downloadScript(codeServerPath) | |
| 57 | 94 | ||
| 58 | 95 | // Downloads the latest code-server and allows it to be executed. | |
@@ -146,22 +183,39 @@ func sshCode(host, dir string, o options) error { | |||
| 146 | 183 | case <-ctx.Done(): | |
| 147 | 184 | case <-c: | |
| 148 | 185 | } | |
| 186 | + flog.Info("exiting") | ||
| 149 | 187 | ||
| 150 | - if !o.syncBack || o.skipSync { | ||
| 151 | - flog.Info("shutting down") | ||
| 152 | - return nil | ||
| 153 | - } | ||
| 188 | + if o.syncBack && !o.skipSync { | ||
| 189 | + flog.Info("synchronizing VS Code back to local") | ||
| 154 | 190 | ||
| 155 | - flog.Info("synchronizing VS Code back to local") | ||
| 191 | + err = syncExtensions(o.sshFlags, host, true) | ||
| 192 | + if err != nil { | ||
| 193 | + flog.Error("failed to sync extensions back: %v", err) | ||
| 194 | + } | ||
| 156 | 195 | ||
| 157 | - err = syncExtensions(o.sshFlags, host, true) | ||
| 158 | - if err != nil { | ||
| 159 | - return xerrors.Errorf("failed to sync extensions back: %w", err) | ||
| 196 | + err = syncUserSettings(o.sshFlags, host, true) | ||
| 197 | + if err != nil { | ||
| 198 | + flog.Error("failed to sync user settings settings back: %v", err) | ||
| 199 | + } | ||
| 160 | 200 | } | |
| 161 | 201 | ||
| 162 | - err = syncUserSettings(o.sshFlags, host, true) | ||
| 163 | - if err != nil { | ||
| 164 | - return xerrors.Errorf("failed to sync user settings settings back: %w", err) | ||
| 202 | + // Kill the master connection if we made one. | ||
| 203 | + if !o.noReuseConnection { | ||
| 204 | + // Try using the -O exit syntax first before killing the master. | ||
| 205 | + sshCmdStr = fmt.Sprintf(`ssh %v -O exit %v`, o.sshFlags, host) | ||
| 206 | + sshCmd = exec.Command("sh", "-c", sshCmdStr) | ||
| 207 | + sshCmd.Stdout = os.Stdout | ||
| 208 | + sshCmd.Stderr = os.Stderr | ||
| 209 | + err = sshCmd.Run() | ||
| 210 | + if err != nil { | ||
| 211 | + flog.Error("failed to gracefully stop SSH master connection, killing: %v", err) | ||
| 212 | + if sshMasterCmd.Process != nil { | ||
| 213 | + err = sshMasterCmd.Process.Kill() | ||
| 214 | + if err != nil { | ||
| 215 | + flog.Error("failed to kill SSH master connection, ignoring: %v", err) | ||
| 216 | + } | ||
| 217 | + } | ||
| 218 | + } | ||
| 165 | 219 | } | |
| 166 | 220 | ||
| 167 | 221 | return nil | |
@@ -263,6 +317,26 @@ func randomPort() (string, error) { | |||
| 263 | 317 | return "", xerrors.Errorf("max number of tries exceeded: %d", maxTries) | |
| 264 | 318 | } | |
| 265 | 319 | ||
| 320 | + // checkSSHMaster polls every second for 30 seconds to check if the SSH master | ||
| 321 | + // is ready. | ||
| 322 | + func checkSSHMaster(sshFlags string, host string) (err error) { | ||
| 323 | + maxTries := 30 | ||
| 324 | + check := func() error { | ||
| 325 | + sshCmdStr := fmt.Sprintf(`ssh %v -O check %v`, sshFlags, host) | ||
| 326 | + sshCmd := exec.Command("sh", "-c", sshCmdStr) | ||
| 327 | + return sshCmd.Run() | ||
| 328 | + } | ||
| 329 | + | ||
| 330 | + for i := 0; i < maxTries; i++ { | ||
| 331 | + err = check() | ||
| 332 | + if err == nil { | ||
| 333 | + return nil | ||
| 334 | + } | ||
| 335 | + time.Sleep(time.Second) | ||
| 336 | + } | ||
| 337 | + return err | ||
| 338 | + } | ||
| 339 | + | ||
| 266 | 340 | func syncUserSettings(sshFlags string, host string, back bool) error { | |
| 267 | 341 | localConfDir, err := configDir() | |
| 268 | 342 | if err != nil { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments