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

Commit 83c9b42

Browse files
committed
Initial commit
1 parent 7a66f48 commit 83c9b42

File tree

7 files changed

+158
-0
lines changed

7 files changed

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

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
```bash
10+
go get go.coder.com/sshcode
11+
```
12+
13+
## Usage
14+
15+
```bash
16+
17+
# Starts code-server on dev.kwc.io and opens in a new browser window.
18+
```

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

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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+
err = sshCmd.Start()
64+
if err != nil {
65+
flog.Fatal("failed to start code-server: %v", err)
66+
}
67+
68+
var openCmd *exec.Cmd
69+
url := "http://127.0.0.1:" + localPort
70+
if commandExists("google-chrome") {
71+
openCmd = exec.Command("google-chrome", "--app="+url, "--disable-extensions", "--disable-plugins")
72+
} else if commandExists("firefox") {
73+
openCmd = exec.Command("firefox", "--url="+url, "-safe-mode")
74+
}
75+
76+
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
77+
defer cancel()
78+
for ctx.Err() == nil {
79+
// waits for code-server to be available before opening the browser
80+
r, _ := http.NewRequest("GET", url, nil)
81+
r = r.WithContext(ctx)
82+
resp, err := http.DefaultClient.Do(r)
83+
if err != nil {
84+
continue
85+
}
86+
resp.Body.Close()
87+
break
88+
}
89+
90+
flog.Success("code-server is running. opening %s", url)
91+
err = openCmd.Start()
92+
if err != nil {
93+
flog.Fatal("failed to open browser: %v", err)
94+
}
95+
sshCmd.Wait()
96+
}
97+
98+
// Checks if a command exists locally
99+
func commandExists(name string) bool {
100+
_, err := exec.LookPath(name)
101+
return err == nil
102+
}
103+
104+
// Scans from 1024+ until an available port is found
105+
func scanAvailablePort() string {
106+
port := uint16(1024)
107+
for {
108+
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
109+
if err != nil {
110+
// if we have an error the port is taken
111+
port++
112+
continue
113+
}
114+
_ = l.Close()
115+
116+
return strconv.Itoa(int(port))
117+
}
118+
}

0 commit comments

Comments
 (0)