improved installer options
This commit is contained in:
parent
7dd9658213
commit
413e0fab9f
|
@ -32,3 +32,5 @@ app.ini
|
|||
create_services.py
|
||||
gen_random.py
|
||||
sync_salt_modules.py
|
||||
rmm-*.exe
|
||||
rmm-*.ps1
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from loguru import logger
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import zlib
|
||||
import json
|
||||
import base64
|
||||
|
@ -8,6 +10,7 @@ from packaging import version as pyver
|
|||
|
||||
from django.conf import settings
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.http import HttpResponse
|
||||
|
||||
from rest_framework.decorators import (
|
||||
api_view,
|
||||
|
@ -306,10 +309,110 @@ def install_agent(request):
|
|||
user=request.user, expiry=dt.timedelta(hours=request.data["expires"])
|
||||
)
|
||||
|
||||
if request.data["installMethod"] == "exe":
|
||||
go_bin = "/usr/local/rmmgo/go/bin/go"
|
||||
|
||||
if not os.path.exists(go_bin):
|
||||
return Response("nogolang", status=status.HTTP_409_CONFLICT)
|
||||
|
||||
api = request.data["api"]
|
||||
atype = request.data["agenttype"]
|
||||
rdp = request.data["rdp"]
|
||||
ping = request.data["ping"]
|
||||
power = request.data["power"]
|
||||
|
||||
file_name = f"rmm-{''.join(client.client.lower().split())}-{''.join(site.site.lower().split())}.exe"
|
||||
exe = os.path.join(settings.EXE_DIR, file_name)
|
||||
|
||||
if os.path.exists(exe):
|
||||
os.remove(exe)
|
||||
|
||||
cmd = [
|
||||
"env",
|
||||
"GOOS=windows",
|
||||
"GOARCH=amd64",
|
||||
go_bin,
|
||||
"build",
|
||||
f"-ldflags=\"-X 'main.Version={version}'",
|
||||
f"-X 'main.Api={api}'",
|
||||
f"-X 'main.Client={client.pk}'",
|
||||
f"-X 'main.Site={site.pk}'",
|
||||
f"-X 'main.Atype={atype}'",
|
||||
f"-X 'main.Rdp={rdp}'",
|
||||
f"-X 'main.Ping={ping}'",
|
||||
f"-X 'main.Power={power}'",
|
||||
f"-X 'main.Token={token}'\"",
|
||||
"-o",
|
||||
exe,
|
||||
os.path.join(settings.BASE_DIR, "core/installer.go"),
|
||||
]
|
||||
|
||||
r = subprocess.run(" ".join(cmd), shell=True)
|
||||
|
||||
if settings.DEBUG:
|
||||
with open(exe, "rb") as f:
|
||||
response = HttpResponse(
|
||||
f.read(),
|
||||
content_type="application/vnd.microsoft.portable-executable",
|
||||
)
|
||||
response["Content-Disposition"] = f"inline; filename={file_name}"
|
||||
return response
|
||||
else:
|
||||
response = HttpResponse()
|
||||
response["Content-Disposition"] = f"attachment; filename={file_name}"
|
||||
response["X-Accel-Redirect"] = f"/private/exe/{file_name}"
|
||||
return response
|
||||
|
||||
elif request.data["installMethod"] == "manual":
|
||||
resp = {"token": token, "client": client.pk, "site": site.pk}
|
||||
resp["showextra"] = True if pyver.parse(version) > pyver.parse("0.10.0") else False
|
||||
resp["showextra"] = (
|
||||
True if pyver.parse(version) > pyver.parse("0.10.0") else False
|
||||
)
|
||||
return Response(resp)
|
||||
|
||||
elif request.data["installMethod"] == "powershell":
|
||||
|
||||
ps = os.path.join(settings.BASE_DIR, "core/installer.ps1")
|
||||
|
||||
with open(ps, "r") as f:
|
||||
text = f.read()
|
||||
|
||||
replace_dict = {
|
||||
"versionchange": request.data["exe"],
|
||||
"clientchange": str(client.pk),
|
||||
"sitechange": str(site.pk),
|
||||
"apichange": request.data["api"],
|
||||
"atypechange": request.data["agenttype"],
|
||||
"powerchange": str(request.data["power"]),
|
||||
"rdpchange": str(request.data["rdp"]),
|
||||
"pingchange": str(request.data["ping"]),
|
||||
"downloadchange": request.data["download"],
|
||||
"tokenchange": token,
|
||||
}
|
||||
|
||||
for i, j in replace_dict.items():
|
||||
text = text.replace(i, j)
|
||||
|
||||
file_name = os.path.join(settings.EXE_DIR, "rmm-installer.ps1")
|
||||
if os.path.exists(file_name):
|
||||
os.remove(file_name)
|
||||
|
||||
with open(file_name, "w") as f:
|
||||
f.write(text)
|
||||
|
||||
if settings.DEBUG:
|
||||
with open(file_name, "r") as f:
|
||||
response = HttpResponse(f.read(), content_type="text/plain",)
|
||||
response["Content-Disposition"] = f"inline; filename={file_name}"
|
||||
return response
|
||||
else:
|
||||
response = HttpResponse()
|
||||
response["Content-Disposition"] = f"attachment; filename={file_name}"
|
||||
response["X-Accel-Redirect"] = f"/private/exe/{file_name}"
|
||||
return response
|
||||
|
||||
return Response(text)
|
||||
|
||||
|
||||
@api_view(["POST"])
|
||||
def recover(request):
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
"flag"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var Version string
|
||||
var Api string
|
||||
var Client string
|
||||
var Site string
|
||||
var Atype string
|
||||
var Power string
|
||||
var Rdp string
|
||||
var Ping string
|
||||
var Token string
|
||||
|
||||
func downloadAgent(filepath string, url string) (err error) {
|
||||
|
||||
out, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("Bad response: %s", resp.Status)
|
||||
}
|
||||
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
func main() {
|
||||
|
||||
debugLog := flag.String("log", "", "Verbose output")
|
||||
localSalt := flag.String("local-salt", "", "Use local salt minion")
|
||||
localMesh := flag.String("local-mesh", "", "Use local mesh agent")
|
||||
flag.Parse()
|
||||
|
||||
agentBinary := fmt.Sprintf("C:\\Windows\\Temp\\winagent-v%s.exe", Version)
|
||||
|
||||
|
||||
url := fmt.Sprintf("https://github.com/wh1te909/winagent/releases/download/v%s/winagent-v%s.exe", Version, Version)
|
||||
fmt.Println("Downloading agent...")
|
||||
dl := downloadAgent(agentBinary, url)
|
||||
if dl != nil {
|
||||
fmt.Println("ERROR: unable to download agent from", url)
|
||||
fmt.Println(dl)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("Extracting files...")
|
||||
winagentCmd := exec.Command(agentBinary, "/VERYSILENT", "/SUPPRESSMSGBOXES")
|
||||
err := winagentCmd.Run()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
time.Sleep(20 * time.Second)
|
||||
|
||||
tacrmm := "C:\\Program Files\\TacticalAgent\\tacticalrmm.exe"
|
||||
|
||||
cmdArgs := []string{
|
||||
"-m", "install", "--api", Api, "--client-id",
|
||||
Client, "--site-id", Site, "--agent-type", Atype,
|
||||
"--power", Power, "--rdp", Rdp, "--ping", Ping,
|
||||
"--auth", Token,
|
||||
}
|
||||
|
||||
if strings.TrimSpace(*debugLog) == "DEBUG" {
|
||||
cmdArgs = append(cmdArgs, "--log", "DEBUG")
|
||||
}
|
||||
|
||||
if len(strings.TrimSpace(*localSalt)) != 0 {
|
||||
cmdArgs = append(cmdArgs, "--local-salt", *localSalt)
|
||||
}
|
||||
|
||||
if len(strings.TrimSpace(*localMesh)) != 0 {
|
||||
cmdArgs = append(cmdArgs, "--local-mesh", *localMesh)
|
||||
}
|
||||
|
||||
fmt.Println("Installation starting.")
|
||||
cmd := exec.Command(tacrmm, cmdArgs...)
|
||||
|
||||
cmdReader, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(cmdReader)
|
||||
go func() {
|
||||
for scanner.Scan() {
|
||||
fmt.Println(scanner.Text())
|
||||
}
|
||||
}()
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
# author: https://github.com/bradhawkins85
|
||||
$installversion = 'versionchange'
|
||||
$api = '"apichange"'
|
||||
$clientid = 'clientchange'
|
||||
$siteid = 'sitechange'
|
||||
$agenttype = '"atypechange"'
|
||||
$power = 'powerchange'
|
||||
$rdp = 'rdpchange'
|
||||
$ping = 'pingchange'
|
||||
$auth = '"tokenchange"'
|
||||
$downloadlink = 'downloadchange'
|
||||
|
||||
$serviceName = 'tacticalagent'
|
||||
If (Get-Service $serviceName -ErrorAction SilentlyContinue) {
|
||||
write-host ('Tactical RMM Is Already Installed')
|
||||
} Else {
|
||||
$OutPath = $env:TMP
|
||||
$output = $installversion
|
||||
|
||||
Try
|
||||
{
|
||||
Invoke-WebRequest -Uri $downloadlink -OutFile $OutPath\$output
|
||||
Start-Process -FilePath $OutPath\$output -ArgumentList ('/VERYSILENT /SUPPRESSMSGBOXES') -Wait
|
||||
write-host ('Extracting...')
|
||||
Start-Sleep -s 20
|
||||
Start-Process -FilePath "C:\Program Files\TacticalAgent\tacticalrmm.exe" -ArgumentList ('-m install --api ', "$api", '--client-id', $clientid, '--site-id', $siteid, '--agent-type', "$agenttype", '--power', $power, '--rdp', $rdp, '--ping', $ping, '--auth', "$auth") -Wait
|
||||
exit 0
|
||||
}
|
||||
Catch
|
||||
{
|
||||
$ErrorMessage = $_.Exception.Message
|
||||
$FailedItem = $_.Exception.ItemName
|
||||
Write-Error -Message "$ErrorMessage $FailedItem"
|
||||
exit 1
|
||||
}
|
||||
Finally
|
||||
{
|
||||
Remove-Item -Path $OutPath\$output
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from time import sleep
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
@ -81,3 +83,21 @@ class Command(BaseCommand):
|
|||
self.stdout.write("\n")
|
||||
self.stdout.write(self.style.ERROR("*" * 100))
|
||||
input("Press Enter to continue...")
|
||||
|
||||
# install go
|
||||
if not os.path.exists("/usr/local/rmmgo/"):
|
||||
self.stdout.write(self.style.SUCCESS("Installing golang"))
|
||||
subprocess.run("sudo mkdir -p /usr/local/rmmgo", shell=True)
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
r = subprocess.run(
|
||||
f"wget https://golang.org/dl/go1.15.linux-amd64.tar.gz -P {tmpdir}",
|
||||
shell=True,
|
||||
)
|
||||
|
||||
gotar = os.path.join(tmpdir, "go1.15.linux-amd64.tar.gz")
|
||||
|
||||
subprocess.run(f"tar -xzf {gotar} -C {tmpdir}", shell=True)
|
||||
|
||||
gofolder = os.path.join(tmpdir, "go")
|
||||
subprocess.run(f"sudo mv {gofolder} /usr/local/rmmgo/", shell=True)
|
||||
shutil.rmtree(tmpdir)
|
||||
|
|
32
install.sh
32
install.sh
|
@ -1,5 +1,21 @@
|
|||
#!/bin/bash
|
||||
|
||||
SCRIPT_VERSION="1"
|
||||
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/develop/install.sh'
|
||||
|
||||
TMP_FILE=$(mktemp -p "" "rmminstall_XXXXXXXXXX")
|
||||
curl -s -L "${SCRIPT_URL}" > ${TMP_FILE}
|
||||
NEW_VER=$(grep "^SCRIPT_VERSION" "$TMP_FILE" | awk -F'[="]' '{print $3}')
|
||||
|
||||
if [ "${SCRIPT_VERSION}" \< "${NEW_VER}" ]; then
|
||||
printf >&2 "${YELLOW}A newer version of this installer script is available.${NC}\n"
|
||||
printf >&2 "${YELLOW}Please download the latest version from ${GREEN}${SCRIPT_URL}${YELLOW} and re-run.${NC}\n"
|
||||
rm -f $TMP_FILE
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f $TMP_FILE
|
||||
|
||||
UBU20=$(grep 20.04 "/etc/"*"release")
|
||||
if ! [[ $UBU20 ]]; then
|
||||
echo -ne "\033[0;31mThis script will only work on Ubuntu 20.04\e[0m\n"
|
||||
|
@ -107,12 +123,24 @@ do
|
|||
sudo certbot certonly --manual -d *.${rootdomain} --agree-tos --no-bootstrap --manual-public-ip-logging-ok --preferred-challenges dns -m ${letsemail} --no-eff-email
|
||||
done
|
||||
|
||||
|
||||
print_green 'Creating saltapi user'
|
||||
|
||||
sudo adduser --no-create-home --disabled-password --gecos "" saltapi
|
||||
echo "saltapi:${SALTPW}" | sudo chpasswd
|
||||
|
||||
print_green 'Installing golang'
|
||||
|
||||
sudo apt install -y curl wget
|
||||
|
||||
sudo mkdir -p /usr/local/rmmgo
|
||||
go_tmp=$(mktemp -d -t rmmgo-XXXXXXXXXX)
|
||||
wget https://golang.org/dl/go1.15.linux-amd64.tar.gz -P ${go_tmp}
|
||||
|
||||
tar -xzf ${go_tmp}/go1.15.linux-amd64.tar.gz -C ${go_tmp}
|
||||
|
||||
sudo mv ${go_tmp}/go /usr/local/rmmgo/
|
||||
rm -rf ${go_tmp}
|
||||
|
||||
print_green 'Installing Nginx'
|
||||
|
||||
sudo apt install -y nginx
|
||||
|
@ -120,8 +148,6 @@ sudo systemctl stop nginx
|
|||
|
||||
print_green 'Installing NodeJS'
|
||||
|
||||
sudo apt install -y curl wget
|
||||
|
||||
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
|
||||
sudo apt update
|
||||
sudo apt install -y gcc g++ make
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<q-card style="min-width: 70vw">
|
||||
<q-card-section class="row">
|
||||
<q-card-actions align="left">
|
||||
<div class="text-h6">Installation Instructions</div>
|
||||
<div class="text-h6">Manual Installation Instructions</div>
|
||||
</q-card-actions>
|
||||
<q-space />
|
||||
<q-card-actions align="right">
|
||||
|
|
|
@ -59,8 +59,16 @@
|
|||
Select Version
|
||||
<q-select dense outlined v-model="version" :options="Object.values(versions)" />
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
Installation Method
|
||||
<div class="q-gutter-sm">
|
||||
<q-radio v-model="installMethod" val="exe" label="Dynamically generated exe" />
|
||||
<q-radio v-model="installMethod" val="powershell" label="Powershell" />
|
||||
<q-radio v-model="installMethod" val="manual" label="Manual" />
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-actions align="left">
|
||||
<q-btn label="Show Install Command" color="primary" type="submit" />
|
||||
<q-btn :label="installButtonText" color="primary" type="submit" />
|
||||
</q-card-actions>
|
||||
</q-form>
|
||||
</q-card-section>
|
||||
|
@ -88,13 +96,14 @@ export default {
|
|||
site: null,
|
||||
version: null,
|
||||
agenttype: "server",
|
||||
expires: 1,
|
||||
expires: 720,
|
||||
power: false,
|
||||
rdp: false,
|
||||
ping: false,
|
||||
github: [],
|
||||
showAgentDownload: false,
|
||||
info: {},
|
||||
installMethod: "exe",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -129,8 +138,26 @@ export default {
|
|||
const release = this.github.filter(i => i.name === this.version)[0];
|
||||
const download = release.assets[0].browser_download_url;
|
||||
const exe = `${release.name}.exe`;
|
||||
const clientStripped = this.client.replace(/\s/g, "").toLowerCase();
|
||||
const siteStripped = this.site.replace(/\s/g, "").toLowerCase();
|
||||
|
||||
const data = { client: this.client, site: this.site, expires: this.expires, version: this.version };
|
||||
const data = {
|
||||
installMethod: this.installMethod,
|
||||
client: this.client,
|
||||
site: this.site,
|
||||
expires: this.expires,
|
||||
version: this.version,
|
||||
agenttype: this.agenttype,
|
||||
power: this.power ? 1 : 0,
|
||||
rdp: this.rdp ? 1 : 0,
|
||||
ping: this.ping ? 1 : 0,
|
||||
api,
|
||||
release,
|
||||
download,
|
||||
exe,
|
||||
};
|
||||
|
||||
if (this.installMethod === "manual") {
|
||||
axios.post("/agents/installagent/", data).then(r => {
|
||||
this.info = {
|
||||
exe,
|
||||
|
@ -142,9 +169,60 @@ export default {
|
|||
rdp: this.rdp ? 1 : 0,
|
||||
ping: this.ping ? 1 : 0,
|
||||
data: r.data,
|
||||
installMethod: this.installMethod,
|
||||
};
|
||||
this.showAgentDownload = true;
|
||||
});
|
||||
} else if (this.installMethod === "exe") {
|
||||
this.$q.loading.show({ message: "Generating executable..." });
|
||||
|
||||
const fileName = `rmm-${clientStripped}-${siteStripped}.exe`;
|
||||
this.$axios
|
||||
.post("/agents/installagent/", data, { responseType: "blob" })
|
||||
.then(r => {
|
||||
this.$q.loading.hide();
|
||||
const blob = new Blob([r.data], { type: "application/vnd.microsoft.portable-executable" });
|
||||
let link = document.createElement("a");
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = fileName;
|
||||
link.click();
|
||||
this.showDLMessage();
|
||||
})
|
||||
.catch(e => {
|
||||
let err;
|
||||
switch (e.response.status) {
|
||||
case 409:
|
||||
err = "Golang is not installed";
|
||||
break;
|
||||
case 412:
|
||||
err = "Golang build failed";
|
||||
break;
|
||||
default:
|
||||
err = "Something went wrong";
|
||||
}
|
||||
this.$q.loading.hide();
|
||||
this.notifyError(err, 4000);
|
||||
});
|
||||
} else if (this.installMethod === "powershell") {
|
||||
const psName = `rmm-${clientStripped}-${siteStripped}.ps1`;
|
||||
this.$axios
|
||||
.post("/agents/installagent/", data, { responseType: "blob" })
|
||||
.then(({ data }) => {
|
||||
const blob = new Blob([data], { type: "text/plain" });
|
||||
let link = document.createElement("a");
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = psName;
|
||||
link.click();
|
||||
this.showDLMessage();
|
||||
})
|
||||
.catch(e => this.notifyError("Something went wrong"));
|
||||
}
|
||||
},
|
||||
showDLMessage() {
|
||||
this.$q.dialog({
|
||||
message: `Installer for ${this.client}, ${this.site} (${this.agenttype}) will now be downloaded.
|
||||
You may reuse this installer for ${this.expires} hours before it expires. No command line arguments are needed.`,
|
||||
});
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
@ -154,6 +232,22 @@ export default {
|
|||
return this.tree[this.client];
|
||||
}
|
||||
},
|
||||
installButtonText() {
|
||||
let text;
|
||||
switch (this.installMethod) {
|
||||
case "exe":
|
||||
text = "Generate and download exe";
|
||||
break;
|
||||
case "powershell":
|
||||
text = "Download powershell script";
|
||||
break;
|
||||
case "manual":
|
||||
text = "Show manual installation instructions";
|
||||
break;
|
||||
}
|
||||
|
||||
return text;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getClientsSites();
|
||||
|
|
Loading…
Reference in New Issue