fileserver now uses go template language.
updated docs for windows.
This commit is contained in:
parent
38869b5faa
commit
6e2ed858e4
@ -1,59 +1,67 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"html/template"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// filters to filter html content.
|
type FileHandlerFilter struct {
|
||||||
var filters map[string]string = make(map[string]string)
|
dir string
|
||||||
|
fileHandler http.Handler
|
||||||
type FilteredFileSystem struct {
|
|
||||||
fs http.FileSystem
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ffs FilteredFileSystem) Open(name string) (http.File, error) {
|
func NewFileHandler(dir string) *FileHandlerFilter {
|
||||||
f, err := ffs.fs.Open(name)
|
handler := FileHandlerFilter{
|
||||||
|
dir: dir,
|
||||||
|
fileHandler: http.FileServer(http.Dir(dir)),
|
||||||
|
}
|
||||||
|
return &handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler FileHandlerFilter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
contextPath := r.URL.Path
|
||||||
|
path := filepath.Join(handler.dir, contextPath)
|
||||||
|
fileInfo, err := os.Stat(path)
|
||||||
|
if err == nil && fileInfo.IsDir() {
|
||||||
|
contextPath = filepath.Join(contextPath, "index.html")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(contextPath, ".html") {
|
||||||
|
handler.fileHandler.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
w.Header().Set("Pragma", "no-cache")
|
||||||
|
w.Header().Set("Expires", "0")
|
||||||
|
|
||||||
|
filters := make(map[string]string)
|
||||||
|
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
|
||||||
|
|
||||||
|
tmpl, err := template.ParseFiles(filepath.Join(handler.dir, contextPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
// let the filehandler generate the rror
|
||||||
|
handler.fileHandler.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
if !strings.HasSuffix(name, ".html") {
|
err = tmpl.Execute(w, filters)
|
||||||
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 {
|
if err != nil {
|
||||||
return FilteredFile{}, err
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
contents := file.fullContents.String()
|
// ok, tmpl has written the response
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func parsePublicId(path string) (publicId string, _ error) {
|
func parsePublicId(path string) (publicId string, _ error) {
|
||||||
@ -27,38 +25,6 @@ func catchAllHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
downloadDir := "downloads"
|
downloadDir := "downloads"
|
||||||
@ -97,15 +63,12 @@ func main() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// websocket endpoints
|
||||||
http.HandleFunc("/agent/", registrationService.Handle)
|
http.HandleFunc("/agent/", registrationService.Handle)
|
||||||
http.HandleFunc("/client/", clientService.Handle)
|
http.HandleFunc("/client/", clientService.Handle)
|
||||||
|
|
||||||
filesystem := http.Dir(downloadDir)
|
// create filehandler with templating for html files.
|
||||||
filteredFilesystem := FilteredFileSystem{
|
fileHandler := NewFileHandler(downloadDir)
|
||||||
fs: filesystem,
|
|
||||||
}
|
|
||||||
fileHandler := FileHandlerFilter{http.FileServer(filteredFilesystem)}
|
|
||||||
|
|
||||||
http.Handle("/docs/", http.StripPrefix("/docs/", fileHandler))
|
http.Handle("/docs/", http.StripPrefix("/docs/", fileHandler))
|
||||||
http.HandleFunc("/", catchAllHandler)
|
http.HandleFunc("/", catchAllHandler)
|
||||||
|
|
||||||
|
@ -12,6 +12,10 @@
|
|||||||
<script src="js/bootstrap.bundle.min.js"
|
<script src="js/bootstrap.bundle.min.js"
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
<h1>About</h1>
|
<h1>About</h1>
|
||||||
@ -61,9 +65,14 @@
|
|||||||
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 http@secure@://@host@/docs/agent > agent
|
# linux
|
||||||
|
curl http{{.secure}}://{{.host}}/docs/agent > agent
|
||||||
chmod 755 agent
|
chmod 755 agent
|
||||||
./agent ws@secure@://@host@/agent/ID
|
./agent ws{{.secure}}://{{.host}}/agent/ID
|
||||||
|
|
||||||
|
# windows
|
||||||
|
curl http{{.secure}}://{{.host}}/docs/agent.exe > agent.exe
|
||||||
|
agent ws{{.secure}}://{{.host}}/agent/ID
|
||||||
</pre>
|
</pre>
|
||||||
<p>
|
<p>
|
||||||
Above, ID is a unique id
|
Above, ID is a unique id
|
||||||
@ -73,23 +82,29 @@
|
|||||||
server to establish a connection to the CI job through converge.
|
server to establish a connection to the CI job through converge.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
NOTE: When running the agent on windows, an exit of the remote session using
|
||||||
|
exit in powershell or command prompt does not terminate the shell completely.
|
||||||
|
To terminate the client ssh session must be killed by closing the terminal.
|
||||||
|
Cleanup of remote processes on the agent appears to work properly despite this
|
||||||
|
problem. It is just that exit of the windows shell (powershell or command prompt)
|
||||||
|
is not detected properly.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2>Local clients: using ssh proxy command </h2>
|
<h2>Local clients: using ssh proxy command </h2>
|
||||||
|
|
||||||
<pre>
|
<p><code>wsproxy</code> is a command that can be used as a proxy command for SSH which performs the connection to the
|
||||||
curl http@secure@://@host@/docs/wsproxy > wsproxy
|
remote server. This command needs to be downloaded only once (see <a href="#downloads">downloads</a> below). It does not depend on
|
||||||
chmod 755 wsproxy
|
the converge implementation but only on the websocket standards. Other tools that
|
||||||
</pre>
|
provide a mapping of stdio to a websocket can also be used instead of wsproxy.
|
||||||
|
</p>
|
||||||
<p>This is a command that can be used as a proxy command for SSH which performs the connection to the remote
|
|
||||||
server.</p>
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Next step is to run a local SSH or SFTP client:
|
Next step is to run a local SSH or SFTP client:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
ssh -oServerAliveInterval=10 -oProxyCommand="wsproxy ws@secure@://@host@/client/ID" abc@localhost
|
ssh -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{{.secure}}://{{.host}}/client/ID" abc@localhost
|
||||||
sftp -oServerAliveInterval=10 -oProxyCommand="wsproxy ws@secure@://@host@/client/ID" abc@localhost
|
sftp -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{{.secure}}://{{.host}}/client/ID" abc@localhost
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -98,25 +113,19 @@
|
|||||||
|
|
||||||
<h2>Local clients: with a local TCP forwarding proxy</h2>
|
<h2>Local clients: with a local TCP forwarding proxy</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
This option is less convenient than the proxy command because it requires two separate
|
This option is less convenient than the proxy command because it requires two separate
|
||||||
commands to execute.
|
commands to execute.
|
||||||
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Local clients can connect using regular ssh and sftp commands through a tunnel that
|
Local clients can connect using regular ssh and sftp commands through a tunnel that
|
||||||
translates a local TCP port to a websocket connection in converge.
|
translates a local TCP port to a websocket connection in converge. See
|
||||||
|
the <a href="#downloads">downloads</a> section.
|
||||||
First step is to download the tcptows program (see below):
|
This runs a local client that allows SSH to port 10000 and connects to converge using
|
||||||
|
a websocket connection.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
|
||||||
curl http@secure@://@host@/docs/tcptows > tcptows
|
|
||||||
chmod 755 tcptows
|
|
||||||
./tcptows 10000 ws@secure@://@host@/client/ID
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
This runs a local client that allows SSH to port 10000 and connects to converge using
|
|
||||||
a websocket connection.</p>
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Next step is to run a local SSH of SFTP client:
|
Next step is to run a local SSH of SFTP client:
|
||||||
@ -131,17 +140,40 @@
|
|||||||
<code>abc</code> is a fixed user defined by converge. It has a very exciting password.
|
<code>abc</code> is a fixed user defined by converge. It has a very exciting password.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h1>Downloads</h1>
|
<h1 id="downloads">Downloads</h1>
|
||||||
|
|
||||||
<ul>
|
<table class="table">
|
||||||
<li><a href="/docs/agent">agent</a>: The agent to run inside aa CI job
|
<thead>
|
||||||
</li>
|
<tr>
|
||||||
<li><a href="/docs/tcptows">tcptows</a>: TCP to WS tunnel for allowing regular
|
<th>Component</th>
|
||||||
SSH and SFTP clients to connect to converge.
|
<th>Purpose</th>
|
||||||
</li>
|
<th>Linux</th>
|
||||||
<li><a href="/docs/wsproxy">wsproxy</a>: SSH proxy command that can be directly used by ssh
|
<th>Windows</th>
|
||||||
</li>
|
</tr>
|
||||||
</ul>
|
</thead>
|
||||||
|
<tr>
|
||||||
|
<td>agent</td>
|
||||||
|
<td>The agent to run inside aa CI job</td>
|
||||||
|
<td><a href="/docs/agent">agent</a></td>
|
||||||
|
<td><a href="/docs/agent.exe">agent.exe</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>wsproxy</td>
|
||||||
|
<td>SSH proxy command that can be directly used by ssh</td>
|
||||||
|
<td><a href="/docs/wsproxy">wsproxy</a></td>
|
||||||
|
<td><a href="/docs/wsproxy.exe">wsproxy.exe</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>tcptows</td>
|
||||||
|
<td>TCP to WS tunnel for allowing regular
|
||||||
|
SSH and SFTP clients to connect to converge</td>
|
||||||
|
<td><a href="/docs/tcptows">tcptows</a></td>
|
||||||
|
<td><a href="/docs/tcptows.exe">tcptows.exe</a></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user