finish install

This commit is contained in:
wh1te909 2020-10-14 20:34:41 -07:00
parent 04d6e02974
commit 5c30b0e766
8 changed files with 476 additions and 70 deletions

View File

@ -34,12 +34,14 @@ type WindowsAgent struct {
DB
Host
ProgramDir string
EXE string
SystemDrive string
SaltCall string
Nssm string
SaltMinion string
SaltInstaller string
MeshInstaller string
MeshSVC string
PyBin string
Headers map[string]string
Logger *logrus.Logger
@ -52,6 +54,7 @@ func New(logger *logrus.Logger, version string) *WindowsAgent {
host, _ := ps.Host()
info := host.Info()
pd := filepath.Join(os.Getenv("ProgramFiles"), "TacticalAgent")
exe := filepath.Join(pd, "tacticalrmm.exe")
dbFile := filepath.Join(pd, "agentdb.db")
sd := os.Getenv("SystemDrive")
pybin := filepath.Join(sd, "\\salt", "bin", "python.exe")
@ -82,12 +85,14 @@ func New(logger *logrus.Logger, version string) *WindowsAgent {
Timezone: info.Timezone,
},
ProgramDir: pd,
EXE: exe,
SystemDrive: sd,
SaltCall: sc,
Nssm: nssm,
SaltMinion: saltexe,
SaltInstaller: saltinstaller,
MeshInstaller: mesh,
MeshSVC: "mesh agent",
PyBin: pybin,
Headers: headers,
Logger: logger,
@ -356,18 +361,24 @@ func (a *WindowsAgent) GetDisks() []Disk {
}
// CMDShell mimics python's `subprocess.run(shell=True)`
//
// Context timeout won't work here since cmd.exe spawns a child process
// and golang's defer will only kill the parent process which will already
//
// for example, passing `ping 8.8.8.8 -t` here will run forever even with a timeout set
//
// Passing `detached` will also ensure the function returns immediately and will never hang,
// rather continue to run in the background
func CMDShell(command string, detached bool) (output [2]string, e error) {
var outb, errb bytes.Buffer
func CMDShell(cmdArgs []string, command string, timeout int, detached bool) (output [2]string, e error) {
var (
outb bytes.Buffer
errb bytes.Buffer
cmd *exec.Cmd
timedOut bool = false
)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
if len(cmdArgs) > 0 && command == "" {
cmdArgs = append([]string{"/C"}, cmdArgs...)
cmd = exec.Command("cmd.exe", cmdArgs...)
} else {
cmd = exec.Command("cmd.exe", "/C", command)
}
cmd := exec.Command("cmd.exe", "/C", command)
// https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
if detached {
cmd.SysProcAttr = &windows.SysProcAttr{
@ -376,10 +387,26 @@ func CMDShell(command string, detached bool) (output [2]string, e error) {
}
cmd.Stdout = &outb
cmd.Stderr = &errb
err := cmd.Run()
err := cmd.Start()
pid := int32(cmd.Process.Pid)
go func(p int32) {
<-ctx.Done()
_ = KillProc(p)
timedOut = true
}(pid)
err = cmd.Wait()
if timedOut {
return [2]string{outb.String(), errb.String()}, ctx.Err()
}
if err != nil {
return [2]string{"", ""}, fmt.Errorf("%s: %s", err, errb.String())
return [2]string{outb.String(), errb.String()}, err
}
return [2]string{outb.String(), errb.String()}, nil
@ -413,9 +440,9 @@ func CMD(exe string, args []string, timeout int, detached bool) (output [2]strin
// EnablePing enables ping
func EnablePing() {
fmt.Println("Enabling ping...")
args := make([]string, 0)
cmd := `netsh advfirewall firewall add rule name="ICMP Allow incoming V4 echo request" protocol=icmpv4:8,any dir=in action=allow`
_, err := CMDShell(cmd, false)
_, err := CMDShell(args, cmd, 10, false)
if err != nil {
fmt.Println(err)
}
@ -423,7 +450,6 @@ func EnablePing() {
// EnableRDP enables Remote Desktop
func EnableRDP() {
fmt.Println("Enabling RDP...")
k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Terminal Server`, registry.ALL_ACCESS)
if err != nil {
fmt.Println(err)
@ -435,8 +461,9 @@ func EnableRDP() {
fmt.Println(err)
}
args := make([]string, 0)
cmd := `netsh advfirewall firewall set rule group="remote desktop" new enable=Yes`
_, cerr := CMDShell(cmd, false)
_, cerr := CMDShell(args, cmd, 10, false)
if cerr != nil {
fmt.Println(cerr)
}
@ -444,7 +471,6 @@ func EnableRDP() {
// DisableSleepHibernate disables sleep and hibernate
func DisableSleepHibernate() {
fmt.Println("Disabling sleep/hibernate...")
k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Session Manager\Power`, registry.ALL_ACCESS)
if err != nil {
fmt.Println(err)
@ -456,21 +482,23 @@ func DisableSleepHibernate() {
fmt.Println(err)
}
args := make([]string, 0)
var wg sync.WaitGroup
currents := []string{"ac", "dc"}
for _, i := range currents {
wg.Add(1)
go func(c string) {
defer wg.Done()
CMDShell(fmt.Sprintf("powercfg /set%svalueindex scheme_current sub_buttons lidaction 0", c), false)
CMDShell(fmt.Sprintf("powercfg /x -standby-timeout-%s 0", c), false)
CMDShell(fmt.Sprintf("powercfg /x -hibernate-timeout-%s 0", c), false)
CMDShell(fmt.Sprintf("powercfg /x -disk-timeout-%s 0", c), false)
CMDShell(fmt.Sprintf("powercfg /x -monitor-timeout-%s 0", c), false)
_, _ = CMDShell(args, fmt.Sprintf("powercfg /set%svalueindex scheme_current sub_buttons lidaction 0", c), 5, false)
_, _ = CMDShell(args, fmt.Sprintf("powercfg /x -standby-timeout-%s 0", c), 5, false)
_, _ = CMDShell(args, fmt.Sprintf("powercfg /x -hibernate-timeout-%s 0", c), 5, false)
_, _ = CMDShell(args, fmt.Sprintf("powercfg /x -disk-timeout-%s 0", c), 5, false)
_, _ = CMDShell(args, fmt.Sprintf("powercfg /x -monitor-timeout-%s 0", c), 5, false)
}(i)
}
wg.Wait()
CMDShell("powercfg -S SCHEME_CURRENT", false)
_, _ = CMDShell(args, "powercfg -S SCHEME_CURRENT", 5, false)
}
// LoggedOnUser returns active logged on console user
@ -564,18 +592,7 @@ func (a *WindowsAgent) RecoverSalt() {
a.Logger.Debugln("Salt recovery completed on", a.Hostname)
}
//RecoverMesh recovers mesh agent
func (a *WindowsAgent) RecoverMesh() {
meshSVC := "mesh agent"
a.Logger.Debugln("Attempting mesh recovery on", a.Hostname)
defer CMD("sc.exe", []string{"start", meshSVC}, 20, false)
args := []string{"stop", meshSVC}
CMD("sc.exe", args, 45, false)
WaitForService(meshSVC, "stopped", 5)
a.ForceKillMesh()
var meshexe string
func (a *WindowsAgent) getMeshEXE() (meshexe string) {
mesh1 := filepath.Join(os.Getenv("ProgramFiles"), "Mesh Agent", "MeshAgent.exe")
mesh2 := filepath.Join(a.ProgramDir, a.MeshInstaller)
if FileExists(mesh1) {
@ -583,6 +600,20 @@ func (a *WindowsAgent) RecoverMesh() {
} else {
meshexe = mesh2
}
return meshexe
}
//RecoverMesh recovers mesh agent
func (a *WindowsAgent) RecoverMesh() {
a.Logger.Debugln("Attempting mesh recovery on", a.Hostname)
defer CMD("sc.exe", []string{"start", a.MeshSVC}, 20, false)
args := []string{"stop", a.MeshSVC}
CMD("sc.exe", args, 45, false)
WaitForService(a.MeshSVC, "stopped", 5)
a.ForceKillMesh()
meshexe := a.getMeshEXE()
out, err := CMD(meshexe, []string{"-nodeidhex"}, 10, false)
if err != nil {
@ -640,10 +671,7 @@ func (a *WindowsAgent) RecoverMesh() {
func (a *WindowsAgent) RecoverCMD(command string) {
a.Logger.Debugln("Attempting shell recovery on", a.Hostname)
a.Logger.Debugln(command)
cmd := exec.Command("cmd.exe", "/C", command)
if err := cmd.Run(); err != nil {
a.Logger.Debugln(err)
}
_, _ = CMDShell([]string{}, command, 18000, true)
}
// ShowStatus prints windows service status

View File

@ -11,6 +11,7 @@ import (
"time"
"github.com/go-resty/resty/v2"
"github.com/gonutz/w32"
)
type Installer struct {
@ -32,6 +33,8 @@ type Installer struct {
}
func (a *WindowsAgent) Install(i *Installer) {
a.checkExistingAndRemove()
i.Headers = map[string]string{
"content-type": "application/json",
"Authorization": fmt.Sprintf("Token %s", i.Token),
@ -45,14 +48,14 @@ func (a *WindowsAgent) Install(i *Installer) {
}
if u.Scheme != "https" && u.Scheme != "http" {
a.installerMsg("Invalid URL (must contain https or http)\nInstallation Failed", "error")
a.installerMsg("Invalid URL (must contain https or http)", "error")
}
i.SaltMaster = u.Host
a.Logger.Debugln("Salt Master:", i.SaltMaster)
baseURL := u.Scheme + "://" + u.Host
a.Logger.Debugln(baseURL)
a.Logger.Debugln("Base URL:", baseURL)
minion := filepath.Join(a.ProgramDir, a.SaltInstaller)
a.Logger.Debugln("Salt Minion:", minion)
@ -60,7 +63,7 @@ func (a *WindowsAgent) Install(i *Installer) {
rClient := resty.New()
rClient.SetCloseConnection(true)
rClient.SetTimeout(i.Timeout * time.Second)
rClient.SetDebug(a.Debug)
//rClient.SetDebug(a.Debug)
// download or copy the salt-minion-setup.exe
saltMin := filepath.Join(a.ProgramDir, a.SaltInstaller)
@ -135,33 +138,41 @@ func (a *WindowsAgent) Install(i *Installer) {
agentToken := r.Result().(*TokenResp).Token
// install mesh agent
out, err := CMD(mesh, []string{"-fullinstall"}, int(60), false)
if err != nil {
a.installerMsg(fmt.Sprintf("Failed to install mesh agent: %s", err.Error()), "error")
a.Logger.Infoln("Installing mesh agent...")
a.Logger.Debugln("Mesh agent:", mesh)
meshOut, meshErr := CMD(mesh, []string{"-fullinstall"}, int(60), false)
if meshErr != nil {
a.installerMsg(fmt.Sprintf("Failed to install mesh agent: %s", meshErr.Error()), "error")
}
if out[1] != "" {
a.installerMsg(fmt.Sprintf("Failed to install mesh agent: %s", out[1]), "error")
if meshOut[1] != "" {
a.installerMsg(fmt.Sprintf("Failed to install mesh agent: %s", meshOut[1]), "error")
}
WaitForService("mesh agent", "running", 10)
fmt.Println(meshOut)
a.Logger.Debugln("Waiting for mesh service to be running")
WaitForService(a.MeshSVC, "running", 15)
a.Logger.Debugln("Mesh service is running")
a.Logger.Debugln("Sleeping for 10")
time.Sleep(10 * time.Second)
meshSuccess := false
var meshNodeID string
for !meshSuccess {
pMesh, err := CMD(mesh, []string{"-nodeidhex"}, int(30), false)
if err != nil {
a.Logger.Errorln(err)
a.Logger.Debugln("Getting mesh node id hex")
pMesh, pErr := CMD(mesh, []string{"-nodeidhex"}, int(30), false)
if pErr != nil {
a.Logger.Errorln(pErr)
time.Sleep(5 * time.Second)
continue
}
if out[1] != "" {
a.Logger.Errorln(out[1])
if pMesh[1] != "" {
a.Logger.Errorln(pMesh[1])
time.Sleep(5 * time.Second)
continue
}
meshNodeID = StripAll(pMesh[0])
a.Logger.Debugln("Node id hex:", meshNodeID)
if strings.Contains(strings.ToLower(meshNodeID), "not defined") {
a.Logger.Errorln(meshNodeID)
time.Sleep(5 * time.Second)
@ -197,7 +208,9 @@ func (a *WindowsAgent) Install(i *Installer) {
agentPK := r.Result().(*NewAgentResp).AgentPK
saltID := r.Result().(*NewAgentResp).SaltID
fmt.Println(agentToken, agentPK, saltID)
a.Logger.Debugln("Agent token:", agentToken)
a.Logger.Debugln("Agent PK:", agentPK)
a.Logger.Debugln("Salt ID:", saltID)
// create the database
db, err := sql.Open("sqlite3", filepath.Join(a.ProgramDir, "agentdb.db"))
@ -240,6 +253,156 @@ func (a *WindowsAgent) Install(i *Installer) {
// refresh our agent with new values
a = New(a.Logger, a.Version)
// install salt
a.Logger.Debugln("changing dir to", a.ProgramDir)
cdErr := os.Chdir(a.ProgramDir)
if cdErr != nil {
a.installerMsg(cdErr.Error(), "error")
}
a.Logger.Infoln("Installing the salt-minion, this might take a while...")
saltInstallArgs := []string{
a.SaltInstaller,
"/S",
"/custom-config=saltcustom",
fmt.Sprintf("/master=%s", i.SaltMaster),
fmt.Sprintf("/minion-name=%s", saltID),
"/start-minion=1",
}
a.Logger.Debugln("Installing salt with:", saltInstallArgs)
_, saltErr := CMDShell(saltInstallArgs, "", int(i.Timeout), false)
if saltErr != nil {
a.installerMsg(fmt.Sprintf("Unable to install salt: %s", saltErr.Error()), "error")
}
a.Logger.Debugln("Waiting for salt-minion service enter the running state")
WaitForService("salt-minion", "running", 30)
a.Logger.Debugln("Salt-minion is running")
_, serr := WinServiceGet("salt-minion")
if serr != nil {
a.installerMsg("Salt installation failed\nCheck the log file in c:\\salt\\var\\log\\salt\\minion", "error")
}
time.Sleep(5 * time.Second)
// set new headers, no longer knox auth...use agent auth
rClient.SetHeaders(a.Headers)
// accept the salt key on the rmm
a.Logger.Debugln("Registering salt with the RMM")
acceptPayload := map[string]string{"saltid": saltID, "agent_id": a.AgentID}
acceptAttempts := 0
acceptRetries := 20
for {
r, err := rClient.R().SetBody(acceptPayload).Post(fmt.Sprintf("%s/api/v2/saltminion/", baseURL))
if err != nil {
a.Logger.Debugln(err)
acceptAttempts++
time.Sleep(5 * time.Second)
}
if r.StatusCode() != 200 {
a.Logger.Debugln(r.String())
acceptAttempts++
time.Sleep(5 * time.Second)
} else {
acceptAttempts = 0
}
if acceptAttempts == 0 {
a.Logger.Debugln(r.String())
break
} else if acceptAttempts >= acceptRetries {
a.installerMsg("Unable to register salt with the RMM\nInstallation failed.", "error")
}
}
time.Sleep(10 * time.Second)
// sync salt modules
a.Logger.Debugln("Syncing salt modules")
syncPayload := map[string]string{"agent_id": a.AgentID}
syncAttempts := 0
syncRetries := 20
for {
r, err := rClient.R().SetBody(syncPayload).Patch(fmt.Sprintf("%s/api/v2/saltminion/", baseURL))
if err != nil {
a.Logger.Debugln(err)
syncAttempts++
time.Sleep(5 * time.Second)
}
if r.StatusCode() != 200 {
a.Logger.Debugln(r.String())
syncAttempts++
time.Sleep(5 * time.Second)
} else {
syncAttempts = 0
}
if syncAttempts == 0 {
a.Logger.Debugln(r.String())
break
} else if syncAttempts >= syncRetries {
a.installerMsg("Unable to sync salt modules\nInstallation failed.", "error")
}
}
// send wmi sysinfo
a.Logger.Debugln("Getting sysinfo with WMI")
a.GetWMI()
// remove existing services if exist
services := []string{"tacticalagent", "checkrunner"}
for _, svc := range services {
_, err := WinServiceGet(svc)
if err == nil {
a.Logger.Debugln(fmt.Sprintf("Found existing %s service. Removing", svc))
_, _ = CMD(a.Nssm, []string{"stop", svc}, 30, false)
_, _ = CMD(a.Nssm, []string{"remove", svc, "confirm"}, 30, false)
}
}
a.Logger.Infoln("Installing services...")
svcCommands := [8][]string{
// winagentsvc
{"install", "tacticalagent", a.EXE, "-m", "winagentsvc"},
{"set", "tacticalagent", "DisplayName", "Tactical RMM Agent"},
{"set", "tacticalagent", "Description", "Tactical RMM Agent"},
{"start", "tacticalagent"},
//checkrunner
{"install", "checkrunner", a.EXE, "-m", "checkrunner"},
{"set", "checkrunner", "DisplayName", "Tactical RMM Check Runner"},
{"set", "checkrunner", "Description", "Tactical RMM Check Runner"},
{"start", "checkrunner"},
}
for _, s := range svcCommands {
a.Logger.Debugln(s)
_, nssmErr := CMD(a.Nssm, s, 15, false)
if nssmErr != nil {
a.installerMsg(nssmErr.Error(), "error")
}
}
if i.Power {
a.Logger.Infoln("Disabling sleep/hibernate...")
DisableSleepHibernate()
}
if i.Ping {
a.Logger.Infoln("Enabling ping...")
EnablePing()
}
if i.RDP {
a.Logger.Infoln("Enabling RDP...")
EnableRDP()
}
a.installerMsg("Installation was successfull!\nAllow a few minutes for the agent to properly display in the RMM", "info")
}
func copyFile(src, dst string) error {
@ -261,3 +424,29 @@ func copyFile(src, dst string) error {
}
return nil
}
func (a *WindowsAgent) checkExistingAndRemove() {
installedMesh := filepath.Join(a.ProgramDir, "Mesh Agent", "MeshAgent.exe")
installedSalt := filepath.Join(a.SystemDrive, "\\salt", "uninst.exe")
agentDB := filepath.Join(a.ProgramDir, "agentdb.db")
if FileExists(installedMesh) || FileExists(installedSalt) || FileExists(agentDB) {
tacUninst := filepath.Join(a.ProgramDir, "unins000.exe")
tacUninstArgs := []string{tacUninst, "/VERYSILENT", "/SUPPRESSMSGBOXES"}
window := w32.GetForegroundWindow()
if window != 0 {
var handle w32.HWND
msg := "Existing installation found\nClick OK to remove, then re-run the installer.\nClick Cancel to abort."
action := w32.MessageBox(handle, msg, "Tactical RMM", w32.MB_OKCANCEL|w32.MB_ICONWARNING)
if action == w32.IDOK {
_, _ = CMDShell(tacUninstArgs, "", 60, true)
w32.MessageBox(handle, "Uninstall finished", "Tactical RMM", w32.MB_OK|w32.MB_ICONINFORMATION)
}
} else {
fmt.Println("Existing installation found and must be removed before attempting to reinstall.")
fmt.Println("Run the following command to uninstall, and then re-run this installer.")
fmt.Printf("\"%s\" %s %s", tacUninstArgs[0], tacUninstArgs[1], tacUninstArgs[2])
}
os.Exit(0)
}
}

View File

@ -29,8 +29,8 @@ type HelloPatch struct {
BootTime int64 `json:"boot_time"`
}
// RunAsService tacticalagent windows nssm service
func (a *WindowsAgent) RunAsService() {
// WinAgentSvc tacticalagent windows nssm service
func (a *WindowsAgent) WinAgentSvc() {
a.Logger.Infoln("Agent service started")
var data map[string]interface{}
var sleep int

BIN
build/onit.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

9
build/saltcustom Normal file
View File

@ -0,0 +1,9 @@
master_tries: -1
keysize: 2048
multiprocessing: True
output: json
grains_cache: True
grains_refresh_every: 180
grains_cache_expiration: 86400
enable_fqdn_grains: False
log_level: error

87
build/setup-x86.iss Normal file
View File

@ -0,0 +1,87 @@
#define MyAppName "Tactical RMM Agent"
#define MyAppVersion "1.0.0"
#define MyAppPublisher "Tactical Techs"
#define MyAppURL "https://github.com/wh1te909"
#define MyAppExeName "tacticalrmm.exe"
#define NSSM "nssm-x86.exe"
#define MESHEXE "meshagent-x86.exe"
#define SALTUNINSTALL "{sd}\salt\uninst.exe"
#define SALTDIR "{sd}\salt"
#define MESHDIR "{sd}\Program Files\Mesh Agent"
[Setup]
AppId={{0D34D278-5FAF-4159-A4A0-4E2D2C08139D}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppName}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName="{sd}\Program Files\TacticalAgent"
DisableDirPage=yes
DisableProgramGroupPage=yes
OutputBaseFilename=winagent-v{#MyAppVersion}-x86
SetupIconFile=C:\Users\Public\Documents\rmmagent\build\onit.ico
WizardSmallImageFile=C:\Users\Public\Documents\rmmagent\build\onit.bmp
UninstallDisplayIcon={app}\{#MyAppExeName}
Compression=lzma
SolidCompression=yes
WizardStyle=modern
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "C:\Users\Public\Documents\rmmagent\tacticalrmm.exe"; DestDir: "{app}"; Flags: ignoreversion; BeforeInstall: StopServices;
Source: "C:\Users\Public\Documents\rmmagent\build\nssm-x86.exe"; DestDir: "{app}"; Flags: ignoreversion;
Source: "C:\Users\Public\Documents\rmmagent\build\saltcustom"; DestDir: "{app}"; Flags: ignoreversion; AfterInstall: StartServices;
[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent runascurrentuser
[UninstallRun]
Filename: "{app}\{#NSSM}"; Parameters: "stop tacticalagent"; RunOnceId: "stoptacagent";
Filename: "{app}\{#NSSM}"; Parameters: "remove tacticalagent confirm"; RunOnceId: "removetacagent";
Filename: "{app}\{#NSSM}"; Parameters: "stop checkrunner"; RunOnceId: "stopcheckrun";
Filename: "{app}\{#NSSM}"; Parameters: "remove checkrunner confirm"; RunOnceId: "removecheckrun";
;Filename: "{app}\{#MyAppExeName}"; Parameters: "-m cleanup"; RunOnceId: "cleanuprm";
Filename: "{#SALTUNINSTALL}"; Parameters: "/S"; RunOnceId: "saltrm";
Filename: "{app}\{#MESHEXE}"; Parameters: "-fulluninstall"; RunOnceId: "meshrm";
[UninstallDelete]
Type: filesandordirs; Name: "{app}";
Type: filesandordirs; Name: "{#SALTDIR}";
Type: filesandordirs; Name: "{#MESHDIR}";
[Code]
procedure StopServices();
var
ResultCode: Integer;
StopTactical: string;
StopCheckrunner: string;
begin
StopTactical := ExpandConstant(' /c "{app}\{#NSSM}"' + ' stop tacticalagent && ping 127.0.0.1 -n 5');
StopCheckrunner := ExpandConstant(' /c "{app}\{#NSSM}"' + ' stop checkrunner && ping 127.0.0.1 -n 5');
Exec('cmd.exe', StopTactical, '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Exec('cmd.exe', StopCheckrunner, '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
procedure StartServices();
var
ResultCode: Integer;
StartTactical: string;
StartCheckrunner: string;
begin
StartTactical := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start tacticalagent && ping 127.0.0.1 -n 7');
StartCheckrunner := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start checkrunner && ping 127.0.0.1 -n 3');
Exec('cmd.exe', StartTactical, '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Exec('cmd.exe', StartCheckrunner, '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;

87
build/setup.iss Normal file
View File

@ -0,0 +1,87 @@
#define MyAppName "Tactical RMM Agent"
#define MyAppVersion "1.0.0"
#define MyAppPublisher "Tactical Techs"
#define MyAppURL "https://github.com/wh1te909"
#define MyAppExeName "tacticalrmm.exe"
#define NSSM "nssm.exe"
#define MESHEXE "meshagent.exe"
#define SALTUNINSTALL "{sd}\salt\uninst.exe"
#define SALTDIR "{sd}\salt"
#define MESHDIR "{sd}\Program Files\Mesh Agent"
[Setup]
AppId={{0D34D278-5FAF-4159-A4A0-4E2D2C08139D}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppName}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName="{sd}\Program Files\TacticalAgent"
DisableDirPage=yes
DisableProgramGroupPage=yes
OutputBaseFilename=winagent-v{#MyAppVersion}
SetupIconFile=C:\Users\Public\Documents\rmmagent\build\onit.ico
WizardSmallImageFile=C:\Users\Public\Documents\rmmagent\build\onit.bmp
UninstallDisplayIcon={app}\{#MyAppExeName}
Compression=lzma
SolidCompression=yes
WizardStyle=modern
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "C:\Users\Public\Documents\rmmagent\tacticalrmm.exe"; DestDir: "{app}"; Flags: ignoreversion; BeforeInstall: StopServices;
Source: "C:\Users\Public\Documents\rmmagent\build\nssm.exe"; DestDir: "{app}"; Flags: ignoreversion;
Source: "C:\Users\Public\Documents\rmmagent\build\saltcustom"; DestDir: "{app}"; Flags: ignoreversion; AfterInstall: StartServices;
[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent runascurrentuser
[UninstallRun]
Filename: "{app}\{#NSSM}"; Parameters: "stop tacticalagent"; RunOnceId: "stoptacagent";
Filename: "{app}\{#NSSM}"; Parameters: "remove tacticalagent confirm"; RunOnceId: "removetacagent";
Filename: "{app}\{#NSSM}"; Parameters: "stop checkrunner"; RunOnceId: "stopcheckrun";
Filename: "{app}\{#NSSM}"; Parameters: "remove checkrunner confirm"; RunOnceId: "removecheckrun";
;Filename: "{app}\{#MyAppExeName}"; Parameters: "-m cleanup"; RunOnceId: "cleanuprm";
Filename: "{#SALTUNINSTALL}"; Parameters: "/S"; RunOnceId: "saltrm";
Filename: "{app}\{#MESHEXE}"; Parameters: "-fulluninstall"; RunOnceId: "meshrm";
[UninstallDelete]
Type: filesandordirs; Name: "{app}";
Type: filesandordirs; Name: "{#SALTDIR}";
Type: filesandordirs; Name: "{#MESHDIR}";
[Code]
procedure StopServices();
var
ResultCode: Integer;
StopTactical: string;
StopCheckrunner: string;
begin
StopTactical := ExpandConstant(' /c "{app}\{#NSSM}"' + ' stop tacticalagent && ping 127.0.0.1 -n 5');
StopCheckrunner := ExpandConstant(' /c "{app}\{#NSSM}"' + ' stop checkrunner && ping 127.0.0.1 -n 5');
Exec('cmd.exe', StopTactical, '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Exec('cmd.exe', StopCheckrunner, '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
procedure StartServices();
var
ResultCode: Integer;
StartTactical: string;
StartCheckrunner: string;
begin
StartTactical := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start tacticalagent && ping 127.0.0.1 -n 7');
StartCheckrunner := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start checkrunner && ping 127.0.0.1 -n 3');
Exec('cmd.exe', StartTactical, '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Exec('cmd.exe', StartCheckrunner, '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;

30
main.go
View File

@ -1,18 +1,11 @@
//go:generate goversioninfo -64
package main
// cross compile from linux for windows
// apt install build-essential gcc-multilib gcc-mingw-w64-x86-64 gcc-mingw-w64-i686
// 64 bit: CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ GOOS=windows GOARCH=amd64 go build -o tacticalrmm.exe
// 32 bit: CGO_ENABLED=1 CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ GOOS=windows GOARCH=386 go build -o tacticalrmm-x86.exe
// building 32 bit from windows from git bash
// env CGO_ENABLED=1 CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ GOARCH=386 go build -o tacticalrmm-x86.exe
import (
"flag"
"fmt"
"os"
"runtime"
"github.com/sirupsen/logrus"
"github.com/wh1te909/rmmagent/agent"
@ -35,7 +28,7 @@ func main() {
siteID := flag.Int("site-id", 0, "Site ID")
timeout := flag.Duration("timeout", 900, "Installer timeout (seconds)")
desc := flag.String("desc", hostname, "Agent's Description")
atype := flag.String("agent-type", "server", "Server or Workstation")
atype := flag.String("agent-type", "server", "server or workstation")
token := flag.String("auth", "", "Token")
power := flag.Bool("power", false, "Disable sleep/hibernate")
rdp := flag.Bool("rdp", false, "Enable RDP")
@ -64,7 +57,15 @@ func main() {
case "checkrunner":
a.CheckRunner()
case "winagentsvc":
a.RunAsService()
a.WinAgentSvc()
case "runchecks":
a.RunChecks()
case "sysinfo":
a.GetWMI()
case "recoversalt":
a.RecoverSalt()
case "recovermesh":
a.RecoverMesh()
case "install":
log.SetOutput(os.Stdout)
if *api == "" || *clientID == 0 || *siteID == 0 || *token == "" {
@ -108,6 +109,11 @@ func setupLogging(level *string, to *string) {
}
func installUsage() {
u := `Usage: tacticalrmm.exe -m install -api <https://api.example.com> -client-id X -site-id X -auth <TOKEN>`
fmt.Println(u)
switch runtime.GOOS {
case "windows":
u := `Usage: tacticalrmm.exe -m install -api <https://api.example.com> -client-id X -site-id X -auth <TOKEN>`
fmt.Println(u)
case "linux":
// todo
}
}