Lots of work on docuemtation. The docs page now shows the correct
installation dependent URLs. For now using ServerALiveInterval to avoid disconnects.
This commit is contained in:
parent
21f50a4005
commit
12ecb72329
@ -92,7 +92,7 @@ func sshServer(hostKeyFile string) *ssh.Server {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Println("starting ssh server")
|
log.Println("starting ssh server, waiting for debug sessions")
|
||||||
server := ssh.Server{
|
server := ssh.Server{
|
||||||
PasswordHandler: passwordAuth,
|
PasswordHandler: passwordAuth,
|
||||||
SubsystemHandlers: map[string]ssh.SubsystemHandler{
|
SubsystemHandlers: map[string]ssh.SubsystemHandler{
|
||||||
@ -164,6 +164,8 @@ func main() {
|
|||||||
log.Println("WebSocket connection error:", err)
|
log.Println("WebSocket connection error:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
conn.SetReadDeadline(time.Time{})
|
||||||
|
conn.SetWriteDeadline(time.Time{})
|
||||||
wsConn := websocketutil.NewWebSocketConn(conn)
|
wsConn := websocketutil.NewWebSocketConn(conn)
|
||||||
defer wsConn.Close()
|
defer wsConn.Close()
|
||||||
|
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"cidebug/pkg/converge"
|
"cidebug/pkg/converge"
|
||||||
"cidebug/pkg/websocketutil"
|
"cidebug/pkg/websocketutil"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parsePublicId(path string) (publicId string, _ error) {
|
func parsePublicId(path string) (publicId string, _ error) {
|
||||||
@ -25,6 +29,90 @@ func catchAllHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filters to filter html content.
|
||||||
|
var filters map[string]string = make(map[string]string)
|
||||||
|
|
||||||
|
type FileHandlerFilter struct {
|
||||||
|
http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler FileHandlerFilter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mutex := sync.Mutex{}
|
||||||
|
// pedantic
|
||||||
|
mutex.Lock()
|
||||||
|
|
||||||
|
if r.TLS == nil {
|
||||||
|
filters["secure"] = ""
|
||||||
|
} else {
|
||||||
|
filters["secure"] = "s"
|
||||||
|
}
|
||||||
|
for _, header := range []string{"X-Forwarded-Proto", "X-Scheme", "X-Forwarded-Scheme"} {
|
||||||
|
values := r.Header.Values(header)
|
||||||
|
for _, value := range values {
|
||||||
|
if strings.ToLower(value) == "https" {
|
||||||
|
filters["secure"] = "s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filters["host"] = r.Host
|
||||||
|
mutex.Unlock()
|
||||||
|
|
||||||
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
w.Header().Set("Pragma", "no-cache")
|
||||||
|
w.Header().Set("Expires", "0")
|
||||||
|
|
||||||
|
handler.Handler.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FilteredFileSystem struct {
|
||||||
|
fs http.FileSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ffs FilteredFileSystem) Open(name string) (http.File, error) {
|
||||||
|
log.Println("Name : " + name)
|
||||||
|
f, err := ffs.fs.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(name, ".html") {
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
return NewFilteredFile(f, filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FilteredFile struct {
|
||||||
|
http.File
|
||||||
|
|
||||||
|
// Read bytes 0..(index-1) already
|
||||||
|
index int
|
||||||
|
fullContents *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFilteredFile(f http.File, filter map[string]string) (FilteredFile, error) {
|
||||||
|
file := FilteredFile{
|
||||||
|
index: 0,
|
||||||
|
fullContents: bytes.NewBuffer(make([]byte, 0)),
|
||||||
|
}
|
||||||
|
file.File = f
|
||||||
|
|
||||||
|
_, err := io.Copy(file.fullContents, file.File)
|
||||||
|
if err != nil {
|
||||||
|
return FilteredFile{}, err
|
||||||
|
}
|
||||||
|
contents := file.fullContents.String()
|
||||||
|
for key, value := range filter {
|
||||||
|
key = "@" + key + "@"
|
||||||
|
contents = strings.ReplaceAll(contents, key, value)
|
||||||
|
}
|
||||||
|
file.fullContents = bytes.NewBufferString(contents)
|
||||||
|
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ff FilteredFile) Read(p []byte) (n int, err error) {
|
||||||
|
return ff.fullContents.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
downloadDir := "downloads"
|
downloadDir := "downloads"
|
||||||
@ -65,7 +153,14 @@ func main() {
|
|||||||
|
|
||||||
http.HandleFunc("/agent/", registrationService.Handle)
|
http.HandleFunc("/agent/", registrationService.Handle)
|
||||||
http.HandleFunc("/client/", clientService.Handle)
|
http.HandleFunc("/client/", clientService.Handle)
|
||||||
http.Handle("/docs/", http.StripPrefix("/docs/", http.FileServer(http.Dir(downloadDir))))
|
|
||||||
|
filesystem := http.Dir(downloadDir)
|
||||||
|
filteredFilesystem := FilteredFileSystem{
|
||||||
|
fs: filesystem,
|
||||||
|
}
|
||||||
|
fileHandler := FileHandlerFilter{http.FileServer(filteredFilesystem)}
|
||||||
|
|
||||||
|
http.Handle("/docs/", http.StripPrefix("/docs/", fileHandler))
|
||||||
http.HandleFunc("/", catchAllHandler)
|
http.HandleFunc("/", catchAllHandler)
|
||||||
|
|
||||||
// Start HTTP server
|
// Start HTTP server
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func closeConnection(conn net.Conn) {
|
func closeConnection(conn net.Conn) {
|
||||||
@ -25,6 +26,8 @@ func handleConnection(conn net.Conn, wsURL string) {
|
|||||||
log.Println("WebSocket connection error:", err)
|
log.Println("WebSocket connection error:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
_wsConn.SetReadDeadline(time.Time{})
|
||||||
|
_wsConn.SetWriteDeadline(time.Time{})
|
||||||
wsConn := websocketutil.NewWebSocketConn(_wsConn)
|
wsConn := websocketutil.NewWebSocketConn(_wsConn)
|
||||||
defer wsConn.Close()
|
defer wsConn.Close()
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func closeConnection(conn net.Conn) {
|
func closeConnection(conn net.Conn) {
|
||||||
@ -29,6 +30,8 @@ func main() {
|
|||||||
wsURL := os.Args[1]
|
wsURL := os.Args[1]
|
||||||
|
|
||||||
_wsConn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
_wsConn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||||||
|
_wsConn.SetReadDeadline(time.Time{})
|
||||||
|
_wsConn.SetWriteDeadline(time.Time{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("WebSocket connection error:", err)
|
log.Println("WebSocket connection error:", err)
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Session is set to expire at %s
|
Session is set to expire at %v
|
||||||
|
|
||||||
The session expires automatically after %d time.
|
The session expires automatically after %d time.
|
||||||
If there are no more sessions after logging out, the agent
|
If there are no more sessions after logging out, the agent
|
||||||
@ -8,6 +8,6 @@ You can extend this time using
|
|||||||
|
|
||||||
touch $agentdir/.hold
|
touch $agentdir/.hold
|
||||||
|
|
||||||
To prevent the agent from exiting after the last sessioni is gone,
|
To prevent the agent from exiting after the last session is gone,
|
||||||
also use the above command in any shell.
|
also use the above command in any shell.
|
||||||
|
|
||||||
|
@ -144,9 +144,9 @@ func fileExists(filename string) bool {
|
|||||||
func (state *AgentState) expiryTime(filename string) time.Time {
|
func (state *AgentState) expiryTime(filename string) time.Time {
|
||||||
stats, err := os.Stat(filename)
|
stats, err := os.Stat(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return state.startTime
|
return state.startTime.Add(state.agentExpriryTime)
|
||||||
}
|
}
|
||||||
return stats.ModTime()
|
return stats.ModTime().Add(state.agentExpriryTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Behavior to implement
|
// Behavior to implement
|
||||||
@ -177,6 +177,11 @@ func check() {
|
|||||||
PrintHelpMessage(session.sshSession)
|
PrintHelpMessage(session.sshSession)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !fileExists(holdFilename) && len(state.sessions) == 0 {
|
||||||
|
log.Printf("All clients disconnected and no '%s' file found, exiting", holdFilename)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func messageUsers(message string) {
|
func messageUsers(message string) {
|
||||||
|
@ -72,6 +72,8 @@ func ConnectWebSocket(conn net.Conn, urlStr string) (net.Conn, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
wsConn.SetReadDeadline(time.Time{})
|
||||||
|
wsConn.SetWriteDeadline(time.Time{})
|
||||||
|
|
||||||
return NewWebSocketConn(wsConn), nil
|
return NewWebSocketConn(wsConn), nil
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebSocketAddr string
|
type WebSocketAddr string
|
||||||
@ -28,6 +29,8 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request,
|
|||||||
log.Println("Error upgrading to WebSocket:", err)
|
log.Println("Error upgrading to WebSocket:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
conn.SetReadDeadline(time.Time{})
|
||||||
|
conn.SetWriteDeadline(time.Time{})
|
||||||
wsConn := NewWebSocketConn(conn)
|
wsConn := NewWebSocketConn(conn)
|
||||||
defer wsConn.Close()
|
defer wsConn.Close()
|
||||||
|
|
||||||
|
@ -14,6 +14,39 @@
|
|||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
|
<h1>About</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Converge is a utility for troubleshooting builds on continuous integration serves.
|
||||||
|
It solves a common problem where the cause of job failure is difficult to determine.
|
||||||
|
This is complicated furhter by the fact that build jobs are usually run on a build
|
||||||
|
farm where there is no access to the build agents or in more modern envrionments when
|
||||||
|
jobs are run in ephemeral containers.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
With Converge it is possible to get remote shell access to such jobs. This works
|
||||||
|
by configuring the build job to connect to a Converge server using an agent program.
|
||||||
|
The agent program can be downloaded from within the CI job using curl or wget.
|
||||||
|
Next, an end-use can connect to the Converge server, a rendez-vous server, that connects
|
||||||
|
the client and server together.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The setup is such that the connection from client (end-user) to server (agent on CI job)
|
||||||
|
is end-to-end encrypted. The Converge server itself is no more than a bitpipe which pumps
|
||||||
|
data between client and agent.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Both ssh and sftp are supported. Multiple shells are also allowed.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
There is a timeout mechanism in the agent such that jobs do not hang indefinetely waiting
|
||||||
|
for a connection.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h1>Usage</h1>
|
<h1>Usage</h1>
|
||||||
|
|
||||||
<h2>Continous integration jobs</h2>
|
<h2>Continous integration jobs</h2>
|
||||||
@ -22,12 +55,12 @@
|
|||||||
In a continous integration job, download the agent, chmod it and run it.
|
In a continous integration job, download the agent, chmod it and run it.
|
||||||
</p>
|
</p>
|
||||||
<pre>
|
<pre>
|
||||||
curl https://HOSTPORT/docs/agent > agent
|
curl http@secure@://@host@/docs/agent > agent
|
||||||
./agent wss://HOST:PORT/agent/ID
|
chmod 755 agent
|
||||||
chmod 755 agent
|
./agent ws@secure@://@host@/agent/ID
|
||||||
</pre>
|
</pre>
|
||||||
<p>
|
<p>
|
||||||
Above, HOST:PORT is the hostname:port of the converge server and ID is a unique id
|
Above, ID is a unique id
|
||||||
for the job. This should not conflict with other ids.
|
for the job. This should not conflict with other ids.
|
||||||
|
|
||||||
This connects the agent to the converge server. Clients can now connect to converge
|
This connects the agent to the converge server. Clients can now connect to converge
|
||||||
@ -37,7 +70,7 @@
|
|||||||
<h2>Local clients: using ssh proxy command </h2>
|
<h2>Local clients: using ssh proxy command </h2>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
curl https://HOST:PORT/docs/wsproxy > wsproxy
|
curl http@secure@://@host@/docs/wsproxy > wsproxy
|
||||||
chmod 755 wsproxy
|
chmod 755 wsproxy
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
@ -49,8 +82,8 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
ssh -oProxyCommand="wsproxy https://HOST:PORT/client/ID" -p 10000 abc@localhost
|
ssh -oServerAliveInterval=10 -oProxyCommand="wsproxy ws@secure@://@host@/client/ID" abc@localhost
|
||||||
sftp -oProxyCommand="wsproxy https://HOST:PORT/client/ID" -oPort 10000 abc@localhost
|
sftp -oServerAliveInterval=10 -oProxyCommand="wsproxy ws@secure@://@host@/client/ID" abc@localhost
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -70,15 +103,9 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
# for HTTP hosted server
|
curl http@secure@://@host@/docs/tcptows > tcptows
|
||||||
curl http://HOST:PORT/docs/tcptows > tcptows
|
|
||||||
chmod 755 tcptows
|
chmod 755 tcptows
|
||||||
./tcptows 10000 ws://HOST:PORT/client/ID
|
./tcptows 10000 ws@secure@://@host@/client/ID
|
||||||
|
|
||||||
# for HTTPS hosted server
|
|
||||||
curl https://HOST:PORT/docs/tcptows > tcptows
|
|
||||||
chmod 755 tcptows
|
|
||||||
./tcptows 10000 wss://HOST:PORT/client/ID
|
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -90,8 +117,8 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
ssh -p 10000 abc@localhost
|
ssh -oServerAliveInterval=10 -p 10000 abc@localhost
|
||||||
sftp -oPort 10000 abc@localhost
|
sftp -oServerAliveInterval=10 -oPort 10000 abc@localhost
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
Loading…
Reference in New Issue
Block a user