The contextpath parameter in converge.go is temporary and should be removed later. What is needed is autodetectio of the context path for the usage page and passing on the context for rendering.
304 lines
12 KiB
Plaintext
304 lines
12 KiB
Plaintext
package templates
|
|
|
|
import "converge/pkg/models"
|
|
|
|
|
|
templ AgentUsage(access models.ConvergeAccess, usageInputs UsageInputs) {
|
|
<div>
|
|
<h2>Downloading and running the agent</h2>
|
|
|
|
<p>
|
|
This is what you run on a remote server, typically in your continuous integration job.
|
|
</p>
|
|
|
|
if usageInputs.RemoteShells[BASH] {
|
|
<pre>{addSshKeys(BASH, usageInputs.SshKeys)}
|
|
curl --fail-with-body http{access.Secure}://{access.HostPort}/downloads/agent > agent{`
|
|
chmod 755 agent
|
|
`}./agent --id {usageInputs.Id} ws{access.Secure}://{access.HostPort}{`
|
|
rm -f agent
|
|
`}</pre>
|
|
}
|
|
if usageInputs.RemoteShells[CMD] {
|
|
<pre>{addSshKeys(CMD, usageInputs.SshKeys)}
|
|
curl --fail-with-body http{access.Secure}://{access.HostPort}/downloads/agent.exe > agent.exe{`
|
|
`}agent --id {usageInputs.Id} ws{access.Secure}://{access.HostPort}{`
|
|
del agent.exe
|
|
`}</pre>
|
|
}
|
|
if usageInputs.RemoteShells[POWERSHELL] {
|
|
<pre>{addSshKeys(POWERSHELL, usageInputs.SshKeys)}
|
|
curl --fail-with-body http{access.Secure}://{access.HostPort}/downloads/agent.exe > agent.exe{`
|
|
`}agent --id {usageInputs.Id} ws{access.Secure}://{access.HostPort}{`
|
|
del agent.exe
|
|
`}</pre>
|
|
}
|
|
|
|
<p>
|
|
The agent has more command-line options than shown here.
|
|
Download the agent and run it without arguments to
|
|
see all options.
|
|
</p>
|
|
<p>
|
|
<b>Tip</b>: Run the above command locally in a similar shell (for instance in a
|
|
docker container) then try to connect to the shell using the commands below. If that
|
|
works, then you are all set.
|
|
</p>
|
|
|
|
|
|
<h2>Connecting to the agent</h2>
|
|
|
|
<p>The embedded ssh server in the agent supports both ssh and sftp. The user name is fixed
|
|
at <code>{ access.Username }</code>. This is the user used to connect to the embedded
|
|
SSH server, after logging in however you will be running in a shell that is started
|
|
by the same user that started the agent.
|
|
</p>
|
|
|
|
<pre>{`
|
|
`}ssh -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.HostPort}/client/{usageInputs.Id}" { access.Username }{"@localhost"} {`
|
|
`}sftp -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.HostPort}/client/{usageInputs.Id}" { access.Username }{"@localhost"} {`
|
|
`}</pre>
|
|
|
|
<p>This requires the <code>wsproxy</code> utility which is available in the
|
|
<a href="downloads.html">downloads</a> section. This utility must be downloaded
|
|
only once since it is quite generic. It will warn you when it a newer version must
|
|
be downloaded.
|
|
</p>
|
|
|
|
<p>For other ssh clients that do not support the openssh ProxyCommand option, there is another
|
|
way to connect. In this method, a local port forwarder is started that forwards a local port
|
|
to the webserver. Then you can start an ssh client that connects to the local tcp port.
|
|
</p>
|
|
<pre>{`
|
|
`}ssh -oServerAliveInterval=10 -p 10000 { access.Username }{"@localhost"} {`
|
|
`}sftp -oServerAliveInterval=10 -p 10000 { access.Username }{"@localhost"} {`
|
|
`}</pre>
|
|
|
|
<p>This requires the <code>tcptows</code> utility which is available in the
|
|
<a href="downloads.html">downloads</a> section. The utility must be started beforehand
|
|
using:
|
|
</p>
|
|
<pre>{`
|
|
`}tcptows ws{access.Secure}://{access.HostPort}/client/{usageInputs.Id} {`
|
|
`}tcptows ws{access.Secure}://{access.HostPort}/client/{usageInputs.Id} {`
|
|
`}</pre>
|
|
|
|
<h2>Working with the agent</h2>
|
|
|
|
if usageInputs.RemoteShells[BASH] {
|
|
<pre>{`
|
|
# cd back to the agent directory
|
|
cd $agentdir
|
|
|
|
# prevent logout when last user exits
|
|
touch $agentdir/.hold
|
|
`}</pre>
|
|
|
|
}
|
|
if usageInputs.RemoteShells[CMD] {
|
|
<pre>{`
|
|
# cd back to the agent directory
|
|
cd %agentdir%
|
|
|
|
# prevent logout when last user exits
|
|
echo > %agentdir%\.hold
|
|
`}</pre>
|
|
}
|
|
if usageInputs.RemoteShells[POWERSHELL] {
|
|
<pre>{`
|
|
# cd back to the agent directory
|
|
cd $env:agentdir
|
|
|
|
# prevent logout when last user exits
|
|
$null > $env:agentdir\.hold
|
|
`}</pre>
|
|
}
|
|
if usageInputs.RemoteShells[CMD] || usageInputs.RemoteShells[POWERSHELL] {
|
|
<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>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
|
|
templ ShellUsage(access models.ConvergeAccess, usageInputs UsageInputs) {
|
|
<div>
|
|
@AgentUsage(access, usageInputs)
|
|
</div>
|
|
}
|
|
|
|
|
|
templ Usage(access models.ConvergeAccess) {
|
|
<div>
|
|
<h1>Usage</h1>
|
|
|
|
<style>
|
|
.minimal-width {
|
|
width: 1%;
|
|
white-space: nowrap;
|
|
}
|
|
</style>
|
|
|
|
<form id="inputs" novalidate
|
|
hx-post="../usage"
|
|
method="post"
|
|
hx-trigger="load,input delay:500ms,change,formdataloaded"
|
|
hx-target="#example-cli"
|
|
hx-on::after-request="saveFormToCookie()">
|
|
<table class="table table-responsive">
|
|
<tr>
|
|
<td class="minimal-width"><label for="rendez-vous-id">rendez-vous id</label></td>
|
|
<td>
|
|
<input id="rendez-vous-id" class="form-control" name="rendez-vous-id" type="text" maxlength="40"></input>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="minimal-width"><label for="ssh-keys">public ssh keys</label></td>
|
|
<td>
|
|
<textarea id="ssh-keys" class="form-control autogrow" name="ssh-keys" rows="5"></textarea>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="minimal-width"><label for="remote-shell">agent environment</label></td>
|
|
<td>
|
|
<input checked id="remote-shell-0" name="remote-shell" type="radio" value={BASH}> <label for="remote-shell-0">*.sh</label>
|
|
<input id="remote-shell-1" name="remote-shell" type="radio" value={CMD}> <label for="remote-shell-1">command prompt</label>
|
|
<input id="remote-shell-2" name="remote-shell" type="radio" value={POWERSHELL}> <label for="remote-shell-2">power shell</label>
|
|
</td>
|
|
</tr>
|
|
<!--tr>
|
|
<td class="minimal-width"><label for="local-shell">local environment</label></td>
|
|
<td>
|
|
<input id="checked local-shell-0" name="local-shell" type="radio" value={BASH}> <label for="local-shell-0">*.sh</label>
|
|
<input id="local-shell-1" name="local-shell" type="radio" value={CMD}> <label for="local-shell-1">command prompt</label>
|
|
<input id="local-shell-2" name="local-shell" type="radio" value={POWERSHELL}> <label for="local-shell-2">powershell</label>
|
|
</td>
|
|
</tr -->
|
|
</table>
|
|
</form>
|
|
|
|
<div id="example-cli">
|
|
</div>
|
|
<script>
|
|
function setCookie(name, value, days) {
|
|
let expires = "";
|
|
if (days) {
|
|
const date = new Date();
|
|
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
expires = "; expires=" + date.toUTCString();
|
|
}
|
|
document.cookie = name + "=" + encodeURIComponent(value) + expires + "; path=/";
|
|
}
|
|
|
|
function getCookie(name) {
|
|
const nameEQ = name + "=";
|
|
const ca = document.cookie.split(';');
|
|
for(let i=0; i < ca.length; i++) {
|
|
let c = ca[i];
|
|
while (c.charAt(0)==' ') c = c.substring(1,c.length);
|
|
if (c.indexOf(nameEQ) == 0) return decodeURIComponent(c.substring(nameEQ.length,c.length));
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function saveFormToCookie() {
|
|
const form = document.getElementById('inputs');
|
|
const formData = new FormData(form);
|
|
for (let element of form.elements) {
|
|
if (element.id) {
|
|
//console.log("Saving " + element.id)
|
|
if (element.type === 'checkbox') {
|
|
//console.log("Checkbox " + element.checked)
|
|
setCookie(element.id, element.checked ? 'true' : 'false', 7);
|
|
} else if (element.type === 'radio') {
|
|
if (element.checked) {
|
|
//console.log("Set cookie " + element.id + " " + element.value)
|
|
setCookie(element.id, element.value, 7);
|
|
} else {
|
|
//console.log("Set cookie " + element.id + " EMPTY")
|
|
setCookie(element.id, "", 7)
|
|
}
|
|
} else {
|
|
setCookie(element.id, element.value, 7);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function loadFormFromCookie() {
|
|
console.log("Load form from cookie")
|
|
const form = document.getElementById('inputs');
|
|
if (!form) {
|
|
return
|
|
}
|
|
updated = false
|
|
for (let element of form.elements) {
|
|
if (element.id) {
|
|
const value = getCookie(element.id);
|
|
//console.log("Loading " + element.id + " value: " + value)
|
|
if (value !== null) {
|
|
if (element.type === 'checkbox') {
|
|
newvalue = value === "true"
|
|
if (element.checked != newvalue) {
|
|
console.log("Setting " + element.id + " checked " + newvalue)
|
|
element.checked = newvalue
|
|
updated = true
|
|
}
|
|
} else if (element.type === 'radio') {
|
|
newvalue = element.value === value
|
|
if (element.checked != newvalue) {
|
|
console.log("Setting " + element.id + " selected " + newvalue)
|
|
element.checked = newvalue;
|
|
updated = true
|
|
}
|
|
} else {
|
|
if (element.value != value) {
|
|
console.log("Setting " + element.id + " " + element.value + " -> " + value)
|
|
element.value = value;
|
|
updated = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (updated) {
|
|
console.log("Sending htmx event to trigger form")
|
|
htmx.trigger(form, 'formdataloaded')
|
|
}
|
|
}
|
|
|
|
// Save form data to cookie on change
|
|
document.getElementById('inputs').addEventListener('change', saveFormToCookie);
|
|
|
|
document.body.addEventListener('htmx:load', function(event) {
|
|
console.log("htmx:load")
|
|
loadFormFromCookie();
|
|
});
|
|
|
|
document.body.addEventListener('htmx:afterSettle', function(event) {
|
|
console.log("htmx:afterSettle")
|
|
loadFormFromCookie();
|
|
});
|
|
|
|
// when hx-boost=false
|
|
// Load form data from cookie on page load
|
|
//document.addEventListener('DOMContentLoaded', loadFormFromCookie);
|
|
</script>
|
|
|
|
</div>
|
|
}
|
|
|
|
|
|
|
|
templ UsageTab(access models.ConvergeAccess) {
|
|
@BasePage(2) {
|
|
@Usage(access)
|
|
}
|
|
}
|