Skip to content
This repository was archived by the owner on Jan 17, 2021. It is now read-only.

Commit 126b411

Browse files
committed
Initial commit
1 parent 7a66f48 commit 126b411

File tree

7 files changed

+166
-0
lines changed

7 files changed

+166
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
vendor

.sail/Dockerfile

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
FROM codercom/ubuntu-dev-go
2+
3+
LABEL project_root "~/go/src/go.coder.com"

README.md

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# sshcode
2+
3+
`sshcode` is a CLI to automatically install and run [code-server](https://github.com/codercom/code-server) over SSH.
4+
5+
![Demo](/demo.gif)
6+
7+
## Install
8+
9+
Chrome is recommended.
10+
11+
```bash
12+
go get go.coder.com/sshcode
13+
```
14+
15+
## Usage
16+
17+
```bash
18+
19+
# Starts code-server on dev.kwc.io and opens in a new browser window.
20+
```

demo.gif

702 KB
Loading

go.mod

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module go.coder.com/sshcode
2+
3+
go 1.12
4+
5+
require (
6+
go.coder.com/flog v0.0.0-20190129195112-eaed154a0db8
7+
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be // indirect
8+
)

go.sum

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
2+
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
3+
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
4+
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
5+
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
6+
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
7+
go.coder.com/flog v0.0.0-20190129195112-eaed154a0db8 h1:PtQ3moPi4EAz3cyQhkUs1IGIXa2QgJpP60yMjOdu0kk=
8+
go.coder.com/flog v0.0.0-20190129195112-eaed154a0db8/go.mod h1:83JsYgXYv0EOaXjIMnaZ1Fl6ddNB3fJnDZ/8845mUJ8=
9+
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be h1:mI+jhqkn68ybP0ORJqunXn+fq+Eeb4hHKqLQcFICjAc=
10+
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

main.go

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"errors"
6+
"flag"
7+
"fmt"
8+
"net"
9+
"net/http"
10+
"os"
11+
"os/exec"
12+
"strconv"
13+
"time"
14+
15+
"go.coder.com/flog"
16+
)
17+
18+
func main() {
19+
flag.Usage = func() {
20+
fmt.Printf(`Usage: %v HOST [SSH ARGS...]
21+
22+
Start code-server over SSH.
23+
More info: https://github.com/codercom/sshcode
24+
`, os.Args[0])
25+
}
26+
27+
flag.Parse()
28+
host := flag.Arg(0)
29+
30+
if host == "" {
31+
// If no host is specified output the usage.
32+
flag.Usage()
33+
os.Exit(1)
34+
}
35+
36+
flog.Info("ensuring code-server is updated...")
37+
38+
// Downloads the latest code-server and allows it to be executed.
39+
sshCmd := exec.Command("ssh",
40+
"-tt",
41+
host,
42+
`/bin/bash -c 'set -euxo pipefail || exit 1
43+
mkdir -p ~/bin
44+
wget -q https://codesrv-ci.cdr.sh/latest-linux -O ~/bin/code-server
45+
chmod +x ~/bin/code-server
46+
'`,
47+
)
48+
output, err := sshCmd.CombinedOutput()
49+
if err != nil {
50+
flog.Fatal("failed to update code-server: %v: %s", err, string(output))
51+
}
52+
53+
flog.Info("starting code-server...")
54+
localPort, err := scanAvailablePort()
55+
if err != nil {
56+
flog.Fatal("failed to scan available port: %v", err)
57+
}
58+
59+
// Starts code-server and forwards the remote port.
60+
sshCmd = exec.Command("ssh",
61+
"-tt",
62+
"-L",
63+
localPort+":localhost:"+localPort,
64+
host,
65+
"~/bin/code-server --host 127.0.0.1 --allow-http --no-auth --port="+localPort,
66+
)
67+
sshCmd.Stdout = os.Stdout
68+
sshCmd.Stderr = os.Stderr
69+
err = sshCmd.Start()
70+
if err != nil {
71+
flog.Fatal("failed to start code-server: %v", err)
72+
}
73+
74+
var openCmd *exec.Cmd
75+
url := "http://127.0.0.1:" + localPort
76+
if commandExists("google-chrome") {
77+
openCmd = exec.Command("google-chrome", "--app="+url, "--disable-extensions", "--disable-plugins")
78+
} else if commandExists("firefox") {
79+
openCmd = exec.Command("firefox", "--url="+url, "-safe-mode")
80+
}
81+
82+
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
83+
defer cancel()
84+
for ctx.Err() == nil {
85+
// Waits for code-server to be available before opening the browser.
86+
r, _ := http.NewRequest("GET", url, nil)
87+
r = r.WithContext(ctx)
88+
resp, err := http.DefaultClient.Do(r)
89+
if err != nil {
90+
continue
91+
}
92+
resp.Body.Close()
93+
break
94+
}
95+
96+
err = openCmd.Start()
97+
if err != nil {
98+
flog.Fatal("failed to open browser: %v", err)
99+
}
100+
sshCmd.Wait()
101+
}
102+
103+
// Checks if a command exists locally.
104+
func commandExists(name string) bool {
105+
_, err := exec.LookPath(name)
106+
return err == nil
107+
}
108+
109+
// scanAvailablePort scans 1024-4096 until an available port is found.
110+
func scanAvailablePort() (string, error) {
111+
for port := 1024; port < 4096; port++ {
112+
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
113+
if err != nil {
114+
// If we have an error the port is taken.
115+
port++
116+
continue
117+
}
118+
_ = l.Close()
119+
120+
return strconv.Itoa(port), nil
121+
}
122+
123+
return "", errors.New("no ports available")
124+
}

0 commit comments

Comments
 (0)