Esempio n. 1
0
    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)
Esempio n. 2
0
    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")
Esempio n. 3
0
    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)
Esempio n. 4
0
    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()
Esempio n. 5
0
    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")
Esempio n. 6
0
 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.")
Esempio n. 7
0
 def remove(self):
     kill_proc(self.process)
     return self.id
Esempio n. 8
0
    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)
Esempio n. 9
0
    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.")