fileserver now uses go template language.

updated docs for windows.
This commit is contained in:
Erik Brakkee 2024-07-23 20:47:51 +02:00
parent 38869b5faa
commit 6e2ed858e4
3 changed files with 190 additions and 187 deletions

View File

@ -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)
} }

View File

@ -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)

View File

@ -12,7 +12,11 @@
<script src="js/bootstrap.bundle.min.js" <script src="js/bootstrap.bundle.min.js"
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
<div class="container"> <div class="container-fluid">
<div class="row">
<div class="col">
<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>