def uninstall_salt(self): print("Stopping salt-minion service", flush=True) r = subprocess.run(["sc", "stop", "salt-minion"], timeout=45, capture_output=True) sleep(15) # clean up any hung salt python procs pids = [] for proc in psutil.process_iter(): with proc.oneshot(): if proc.name() == "python.exe" and "salt" in proc.exe(): pids.append(proc.pid) for pid in pids: self.logger.debug(f"Killing salt process with pid {pid}") try: kill_proc(pid) except: continue print("Uninstalling existing salt-minion", flush=True) r = subprocess.run(["c:\\salt\\uninst.exe", "/S"], timeout=120, capture_output=True) sleep(20) try: shutil.rmtree("C:\\salt") sleep(1) os.system('rmdir /S /Q "{}"'.format("C:\\salt")) except Exception: pass print("Salt was removed", flush=True)
def fix_mesh(self): """ Mesh agent will randomly bug out and kill cpu usage This functions runs every hour as a scheduled task to solve that """ mesh = [ proc.info for proc in psutil.process_iter(attrs=["pid", "name"]) if "meshagent" in proc.info["name"].lower() ] if mesh: try: proc = psutil.Process(mesh[0]["pid"]) except psutil.NoSuchProcess: try: self._mesh_service_action("stop") sleep(3) self._mesh_service_action("start") finally: return cpu_usage = proc.cpu_percent(10) / psutil.cpu_count() if cpu_usage >= 18.0: self.logger.warning( f"Mesh agent cpu usage: {cpu_usage}%. Restarting..." ) self._mesh_service_action("stop") attempts = 0 while 1: svc = psutil.win_service_get("mesh agent") if svc.status() != "stopped": attempts += 1 sleep(1) else: attempts = 0 if attempts == 0 or attempts >= 30: break # sometimes stopping service doesn't kill the hung proc mesh2 = [ proc.info for proc in psutil.process_iter(attrs=["pid", "name"]) if "meshagent" in proc.info["name"].lower() ] if mesh2: pids = [] for proc in mesh2: pids.append(proc["pid"]) for pid in pids: kill_proc(pid) sleep(1) self._mesh_service_action("start")
def fix_salt(self, by_time=True): """ Script checks use salt-call, which for whatever reason becomes unstable after around 24 hours of uptime This leads to tons of hung python processes not being killed even with timeout set in salt's cmd.script module This function runs every hour as a scheduled task to clean up hung processes """ # strings that will be in the scriptchecks command line args # we check to see if any of these are in our long running processes # we don't want to kill salt's main process, just the ones that have # any of the following args script_checks = ( "win_agent.run_python_script", "salt-call", "userdefined", "salt://scripts", "cmd.script", ) pids = [] for proc in psutil.process_iter(): with proc.oneshot(): if proc.name() == "python.exe" or proc.name == "pythonw.exe": if "salt" in proc.exe(): if any(_ in proc.cmdline() for _ in script_checks): if by_time: # psutil returns the process creation time as seconds since epoch # convert it and the current local time now to utc so we can compare them proc_ct = dt.datetime.fromtimestamp( proc.create_time()).replace( tzinfo=dt.timezone.utc) utc_now = dt.datetime.now(dt.timezone.utc) # seconds since the process was created seconds = int( abs(utc_now - proc_ct).total_seconds()) # if process has been running for > 24 hours, need to kill it if seconds > 86_400: pids.append(proc.pid) else: # if we are uninstalling, don't care about time. # kill everything that's hung pids.append(proc.pid) if pids: this_proc = os.getpid() for pid in pids: if pid == this_proc: # don't kill myself continue self.logger.warning(f"Killing salt pid: {pid}") kill_proc(pid)
def execute(self, proc, log_path, timeout): timer = threading.Timer(timeout, lambda p: kill_proc(p), [proc]) timer.start() stdout, stderr = proc.communicate() ret = proc.returncode self.write_log(log_path, stdout, stderr, ret) timer.cancel()
def recover_mesh(self): self._mesh_service_action("stop") sleep(5) pids = [ proc.info for proc in psutil.process_iter(attrs=["pid", "name"]) if "meshagent" in proc.info["name"].lower() ] for pid in pids: kill_proc(pid["pid"]) mesh1 = os.path.join("C:\\Program Files\\Mesh Agent", "MeshAgent.exe") mesh2 = os.path.join(self.programdir, "meshagent.exe") if os.path.exists(mesh1): exe = mesh1 else: exe = mesh2 r = subprocess.run([exe, "-nodeidhex"], capture_output=True, timeout=30) if r.returncode != 0: self._mesh_service_action("start") return node_hex = r.stdout.decode("utf-8", errors="ignore").strip() if "not defined" in node_hex.lower(): self._mesh_service_action("start") return try: mesh_info = f"{self.astor.server}/api/v1/{self.astor.agentpk}/meshinfo/" resp = requests.get(mesh_info, headers=self.headers, timeout=15) except Exception: self._mesh_service_action("start") return if resp.status_code == 200 and isinstance(resp.json(), str): if node_hex != resp.json(): payload = {"nodeidhex": node_hex} requests.patch( mesh_info, json.dumps(payload), headers=self.headers, timeout=15 ) self._mesh_service_action("start")
def rig_schedule_task(self): # Check if enable or disable script. if self.config_file("ScriptEnable", "get") != "true": kill_proc(os.getppid()) else: # Check net connection, power off the rig if connection fail and fleg is true. if not connected(): dprint("Internet connection fail, rig will power off, " + str(self.rig_up_time)) if self.config_file("RigOffWhenConnectionFail", "get") == "true": # Ethos shoutdown os.system("sudo poweroff") else: dprint( "Can not power off rig, check RigOffWhenConnectionFail fleg" ) else: # Send notification mail, reset rig if gpu fail and flegs are true. self.get_rig_fields_from_stats_file() self.rig_min_hash = self.get_min_hash() if int(self.rig_min_hash) > int(self.rig_hash): dprint("RigName=" + self.rig_name + " RigMinHash=" + str(self.rig_min_hash) + " RigHash=" + str(self.rig_hash) + " HASH TEST FAIL.") if self.config_file("NotificationEnable", "get") == "true": subject = "Rig name=" + str( self.rig_name) + " Rig hash=" + str( self.rig_hash) + " Rig up time=" + str( self.rig_up_time) send_mail(self.rig_mail_addr, self.rig_mail_pass, self.notification_addr, subject, "Rig just reset: " + str(self.rig_up_time), str(self.stats_file)) else: dprint( "Can not send mail, check NotificationEnable fleg") self.reset_rig() else: dprint("RigName=" + self.rig_name + " RigMinHash=" + str(self.rig_min_hash) + " RigHash=" + str(self.rig_hash) + " HASH TEST PASS.")
def remove(self): kill_proc(self.process) return self.id
def install(self): # check for existing installation and exit if found try: tac = psutil.win_service_get("tacticalagent") except psutil.NoSuchProcess: pass else: print( "Found tacticalagent service. Please uninstall the existing Tactical Agent first before reinstalling.", flush=True, ) print( "If you're trying to perform an upgrade, do so from the RMM web interface.", flush=True, ) sys.exit(1) # generate the agent id try: r = subprocess.run(["wmic", "csproduct", "get", "uuid"], capture_output=True) wmic_id = r.stdout.decode("utf-8", errors="ignore").splitlines()[2].strip() except Exception: self.agent_id = f"{self.rand_string()}|{self.agent_hostname}" else: self.agent_id = f"{wmic_id}|{self.agent_hostname}" self.logger.debug(f"Agent ID: {self.agent_id}") sys.stdout.flush() # validate the url and get the salt master r = urlparse(self.api_url) if r.scheme != "https" and r.scheme != "http": print("ERROR: api url must contain https or http", flush=True) sys.exit(1) if validators.domain(r.netloc): self.salt_master = r.netloc # will match either ipv4 , or ipv4:port elif re.match(r"[0-9]+(?:\.[0-9]+){3}(:[0-9]+)?", r.netloc): if validators.ipv4(r.netloc): self.salt_master = r.netloc else: self.salt_master = r.netloc.split(":")[0] else: print("Error parsing api url, unable to get salt-master", flush=True) sys.exit(1) self.logger.debug(f"Salt master is: {self.salt_master}") sys.stdout.flush() # set the api base url self.api = f"{r.scheme}://{r.netloc}" # get the agent's token url = f"{self.api}/api/v1/token/" payload = {"agent_id": self.agent_id} try: r = requests.post(url, json.dumps(payload), headers=self.headers, timeout=15) except Exception as e: self.logger.error(e) sys.stdout.flush() print( "ERROR: Unable to contact the RMM. Please check your internet connection.", flush=True, ) sys.exit(1) if r.status_code == 401: print( "ERROR: Token has expired. Please generate a new one from the rmm.", flush=True, ) sys.exit(1) elif r.status_code != 200: e = json.loads(r.text)["error"] self.logger.error(e) sys.stdout.flush() sys.exit(1) else: self.agent_token = json.loads(r.text)["token"] if not self.local_salt: # download salt print("Downloading salt minion", flush=True) try: r = requests.get( "https://github.com/wh1te909/winagent/raw/master/bin/salt-minion-setup.exe", stream=True, timeout=900, ) except Exception as e: self.logger.error(e) sys.stdout.flush() print("ERROR: Timed out trying to download the salt-minion", flush=True) sys.exit(1) if r.status_code != 200: print( "ERROR: Something went wrong while downloading the salt-minion", flush=True, ) sys.exit(1) minion = os.path.join(self.programdir, "salt-minion-setup.exe") with open(minion, "wb") as f: for chunk in r.iter_content(chunk_size=1024): if chunk: f.write(chunk) del r else: try: shutil.copy2( self.local_salt, os.path.join(self.programdir, "salt-minion-setup.exe"), ) except Exception as e: print(e, flush=True) print( f"\nERROR: unable to copy the file {self.local_salt} to {self.programdir}", flush=True, ) sys.exit(1) else: minion = os.path.join(self.programdir, "salt-minion-setup.exe") if not self.local_mesh: # download mesh agent url = f"{self.api}/api/v1/getmeshexe/" try: r = requests.post(url, headers=self.headers, stream=True, timeout=400) except Exception as e: self.logger.error(e) sys.stdout.flush() print("ERROR: Timed out trying to download the Mesh Agent", flush=True) sys.exit(1) if r.status_code != 200: print( "ERROR: Something went wrong while downloading the Mesh Agent", flush=True, ) sys.exit(1) mesh = os.path.join(self.programdir, "meshagent.exe") with open(mesh, "wb") as f: for chunk in r.iter_content(chunk_size=1024): if chunk: f.write(chunk) del r else: try: shutil.copy2(self.local_mesh, os.path.join(self.programdir, "meshagent.exe")) except Exception as e: print(e, flush=True) print( f"\nERROR: unable to copy the file {self.local_mesh} to {self.programdir}", flush=True, ) sys.exit(1) else: mesh = os.path.join(self.programdir, "meshagent.exe") # check for existing mesh installations and remove mesh_exists = False mesh_one_dir = "C:\\Program Files\\Mesh Agent" mesh_two_dir = "C:\\Program Files\\mesh\\Mesh Agent" if os.path.exists(mesh_one_dir): mesh_exists = True mesh_cleanup_dir = mesh_one_dir elif os.path.exists(mesh_two_dir): mesh_exists = True mesh_cleanup_dir = mesh_two_dir if mesh_exists: print("Found existing Mesh Agent. Removing...", flush=True) try: subprocess.run(["sc", "stop", "mesh agent"], capture_output=True, timeout=30) sleep(5) except: pass mesh_pids = [] mesh_procs = [ p.info for p in psutil.process_iter(attrs=["pid", "name"]) if "meshagent" in p.info["name"].lower() ] if mesh_procs: for proc in mesh_procs: mesh_pids.append(proc["pid"]) if mesh_pids: for pid in mesh_pids: kill_proc(pid) try: r = subprocess.run([mesh, "-fulluninstall"], capture_output=True, timeout=60) except: print("Timed out trying to uninstall existing Mesh Agent", flush=True) if os.path.exists(mesh_cleanup_dir): try: shutil.rmtree(mesh_cleanup_dir) sleep(1) os.system('rmdir /S /Q "{}"'.format(mesh_cleanup_dir)) except: pass # install the mesh agent print("Installing mesh agent", flush=True) try: ret = subprocess.run([mesh, "-fullinstall"], capture_output=True, timeout=120) except: print("Timed out trying to install the Mesh Agent", flush=True) sleep(15) # meshcentral changed their installation path recently mesh_one = os.path.join(mesh_one_dir, "MeshAgent.exe") mesh_two = os.path.join(mesh_two_dir, "MeshAgent.exe") if os.path.exists(mesh_one): mesh_exe = mesh_one elif os.path.exists(mesh_two): mesh_exe = mesh_two else: mesh_exe = mesh mesh_attempts = 0 mesh_retries = 20 while 1: try: mesh_cmd = subprocess.run([mesh_exe, "-nodeidhex"], capture_output=True, timeout=30) mesh_node_id = mesh_cmd.stdout.decode("utf-8", errors="ignore").strip() except Exception: mesh_attempts += 1 print( f"Failed to get mesh node id: attempt {mesh_attempts} of {mesh_retries}", flush=True, ) sleep(5) else: if "not defined" in mesh_node_id.lower(): mesh_attempts += 1 print( f"Failed to get mesh node id: attempt {mesh_attempts} of {mesh_retries}", flush=True, ) sleep(5) else: mesh_attempts = 0 if mesh_attempts == 0: break elif mesh_attempts > mesh_retries: self.mesh_success = False mesh_node_id = "error installing meshagent" break self.mesh_node_id = mesh_node_id self.logger.debug(f"Mesh node id: {mesh_node_id}") sys.stdout.flush() print("Adding agent to dashboard", flush=True) url = f"{self.api}/api/v1/add/" payload = { "agent_id": self.agent_id, "hostname": self.agent_hostname, "client": self.client_id, "site": self.site_id, "mesh_node_id": self.mesh_node_id, "description": self.agent_desc, "monitoring_type": self.agent_type, } self.logger.debug(payload) sys.stdout.flush() try: r = requests.post(url, json.dumps(payload), headers=self.headers, timeout=60) except Exception as e: self.logger.error(e) sys.stdout.flush() sys.exit(1) if r.status_code != 200: print("Error adding agent to dashboard", flush=True) sys.exit(1) self.agent_pk = r.json()["pk"] self.salt_id = f"{self.agent_hostname}-{self.agent_pk}" try: with db: db.create_tables([AgentStorage]) AgentStorage( server=self.api, agentid=self.agent_id, mesh_node_id=self.mesh_node_id, token=self.agent_token, agentpk=self.agent_pk, salt_master=self.salt_master, salt_id=self.salt_id, ).save() except Exception as e: print(f"Error creating database: {e}", flush=True) sys.exit(1) # install salt, remove any existing installations first try: oldsalt = psutil.win_service_get("salt-minion") except psutil.NoSuchProcess: pass else: print("Found existing salt-minion. Removing", flush=True) self.uninstall_salt() print("Installing the salt-minion, this might take a while...", flush=True) salt_cmd = [ "salt-minion-setup.exe", "/S", "/custom-config=saltcustom", f"/master={self.salt_master}", f"/minion-name={self.salt_id}", "/start-minion=1", ] install_salt = subprocess.run(salt_cmd, cwd=self.programdir, shell=True) # give time for salt to fully install since the above command returns immmediately sleep(60) # accept the salt key on the master url = f"{self.api}/api/v1/acceptsaltkey/" payload = {"saltid": self.salt_id} accept_attempts = 0 salt_retries = 20 while 1: try: r = requests.post(url, json.dumps(payload), headers=self.headers, timeout=30) except Exception as e: logger.debug(e) sys.stdout.flush() accept_attempts += 1 sleep(5) else: if r.status_code != 200: accept_attempts += 1 print( f"Salt-key was not accepted: attempt {accept_attempts} of {salt_retries}", flush=True, ) sleep(5) else: accept_attempts = 0 if accept_attempts == 0: print("Salt-key was accepted!", flush=True) break elif accept_attempts > salt_retries: self.accept_success = False break print("Waiting for salt to sync with the master", flush=True) sleep(10) # wait for salt to sync # sync our custom salt modules print("Syncing custom modules", flush=True) url = f"{self.api}/api/v1/firstinstall/" payload = {"pk": self.agent_pk} sync_attempts = 0 sync_retries = 20 while 1: try: r = requests.post(url, json.dumps(payload), headers=self.headers, timeout=30) except Exception as e: self.logger.debug(e) sys.stdout.flush() sync_attempts += 1 sleep(5) else: if r.status_code != 200: sync_attempts += 1 print( f"Syncing modules failed: attempt {sync_attempts} of {sync_retries}", flush=True, ) sleep(5) else: sync_attempts = 0 if sync_attempts == 0: print("Modules were synced!", flush=True) break elif sync_attempts > sync_retries: self.sync_success = False break sleep(10) # wait a bit for modules to fully sync # create the scheduled tasks from agent import WindowsAgent try: agent = WindowsAgent() agent.create_fix_salt_task() agent.create_fix_mesh_task() except Exception as e: self.logger.debug(e) sys.stdout.flush() # remove services if they exists try: tac = psutil.win_service_get("tacticalagent") except psutil.NoSuchProcess: pass else: print("Found tacticalagent service. Removing...", flush=True) subprocess.run([self.nssm, "stop", "tacticalagent"], capture_output=True) subprocess.run([self.nssm, "remove", "tacticalagent", "confirm"], capture_output=True) try: chk = psutil.win_service_get("checkrunner") except psutil.NoSuchProcess: pass else: print("Found checkrunner service. Removing...", flush=True) subprocess.run([self.nssm, "stop", "checkrunner"], capture_output=True) subprocess.run([self.nssm, "remove", "checkrunner", "confirm"], capture_output=True) # install the windows services print("Installing services...", flush=True) svc_commands = [ [ self.nssm, "install", "tacticalagent", self.tacticalrmm, "-m", "winagentsvc", ], [ self.nssm, "set", "tacticalagent", "DisplayName", r"Tactical RMM Agent" ], [ self.nssm, "set", "tacticalagent", "Description", r"Tactical RMM Agent" ], [self.nssm, "start", "tacticalagent"], [ self.nssm, "install", "checkrunner", self.tacticalrmm, "-m", "checkrunner", ], [ self.nssm, "set", "checkrunner", "DisplayName", r"Tactical RMM Check Runner", ], [ self.nssm, "set", "checkrunner", "Description", r"Tactical RMM Check Runner", ], [self.nssm, "start", "checkrunner"], ] for cmd in svc_commands: subprocess.run(cmd, capture_output=True) if self.disable_power: print("Disabling sleep/hibernate...", flush=True) try: disable_sleep_hibernate() except: pass if self.enable_rdp: print("Enabling RDP...", flush=True) try: enable_rdp() except: pass if self.enable_ping: print("Enabling ping...", flush=True) try: enable_ping() except: pass # finish up if not self.accept_success: print("-" * 75, flush=True) print("ERROR: The RMM was unable to accept the salt minion.", flush=True) print("Salt may not have been properly installed.", flush=True) print("Try running the following command on the rmm:", flush=True) print(f"sudo salt-key -y -a '{self.salt_id}'", flush=True) print("-" * 75, flush=True) if not self.sync_success: print("-" * 75, flush=True) print("Unable to sync salt modules.", flush=True) print("Salt may not have been properly installed.", flush=True) print("-" * 75, flush=True) if not self.mesh_success: print("-" * 75, flush=True) print("The Mesh Agent was not installed properly.", flush=True) print("Some features will not work.", flush=True) print("-" * 75, flush=True) if self.accept_success and self.sync_success and self.mesh_success: print("Installation was successfull!", flush=True) print( "Allow a few minutes for the agent to properly display in the RMM", flush=True, ) else: print("*****Installation finished with errors.*****", flush=True)
def install(self): # generate the agent id try: r = subprocess.run(["wmic", "csproduct", "get", "uuid"], capture_output=True) wmic_id = r.stdout.decode().splitlines()[2].strip() except Exception: self.agent_id = f"{self.rand_string()}|{self.agent_hostname}" else: self.agent_id = f"{wmic_id}|{self.agent_hostname}" # validate the url and get the salt master r = urlparse(self.api_url) if r.scheme != "https" and r.scheme != "http": print("api url must contain https or http") raise SystemExit() if validators.domain(r.netloc): self.salt_master = r.netloc # will match either ipv4 , or ipv4:port elif re.match(r"[0-9]+(?:\.[0-9]+){3}(:[0-9]+)?", r.netloc): if validators.ipv4(r.netloc): self.salt_master = r.netloc else: self.salt_master = r.netloc.split(":")[0] else: print("Error parsing api url") raise SystemExit() # set the api base url self.api = f"{r.scheme}://{r.netloc}" # get the agent's token url = f"{self.api}/api/v1/token/" payload = {"agent_id": self.agent_id} r = requests.post(url, json.dumps(payload), headers=self.headers) if r.status_code == 401: print("Token has expired. Please generate a new one from the rmm.") raise SystemExit() elif r.status_code != 200: e = json.loads(r.text)["error"] print(e) raise SystemExit() else: self.agent_token = json.loads(r.text)["token"] # download salt print("Downloading salt minion") r = requests.get( "https://github.com/wh1te909/winagent/raw/master/bin/salt-minion-setup.exe", stream=True, ) if r.status_code != 200: print("Unable to download salt-minion") raise SystemExit() minion = os.path.join(self.programdir, "salt-minion-setup.exe") with open(minion, "wb") as f: for chunk in r.iter_content(chunk_size=1024): if chunk: f.write(chunk) del r # download mesh agent url = f"{self.api}/api/v1/getmeshexe/" r = requests.post(url, headers=self.headers, stream=True) if r.status_code != 200: print("Unable to download meshagent.") print( "Please refer to the readme for instructions on how to upload it." ) raise SystemExit() mesh = os.path.join(self.programdir, "meshagent.exe") with open(mesh, "wb") as f: for chunk in r.iter_content(chunk_size=1024): if chunk: f.write(chunk) del r # check for existing mesh installations and remove mesh_exists = False mesh_one_dir = "C:\\Program Files\\Mesh Agent" mesh_two_dir = "C:\\Program Files\\mesh\\Mesh Agent" if os.path.exists(mesh_one_dir): mesh_exists = True mesh_cleanup_dir = mesh_one_dir elif os.path.exists(mesh_two_dir): mesh_exists = True mesh_cleanup_dir = mesh_two_dir if mesh_exists: print("Found existing Mesh Agent. Removing...") try: subprocess.run(["sc", "stop", "mesh agent"], capture_output=True, timeout=30) sleep(5) except: pass mesh_pids = [] mesh_procs = [ p.info for p in psutil.process_iter(attrs=["pid", "name"]) if "meshagent" in p.info["name"].lower() ] if mesh_procs: for proc in mesh_procs: mesh_pids.append(proc["pid"]) if mesh_pids: for pid in mesh_pids: kill_proc(pid) r = subprocess.run([mesh, "-fulluninstall"], capture_output=True, timeout=60) if os.path.exists(mesh_cleanup_dir): try: shutil.rmtree(mesh_cleanup_dir) sleep(1) os.system('rmdir /S /Q "{}"'.format(mesh_cleanup_dir)) except: pass # install the mesh agent print("Installing mesh agent") ret = subprocess.run([mesh, "-fullinstall"], capture_output=True) sleep(10) # meshcentral changed their installation path recently mesh_one = os.path.join(mesh_one_dir, "MeshAgent.exe") mesh_two = os.path.join(mesh_two_dir, "MeshAgent.exe") if os.path.exists(mesh_one): mesh_exe = mesh_one elif os.path.exists(mesh_two): mesh_exe = mesh_two else: mesh_exe = mesh mesh_attempts = 0 while 1: try: mesh_cmd = subprocess.run([mesh_exe, "-nodeidhex"], capture_output=True) mesh_node_id = mesh_cmd.stdout.decode().strip() except Exception: mesh_attempts += 1 sleep(5) else: if "not defined" in mesh_node_id.lower(): sleep(5) mesh_attempts += 1 else: mesh_attempts = 0 if mesh_attempts == 0: break elif mesh_attempts > 20: self.mesh_success = False mesh_node_id = "error installing meshagent" break self.mesh_node_id = mesh_node_id # add the agent to the dashboard print("Adding agent to dashboard") url = f"{self.api}/api/v1/add/" payload = { "agent_id": self.agent_id, "hostname": self.agent_hostname, "client": self.client_id, "site": self.site_id, "mesh_node_id": self.mesh_node_id, "description": self.agent_desc, "monitoring_type": self.agent_type, } r = requests.post(url, json.dumps(payload), headers=self.headers) if r.status_code != 200: print("Error adding agent to dashboard") raise SystemExit() self.agent_pk = r.json()["pk"] self.salt_id = f"{self.agent_hostname}-{self.agent_pk}" try: with db: db.create_tables([AgentStorage]) AgentStorage( server=self.api, agentid=self.agent_id, mesh_node_id=self.mesh_node_id, token=self.agent_token, agentpk=self.agent_pk, salt_master=self.salt_master, salt_id=self.salt_id, ).save() except Exception as e: print(f"Error creating database: {e}") raise SystemExit() # install salt print("Installing salt") salt_cmd = [ "salt-minion-setup.exe", "/S", "/custom-config=saltcustom", f"/master={self.salt_master}", f"/minion-name={self.salt_id}", "/start-minion=1", ] install_salt = subprocess.run(salt_cmd, cwd=self.programdir, shell=True) sleep(15) # wait for salt to register on the master # accept the salt key on the master url = f"{self.api}/api/v1/acceptsaltkey/" payload = {"saltid": self.salt_id} accept_attempts = 0 while 1: r = requests.post(url, json.dumps(payload), headers=self.headers) if r.status_code != 200: accept_attempts += 1 sleep(5) else: accept_attempts = 0 if accept_attempts == 0: break else: if accept_attempts > 20: self.accept_success = False break sleep(15) # wait for salt to start # sync our custom salt modules url = f"{self.api}/api/v1/firstinstall/" payload = {"pk": self.agent_pk} sync_attempts = 0 while 1: r = requests.post(url, json.dumps(payload), headers=self.headers) if r.status_code != 200: sync_attempts += 1 sleep(5) else: sync_attempts = 0 if sync_attempts == 0: break else: if sync_attempts > 20: self.sync_success = False break sleep(10) # wait a bit for modules to fully sync # create the scheduled tasks from agent import WindowsAgent agent = WindowsAgent() agent.create_fix_salt_task() agent.create_fix_mesh_task() # remove services if they exists try: tac = psutil.win_service_get("tacticalagent") except psutil.NoSuchProcess: pass else: print("Found tacticalagent service. Removing...") subprocess.run([self.nssm, "stop", "tacticalagent"]) subprocess.run([self.nssm, "remove", "tacticalagent", "confirm"]) try: chk = psutil.win_service_get("checkrunner") except psutil.NoSuchProcess: pass else: print("Found checkrunner service. Removing...") subprocess.run([self.nssm, "stop", "checkrunner"]) subprocess.run([self.nssm, "remove", "checkrunner", "confirm"]) # install the windows services # winagent subprocess.run([ self.nssm, "install", "tacticalagent", self.tacticalrmm, "-m", "winagentsvc", ]) subprocess.run([ self.nssm, "set", "tacticalagent", "DisplayName", r"Tactical RMM Agent" ]) subprocess.run([ self.nssm, "set", "tacticalagent", "Description", r"Tactical RMM Agent", ]) subprocess.run([self.nssm, "start", "tacticalagent"]) # checkrunner subprocess.run([ self.nssm, "install", "checkrunner", self.tacticalrmm, "-m", "checkrunner", ]) subprocess.run([ self.nssm, "set", "checkrunner", "DisplayName", r"Tactical RMM Check Runner", ]) subprocess.run([ self.nssm, "set", "checkrunner", "Description", r"Tactical RMM Check Runner", ]) subprocess.run([self.nssm, "start", "checkrunner"]) # finish up if not self.accept_success: print("The RMM was unable to accept the salt minion.") print("Run the following command on the rmm:") print(f"sudo salt-key -y -a '{self.salt_id}'") if not self.sync_success: print("Unable to sync salt modules.") print("Salt may not have been properly installed.") if not self.mesh_success: print("The Mesh Agent was not installed properly.") print("Some features will not work.") if self.accept_success and self.sync_success and self.mesh_success: print("Installation was successfull.") else: print("Installation finished with errors.")