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