converge/pkg/server/templates/usage.templ
Erik Brakkee db44a20d5a large parts of the usage is now dynamic.
Still need to generate instructions for authorized keys. A lot of troubleshooting for the form to cookie persistence.
2024-08-03 18:29:14 +02:00

301 lines
10 KiB
Plaintext

package templates
import "converge/pkg/models"
templ AgentUsage(access models.ConvergeAccess, shells map[string]bool, 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 shells[BASH] {
<pre>{`
`}curl http{access.Secure}://{access.HostPort}/static/agent > agent{`
chmod 755 agent
`}./agent --id {usageInputs.Id} ws{access.Secure}://{access.HostPort}{`
rm -f agent
`}</pre>
}
if shells[CMD] || shells[POWERSHELL] {
<pre>{`
`}curl http{access.Secure}://{access.HostPort}/static/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>
<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>
<h2>Working with the agent</h2>
if shells[BASH] {
<pre>{`
# cd back to the agent directory
cd $agentdir
# prevent logout when last user exits
touch $agentdir/.hold
`}</pre>
}
if shells[CMD] {
<pre>{`
# cd back to the agent directory
cd %agentdir%
# prevent logout when last user exits
echo > %agentdir%\.hold
`}</pre>
}
if shells[POWERSHELL] {
<pre>{`
# cd back to the agent directory
cd $env:agentdir
# prevent logout when last user exits
$null > $env:agentdir\.hold
`}</pre>
}
if shells[CMD] || shells[POWERSHELL] {
<p>windows</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>
}
</div>
}
templ LocalShellUsage(access models.ConvergeAccess, shells map[string]bool, usageInput UsageInputs) {
<div>
if shells[BASH] {
<p>bash</p>
}
if shells[CMD] {
<p>cmd</p>
}
if shells[POWERSHELL] {
<p>powershell</p>
}
</div>
}
templ ShellUsage(access models.ConvergeAccess, usageInputs UsageInputs) {
<div>
@AgentUsage(access, usageInputs.RemoteShells, usageInputs)
</div>
@LocalShellUsage(access, usageInputs.LocalShells, usageInputs)
}
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>
<pre> {`
`}# linux {`
`}echo "ssh-rsa dkddkdkkk a@b.c" > .authorized_keys {`
`}echo "ssh-rsa adfadjfdf d@e.f" >> .authorized_keys {`
`} {`
`}# windows {`
`}echo ssh-rsa dkddkdkkk a@b.c > .authorized_keys {`
`}echo ssh-rsa adfadjfdf d@e.f >> .authorized_keys
</pre>
<p>
Note that on windows you should not used quotes.
</p>
<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)
}
}