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

Commit 38698a8

Browse files
committed
Initial commit
1 parent 7a66f48 commit 38698a8

File tree

7 files changed

+161
-0
lines changed

7 files changed

+161
-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

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

0 commit comments

Comments
 (0)