Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
153351cc9f | ||
|
|
1b1eec40a7 | ||
|
|
763877541a | ||
|
|
1fad7d72a2 | ||
|
|
51ea2ea879 | ||
|
|
d77a478bf0 | ||
|
|
e413c0264a | ||
|
|
f88e7f898c | ||
|
|
d07bd4a6db | ||
|
|
fb34c099d5 | ||
|
|
1d2ee56a15 | ||
|
|
86665f7f09 | ||
|
|
0d2b4af986 | ||
|
|
dc2b2eeb9f | ||
|
|
e5dbb66d53 | ||
|
|
3474b1c471 | ||
|
|
3886de5b7c | ||
|
|
2b3cec06b3 | ||
|
|
8536754d14 | ||
|
|
1f36235801 |
@@ -164,13 +164,11 @@ class Agent(BaseAuditModel):
|
|||||||
elif i.status == "failing":
|
elif i.status == "failing":
|
||||||
failing += 1
|
failing += 1
|
||||||
|
|
||||||
has_failing_checks = True if failing > 0 else False
|
|
||||||
|
|
||||||
ret = {
|
ret = {
|
||||||
"total": total,
|
"total": total,
|
||||||
"passing": passing,
|
"passing": passing,
|
||||||
"failing": failing,
|
"failing": failing,
|
||||||
"has_failing_checks": has_failing_checks,
|
"has_failing_checks": failing > 0,
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from time import sleep
|
|||||||
import random
|
import random
|
||||||
import requests
|
import requests
|
||||||
from packaging import version as pyver
|
from packaging import version as pyver
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
@@ -14,40 +15,27 @@ from logs.models import PendingAction
|
|||||||
|
|
||||||
logger.configure(**settings.LOG_CONFIG)
|
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:
|
||||||
@app.task
|
|
||||||
def send_agent_update_task(pks, version):
|
|
||||||
assert isinstance(pks, list)
|
|
||||||
|
|
||||||
q = Agent.objects.filter(pk__in=pks)
|
|
||||||
agents = [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)
|
agent = Agent.objects.get(pk=pk)
|
||||||
|
|
||||||
# skip if we can't determine the arch
|
# skip if we can't determine the arch
|
||||||
if agent.arch is None:
|
if agent.arch is None:
|
||||||
logger.warning(
|
logger.warning(f"Unable to determine arch on {agent.hostname}. Skipping.")
|
||||||
f"Unable to determine arch on {agent.salt_id}. Skipping."
|
return "noarch"
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# golang agent only backwards compatible with py agent 0.11.2
|
# force an update to 1.1.5 since 1.1.6 needs agent to be on 1.1.5 first
|
||||||
# force an upgrade to the latest python agent if version < 0.11.2
|
if pyver.parse(agent.version) < pyver.parse("1.1.5"):
|
||||||
if pyver.parse(agent.version) < pyver.parse("0.11.2"):
|
version = "1.1.5"
|
||||||
url = OLD_64_PY_AGENT if agent.arch == "64" else OLD_32_PY_AGENT
|
if agent.arch == "64":
|
||||||
inno = (
|
url = "https://github.com/wh1te909/rmmagent/releases/download/v1.1.5/winagent-v1.1.5.exe"
|
||||||
"winagent-v0.11.2.exe"
|
inno = "winagent-v1.1.5.exe"
|
||||||
if agent.arch == "64"
|
elif agent.arch == "32":
|
||||||
else "winagent-v0.11.2-x86.exe"
|
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:
|
else:
|
||||||
|
return "nover"
|
||||||
|
else:
|
||||||
|
version = settings.LATEST_AGENT_VER
|
||||||
url = agent.winagent_dl
|
url = agent.winagent_dl
|
||||||
inno = agent.win_inno_exe
|
inno = agent.win_inno_exe
|
||||||
|
|
||||||
@@ -58,109 +46,61 @@ def send_agent_update_task(pks, version):
|
|||||||
action = agent.pendingactions.filter(
|
action = agent.pendingactions.filter(
|
||||||
action_type="agentupdate", status="pending"
|
action_type="agentupdate", status="pending"
|
||||||
).last()
|
).last()
|
||||||
if pyver.parse(action.details["version"]) < pyver.parse(
|
if pyver.parse(action.details["version"]) < pyver.parse(version):
|
||||||
settings.LATEST_AGENT_VER
|
|
||||||
):
|
|
||||||
action.delete()
|
action.delete()
|
||||||
else:
|
else:
|
||||||
continue
|
return "pending"
|
||||||
|
|
||||||
PendingAction.objects.create(
|
PendingAction.objects.create(
|
||||||
agent=agent,
|
agent=agent,
|
||||||
action_type="agentupdate",
|
action_type="agentupdate",
|
||||||
details={
|
details={
|
||||||
"url": agent.winagent_dl,
|
"url": url,
|
||||||
"version": settings.LATEST_AGENT_VER,
|
"version": version,
|
||||||
"inno": agent.win_inno_exe,
|
"inno": inno,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
return "created"
|
||||||
# TODO
|
# TODO
|
||||||
# Salt is deprecated, remove this once salt is gone
|
# Salt is deprecated, remove this once salt is gone
|
||||||
else:
|
else:
|
||||||
r = agent.salt_api_async(
|
agent.salt_api_async(
|
||||||
func="win_agent.do_agent_update_v2",
|
func="win_agent.do_agent_update_v2",
|
||||||
kwargs={
|
kwargs={
|
||||||
"inno": inno,
|
"inno": inno,
|
||||||
"url": url,
|
"url": url,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
sleep(5)
|
return "salt"
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def auto_self_agent_update_task():
|
def send_agent_update_task(pks: List[int], version: str) -> None:
|
||||||
|
q = Agent.objects.filter(pk__in=pks)
|
||||||
|
agents: List[int] = [
|
||||||
|
i.pk for i in q if pyver.parse(i.version) < pyver.parse(version)
|
||||||
|
]
|
||||||
|
|
||||||
|
for pk in agents:
|
||||||
|
agent_update(pk)
|
||||||
|
|
||||||
|
|
||||||
|
@app.task
|
||||||
|
def auto_self_agent_update_task() -> None:
|
||||||
core = CoreSettings.objects.first()
|
core = CoreSettings.objects.first()
|
||||||
if not core.agent_auto_update:
|
if not core.agent_auto_update:
|
||||||
logger.info("Agent auto update is disabled. Skipping.")
|
logger.info("Agent auto update is disabled. Skipping.")
|
||||||
return
|
return
|
||||||
|
|
||||||
q = Agent.objects.only("pk", "version")
|
q = Agent.objects.only("pk", "version")
|
||||||
agents = [
|
pks: List[int] = [
|
||||||
i.pk
|
i.pk
|
||||||
for i in q
|
for i in q
|
||||||
if pyver.parse(i.version) < pyver.parse(settings.LATEST_AGENT_VER)
|
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():
|
|
||||||
action = agent.pendingactions.filter(
|
|
||||||
action_type="agentupdate", status="pending"
|
|
||||||
).last()
|
|
||||||
if pyver.parse(action.details["version"]) < pyver.parse(
|
|
||||||
settings.LATEST_AGENT_VER
|
|
||||||
):
|
|
||||||
action.delete()
|
|
||||||
else:
|
|
||||||
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
|
@app.task
|
||||||
|
|||||||
@@ -5,19 +5,20 @@ from unittest.mock import patch
|
|||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
from itertools import cycle
|
from itertools import cycle
|
||||||
|
|
||||||
|
from django.test import TestCase, override_settings
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone as djangotime
|
from django.utils import timezone as djangotime
|
||||||
|
from logs.models import PendingAction
|
||||||
|
|
||||||
from tacticalrmm.test import TacticalTestCase
|
from tacticalrmm.test import TacticalTestCase
|
||||||
from .serializers import AgentSerializer
|
from .serializers import AgentSerializer
|
||||||
from winupdate.serializers import WinUpdatePolicySerializer
|
from winupdate.serializers import WinUpdatePolicySerializer
|
||||||
from .models import Agent
|
from .models import Agent
|
||||||
from .tasks import (
|
from .tasks import (
|
||||||
|
agent_recovery_sms_task,
|
||||||
auto_self_agent_update_task,
|
auto_self_agent_update_task,
|
||||||
sync_salt_modules_task,
|
sync_salt_modules_task,
|
||||||
batch_sync_modules_task,
|
batch_sync_modules_task,
|
||||||
OLD_64_PY_AGENT,
|
|
||||||
OLD_32_PY_AGENT,
|
|
||||||
)
|
)
|
||||||
from winupdate.models import WinUpdatePolicy
|
from winupdate.models import WinUpdatePolicy
|
||||||
|
|
||||||
@@ -786,6 +787,70 @@ class TestAgentTasks(TacticalTestCase):
|
|||||||
self.assertEqual(ret.status, "SUCCESS")
|
self.assertEqual(ret.status, "SUCCESS")
|
||||||
|
|
||||||
@patch("agents.models.Agent.salt_api_async")
|
@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)
|
@patch("agents.tasks.sleep", return_value=None)
|
||||||
def test_auto_self_agent_update_task(self, mock_sleep, salt_api_async):
|
def test_auto_self_agent_update_task(self, mock_sleep, salt_api_async):
|
||||||
# test 64bit golang agent
|
# test 64bit golang agent
|
||||||
@@ -888,4 +953,4 @@ class TestAgentTasks(TacticalTestCase):
|
|||||||
"url": OLD_32_PY_AGENT,
|
"url": OLD_32_PY_AGENT,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertEqual(ret.status, "SUCCESS")
|
self.assertEqual(ret.status, "SUCCESS") """
|
||||||
@@ -2,6 +2,7 @@ from django.urls import path
|
|||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path("checkin/", views.CheckIn.as_view()),
|
||||||
path("hello/", views.Hello.as_view()),
|
path("hello/", views.Hello.as_view()),
|
||||||
path("checkrunner/", views.CheckRunner.as_view()),
|
path("checkrunner/", views.CheckRunner.as_view()),
|
||||||
path("<str:agentid>/checkrunner/", views.CheckRunner.as_view()),
|
path("<str:agentid>/checkrunner/", views.CheckRunner.as_view()),
|
||||||
|
|||||||
@@ -39,7 +39,110 @@ from tacticalrmm.utils import notify_error, reload_nats, filter_software, Softwa
|
|||||||
logger.configure(**settings.LOG_CONFIG)
|
logger.configure(**settings.LOG_CONFIG)
|
||||||
|
|
||||||
|
|
||||||
|
class CheckIn(APIView):
|
||||||
|
"""
|
||||||
|
The agent's checkin endpoint
|
||||||
|
patch: called every 45 to 110 seconds, handles agent updates and recovery
|
||||||
|
put: called every 5 to 10 minutes, handles basic system info
|
||||||
|
post: called once on windows service startup
|
||||||
|
"""
|
||||||
|
|
||||||
|
authentication_classes = [TokenAuthentication]
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def patch(self, request):
|
||||||
|
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
|
||||||
|
agent.version = request.data["version"]
|
||||||
|
agent.last_seen = djangotime.now()
|
||||||
|
agent.save(update_fields=["version", "last_seen"])
|
||||||
|
|
||||||
|
if agent.agentoutages.exists() and agent.agentoutages.last().is_active:
|
||||||
|
last_outage = agent.agentoutages.last()
|
||||||
|
last_outage.recovery_time = djangotime.now()
|
||||||
|
last_outage.save(update_fields=["recovery_time"])
|
||||||
|
|
||||||
|
if agent.overdue_email_alert:
|
||||||
|
agent_recovery_email_task.delay(pk=last_outage.pk)
|
||||||
|
if agent.overdue_text_alert:
|
||||||
|
agent_recovery_sms_task.delay(pk=last_outage.pk)
|
||||||
|
|
||||||
|
recovery = agent.recoveryactions.filter(last_run=None).last()
|
||||||
|
if recovery is not None:
|
||||||
|
recovery.last_run = djangotime.now()
|
||||||
|
recovery.save(update_fields=["last_run"])
|
||||||
|
return Response(recovery.send())
|
||||||
|
|
||||||
|
# handle agent update
|
||||||
|
if agent.pendingactions.filter(
|
||||||
|
action_type="agentupdate", status="pending"
|
||||||
|
).exists():
|
||||||
|
update = agent.pendingactions.filter(
|
||||||
|
action_type="agentupdate", status="pending"
|
||||||
|
).last()
|
||||||
|
update.status = "completed"
|
||||||
|
update.save(update_fields=["status"])
|
||||||
|
return Response(update.details)
|
||||||
|
|
||||||
|
# get any pending actions
|
||||||
|
if agent.pendingactions.filter(status="pending").exists():
|
||||||
|
agent.handle_pending_actions()
|
||||||
|
|
||||||
|
return Response("ok")
|
||||||
|
|
||||||
|
def put(self, request):
|
||||||
|
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
|
||||||
|
serializer = WinAgentSerializer(instance=agent, data=request.data, partial=True)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
disks = request.data["disks"]
|
||||||
|
new = []
|
||||||
|
# python agent
|
||||||
|
if isinstance(disks, dict):
|
||||||
|
for k, v in disks.items():
|
||||||
|
new.append(v)
|
||||||
|
else:
|
||||||
|
# golang agent
|
||||||
|
for disk in disks:
|
||||||
|
tmp = {}
|
||||||
|
for k, v in disk.items():
|
||||||
|
tmp["device"] = disk["device"]
|
||||||
|
tmp["fstype"] = disk["fstype"]
|
||||||
|
tmp["total"] = bytes2human(disk["total"])
|
||||||
|
tmp["used"] = bytes2human(disk["used"])
|
||||||
|
tmp["free"] = bytes2human(disk["free"])
|
||||||
|
tmp["percent"] = int(disk["percent"])
|
||||||
|
new.append(tmp)
|
||||||
|
|
||||||
|
if request.data["logged_in_username"] == "None":
|
||||||
|
serializer.save(disks=new)
|
||||||
|
else:
|
||||||
|
serializer.save(
|
||||||
|
disks=new,
|
||||||
|
last_logged_in_user=request.data["logged_in_username"],
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response("ok")
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
|
||||||
|
|
||||||
|
serializer = WinAgentSerializer(instance=agent, data=request.data, partial=True)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
serializer.save(last_seen=djangotime.now())
|
||||||
|
|
||||||
|
sync_salt_modules_task.delay(agent.pk)
|
||||||
|
check_for_updates_task.apply_async(
|
||||||
|
queue="wupdate", kwargs={"pk": agent.pk, "wait": True}
|
||||||
|
)
|
||||||
|
|
||||||
|
if not agent.choco_installed:
|
||||||
|
install_chocolatey.delay(agent.pk, wait=True)
|
||||||
|
|
||||||
|
return Response("ok")
|
||||||
|
|
||||||
|
|
||||||
class Hello(APIView):
|
class Hello(APIView):
|
||||||
|
#### DEPRECATED, for agents <= 1.1.9 ####
|
||||||
"""
|
"""
|
||||||
The agent's checkin endpoint
|
The agent's checkin endpoint
|
||||||
patch: called every 30 to 120 seconds
|
patch: called every 30 to 120 seconds
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ class Client(BaseAuditModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def has_failing_checks(self):
|
def has_failing_checks(self):
|
||||||
|
|
||||||
agents = (
|
agents = (
|
||||||
Agent.objects.only(
|
Agent.objects.only(
|
||||||
"pk",
|
"pk",
|
||||||
@@ -50,14 +49,17 @@ class Client(BaseAuditModel):
|
|||||||
.filter(site__client=self)
|
.filter(site__client=self)
|
||||||
.prefetch_related("agentchecks")
|
.prefetch_related("agentchecks")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
failing = 0
|
||||||
for agent in agents:
|
for agent in agents:
|
||||||
if agent.checks["has_failing_checks"]:
|
if agent.checks["has_failing_checks"]:
|
||||||
return True
|
failing += 1
|
||||||
|
|
||||||
if agent.overdue_email_alert or agent.overdue_text_alert:
|
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
|
@staticmethod
|
||||||
def serialize(client):
|
def serialize(client):
|
||||||
@@ -98,7 +100,6 @@ class Site(BaseAuditModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def has_failing_checks(self):
|
def has_failing_checks(self):
|
||||||
|
|
||||||
agents = (
|
agents = (
|
||||||
Agent.objects.only(
|
Agent.objects.only(
|
||||||
"pk",
|
"pk",
|
||||||
@@ -110,14 +111,17 @@ class Site(BaseAuditModel):
|
|||||||
.filter(site=self)
|
.filter(site=self)
|
||||||
.prefetch_related("agentchecks")
|
.prefetch_related("agentchecks")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
failing = 0
|
||||||
for agent in agents:
|
for agent in agents:
|
||||||
if agent.checks["has_failing_checks"]:
|
if agent.checks["has_failing_checks"]:
|
||||||
return True
|
failing += 1
|
||||||
|
|
||||||
if agent.overdue_email_alert or agent.overdue_text_alert:
|
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
|
@staticmethod
|
||||||
def serialize(site):
|
def serialize(site):
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ func main() {
|
|||||||
debugLog := flag.String("log", "", "Verbose output")
|
debugLog := flag.String("log", "", "Verbose output")
|
||||||
localMesh := flag.String("local-mesh", "", "Use local mesh agent")
|
localMesh := flag.String("local-mesh", "", "Use local mesh agent")
|
||||||
noSalt := flag.Bool("nosalt", false, "Does not install salt")
|
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")
|
cert := flag.String("cert", "", "Path to ca.pem")
|
||||||
timeout := flag.String("timeout", "", "Timeout for subprocess calls")
|
timeout := flag.String("timeout", "", "Timeout for subprocess calls")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
@@ -78,7 +79,11 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
cmdArgs = append(cmdArgs, "--log", "DEBUG")
|
cmdArgs = append(cmdArgs, "-log", "debug")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *silent {
|
||||||
|
cmdArgs = append(cmdArgs, "-silent")
|
||||||
}
|
}
|
||||||
|
|
||||||
if *noSalt {
|
if *noSalt {
|
||||||
@@ -86,27 +91,27 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(strings.TrimSpace(*localMesh)) != 0 {
|
if len(strings.TrimSpace(*localMesh)) != 0 {
|
||||||
cmdArgs = append(cmdArgs, "--local-mesh", *localMesh)
|
cmdArgs = append(cmdArgs, "-local-mesh", *localMesh)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(strings.TrimSpace(*cert)) != 0 {
|
if len(strings.TrimSpace(*cert)) != 0 {
|
||||||
cmdArgs = append(cmdArgs, "--cert", *cert)
|
cmdArgs = append(cmdArgs, "-cert", *cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(strings.TrimSpace(*timeout)) != 0 {
|
if len(strings.TrimSpace(*timeout)) != 0 {
|
||||||
cmdArgs = append(cmdArgs, "--timeout", *timeout)
|
cmdArgs = append(cmdArgs, "-timeout", *timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
if Rdp == "1" {
|
if Rdp == "1" {
|
||||||
cmdArgs = append(cmdArgs, "--rdp")
|
cmdArgs = append(cmdArgs, "-rdp")
|
||||||
}
|
}
|
||||||
|
|
||||||
if Ping == "1" {
|
if Ping == "1" {
|
||||||
cmdArgs = append(cmdArgs, "--ping")
|
cmdArgs = append(cmdArgs, "-ping")
|
||||||
}
|
}
|
||||||
|
|
||||||
if Power == "1" {
|
if Power == "1" {
|
||||||
cmdArgs = append(cmdArgs, "--power")
|
cmdArgs = append(cmdArgs, "-power")
|
||||||
}
|
}
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
|
|||||||
@@ -15,17 +15,17 @@ EXE_DIR = os.path.join(BASE_DIR, "tacticalrmm/private/exe")
|
|||||||
AUTH_USER_MODEL = "accounts.User"
|
AUTH_USER_MODEL = "accounts.User"
|
||||||
|
|
||||||
# latest release
|
# latest release
|
||||||
TRMM_VERSION = "0.2.8"
|
TRMM_VERSION = "0.2.15"
|
||||||
|
|
||||||
# bump this version everytime vue code is changed
|
# bump this version everytime vue code is changed
|
||||||
# to alert user they need to manually refresh their browser
|
# to alert user they need to manually refresh their browser
|
||||||
APP_VER = "0.0.97"
|
APP_VER = "0.0.99"
|
||||||
|
|
||||||
# https://github.com/wh1te909/salt
|
# https://github.com/wh1te909/salt
|
||||||
LATEST_SALT_VER = "1.1.0"
|
LATEST_SALT_VER = "1.1.0"
|
||||||
|
|
||||||
# https://github.com/wh1te909/rmmagent
|
# https://github.com/wh1te909/rmmagent
|
||||||
LATEST_AGENT_VER = "1.1.3"
|
LATEST_AGENT_VER = "1.1.10"
|
||||||
|
|
||||||
MESH_VER = "0.7.14"
|
MESH_VER = "0.7.14"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SCRIPT_VERSION="4"
|
SCRIPT_VERSION="5"
|
||||||
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/backup.sh'
|
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/backup.sh'
|
||||||
|
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
@@ -50,6 +50,11 @@ if [ -d /meshcentral/meshcentral-coredumps ]; then
|
|||||||
rm -f /meshcentral/meshcentral-coredumps/*
|
rm -f /meshcentral/meshcentral-coredumps/*
|
||||||
fi
|
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')
|
dt_now=$(date '+%Y_%m_%d__%H_%M_%S')
|
||||||
tmp_dir=$(mktemp -d -t tacticalrmm-XXXXXXXXXXXXXXXXXXXXX)
|
tmp_dir=$(mktemp -d -t tacticalrmm-XXXXXXXXXXXXXXXXXXXXX)
|
||||||
sysd="/etc/systemd/system"
|
sysd="/etc/systemd/system"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SCRIPT_VERSION="99"
|
SCRIPT_VERSION="100"
|
||||||
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/update.sh'
|
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'
|
LATEST_SETTINGS_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/api/tacticalrmm/tacticalrmm/settings.py'
|
||||||
YELLOW='\033[1;33m'
|
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 chown ${USER}:${USER} /usr/local/bin/goversioninfo
|
||||||
sudo chmod +x /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
|
if [[ "${CURRENT_PIP_VER}" != "${LATEST_PIP_VER}" ]]; then
|
||||||
rm -rf /rmm/api/env
|
rm -rf /rmm/api/env
|
||||||
cd /rmm/api
|
cd /rmm/api
|
||||||
|
|||||||
@@ -26,19 +26,25 @@
|
|||||||
>
|
>
|
||||||
<div class="q-pa-xs q-gutter-xs">
|
<div class="q-pa-xs q-gutter-xs">
|
||||||
<q-badge class="text-caption q-mr-xs" color="grey" text-color="black">
|
<q-badge class="text-caption q-mr-xs" color="grey" text-color="black">
|
||||||
<code>--log DEBUG</code>
|
<code>-log debug</code>
|
||||||
</q-badge>
|
</q-badge>
|
||||||
<span>To enable verbose output during the install</span>
|
<span>To enable verbose output during the install</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="q-pa-xs q-gutter-xs">
|
<div class="q-pa-xs q-gutter-xs">
|
||||||
<q-badge class="text-caption q-mr-xs" color="grey" text-color="black">
|
<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>
|
</q-badge>
|
||||||
<span> Do not install salt during agent install. </span>
|
<span> Do not install salt during agent install. </span>
|
||||||
</div>
|
</div>
|
||||||
<div class="q-pa-xs q-gutter-xs">
|
<div class="q-pa-xs q-gutter-xs">
|
||||||
<q-badge class="text-caption q-mr-xs" color="grey" text-color="black">
|
<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>
|
</q-badge>
|
||||||
<span>
|
<span>
|
||||||
To skip downloading the Mesh Agent during the install. Download it
|
To skip downloading the Mesh Agent during the install. Download it
|
||||||
@@ -49,7 +55,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="q-pa-xs q-gutter-xs">
|
<div class="q-pa-xs q-gutter-xs">
|
||||||
<q-badge class="text-caption q-mr-xs" color="grey" text-color="black">
|
<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>
|
</q-badge>
|
||||||
<span> To use a domain CA </span>
|
<span> To use a domain CA </span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,10 +27,13 @@
|
|||||||
<p>Fix issues with the Tactical Checkrunner windows service which handles running all checks.</p>
|
<p>Fix issues with the Tactical Checkrunner windows service which handles running all checks.</p>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section v-show="mode === 'salt'">
|
<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>
|
||||||
<q-card-section v-show="mode === 'rpc'">
|
<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>
|
||||||
<q-card-section v-show="mode === 'command'">
|
<q-card-section v-show="mode === 'command'">
|
||||||
<p>Run a shell command on the agent.</p>
|
<p>Run a shell command on the agent.</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user