Compare commits
	
		
			36 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e413c0264a | ||
|  | f88e7f898c | ||
|  | d07bd4a6db | ||
|  | fb34c099d5 | ||
|  | 1d2ee56a15 | ||
|  | 86665f7f09 | ||
|  | 0d2b4af986 | ||
|  | dc2b2eeb9f | ||
|  | e5dbb66d53 | ||
|  | 3474b1c471 | ||
|  | 3886de5b7c | ||
|  | 2b3cec06b3 | ||
|  | 8536754d14 | ||
|  | 1f36235801 | ||
|  | a4194b14f9 | ||
|  | 2dcc629d9d | ||
|  | 98ddadc6bc | ||
|  | f6e47b7383 | ||
|  | f073ddc906 | ||
|  | 3e00631925 | ||
|  | 9b7ac58562 | ||
|  | f242ddd801 | ||
|  | c129886fe2 | ||
|  | f577e814cf | ||
|  | c860a0cedd | ||
|  | ae7e28e492 | ||
|  | 90a63234ad | ||
|  | 14bca52e8f | ||
|  | 2f3c3361cf | ||
|  | 4034134055 | ||
|  | c04f94cb7b | ||
|  | fd1bbc7925 | ||
|  | ff69bed394 | ||
|  | d6e8c5146f | ||
|  | 9a04cf99d7 | ||
|  | 86e7c11e71 | 
							
								
								
									
										12
									
								
								.github/workflows/docker-build-push.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/docker-build-push.yml
									
									
									
									
										vendored
									
									
								
							| @@ -35,7 +35,7 @@ jobs: | ||||
|           pull: true | ||||
|           file: ./docker/containers/tactical/dockerfile | ||||
|           platforms: linux/amd64 | ||||
|           tags: tacticalrmm/tactical:${{ steps.prep.outputs.version }},sadnub/tactical:latest | ||||
|           tags: tacticalrmm/tactical:${{ steps.prep.outputs.version }},tacticalrmm/tactical:latest | ||||
|            | ||||
|       - name: Build and Push Tactical MeshCentral Image | ||||
|         uses: docker/build-push-action@v2 | ||||
| @@ -45,7 +45,7 @@ jobs: | ||||
|           pull: true | ||||
|           file: ./docker/containers/tactical-meshcentral/dockerfile | ||||
|           platforms: linux/amd64 | ||||
|           tags: tacticalrmm/tactical-meshcentral:${{ steps.prep.outputs.version }},sadnub/tactical-meshcentral:latest | ||||
|           tags: tacticalrmm/tactical-meshcentral:${{ steps.prep.outputs.version }},tacticalrmm/tactical-meshcentral:latest | ||||
|            | ||||
|       - name: Build and Push Tactical NATS Image | ||||
|         uses: docker/build-push-action@v2 | ||||
| @@ -55,7 +55,7 @@ jobs: | ||||
|           pull: true | ||||
|           file: ./docker/containers/tactical-nats/dockerfile | ||||
|           platforms: linux/amd64 | ||||
|           tags: tacticalrmm/tactical-nats:${{ steps.prep.outputs.version }},sadnub/tactical-nats:latest | ||||
|           tags: tacticalrmm/tactical-nats:${{ steps.prep.outputs.version }},tacticalrmm/tactical-nats:latest | ||||
|            | ||||
|       - name: Build and Push Tactical Salt Image | ||||
|         uses: docker/build-push-action@v2 | ||||
| @@ -65,7 +65,7 @@ jobs: | ||||
|           pull: true | ||||
|           file: ./docker/containers/tactical-salt/dockerfile | ||||
|           platforms: linux/amd64 | ||||
|           tags: tacticalrmm/tactical-salt:${{ steps.prep.outputs.version }},sadnub/tactical-salt:latest | ||||
|           tags: tacticalrmm/tactical-salt:${{ steps.prep.outputs.version }},tacticalrmm/tactical-salt:latest | ||||
|            | ||||
|       - name: Build and Push Tactical Frontend Image | ||||
|         uses: docker/build-push-action@v2 | ||||
| @@ -75,7 +75,7 @@ jobs: | ||||
|           pull: true | ||||
|           file: ./docker/containers/tactical-frontend/dockerfile | ||||
|           platforms: linux/amd64 | ||||
|           tags: tacticalrmm/tactical-frontend:${{ steps.prep.outputs.version }},sadnub/tactical-frontend:latest | ||||
|           tags: tacticalrmm/tactical-frontend:${{ steps.prep.outputs.version }},tacticalrmm/tactical-frontend:latest | ||||
|            | ||||
|       - name: Build and Push Tactical Nginx Image | ||||
|         uses: docker/build-push-action@v2 | ||||
| @@ -85,4 +85,4 @@ jobs: | ||||
|           pull: true | ||||
|           file: ./docker/containers/tactical-nginx/dockerfile | ||||
|           platforms: linux/amd64 | ||||
|           tags: tacticalrmm/tactical-nginx:${{ steps.prep.outputs.version }},sadnub/tactical-nginx:latest | ||||
|           tags: tacticalrmm/tactical-nginx:${{ steps.prep.outputs.version }},tacticalrmm/tactical-nginx:latest | ||||
|   | ||||
| @@ -20,6 +20,5 @@ omit = | ||||
|     */urls.py | ||||
|     */tests.py | ||||
|     */test.py | ||||
|     api/*.py | ||||
|     checks/utils.py | ||||
|      | ||||
|   | ||||
| @@ -164,13 +164,11 @@ class Agent(BaseAuditModel): | ||||
|                 elif i.status == "failing": | ||||
|                     failing += 1 | ||||
|  | ||||
|         has_failing_checks = True if failing > 0 else False | ||||
|  | ||||
|         ret = { | ||||
|             "total": total, | ||||
|             "passing": passing, | ||||
|             "failing": failing, | ||||
|             "has_failing_checks": has_failing_checks, | ||||
|             "has_failing_checks": failing > 0, | ||||
|         } | ||||
|         return ret | ||||
|  | ||||
| @@ -546,6 +544,7 @@ class Agent(BaseAuditModel): | ||||
|  | ||||
|         ret = AgentEditSerializer(agent).data | ||||
|         del ret["all_timezones"] | ||||
|         del ret["client"] | ||||
|         return ret | ||||
|  | ||||
|     @staticmethod | ||||
|   | ||||
| @@ -1,8 +1,10 @@ | ||||
| import asyncio | ||||
| from loguru import logger | ||||
| from time import sleep | ||||
| import random | ||||
| import requests | ||||
| from packaging import version as pyver | ||||
| from typing import List | ||||
|  | ||||
| from django.conf import settings | ||||
|  | ||||
| @@ -13,137 +15,104 @@ from logs.models import PendingAction | ||||
|  | ||||
| logger.configure(**settings.LOG_CONFIG) | ||||
|  | ||||
| OLD_64_PY_AGENT = "https://github.com/wh1te909/winagent/releases/download/v0.11.2/winagent-v0.11.2.exe" | ||||
| OLD_32_PY_AGENT = "https://github.com/wh1te909/winagent/releases/download/v0.11.2/winagent-v0.11.2-x86.exe" | ||||
|  | ||||
| def agent_update(pk: int) -> str: | ||||
|     agent = Agent.objects.get(pk=pk) | ||||
|     # skip if we can't determine the arch | ||||
|     if agent.arch is None: | ||||
|         logger.warning(f"Unable to determine arch on {agent.hostname}. Skipping.") | ||||
|         return "noarch" | ||||
|  | ||||
|     # force an update to 1.1.5 since 1.1.6 needs agent to be on 1.1.5 first | ||||
|     if pyver.parse(agent.version) < pyver.parse("1.1.5"): | ||||
|         version = "1.1.5" | ||||
|         if agent.arch == "64": | ||||
|             url = "https://github.com/wh1te909/rmmagent/releases/download/v1.1.5/winagent-v1.1.5.exe" | ||||
|             inno = "winagent-v1.1.5.exe" | ||||
|         elif agent.arch == "32": | ||||
|             url = "https://github.com/wh1te909/rmmagent/releases/download/v1.1.5/winagent-v1.1.5-x86.exe" | ||||
|             inno = "winagent-v1.1.5-x86.exe" | ||||
|         else: | ||||
|             return "nover" | ||||
|     else: | ||||
|         version = settings.LATEST_AGENT_VER | ||||
|         url = agent.winagent_dl | ||||
|         inno = agent.win_inno_exe | ||||
|  | ||||
|     if agent.has_nats: | ||||
|         if agent.pendingactions.filter( | ||||
|             action_type="agentupdate", status="pending" | ||||
|         ).exists(): | ||||
|             action = agent.pendingactions.filter( | ||||
|                 action_type="agentupdate", status="pending" | ||||
|             ).last() | ||||
|             if pyver.parse(action.details["version"]) < pyver.parse(version): | ||||
|                 action.delete() | ||||
|             else: | ||||
|                 return "pending" | ||||
|  | ||||
|         PendingAction.objects.create( | ||||
|             agent=agent, | ||||
|             action_type="agentupdate", | ||||
|             details={ | ||||
|                 "url": url, | ||||
|                 "version": version, | ||||
|                 "inno": inno, | ||||
|             }, | ||||
|         ) | ||||
|         return "created" | ||||
|     # TODO | ||||
|     # Salt is deprecated, remove this once salt is gone | ||||
|     else: | ||||
|         agent.salt_api_async( | ||||
|             func="win_agent.do_agent_update_v2", | ||||
|             kwargs={ | ||||
|                 "inno": inno, | ||||
|                 "url": url, | ||||
|             }, | ||||
|         ) | ||||
|         return "salt" | ||||
|  | ||||
|  | ||||
| @app.task | ||||
| def send_agent_update_task(pks, version): | ||||
|     assert isinstance(pks, list) | ||||
|  | ||||
| def send_agent_update_task(pks: List[int], version: str) -> None: | ||||
|     q = Agent.objects.filter(pk__in=pks) | ||||
|     agents = [i.pk for i in q if pyver.parse(i.version) < pyver.parse(version)] | ||||
|     agents: List[int] = [ | ||||
|         i.pk for i in q if pyver.parse(i.version) < pyver.parse(version) | ||||
|     ] | ||||
|  | ||||
|     chunks = (agents[i : i + 30] for i in range(0, len(agents), 30)) | ||||
|  | ||||
|     for chunk in chunks: | ||||
|         for pk in chunk: | ||||
|             agent = Agent.objects.get(pk=pk) | ||||
|  | ||||
|             # skip if we can't determine the arch | ||||
|             if agent.arch is None: | ||||
|                 logger.warning( | ||||
|                     f"Unable to determine arch on {agent.salt_id}. Skipping." | ||||
|                 ) | ||||
|                 continue | ||||
|  | ||||
|             # golang agent only backwards compatible with py agent 0.11.2 | ||||
|             # force an upgrade to the latest python agent if version < 0.11.2 | ||||
|             if pyver.parse(agent.version) < pyver.parse("0.11.2"): | ||||
|                 url = OLD_64_PY_AGENT if agent.arch == "64" else OLD_32_PY_AGENT | ||||
|                 inno = ( | ||||
|                     "winagent-v0.11.2.exe" | ||||
|                     if agent.arch == "64" | ||||
|                     else "winagent-v0.11.2-x86.exe" | ||||
|                 ) | ||||
|             else: | ||||
|                 url = agent.winagent_dl | ||||
|                 inno = agent.win_inno_exe | ||||
|  | ||||
|             if agent.has_nats: | ||||
|                 if agent.pendingactions.filter( | ||||
|                     action_type="agentupdate", status="pending" | ||||
|                 ).exists(): | ||||
|                     continue | ||||
|  | ||||
|                 PendingAction.objects.create( | ||||
|                     agent=agent, | ||||
|                     action_type="agentupdate", | ||||
|                     details={ | ||||
|                         "url": agent.winagent_dl, | ||||
|                         "version": settings.LATEST_AGENT_VER, | ||||
|                         "inno": agent.win_inno_exe, | ||||
|                     }, | ||||
|                 ) | ||||
|             # TODO | ||||
|             # Salt is deprecated, remove this once salt is gone | ||||
|             else: | ||||
|                 r = agent.salt_api_async( | ||||
|                     func="win_agent.do_agent_update_v2", | ||||
|                     kwargs={ | ||||
|                         "inno": inno, | ||||
|                         "url": url, | ||||
|                     }, | ||||
|                 ) | ||||
|         sleep(5) | ||||
|     for pk in agents: | ||||
|         agent_update(pk) | ||||
|  | ||||
|  | ||||
| @app.task | ||||
| def auto_self_agent_update_task(): | ||||
| def auto_self_agent_update_task() -> None: | ||||
|     core = CoreSettings.objects.first() | ||||
|     if not core.agent_auto_update: | ||||
|         logger.info("Agent auto update is disabled. Skipping.") | ||||
|         return | ||||
|  | ||||
|     q = Agent.objects.only("pk", "version") | ||||
|     agents = [ | ||||
|     pks: List[int] = [ | ||||
|         i.pk | ||||
|         for i in q | ||||
|         if pyver.parse(i.version) < pyver.parse(settings.LATEST_AGENT_VER) | ||||
|     ] | ||||
|  | ||||
|     chunks = (agents[i : i + 30] for i in range(0, len(agents), 30)) | ||||
|     for pk in pks: | ||||
|         agent_update(pk) | ||||
|  | ||||
|     for chunk in chunks: | ||||
|         for pk in chunk: | ||||
|             agent = Agent.objects.get(pk=pk) | ||||
|  | ||||
|             # skip if we can't determine the arch | ||||
|             if agent.arch is None: | ||||
|                 logger.warning( | ||||
|                     f"Unable to determine arch on {agent.salt_id}. Skipping." | ||||
|                 ) | ||||
|                 continue | ||||
|  | ||||
|             # golang agent only backwards compatible with py agent 0.11.2 | ||||
|             # force an upgrade to the latest python agent if version < 0.11.2 | ||||
|             if pyver.parse(agent.version) < pyver.parse("0.11.2"): | ||||
|                 url = OLD_64_PY_AGENT if agent.arch == "64" else OLD_32_PY_AGENT | ||||
|                 inno = ( | ||||
|                     "winagent-v0.11.2.exe" | ||||
|                     if agent.arch == "64" | ||||
|                     else "winagent-v0.11.2-x86.exe" | ||||
|                 ) | ||||
|             else: | ||||
|                 url = agent.winagent_dl | ||||
|                 inno = agent.win_inno_exe | ||||
|  | ||||
|             if agent.has_nats: | ||||
|                 if agent.pendingactions.filter( | ||||
|                     action_type="agentupdate", status="pending" | ||||
|                 ).exists(): | ||||
|                     continue | ||||
|  | ||||
|                 PendingAction.objects.create( | ||||
|                     agent=agent, | ||||
|                     action_type="agentupdate", | ||||
|                     details={ | ||||
|                         "url": agent.winagent_dl, | ||||
|                         "version": settings.LATEST_AGENT_VER, | ||||
|                         "inno": agent.win_inno_exe, | ||||
|                     }, | ||||
|                 ) | ||||
|             # TODO | ||||
|             # Salt is deprecated, remove this once salt is gone | ||||
|             else: | ||||
|                 r = agent.salt_api_async( | ||||
|                     func="win_agent.do_agent_update_v2", | ||||
|                     kwargs={ | ||||
|                         "inno": inno, | ||||
|                         "url": url, | ||||
|                     }, | ||||
|                 ) | ||||
|         sleep(5) | ||||
| @app.task | ||||
| def sync_sysinfo_task(): | ||||
|     agents = Agent.objects.all() | ||||
|     online = [ | ||||
|         i | ||||
|         for i in agents | ||||
|         if pyver.parse(i.version) >= pyver.parse("1.1.3") and i.status == "online" | ||||
|     ] | ||||
|     for agent in online: | ||||
|         asyncio.run(agent.nats_cmd({"func": "sync"}, wait=False)) | ||||
|  | ||||
|  | ||||
| @app.task | ||||
|   | ||||
| @@ -5,19 +5,20 @@ from unittest.mock import patch | ||||
| from model_bakery import baker | ||||
| from itertools import cycle | ||||
|  | ||||
| from django.test import TestCase, override_settings | ||||
| from django.conf import settings | ||||
| from django.utils import timezone as djangotime | ||||
| from logs.models import PendingAction | ||||
|  | ||||
| from tacticalrmm.test import TacticalTestCase | ||||
| from .serializers import AgentSerializer | ||||
| from winupdate.serializers import WinUpdatePolicySerializer | ||||
| from .models import Agent | ||||
| from .tasks import ( | ||||
|     agent_recovery_sms_task, | ||||
|     auto_self_agent_update_task, | ||||
|     sync_salt_modules_task, | ||||
|     batch_sync_modules_task, | ||||
|     OLD_64_PY_AGENT, | ||||
|     OLD_32_PY_AGENT, | ||||
| ) | ||||
| from winupdate.models import WinUpdatePolicy | ||||
|  | ||||
| @@ -786,6 +787,70 @@ class TestAgentTasks(TacticalTestCase): | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|  | ||||
|     @patch("agents.models.Agent.salt_api_async") | ||||
|     def test_agent_update(self, salt_api_async): | ||||
|         from agents.tasks import agent_update | ||||
|  | ||||
|         agent_noarch = baker.make_recipe( | ||||
|             "agents.agent", | ||||
|             operating_system="Error getting OS", | ||||
|             version="1.1.0", | ||||
|         ) | ||||
|         r = agent_update(agent_noarch.pk) | ||||
|         self.assertEqual(r, "noarch") | ||||
|         self.assertEqual( | ||||
|             PendingAction.objects.filter( | ||||
|                 agent=agent_noarch, action_type="agentupdate" | ||||
|             ).count(), | ||||
|             0, | ||||
|         ) | ||||
|  | ||||
|         agent64_nats = baker.make_recipe( | ||||
|             "agents.agent", | ||||
|             operating_system="Windows 10 Pro, 64 bit (build 19041.450)", | ||||
|             version="1.1.0", | ||||
|         ) | ||||
|  | ||||
|         r = agent_update(agent64_nats.pk) | ||||
|         self.assertEqual(r, "created") | ||||
|         action = PendingAction.objects.get(agent__pk=agent64_nats.pk) | ||||
|         self.assertEqual(action.action_type, "agentupdate") | ||||
|         self.assertEqual(action.status, "pending") | ||||
|         self.assertEqual(action.details["url"], settings.DL_64) | ||||
|         self.assertEqual( | ||||
|             action.details["inno"], f"winagent-v{settings.LATEST_AGENT_VER}.exe" | ||||
|         ) | ||||
|         self.assertEqual(action.details["version"], settings.LATEST_AGENT_VER) | ||||
|  | ||||
|         agent64_salt = baker.make_recipe( | ||||
|             "agents.agent", | ||||
|             operating_system="Windows 10 Pro, 64 bit (build 19041.450)", | ||||
|             version="1.0.0", | ||||
|         ) | ||||
|         salt_api_async.return_value = True | ||||
|         r = agent_update(agent64_salt.pk) | ||||
|         self.assertEqual(r, "salt") | ||||
|         salt_api_async.assert_called_with( | ||||
|             func="win_agent.do_agent_update_v2", | ||||
|             kwargs={ | ||||
|                 "inno": f"winagent-v{settings.LATEST_AGENT_VER}.exe", | ||||
|                 "url": settings.DL_64, | ||||
|             }, | ||||
|         ) | ||||
|         salt_api_async.reset_mock() | ||||
|  | ||||
|         agent32_nats = baker.make_recipe( | ||||
|             "agents.agent", | ||||
|             operating_system="Windows 7 Professional, 32 bit (build 7601.23964)", | ||||
|             version="1.1.0", | ||||
|         ) | ||||
|  | ||||
|         agent32_salt = baker.make_recipe( | ||||
|             "agents.agent", | ||||
|             operating_system="Windows 7 Professional, 32 bit (build 7601.23964)", | ||||
|             version="1.0.0", | ||||
|         ) | ||||
|  | ||||
|     """ @patch("agents.models.Agent.salt_api_async") | ||||
|     @patch("agents.tasks.sleep", return_value=None) | ||||
|     def test_auto_self_agent_update_task(self, mock_sleep, salt_api_async): | ||||
|         # test 64bit golang agent | ||||
| @@ -888,4 +953,4 @@ class TestAgentTasks(TacticalTestCase): | ||||
|                 "url": OLD_32_PY_AGENT, | ||||
|             }, | ||||
|         ) | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|         self.assertEqual(ret.status, "SUCCESS") """ | ||||
| @@ -30,4 +30,5 @@ urlpatterns = [ | ||||
|     path("bulk/", views.bulk), | ||||
|     path("agent_counts/", views.agent_counts), | ||||
|     path("maintenance/", views.agent_maintenance), | ||||
|     path("<int:pk>/wmi/", views.WMI.as_view()), | ||||
| ] | ||||
|   | ||||
| @@ -402,6 +402,9 @@ class Reboot(APIView): | ||||
|             }, | ||||
|         } | ||||
|  | ||||
|         if pyver.parse(agent.version) >= pyver.parse("1.1.2"): | ||||
|             nats_data["schedtaskpayload"]["deleteafter"] = True | ||||
|  | ||||
|         r = asyncio.run(agent.nats_cmd(nats_data, timeout=10)) | ||||
|         if r != "ok": | ||||
|             return notify_error(r) | ||||
| @@ -895,3 +898,15 @@ def agent_maintenance(request): | ||||
|         return notify_error("Invalid data") | ||||
|  | ||||
|     return Response("ok") | ||||
|  | ||||
|  | ||||
| class WMI(APIView): | ||||
|     def get(self, request, pk): | ||||
|         agent = get_object_or_404(Agent, pk=pk) | ||||
|         if pyver.parse(agent.version) < pyver.parse("1.1.2"): | ||||
|             return notify_error("Requires agent version 1.1.2 or greater") | ||||
|  | ||||
|         r = asyncio.run(agent.nats_cmd({"func": "sysinfo"}, timeout=20)) | ||||
|         if r != "ok": | ||||
|             return notify_error("Unable to contact the agent") | ||||
|         return Response("ok") | ||||
| @@ -5,6 +5,7 @@ from tacticalrmm.celery import app | ||||
| from django.conf import settings | ||||
| import pytz | ||||
| from django.utils import timezone as djangotime | ||||
| from packaging import version as pyver | ||||
|  | ||||
| from .models import AutomatedTask | ||||
| from logs.models import PendingAction | ||||
| @@ -56,6 +57,11 @@ def create_win_task_schedule(pk, pending_action=False): | ||||
|             }, | ||||
|         } | ||||
|  | ||||
|         if task.remove_if_not_scheduled and pyver.parse( | ||||
|             task.agent.version | ||||
|         ) >= pyver.parse("1.1.2"): | ||||
|             nats_data["schedtaskpayload"]["deleteafter"] = True | ||||
|  | ||||
|     elif task.task_type == "checkfailure" or task.task_type == "manual": | ||||
|         nats_data = { | ||||
|             "func": "schedtask", | ||||
| @@ -201,6 +207,7 @@ def remove_orphaned_win_tasks(agentpk): | ||||
|         "TacticalRMM_fixmesh", | ||||
|         "TacticalRMM_SchedReboot", | ||||
|         "TacticalRMM_sync", | ||||
|         "TacticalRMM_agentupdate", | ||||
|     ) | ||||
|  | ||||
|     for task in r: | ||||
|   | ||||
| @@ -38,7 +38,6 @@ class Client(BaseAuditModel): | ||||
|  | ||||
|     @property | ||||
|     def has_failing_checks(self): | ||||
|  | ||||
|         agents = ( | ||||
|             Agent.objects.only( | ||||
|                 "pk", | ||||
| @@ -50,14 +49,17 @@ class Client(BaseAuditModel): | ||||
|             .filter(site__client=self) | ||||
|             .prefetch_related("agentchecks") | ||||
|         ) | ||||
|  | ||||
|         failing = 0 | ||||
|         for agent in agents: | ||||
|             if agent.checks["has_failing_checks"]: | ||||
|                 return True | ||||
|                 failing += 1 | ||||
|  | ||||
|             if agent.overdue_email_alert or agent.overdue_text_alert: | ||||
|                 return agent.status == "overdue" | ||||
|                 if agent.status == "overdue": | ||||
|                     failing += 1 | ||||
|  | ||||
|         return False | ||||
|         return failing > 0 | ||||
|  | ||||
|     @staticmethod | ||||
|     def serialize(client): | ||||
| @@ -98,7 +100,6 @@ class Site(BaseAuditModel): | ||||
|  | ||||
|     @property | ||||
|     def has_failing_checks(self): | ||||
|  | ||||
|         agents = ( | ||||
|             Agent.objects.only( | ||||
|                 "pk", | ||||
| @@ -110,14 +111,17 @@ class Site(BaseAuditModel): | ||||
|             .filter(site=self) | ||||
|             .prefetch_related("agentchecks") | ||||
|         ) | ||||
|  | ||||
|         failing = 0 | ||||
|         for agent in agents: | ||||
|             if agent.checks["has_failing_checks"]: | ||||
|                 return True | ||||
|                 failing += 1 | ||||
|  | ||||
|             if agent.overdue_email_alert or agent.overdue_text_alert: | ||||
|                 return agent.status == "overdue" | ||||
|                 if agent.status == "overdue": | ||||
|                     failing += 1 | ||||
|  | ||||
|         return False | ||||
|         return failing > 0 | ||||
|  | ||||
|     @staticmethod | ||||
|     def serialize(site): | ||||
|   | ||||
| @@ -58,6 +58,7 @@ func main() { | ||||
| 	debugLog := flag.String("log", "", "Verbose output") | ||||
| 	localMesh := flag.String("local-mesh", "", "Use local mesh agent") | ||||
| 	noSalt := flag.Bool("nosalt", false, "Does not install salt") | ||||
| 	silent := flag.Bool("silent", false, "Do not popup any message boxes during installation") | ||||
| 	cert := flag.String("cert", "", "Path to ca.pem") | ||||
| 	timeout := flag.String("timeout", "", "Timeout for subprocess calls") | ||||
| 	flag.Parse() | ||||
| @@ -78,7 +79,11 @@ func main() { | ||||
| 	} | ||||
|  | ||||
| 	if debug { | ||||
| 		cmdArgs = append(cmdArgs, "--log", "DEBUG") | ||||
| 		cmdArgs = append(cmdArgs, "-log", "debug") | ||||
| 	} | ||||
|  | ||||
| 	if *silent { | ||||
| 		cmdArgs = append(cmdArgs, "-silent") | ||||
| 	} | ||||
|  | ||||
| 	if *noSalt { | ||||
| @@ -86,27 +91,27 @@ func main() { | ||||
| 	} | ||||
|  | ||||
| 	if len(strings.TrimSpace(*localMesh)) != 0 { | ||||
| 		cmdArgs = append(cmdArgs, "--local-mesh", *localMesh) | ||||
| 		cmdArgs = append(cmdArgs, "-local-mesh", *localMesh) | ||||
| 	} | ||||
|  | ||||
| 	if len(strings.TrimSpace(*cert)) != 0 { | ||||
| 		cmdArgs = append(cmdArgs, "--cert", *cert) | ||||
| 		cmdArgs = append(cmdArgs, "-cert", *cert) | ||||
| 	} | ||||
|  | ||||
| 	if len(strings.TrimSpace(*timeout)) != 0 { | ||||
| 		cmdArgs = append(cmdArgs, "--timeout", *timeout) | ||||
| 		cmdArgs = append(cmdArgs, "-timeout", *timeout) | ||||
| 	} | ||||
|  | ||||
| 	if Rdp == "1" { | ||||
| 		cmdArgs = append(cmdArgs, "--rdp") | ||||
| 		cmdArgs = append(cmdArgs, "-rdp") | ||||
| 	} | ||||
|  | ||||
| 	if Ping == "1" { | ||||
| 		cmdArgs = append(cmdArgs, "--ping") | ||||
| 		cmdArgs = append(cmdArgs, "-ping") | ||||
| 	} | ||||
|  | ||||
| 	if Power == "1" { | ||||
| 		cmdArgs = append(cmdArgs, "--power") | ||||
| 		cmdArgs = append(cmdArgs, "-power") | ||||
| 	} | ||||
|  | ||||
| 	if debug { | ||||
|   | ||||
| @@ -114,7 +114,7 @@ def agent_pending_actions(request, pk): | ||||
|  | ||||
| @api_view() | ||||
| def all_pending_actions(request): | ||||
|     actions = PendingAction.objects.all() | ||||
|     actions = PendingAction.objects.all().select_related("agent") | ||||
|     return Response(PendingActionSerializer(actions, many=True).data) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,14 +1,14 @@ | ||||
| amqp==2.6.1 | ||||
| asgiref==3.3.0 | ||||
| asgiref==3.3.1 | ||||
| asyncio-nats-client==0.11.4 | ||||
| billiard==3.6.3.0 | ||||
| celery==4.4.6 | ||||
| certifi==2020.11.8 | ||||
| certifi==2020.12.5 | ||||
| cffi==1.14.3 | ||||
| chardet==3.0.4 | ||||
| cryptography==3.2.1 | ||||
| decorator==4.4.2 | ||||
| Django==3.1.3 | ||||
| Django==3.1.4 | ||||
| django-cors-headers==3.5.0 | ||||
| django-rest-knox==4.1.0 | ||||
| djangorestframework==3.12.2 | ||||
| @@ -26,12 +26,11 @@ pyparsing==2.4.7 | ||||
| pytz==2020.4 | ||||
| qrcode==6.1 | ||||
| redis==3.5.3 | ||||
| requests==2.24.0 | ||||
| requests==2.25.0 | ||||
| six==1.15.0 | ||||
| sqlparse==0.4.1 | ||||
| tldextract==3.0.2 | ||||
| twilio==6.47.0 | ||||
| urllib3==1.25.11 | ||||
| twilio==6.49.0 | ||||
| urllib3==1.26.2 | ||||
| uWSGI==2.0.19.1 | ||||
| validators==0.18.1 | ||||
| vine==1.3.0 | ||||
|   | ||||
| @@ -9,21 +9,6 @@ class TestServiceViews(TacticalTestCase): | ||||
|     def setUp(self): | ||||
|         self.authenticate() | ||||
|  | ||||
|     def test_get_services(self): | ||||
|  | ||||
|         # test a call where agent doesn't exist | ||||
|         resp = self.client.get("/services/500/services/", format="json") | ||||
|         self.assertEqual(resp.status_code, 404) | ||||
|  | ||||
|         agent = baker.make_recipe("agents.agent_with_services") | ||||
|         url = f"/services/{agent.pk}/services/" | ||||
|         serializer = ServicesSerializer(agent) | ||||
|         resp = self.client.get(url, format="json") | ||||
|         self.assertEqual(resp.status_code, 200) | ||||
|         self.assertEqual(serializer.data, resp.data) | ||||
|  | ||||
|         self.check_not_authenticated("get", url) | ||||
|  | ||||
|     def test_default_services(self): | ||||
|         url = "/services/defaultservices/" | ||||
|         resp = self.client.get(url, format="json") | ||||
| @@ -33,13 +18,13 @@ class TestServiceViews(TacticalTestCase): | ||||
|         self.check_not_authenticated("get", url) | ||||
|  | ||||
|     @patch("agents.models.Agent.nats_cmd") | ||||
|     def test_get_refreshed_services(self, nats_cmd): | ||||
|     def test_get_services(self, nats_cmd): | ||||
|         # test a call where agent doesn't exist | ||||
|         resp = self.client.get("/services/500/refreshedservices/", format="json") | ||||
|         resp = self.client.get("/services/500/services/", format="json") | ||||
|         self.assertEqual(resp.status_code, 404) | ||||
|  | ||||
|         agent = baker.make_recipe("agents.agent_with_services") | ||||
|         url = f"/services/{agent.pk}/refreshedservices/" | ||||
|         url = f"/services/{agent.pk}/services/" | ||||
|  | ||||
|         nats_return = [ | ||||
|             { | ||||
|   | ||||
| @@ -4,7 +4,6 @@ from . import views | ||||
| urlpatterns = [ | ||||
|     path("<int:pk>/services/", views.get_services), | ||||
|     path("defaultservices/", views.default_services), | ||||
|     path("<int:pk>/refreshedservices/", views.get_refreshed_services), | ||||
|     path("serviceaction/", views.service_action), | ||||
|     path("<int:pk>/<svcname>/servicedetail/", views.service_detail), | ||||
|     path("editservice/", views.edit_service), | ||||
|   | ||||
| @@ -19,17 +19,6 @@ logger.configure(**settings.LOG_CONFIG) | ||||
|  | ||||
| @api_view() | ||||
| def get_services(request, pk): | ||||
|     agent = get_object_or_404(Agent, pk=pk) | ||||
|     return Response(ServicesSerializer(agent).data) | ||||
|  | ||||
|  | ||||
| @api_view() | ||||
| def default_services(request): | ||||
|     return Response(Check.load_default_services()) | ||||
|  | ||||
|  | ||||
| @api_view() | ||||
| def get_refreshed_services(request, pk): | ||||
|     agent = get_object_or_404(Agent, pk=pk) | ||||
|     if not agent.has_nats: | ||||
|         return notify_error("Requires agent version 1.1.0 or greater") | ||||
| @@ -43,6 +32,11 @@ def get_refreshed_services(request, pk): | ||||
|     return Response(ServicesSerializer(agent).data) | ||||
|  | ||||
|  | ||||
| @api_view() | ||||
| def default_services(request): | ||||
|     return Response(Check.load_default_services()) | ||||
|  | ||||
|  | ||||
| @api_view(["POST"]) | ||||
| def service_action(request): | ||||
|     agent = get_object_or_404(Agent, pk=request.data["pk"]) | ||||
|   | ||||
| @@ -41,6 +41,10 @@ app.conf.beat_schedule = { | ||||
|         "task": "agents.tasks.auto_self_agent_update_task", | ||||
|         "schedule": crontab(minute=35, hour="*"), | ||||
|     }, | ||||
|     "agents-sync": { | ||||
|         "task": "agents.tasks.sync_sysinfo_task", | ||||
|         "schedule": crontab(minute=55, hour="*"), | ||||
|     }, | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -16,16 +16,15 @@ def get_debug_info(): | ||||
| EXCLUDE_PATHS = ( | ||||
|     "/api/v3", | ||||
|     "/api/v2", | ||||
|     "/api/v1", | ||||
|     "/logs/auditlogs", | ||||
|     "/winupdate/winupdater", | ||||
|     "/winupdate/results", | ||||
|     f"/{settings.ADMIN_URL}", | ||||
|     "/logout", | ||||
|     "/agents/installagent", | ||||
|     "/logs/downloadlog", | ||||
| ) | ||||
|  | ||||
| ENDS_WITH = "/services/" | ||||
|  | ||||
|  | ||||
| class AuditMiddleware: | ||||
|     def __init__(self, get_response): | ||||
| @@ -37,7 +36,9 @@ class AuditMiddleware: | ||||
|         return response | ||||
|  | ||||
|     def process_view(self, request, view_func, view_args, view_kwargs): | ||||
|         if not request.path.startswith(EXCLUDE_PATHS): | ||||
|         if not request.path.startswith(EXCLUDE_PATHS) and not request.path.endswith( | ||||
|             ENDS_WITH | ||||
|         ): | ||||
|             # https://stackoverflow.com/questions/26240832/django-and-middleware-which-uses-request-user-is-always-anonymous | ||||
|             try: | ||||
|                 # DRF saves the class of the view function as the .cls property | ||||
|   | ||||
| @@ -15,25 +15,25 @@ EXE_DIR = os.path.join(BASE_DIR, "tacticalrmm/private/exe") | ||||
| AUTH_USER_MODEL = "accounts.User" | ||||
|  | ||||
| # latest release | ||||
| TRMM_VERSION = "0.2.4" | ||||
| TRMM_VERSION = "0.2.12" | ||||
|  | ||||
| # bump this version everytime vue code is changed | ||||
| # to alert user they need to manually refresh their browser | ||||
| APP_VER = "0.0.94" | ||||
| APP_VER = "0.0.99" | ||||
|  | ||||
| # https://github.com/wh1te909/salt | ||||
| LATEST_SALT_VER = "1.1.0" | ||||
|  | ||||
| # https://github.com/wh1te909/rmmagent | ||||
| LATEST_AGENT_VER = "1.1.1" | ||||
| LATEST_AGENT_VER = "1.1.7" | ||||
|  | ||||
| MESH_VER = "0.7.9" | ||||
| MESH_VER = "0.7.14" | ||||
|  | ||||
| SALT_MASTER_VER = "3002.2" | ||||
|  | ||||
| # for the update script, bump when need to recreate venv or npm install | ||||
| PIP_VER = "3" | ||||
| NPM_VER = "2" | ||||
| PIP_VER = "4" | ||||
| NPM_VER = "3" | ||||
|  | ||||
| DL_64 = f"https://github.com/wh1te909/rmmagent/releases/download/v{LATEST_AGENT_VER}/winagent-v{LATEST_AGENT_VER}.exe" | ||||
| DL_32 = f"https://github.com/wh1te909/rmmagent/releases/download/v{LATEST_AGENT_VER}/winagent-v{LATEST_AGENT_VER}-x86.exe" | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import json | ||||
| import os | ||||
| import string | ||||
| import subprocess | ||||
| import time | ||||
| from typing import List, Dict | ||||
| from loguru import logger | ||||
|  | ||||
| @@ -117,6 +118,7 @@ def reload_nats(): | ||||
|         json.dump(config, f) | ||||
|  | ||||
|     if not settings.DOCKER_BUILD: | ||||
|         time.sleep(0.5) | ||||
|         subprocess.run( | ||||
|             ["/usr/local/bin/nats-server", "-signal", "reload"], capture_output=True | ||||
|         ) | ||||
|   | ||||
| @@ -107,7 +107,7 @@ def check_agent_update_schedule_task(): | ||||
| def check_for_updates_task(pk, wait=False, auto_approve=False): | ||||
|  | ||||
|     if wait: | ||||
|         sleep(70) | ||||
|         sleep(120) | ||||
|  | ||||
|     agent = Agent.objects.get(pk=pk) | ||||
|     ret = agent.salt_api_cmd( | ||||
|   | ||||
| @@ -1,12 +1,9 @@ | ||||
| from django.urls import path | ||||
| from . import views | ||||
| from apiv3 import views as v3_views | ||||
|  | ||||
| urlpatterns = [ | ||||
|     path("<int:pk>/getwinupdates/", views.get_win_updates), | ||||
|     path("<int:pk>/runupdatescan/", views.run_update_scan), | ||||
|     path("editpolicy/", views.edit_policy), | ||||
|     path("winupdater/", views.win_updater), | ||||
|     path("results/", v3_views.WinUpdater.as_view()), | ||||
|     path("<int:pk>/installnow/", views.install_updates), | ||||
| ] | ||||
|   | ||||
| @@ -58,20 +58,3 @@ def edit_policy(request): | ||||
|     patch.action = request.data["policy"] | ||||
|     patch.save(update_fields=["action"]) | ||||
|     return Response("ok") | ||||
|  | ||||
|  | ||||
| @api_view() | ||||
| @authentication_classes((TokenAuthentication,)) | ||||
| @permission_classes((IsAuthenticated,)) | ||||
| def win_updater(request): | ||||
|     agent = get_object_or_404(Agent, agent_id=request.data["agent_id"]) | ||||
|     agent.delete_superseded_updates() | ||||
|     patches = ( | ||||
|         WinUpdate.objects.filter(agent=agent) | ||||
|         .exclude(installed=True) | ||||
|         .filter(action="approve") | ||||
|     ) | ||||
|     if patches: | ||||
|         return Response(ApprovedUpdateSerializer(patches, many=True).data) | ||||
|  | ||||
|     return Response("nopatches") | ||||
| @@ -27,15 +27,21 @@ jobs: | ||||
|           source env/bin/activate | ||||
|           cd /myagent/_work/1/s/api/tacticalrmm | ||||
|           pip install --no-cache-dir --upgrade pip | ||||
|           pip install --no-cache-dir setuptools==49.6.0 wheel==0.35.1 | ||||
|           pip install --no-cache-dir setuptools==50.3.2 wheel==0.36.1 | ||||
|           pip install --no-cache-dir -r requirements.txt -r requirements-test.txt | ||||
|         displayName: "Install Python Dependencies" | ||||
|  | ||||
|       - script: | | ||||
|           cd /myagent/_work/1/s/api | ||||
|           git config user.email "admin@example.com" | ||||
|           git config user.name "Bob" | ||||
|           git fetch | ||||
|           git checkout develop | ||||
|           git pull | ||||
|           source env/bin/activate | ||||
|           cd /myagent/_work/1/s/api/tacticalrmm | ||||
|           python manage.py test -v 2 | ||||
|           coverage run manage.py test -v 2 | ||||
|           coveralls | ||||
|         displayName: "Run django tests" | ||||
|  | ||||
|       - script: | | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| SCRIPT_VERSION="4" | ||||
| SCRIPT_VERSION="5" | ||||
| SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/backup.sh' | ||||
|  | ||||
| GREEN='\033[0;32m' | ||||
| @@ -50,6 +50,11 @@ if [ -d /meshcentral/meshcentral-coredumps ]; then | ||||
|     rm -f /meshcentral/meshcentral-coredumps/* | ||||
| fi | ||||
|  | ||||
| printf >&2 "${GREEN}Running postgres vacuum${NC}\n" | ||||
| sudo -u postgres psql -d tacticalrmm -c "vacuum full logs_auditlog" | ||||
| sudo -u postgres psql -d tacticalrmm -c "vacuum full logs_pendingaction" | ||||
| sudo -u postgres psql -d tacticalrmm -c "vacuum full agents_agentoutage" | ||||
|  | ||||
| dt_now=$(date '+%Y_%m_%d__%H_%M_%S') | ||||
| tmp_dir=$(mktemp -d -t tacticalrmm-XXXXXXXXXXXXXXXXXXXXX) | ||||
| sysd="/etc/systemd/system" | ||||
|   | ||||
| @@ -70,7 +70,7 @@ DEBUG = False | ||||
| DOCKER_BUILD = True | ||||
|  | ||||
| CERT_FILE = '/opt/tactical/certs/fullchain.pem' | ||||
| CERT_KEY = '/opt/tactical/certs/privkey.pem' | ||||
| KEY_FILE = '/opt/tactical/certs/privkey.pem' | ||||
|  | ||||
| SCRIPTS_DIR = '/opt/tactical/scripts' | ||||
|  | ||||
| @@ -180,4 +180,4 @@ fi | ||||
| if [ "$1" = 'tactical-celerywinupdate' ]; then | ||||
|   check_tactical_ready | ||||
|   celery -A tacticalrmm worker -Q wupdate | ||||
| fi | ||||
| fi | ||||
|   | ||||
							
								
								
									
										82
									
								
								install.sh
									
									
									
									
									
								
							
							
						
						
									
										82
									
								
								install.sh
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| SCRIPT_VERSION="25" | ||||
| SCRIPT_VERSION="27" | ||||
| SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/install.sh' | ||||
|  | ||||
| GREEN='\033[0;32m' | ||||
| @@ -205,12 +205,47 @@ sudo apt install -y mongodb-org | ||||
| sudo systemctl enable mongod | ||||
| sudo systemctl restart mongod | ||||
|  | ||||
|  | ||||
|  | ||||
| print_green 'Installing python, redis and git' | ||||
|  | ||||
| sudo apt update | ||||
| sudo apt install -y python3.8-venv python3.8-dev python3-pip python3-cherrypy3 python3-setuptools python3-wheel ca-certificates redis git | ||||
|  | ||||
| print_green 'Installing postgresql' | ||||
|  | ||||
| sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' | ||||
| wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - | ||||
| sudo apt update | ||||
| sudo apt install -y postgresql-13 | ||||
|  | ||||
| print_green 'Creating database for the rmm' | ||||
|  | ||||
| sudo -u postgres psql -c "CREATE DATABASE tacticalrmm" | ||||
| sudo -u postgres psql -c "CREATE USER ${pgusername} WITH PASSWORD '${pgpw}'" | ||||
| sudo -u postgres psql -c "ALTER ROLE ${pgusername} SET client_encoding TO 'utf8'" | ||||
| sudo -u postgres psql -c "ALTER ROLE ${pgusername} SET default_transaction_isolation TO 'read committed'" | ||||
| sudo -u postgres psql -c "ALTER ROLE ${pgusername} SET timezone TO 'UTC'" | ||||
| sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE tacticalrmm TO ${pgusername}" | ||||
|  | ||||
| sudo mkdir /rmm | ||||
| sudo chown ${USER}:${USER} /rmm | ||||
| sudo mkdir -p /var/log/celery | ||||
| sudo chown ${USER}:${USER} /var/log/celery | ||||
| git clone https://github.com/wh1te909/tacticalrmm.git /rmm/ | ||||
| cd /rmm | ||||
| git config user.email "admin@example.com" | ||||
| git config user.name "Bob" | ||||
| git checkout master | ||||
|  | ||||
| print_green 'Installing MeshCentral' | ||||
|  | ||||
| MESH_VER=$(grep "^MESH_VER" /rmm/api/tacticalrmm/tacticalrmm/settings.py | awk -F'[= "]' '{print $5}') | ||||
|  | ||||
| sudo mkdir -p /meshcentral/meshcentral-data | ||||
| sudo chown ${USER}:${USER} -R /meshcentral | ||||
| cd /meshcentral | ||||
| npm install meshcentral@0.7.9 | ||||
| npm install meshcentral@${MESH_VER} | ||||
| sudo chown ${USER}:${USER} -R /meshcentral | ||||
|  | ||||
| meshcfg="$(cat << EOF | ||||
| @@ -253,37 +288,6 @@ EOF | ||||
| )" | ||||
| echo "${meshcfg}" > /meshcentral/meshcentral-data/config.json | ||||
|  | ||||
| print_green 'Installing python, redis and git' | ||||
|  | ||||
| sudo apt update | ||||
| sudo apt install -y python3.8-venv python3.8-dev python3-pip python3-cherrypy3 python3-setuptools python3-wheel ca-certificates redis git | ||||
|  | ||||
| print_green 'Installing postgresql' | ||||
|  | ||||
| sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' | ||||
| wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - | ||||
| sudo apt update | ||||
| sudo apt install -y postgresql-13 | ||||
|  | ||||
| print_green 'Creating database for the rmm' | ||||
|  | ||||
| sudo -u postgres psql -c "CREATE DATABASE tacticalrmm" | ||||
| sudo -u postgres psql -c "CREATE USER ${pgusername} WITH PASSWORD '${pgpw}'" | ||||
| sudo -u postgres psql -c "ALTER ROLE ${pgusername} SET client_encoding TO 'utf8'" | ||||
| sudo -u postgres psql -c "ALTER ROLE ${pgusername} SET default_transaction_isolation TO 'read committed'" | ||||
| sudo -u postgres psql -c "ALTER ROLE ${pgusername} SET timezone TO 'UTC'" | ||||
| sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE tacticalrmm TO ${pgusername}" | ||||
|  | ||||
| sudo mkdir /rmm | ||||
| sudo chown ${USER}:${USER} /rmm | ||||
| sudo mkdir -p /var/log/celery | ||||
| sudo chown ${USER}:${USER} /var/log/celery | ||||
| git clone https://github.com/wh1te909/tacticalrmm.git /rmm/ | ||||
| cd /rmm | ||||
| git config user.email "admin@example.com" | ||||
| git config user.name "Bob" | ||||
| git checkout master | ||||
|  | ||||
| localvars="$(cat << EOF | ||||
| SECRET_KEY = "${DJANGO_SEKRET}" | ||||
|  | ||||
| @@ -348,7 +352,7 @@ python3 -m venv env | ||||
| source /rmm/api/env/bin/activate | ||||
| cd /rmm/api/tacticalrmm | ||||
| pip install --no-cache-dir --upgrade pip | ||||
| pip install --no-cache-dir setuptools==49.6.0 wheel==0.35.1 | ||||
| pip install --no-cache-dir setuptools==50.3.2 wheel==0.36.1 | ||||
| pip install --no-cache-dir -r /rmm/api/tacticalrmm/requirements.txt | ||||
| python manage.py migrate | ||||
| python manage.py collectstatic --no-input | ||||
| @@ -504,14 +508,7 @@ nginxmesh="$(cat << EOF | ||||
| server { | ||||
|   listen 80; | ||||
|   server_name ${meshdomain}; | ||||
|   location / { | ||||
|      proxy_pass http://127.0.0.1:800; | ||||
|      proxy_http_version 1.1; | ||||
|      proxy_set_header X-Forwarded-Host \$host:\$server_port; | ||||
|      proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||||
|      proxy_set_header X-Forwarded-Proto \$scheme; | ||||
|   } | ||||
|  | ||||
|   return 301 https://\$server_name\$request_uri; | ||||
| } | ||||
|  | ||||
| server { | ||||
| @@ -530,6 +527,7 @@ server { | ||||
|         proxy_pass http://127.0.0.1:4430/; | ||||
|         proxy_http_version 1.1; | ||||
|  | ||||
|         proxy_set_header Host \$host; | ||||
|         proxy_set_header Upgrade \$http_upgrade; | ||||
|         proxy_set_header Connection "upgrade"; | ||||
|         proxy_set_header X-Forwarded-Host \$host:\$server_port; | ||||
|   | ||||
							
								
								
									
										24
									
								
								restore.sh
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								restore.sh
									
									
									
									
									
								
							| @@ -7,7 +7,7 @@ pgpw="hunter2" | ||||
|  | ||||
| ##################################################### | ||||
|  | ||||
| SCRIPT_VERSION="8" | ||||
| SCRIPT_VERSION="10" | ||||
| SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/restore.sh' | ||||
|  | ||||
| GREEN='\033[0;32m' | ||||
| @@ -207,15 +207,6 @@ sudo systemctl restart mongod | ||||
| sleep 5 | ||||
| mongorestore --gzip $tmp_dir/meshcentral/mongo | ||||
|  | ||||
| print_green 'Restoring MeshCentral' | ||||
|  | ||||
| sudo tar -xzf $tmp_dir/meshcentral/mesh.tar.gz -C / | ||||
| sudo chown ${USER}:${USER} -R /meshcentral | ||||
| cd /meshcentral | ||||
| npm install meshcentral@0.7.9 | ||||
|  | ||||
|  | ||||
| print_green 'Restoring the backend' | ||||
|  | ||||
| sudo mkdir /rmm | ||||
| sudo chown ${USER}:${USER} /rmm | ||||
| @@ -227,6 +218,17 @@ git config user.email "admin@example.com" | ||||
| git config user.name "Bob" | ||||
| git checkout master | ||||
|  | ||||
| print_green 'Restoring MeshCentral' | ||||
|  | ||||
| MESH_VER=$(grep "^MESH_VER" /rmm/api/tacticalrmm/tacticalrmm/settings.py | awk -F'[= "]' '{print $5}') | ||||
| sudo tar -xzf $tmp_dir/meshcentral/mesh.tar.gz -C / | ||||
| sudo chown ${USER}:${USER} -R /meshcentral | ||||
| cd /meshcentral | ||||
| npm install meshcentral@${MESH_VER} | ||||
|  | ||||
|  | ||||
| print_green 'Restoring the backend' | ||||
|  | ||||
| cp $tmp_dir/rmm/local_settings.py /rmm/api/tacticalrmm/tacticalrmm/ | ||||
| cp $tmp_dir/rmm/env /rmm/web/.env | ||||
| cp $tmp_dir/rmm/app.ini /rmm/api/tacticalrmm/ | ||||
| @@ -244,7 +246,7 @@ python3 -m venv env | ||||
| source /rmm/api/env/bin/activate | ||||
| cd /rmm/api/tacticalrmm | ||||
| pip install --no-cache-dir --upgrade pip | ||||
| pip install --no-cache-dir setuptools==49.6.0 wheel==0.35.1 | ||||
| pip install --no-cache-dir setuptools==50.3.2 wheel==0.36.1 | ||||
| pip install --no-cache-dir -r /rmm/api/tacticalrmm/requirements.txt | ||||
| python manage.py collectstatic --no-input | ||||
| python manage.py reload_nats | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| SCRIPT_VERSION="98" | ||||
| SCRIPT_VERSION="100" | ||||
| SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/update.sh' | ||||
| LATEST_SETTINGS_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/api/tacticalrmm/tacticalrmm/settings.py' | ||||
| YELLOW='\033[1;33m' | ||||
| @@ -177,6 +177,11 @@ sudo cp /rmm/api/tacticalrmm/core/goinstaller/bin/goversioninfo /usr/local/bin/ | ||||
| sudo chown ${USER}:${USER} /usr/local/bin/goversioninfo | ||||
| sudo chmod +x /usr/local/bin/goversioninfo | ||||
|  | ||||
| printf >&2 "${GREEN}Running postgres vacuum${NC}\n" | ||||
| sudo -u postgres psql -d tacticalrmm -c "vacuum full logs_auditlog" | ||||
| sudo -u postgres psql -d tacticalrmm -c "vacuum full logs_pendingaction" | ||||
| sudo -u postgres psql -d tacticalrmm -c "vacuum full agents_agentoutage" | ||||
|  | ||||
| if [[ "${CURRENT_PIP_VER}" != "${LATEST_PIP_VER}" ]]; then | ||||
|   rm -rf /rmm/api/env | ||||
|   cd /rmm/api | ||||
| @@ -184,7 +189,7 @@ if [[ "${CURRENT_PIP_VER}" != "${LATEST_PIP_VER}" ]]; then | ||||
|   source /rmm/api/env/bin/activate | ||||
|   cd /rmm/api/tacticalrmm | ||||
|   pip install --no-cache-dir --upgrade pip | ||||
|   pip install --no-cache-dir setuptools==49.6.0 wheel==0.35.1 | ||||
|   pip install --no-cache-dir setuptools==50.3.2 wheel==0.36.1 | ||||
|   pip install --no-cache-dir -r requirements.txt | ||||
| else | ||||
|   source /rmm/api/env/bin/activate | ||||
|   | ||||
							
								
								
									
										856
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										856
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -13,10 +13,10 @@ | ||||
|     "axios": "^0.21.0", | ||||
|     "dotenv": "^8.2.0", | ||||
|     "qrcode.vue": "^1.7.0", | ||||
|     "quasar": "^1.14.5" | ||||
|     "quasar": "^1.14.6" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@quasar/app": "^2.1.8", | ||||
|     "@quasar/app": "^2.1.9", | ||||
|     "@quasar/cli": "^1.1.2", | ||||
|     "@quasar/quasar-app-extension-testing": "^1.0.0", | ||||
|     "@quasar/quasar-app-extension-testing-unit-jest": "^1.0.1", | ||||
|   | ||||
| @@ -13,7 +13,7 @@ | ||||
|       hide-bottom | ||||
|     > | ||||
|       <template v-slot:top> | ||||
|         <q-btn dense flat push @click="refreshServices" icon="refresh" /> | ||||
|         <q-btn dense flat push @click="getServices" icon="refresh" /> | ||||
|         <q-space /> | ||||
|         <q-input v-model="filter" outlined label="Search" dense clearable> | ||||
|           <template v-slot:prepend> | ||||
| @@ -242,7 +242,7 @@ export default { | ||||
|         .then(r => { | ||||
|           this.serviceDetailVisible = false; | ||||
|           this.serviceDetailsModal = false; | ||||
|           this.refreshServices(); | ||||
|           this.getServices(); | ||||
|           this.notifySuccess(`Service ${name} was edited!`); | ||||
|         }) | ||||
|         .catch(e => { | ||||
| @@ -303,7 +303,7 @@ export default { | ||||
|       this.$axios | ||||
|         .post("/services/serviceaction/", data) | ||||
|         .then(r => { | ||||
|           this.refreshServices(); | ||||
|           this.getServices(); | ||||
|           this.serviceDetailsModal = false; | ||||
|           this.notifySuccess(`Service ${fullname} was ${status}!`); | ||||
|         }) | ||||
| @@ -313,19 +313,9 @@ export default { | ||||
|         }); | ||||
|     }, | ||||
|     getServices() { | ||||
|       this.$q.loading.show({ message: "Loading services..." }); | ||||
|       this.$axios | ||||
|         .get(`/services/${this.pk}/services/`) | ||||
|         .then(r => { | ||||
|           this.servicesData = [r.data][0].services; | ||||
|         }) | ||||
|         .catch(e => { | ||||
|           this.notifyError(e.response.data); | ||||
|         }); | ||||
|     }, | ||||
|     refreshServices() { | ||||
|       this.$q.loading.show({ message: "Reloading services..." }); | ||||
|       this.$axios | ||||
|         .get(`/services/${this.pk}/refreshedservices/`) | ||||
|         .then(r => { | ||||
|           this.servicesData = [r.data][0].services; | ||||
|           this.$q.loading.hide(); | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| <template> | ||||
|   <div v-if="Object.keys(summary).length === 0">No agent selected</div> | ||||
|   <div v-else> | ||||
|     <q-btn class="q-mr-sm" dense flat push icon="refresh" @click="refreshSummary" /> | ||||
|     <span> | ||||
|       <b>{{ summary.hostname }}</b> | ||||
|       <span v-if="summary.maintenance_mode"> • <q-badge color="warning"> Maintenance Mode </q-badge> </span> | ||||
| @@ -87,8 +88,12 @@ | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapGetters } from "vuex"; | ||||
| import mixins from "@/mixins/mixins"; | ||||
|  | ||||
| export default { | ||||
|   name: "SummaryTab", | ||||
|   mixins: [mixins], | ||||
|   data() { | ||||
|     return {}; | ||||
|   }, | ||||
| @@ -96,8 +101,22 @@ export default { | ||||
|     awaitingSync(total, passing, failing) { | ||||
|       return total !== 0 && passing === 0 && failing === 0 ? true : false; | ||||
|     }, | ||||
|     refreshSummary() { | ||||
|       this.$q.loading.show(); | ||||
|       this.$axios | ||||
|         .get(`/agents/${this.selectedAgentPk}/wmi/`) | ||||
|         .then(r => { | ||||
|           this.$store.dispatch("loadSummary", this.selectedAgentPk); | ||||
|           this.$q.loading.hide(); | ||||
|         }) | ||||
|         .catch(e => { | ||||
|           this.$q.loading.hide(); | ||||
|           this.notifyError(e.response.data); | ||||
|         }); | ||||
|     }, | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters(["selectedAgentPk"]), | ||||
|     summary() { | ||||
|       return this.$store.state.agentSummary; | ||||
|     }, | ||||
|   | ||||
| @@ -26,19 +26,25 @@ | ||||
|       > | ||||
|         <div class="q-pa-xs q-gutter-xs"> | ||||
|           <q-badge class="text-caption q-mr-xs" color="grey" text-color="black"> | ||||
|             <code>--log DEBUG</code> | ||||
|             <code>-log debug</code> | ||||
|           </q-badge> | ||||
|           <span>To enable verbose output during the install</span> | ||||
|         </div> | ||||
|         <div class="q-pa-xs q-gutter-xs"> | ||||
|           <q-badge class="text-caption q-mr-xs" color="grey" text-color="black"> | ||||
|             <code>--nosalt</code> | ||||
|             <code>-silent</code> | ||||
|           </q-badge> | ||||
|           <span>Do not popup any message boxes during install</span> | ||||
|         </div> | ||||
|         <div class="q-pa-xs q-gutter-xs"> | ||||
|           <q-badge class="text-caption q-mr-xs" color="grey" text-color="black"> | ||||
|             <code>-nosalt</code> | ||||
|           </q-badge> | ||||
|           <span> Do not install salt during agent install. </span> | ||||
|         </div> | ||||
|         <div class="q-pa-xs q-gutter-xs"> | ||||
|           <q-badge class="text-caption q-mr-xs" color="grey" text-color="black"> | ||||
|             <code>--local-mesh "C:\\<some folder or path>\\meshagent.exe"</code> | ||||
|             <code>-local-mesh "C:\\<some folder or path>\\meshagent.exe"</code> | ||||
|           </q-badge> | ||||
|           <span> | ||||
|             To skip downloading the Mesh Agent during the install. Download it | ||||
| @@ -49,7 +55,7 @@ | ||||
|         </div> | ||||
|         <div class="q-pa-xs q-gutter-xs"> | ||||
|           <q-badge class="text-caption q-mr-xs" color="grey" text-color="black"> | ||||
|             <code>--cert "C:\\<some folder or path>\\ca.pem"</code> | ||||
|             <code>-cert "C:\\<some folder or path>\\ca.pem"</code> | ||||
|           </q-badge> | ||||
|           <span> To use a domain CA </span> | ||||
|         </div> | ||||
|   | ||||
| @@ -27,10 +27,13 @@ | ||||
|         <p>Fix issues with the Tactical Checkrunner windows service which handles running all checks.</p> | ||||
|       </q-card-section> | ||||
|       <q-card-section v-show="mode === 'salt'"> | ||||
|         <p>Fix issues with the salt-minion which handles windows updates, chocolatey and scheduled tasks.</p> | ||||
|         <p>Fix issues with the salt-minion which handles windows updates and chocolatey.</p> | ||||
|       </q-card-section> | ||||
|       <q-card-section v-show="mode === 'rpc'"> | ||||
|         <p>Fix issues with the Tactical RPC service which handles most of the agent's realtime functions.</p> | ||||
|         <p> | ||||
|           Fix issues with the Tactical RPC service which handles most of the agent's realtime functions and scheduled | ||||
|           tasks. | ||||
|         </p> | ||||
|       </q-card-section> | ||||
|       <q-card-section v-show="mode === 'command'"> | ||||
|         <p>Run a shell command on the agent.</p> | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
|         <div class="col"> | ||||
|           <q-btn | ||||
|             label="Cancel Action" | ||||
|             :disable="selectedRow === null || selectedStatus === 'completed' || actionType === 'taskaction'" | ||||
|             :disable="selectedRow === null || selectedStatus === 'completed' || actionType !== 'schedreboot'" | ||||
|             color="red" | ||||
|             icon="cancel" | ||||
|             dense | ||||
| @@ -98,26 +98,26 @@ export default { | ||||
|       hostname: "", | ||||
|       pagination: { | ||||
|         rowsPerPage: 0, | ||||
|         sortBy: "due", | ||||
|         descending: true, | ||||
|         sortBy: "status", | ||||
|         descending: false, | ||||
|       }, | ||||
|       all_columns: [ | ||||
|         { name: "id", field: "id" }, | ||||
|         { name: "status", field: "status" }, | ||||
|         { name: "type", label: "Type", align: "left", sortable: true }, | ||||
|         { name: "type", label: "Type", field: "action_type", align: "left", sortable: true }, | ||||
|         { name: "due", label: "Due", field: "due", align: "left", sortable: true }, | ||||
|         { name: "desc", label: "Description", align: "left", sortable: true }, | ||||
|         { name: "agent", label: "Agent", align: "left", sortable: true }, | ||||
|         { name: "client", label: "Client", align: "left", sortable: true }, | ||||
|         { name: "site", label: "Site", align: "left", sortable: true }, | ||||
|         { name: "desc", label: "Description", field: "description", align: "left", sortable: true }, | ||||
|         { name: "agent", label: "Agent", field: "hostname", align: "left", sortable: true }, | ||||
|         { name: "client", label: "Client", field: "client", align: "left", sortable: true }, | ||||
|         { name: "site", label: "Site", field: "site", align: "left", sortable: true }, | ||||
|       ], | ||||
|       all_visibleColumns: ["type", "due", "desc", "agent", "client", "site"], | ||||
|       agent_columns: [ | ||||
|         { name: "id", field: "id" }, | ||||
|         { name: "status", field: "status" }, | ||||
|         { name: "type", label: "Type", align: "left", sortable: true }, | ||||
|         { name: "type", label: "Type", field: "action_type", align: "left", sortable: true }, | ||||
|         { name: "due", label: "Due", field: "due", align: "left", sortable: true }, | ||||
|         { name: "desc", label: "Description", align: "left", sortable: true }, | ||||
|         { name: "desc", label: "Description", field: "description", align: "left", sortable: true }, | ||||
|       ], | ||||
|       agent_visibleColumns: ["type", "due", "desc"], | ||||
|     }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user