Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59c880dc36 | ||
|
|
e5c355e8f9 | ||
|
|
d36fadf3ca | ||
|
|
b618cbdf7c | ||
|
|
15ec7173aa | ||
|
|
4166e92754 | ||
|
|
85166b6e8b | ||
|
|
5278599675 | ||
|
|
18cac8ba5d | ||
|
|
dfccbceea6 | ||
|
|
fc4b651e46 | ||
|
|
fb89922ecf | ||
|
|
8ab23c8cd9 | ||
|
|
787a2c5071 | ||
|
|
da76a20345 | ||
|
|
9688dbdb36 | ||
|
|
6fa16e1a5e | ||
|
|
71a2e3cfca | ||
|
|
e9c0f7e200 | ||
|
|
25154a4331 | ||
|
|
22c152f600 | ||
|
|
3eab61cbc3 | ||
|
|
a029c1d0db | ||
|
|
706757d215 | ||
|
|
9054c233f4 |
@@ -39,7 +39,7 @@ Demo database resets every hour. A lot of features are disabled for obvious reas
|
|||||||
|
|
||||||
## Mac agent versions supported
|
## Mac agent versions supported
|
||||||
|
|
||||||
- 64 bit Intel and Apple Silicon (M1, M2)
|
- 64 bit Intel and Apple Silicon (M-Series)
|
||||||
|
|
||||||
## Installation / Backup / Restore / Usage
|
## Installation / Backup / Restore / Usage
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 4.2.16 on 2024-10-06 05:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("accounts", "0037_role_can_run_server_scripts_role_can_use_webterm"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="role",
|
||||||
|
name="can_edit_global_keystore",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="role",
|
||||||
|
name="can_view_global_keystore",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -131,6 +131,8 @@ class Role(BaseAuditModel):
|
|||||||
can_manage_customfields = models.BooleanField(default=False)
|
can_manage_customfields = models.BooleanField(default=False)
|
||||||
can_run_server_scripts = models.BooleanField(default=False)
|
can_run_server_scripts = models.BooleanField(default=False)
|
||||||
can_use_webterm = models.BooleanField(default=False)
|
can_use_webterm = models.BooleanField(default=False)
|
||||||
|
can_view_global_keystore = models.BooleanField(default=False)
|
||||||
|
can_edit_global_keystore = models.BooleanField(default=False)
|
||||||
|
|
||||||
# checks
|
# checks
|
||||||
can_list_checks = models.BooleanField(default=False)
|
can_list_checks = models.BooleanField(default=False)
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Generated by Django 4.2.16 on 2024-10-05 20:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0047_alter_coresettings_notify_on_warning_alerts"),
|
||||||
|
("agents", "0059_alter_agenthistory_id"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="agenthistory",
|
||||||
|
name="collector_all_output",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="agenthistory",
|
||||||
|
name="custom_field",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="history",
|
||||||
|
to="core.customfield",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="agenthistory",
|
||||||
|
name="save_to_agent_note",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1122,6 +1122,15 @@ class AgentHistory(models.Model):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
)
|
)
|
||||||
script_results = models.JSONField(null=True, blank=True)
|
script_results = models.JSONField(null=True, blank=True)
|
||||||
|
custom_field = models.ForeignKey(
|
||||||
|
"core.CustomField",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="history",
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
)
|
||||||
|
collector_all_output = models.BooleanField(default=False)
|
||||||
|
save_to_agent_note = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.agent.hostname} - {self.type}"
|
return f"{self.agent.hostname} - {self.type}"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
from itertools import cycle
|
from itertools import cycle
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import patch
|
from unittest.mock import PropertyMock, patch
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -768,6 +768,67 @@ class TestAgentViews(TacticalTestCase):
|
|||||||
|
|
||||||
self.assertEqual(Note.objects.get(agent=self.agent).note, "ok")
|
self.assertEqual(Note.objects.get(agent=self.agent).note, "ok")
|
||||||
|
|
||||||
|
# test run on server
|
||||||
|
with patch("core.utils.run_server_script") as mock_run_server_script:
|
||||||
|
mock_run_server_script.return_value = ("output", "error", 1.23456789, 0)
|
||||||
|
data = {
|
||||||
|
"script": script.pk,
|
||||||
|
"output": "wait",
|
||||||
|
"args": ["arg1", "arg2"],
|
||||||
|
"timeout": 15,
|
||||||
|
"run_as_user": False,
|
||||||
|
"env_vars": ["key1=val1", "key2=val2"],
|
||||||
|
"run_on_server": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
r = self.client.post(url, data, format="json")
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
hist = AgentHistory.objects.filter(agent=self.agent, script=script).last()
|
||||||
|
if not hist:
|
||||||
|
raise AgentHistory.DoesNotExist
|
||||||
|
|
||||||
|
mock_run_server_script.assert_called_with(
|
||||||
|
body=script.script_body,
|
||||||
|
args=script.parse_script_args(self.agent, script.shell, data["args"]),
|
||||||
|
env_vars=script.parse_script_env_vars(
|
||||||
|
self.agent, script.shell, data["env_vars"]
|
||||||
|
),
|
||||||
|
shell=script.shell,
|
||||||
|
timeout=18,
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_ret = {
|
||||||
|
"stdout": "output",
|
||||||
|
"stderr": "error",
|
||||||
|
"execution_time": "1.2346",
|
||||||
|
"retcode": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(r.data, expected_ret)
|
||||||
|
|
||||||
|
hist.refresh_from_db()
|
||||||
|
expected_script_results = {**expected_ret, "id": hist.pk}
|
||||||
|
self.assertEqual(hist.script_results, expected_script_results)
|
||||||
|
|
||||||
|
# test run on server with server scripts disabled
|
||||||
|
with patch(
|
||||||
|
"core.models.CoreSettings.server_scripts_enabled",
|
||||||
|
new_callable=PropertyMock,
|
||||||
|
) as server_scripts_enabled:
|
||||||
|
server_scripts_enabled.return_value = False
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"script": script.pk,
|
||||||
|
"output": "wait",
|
||||||
|
"args": ["arg1", "arg2"],
|
||||||
|
"timeout": 15,
|
||||||
|
"run_as_user": False,
|
||||||
|
"env_vars": ["key1=val1", "key2=val2"],
|
||||||
|
"run_on_server": True,
|
||||||
|
}
|
||||||
|
r = self.client.post(url, data, format="json")
|
||||||
|
self.assertEqual(r.status_code, 400)
|
||||||
|
|
||||||
def test_get_notes(self):
|
def test_get_notes(self):
|
||||||
url = f"{base_url}/notes/"
|
url = f"{base_url}/notes/"
|
||||||
|
|
||||||
|
|||||||
@@ -768,6 +768,10 @@ def run_script(request, agent_id):
|
|||||||
run_as_user: bool = request.data["run_as_user"]
|
run_as_user: bool = request.data["run_as_user"]
|
||||||
env_vars: list[str] = request.data["env_vars"]
|
env_vars: list[str] = request.data["env_vars"]
|
||||||
req_timeout = int(request.data["timeout"]) + 3
|
req_timeout = int(request.data["timeout"]) + 3
|
||||||
|
run_on_server: bool | None = request.data.get("run_on_server")
|
||||||
|
|
||||||
|
if run_on_server and not get_core_settings().server_scripts_enabled:
|
||||||
|
return notify_error("This feature is disabled.")
|
||||||
|
|
||||||
AuditLog.audit_script_run(
|
AuditLog.audit_script_run(
|
||||||
username=request.user.username,
|
username=request.user.username,
|
||||||
@@ -784,6 +788,29 @@ def run_script(request, agent_id):
|
|||||||
)
|
)
|
||||||
history_pk = hist.pk
|
history_pk = hist.pk
|
||||||
|
|
||||||
|
if run_on_server:
|
||||||
|
from core.utils import run_server_script
|
||||||
|
|
||||||
|
r = run_server_script(
|
||||||
|
body=script.script_body,
|
||||||
|
args=script.parse_script_args(agent, script.shell, args),
|
||||||
|
env_vars=script.parse_script_env_vars(agent, script.shell, env_vars),
|
||||||
|
shell=script.shell,
|
||||||
|
timeout=req_timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
ret = {
|
||||||
|
"stdout": r[0],
|
||||||
|
"stderr": r[1],
|
||||||
|
"execution_time": "{:.4f}".format(r[2]),
|
||||||
|
"retcode": r[3],
|
||||||
|
}
|
||||||
|
|
||||||
|
hist.script_results = {**ret, "id": history_pk}
|
||||||
|
hist.save(update_fields=["script_results"])
|
||||||
|
|
||||||
|
return Response(ret)
|
||||||
|
|
||||||
if output == "wait":
|
if output == "wait":
|
||||||
r = agent.run_script(
|
r = agent.run_script(
|
||||||
scriptpk=script.pk,
|
scriptpk=script.pk,
|
||||||
@@ -1008,6 +1035,16 @@ def bulk(request):
|
|||||||
elif request.data["mode"] == "script":
|
elif request.data["mode"] == "script":
|
||||||
script = get_object_or_404(Script, pk=request.data["script"])
|
script = get_object_or_404(Script, pk=request.data["script"])
|
||||||
|
|
||||||
|
# prevent API from breaking for those who haven't updated payload
|
||||||
|
try:
|
||||||
|
custom_field_pk = request.data["custom_field"]
|
||||||
|
collector_all_output = request.data["collector_all_output"]
|
||||||
|
save_to_agent_note = request.data["save_to_agent_note"]
|
||||||
|
except KeyError:
|
||||||
|
custom_field_pk = None
|
||||||
|
collector_all_output = False
|
||||||
|
save_to_agent_note = False
|
||||||
|
|
||||||
bulk_script_task.delay(
|
bulk_script_task.delay(
|
||||||
script_pk=script.pk,
|
script_pk=script.pk,
|
||||||
agent_pks=agents,
|
agent_pks=agents,
|
||||||
@@ -1016,6 +1053,9 @@ def bulk(request):
|
|||||||
username=request.user.username[:50],
|
username=request.user.username[:50],
|
||||||
run_as_user=request.data["run_as_user"],
|
run_as_user=request.data["run_as_user"],
|
||||||
env_vars=request.data["env_vars"],
|
env_vars=request.data["env_vars"],
|
||||||
|
custom_field_pk=custom_field_pk,
|
||||||
|
collector_all_output=collector_all_output,
|
||||||
|
save_to_agent_note=save_to_agent_note,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response(f"{script.name} will now be run on {len(agents)} agents. {ht}")
|
return Response(f"{script.name} will now be run on {len(agents)} agents. {ht}")
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from accounts.models import User
|
from accounts.models import User
|
||||||
from agents.models import Agent, AgentHistory
|
from agents.models import Agent, AgentHistory, Note
|
||||||
from agents.serializers import AgentHistorySerializer
|
from agents.serializers import AgentHistorySerializer
|
||||||
from alerts.tasks import cache_agents_alert_template
|
from alerts.tasks import cache_agents_alert_template
|
||||||
from apiv3.utils import get_agent_config
|
from apiv3.utils import get_agent_config
|
||||||
@@ -40,6 +40,7 @@ from tacticalrmm.constants import (
|
|||||||
AuditActionType,
|
AuditActionType,
|
||||||
AuditObjType,
|
AuditObjType,
|
||||||
CheckStatus,
|
CheckStatus,
|
||||||
|
CustomFieldModel,
|
||||||
DebugLogType,
|
DebugLogType,
|
||||||
GoArch,
|
GoArch,
|
||||||
MeshAgentIdent,
|
MeshAgentIdent,
|
||||||
@@ -581,11 +582,39 @@ class AgentHistoryResult(APIView):
|
|||||||
request.data["script_results"]["retcode"] = 1
|
request.data["script_results"]["retcode"] = 1
|
||||||
|
|
||||||
hist = get_object_or_404(
|
hist = get_object_or_404(
|
||||||
AgentHistory.objects.filter(agent__agent_id=agentid), pk=pk
|
AgentHistory.objects.select_related("custom_field").filter(
|
||||||
|
agent__agent_id=agentid
|
||||||
|
),
|
||||||
|
pk=pk,
|
||||||
)
|
)
|
||||||
s = AgentHistorySerializer(instance=hist, data=request.data, partial=True)
|
s = AgentHistorySerializer(instance=hist, data=request.data, partial=True)
|
||||||
s.is_valid(raise_exception=True)
|
s.is_valid(raise_exception=True)
|
||||||
s.save()
|
s.save()
|
||||||
|
|
||||||
|
if hist.custom_field:
|
||||||
|
if hist.custom_field.model == CustomFieldModel.AGENT:
|
||||||
|
field = hist.custom_field.get_or_create_field_value(hist.agent)
|
||||||
|
elif hist.custom_field.model == CustomFieldModel.CLIENT:
|
||||||
|
field = hist.custom_field.get_or_create_field_value(hist.agent.client)
|
||||||
|
elif hist.custom_field.model == CustomFieldModel.SITE:
|
||||||
|
field = hist.custom_field.get_or_create_field_value(hist.agent.site)
|
||||||
|
|
||||||
|
r = request.data["script_results"]["stdout"]
|
||||||
|
value = (
|
||||||
|
r.strip()
|
||||||
|
if hist.collector_all_output
|
||||||
|
else r.strip().split("\n")[-1].strip()
|
||||||
|
)
|
||||||
|
|
||||||
|
field.save_to_field(value)
|
||||||
|
|
||||||
|
if hist.save_to_agent_note:
|
||||||
|
Note.objects.create(
|
||||||
|
agent=hist.agent,
|
||||||
|
user=request.user,
|
||||||
|
note=request.data["script_results"]["stdout"],
|
||||||
|
)
|
||||||
|
|
||||||
return Response("ok")
|
return Response("ok")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -365,9 +365,11 @@ class CheckResult(models.Model):
|
|||||||
if len(self.history) > 15:
|
if len(self.history) > 15:
|
||||||
self.history = self.history[-15:]
|
self.history = self.history[-15:]
|
||||||
|
|
||||||
update_fields.extend(["history"])
|
update_fields.extend(["history", "more_info"])
|
||||||
|
|
||||||
avg = int(mean(self.history))
|
avg = int(mean(self.history))
|
||||||
|
txt = "Memory Usage" if check.check_type == CheckType.MEMORY else "CPU Load"
|
||||||
|
self.more_info = f"Average {txt}: {avg}%"
|
||||||
|
|
||||||
if check.error_threshold and avg > check.error_threshold:
|
if check.error_threshold and avg > check.error_threshold:
|
||||||
self.status = CheckStatus.FAILING
|
self.status = CheckStatus.FAILING
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ class Site(BaseAuditModel):
|
|||||||
old_site.alert_template != self.alert_template
|
old_site.alert_template != self.alert_template
|
||||||
or old_site.workstation_policy != self.workstation_policy
|
or old_site.workstation_policy != self.workstation_policy
|
||||||
or old_site.server_policy != self.server_policy
|
or old_site.server_policy != self.server_policy
|
||||||
|
or old_site.client != self.client
|
||||||
):
|
):
|
||||||
cache_agents_alert_template.delay()
|
cache_agents_alert_template.delay()
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,14 @@ class CoreSettingsPerms(permissions.BasePermission):
|
|||||||
return _has_perm(r, "can_edit_core_settings")
|
return _has_perm(r, "can_edit_core_settings")
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalKeyStorePerms(permissions.BasePermission):
|
||||||
|
def has_permission(self, r, view) -> bool:
|
||||||
|
if r.method == "GET":
|
||||||
|
return _has_perm(r, "can_view_global_keystore")
|
||||||
|
|
||||||
|
return _has_perm(r, "can_edit_global_keystore")
|
||||||
|
|
||||||
|
|
||||||
class URLActionPerms(permissions.BasePermission):
|
class URLActionPerms(permissions.BasePermission):
|
||||||
def has_permission(self, r, view) -> bool:
|
def has_permission(self, r, view) -> bool:
|
||||||
if r.method in {"GET", "PATCH"}:
|
if r.method in {"GET", "PATCH"}:
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ from .permissions import (
|
|||||||
CodeSignPerms,
|
CodeSignPerms,
|
||||||
CoreSettingsPerms,
|
CoreSettingsPerms,
|
||||||
CustomFieldPerms,
|
CustomFieldPerms,
|
||||||
|
GlobalKeyStorePerms,
|
||||||
RunServerScriptPerms,
|
RunServerScriptPerms,
|
||||||
ServerMaintPerms,
|
ServerMaintPerms,
|
||||||
URLActionPerms,
|
URLActionPerms,
|
||||||
@@ -310,7 +311,7 @@ class CodeSign(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class GetAddKeyStore(APIView):
|
class GetAddKeyStore(APIView):
|
||||||
permission_classes = [IsAuthenticated, CoreSettingsPerms]
|
permission_classes = [IsAuthenticated, GlobalKeyStorePerms]
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
keys = GlobalKVStore.objects.all()
|
keys = GlobalKVStore.objects.all()
|
||||||
@@ -325,7 +326,7 @@ class GetAddKeyStore(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class UpdateDeleteKeyStore(APIView):
|
class UpdateDeleteKeyStore(APIView):
|
||||||
permission_classes = [IsAuthenticated, CoreSettingsPerms]
|
permission_classes = [IsAuthenticated, GlobalKeyStorePerms]
|
||||||
|
|
||||||
def put(self, request, pk):
|
def put(self, request, pk):
|
||||||
key = get_object_or_404(GlobalKVStore, pk=pk)
|
key = get_object_or_404(GlobalKVStore, pk=pk)
|
||||||
|
|||||||
@@ -1,47 +1,46 @@
|
|||||||
adrf==0.1.6
|
|
||||||
asgiref==3.8.1
|
asgiref==3.8.1
|
||||||
celery==5.4.0
|
celery==5.4.0
|
||||||
certifi==2024.7.4
|
certifi==2024.8.30
|
||||||
cffi==1.16.0
|
cffi==1.17.1
|
||||||
channels==4.1.0
|
channels==4.1.0
|
||||||
channels_redis==4.2.0
|
channels_redis==4.2.0
|
||||||
cryptography==42.0.8
|
cryptography==43.0.3
|
||||||
Django==4.2.14
|
Django==4.2.16
|
||||||
django-cors-headers==4.4.0
|
django-cors-headers==4.5.0
|
||||||
django-filter==24.2
|
django-filter==24.3
|
||||||
django-rest-knox==4.2.0
|
django-rest-knox==4.2.0
|
||||||
djangorestframework==3.15.2
|
djangorestframework==3.15.2
|
||||||
drf-spectacular==0.27.2
|
drf-spectacular==0.27.2
|
||||||
hiredis==2.3.2
|
hiredis==2.3.2
|
||||||
kombu==5.3.7
|
kombu==5.3.7
|
||||||
meshctrl==0.1.15
|
meshctrl==0.1.15
|
||||||
msgpack==1.0.8
|
msgpack==1.1.0
|
||||||
nats-py==2.8.0
|
nats-py==2.9.0
|
||||||
packaging==24.1
|
packaging==24.1
|
||||||
psutil==5.9.8
|
psutil==6.0.0
|
||||||
psycopg[binary]==3.1.19
|
psycopg[binary]==3.2.3
|
||||||
pycparser==2.22
|
pycparser==2.22
|
||||||
pycryptodome==3.20.0
|
pycryptodome==3.21.0
|
||||||
pyotp==2.9.0
|
pyotp==2.9.0
|
||||||
pyparsing==3.1.2
|
pyparsing==3.1.4
|
||||||
python-ipware==2.0.2
|
python-ipware==2.0.2
|
||||||
qrcode==7.4.2
|
qrcode==8.0
|
||||||
redis==5.0.7
|
redis==5.0.8
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
sqlparse==0.5.0
|
sqlparse==0.5.1
|
||||||
twilio==8.13.0
|
twilio==8.13.0
|
||||||
urllib3==2.2.2
|
urllib3==2.2.3
|
||||||
uvicorn[standard]==0.30.1
|
uvicorn[standard]==0.31.1
|
||||||
uWSGI==2.0.26
|
uWSGI==2.0.27
|
||||||
validators==0.24.0
|
validators==0.24.0
|
||||||
vine==5.1.0
|
vine==5.1.0
|
||||||
websockets==12.0
|
websockets==13.1
|
||||||
zipp==3.19.2
|
zipp==3.20.2
|
||||||
pandas==2.2.2
|
pandas==2.2.3
|
||||||
kaleido==0.2.1
|
kaleido==0.2.1
|
||||||
jinja2==3.1.4
|
jinja2==3.1.4
|
||||||
markdown==3.6
|
markdown==3.7
|
||||||
plotly==5.22.0
|
plotly==5.24.1
|
||||||
weasyprint==62.3
|
weasyprint==62.3
|
||||||
ocxsect==0.1.5
|
ocxsect==0.1.5
|
||||||
@@ -118,8 +118,14 @@ class Script(BaseAuditModel):
|
|||||||
|
|
||||||
args = script["args"] if "args" in script.keys() else []
|
args = script["args"] if "args" in script.keys() else []
|
||||||
|
|
||||||
|
env = script["env"] if "env" in script.keys() else []
|
||||||
|
|
||||||
syntax = script["syntax"] if "syntax" in script.keys() else ""
|
syntax = script["syntax"] if "syntax" in script.keys() else ""
|
||||||
|
|
||||||
|
run_as_user = (
|
||||||
|
script["run_as_user"] if "run_as_user" in script.keys() else False
|
||||||
|
)
|
||||||
|
|
||||||
supported_platforms = (
|
supported_platforms = (
|
||||||
script["supported_platforms"]
|
script["supported_platforms"]
|
||||||
if "supported_platforms" in script.keys()
|
if "supported_platforms" in script.keys()
|
||||||
@@ -135,7 +141,9 @@ class Script(BaseAuditModel):
|
|||||||
i.shell = script["shell"]
|
i.shell = script["shell"]
|
||||||
i.default_timeout = default_timeout
|
i.default_timeout = default_timeout
|
||||||
i.args = args
|
i.args = args
|
||||||
|
i.env_vars = env
|
||||||
i.syntax = syntax
|
i.syntax = syntax
|
||||||
|
i.run_as_user = run_as_user
|
||||||
i.filename = script["filename"]
|
i.filename = script["filename"]
|
||||||
i.supported_platforms = supported_platforms
|
i.supported_platforms = supported_platforms
|
||||||
|
|
||||||
@@ -163,8 +171,10 @@ class Script(BaseAuditModel):
|
|||||||
category=category,
|
category=category,
|
||||||
default_timeout=default_timeout,
|
default_timeout=default_timeout,
|
||||||
args=args,
|
args=args,
|
||||||
|
env_vars=env,
|
||||||
filename=script["filename"],
|
filename=script["filename"],
|
||||||
syntax=syntax,
|
syntax=syntax,
|
||||||
|
run_as_user=run_as_user,
|
||||||
supported_platforms=supported_platforms,
|
supported_platforms=supported_platforms,
|
||||||
)
|
)
|
||||||
# new_script.hash_script_body() # also saves script
|
# new_script.hash_script_body() # also saves script
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class ScriptSerializer(ModelSerializer):
|
|||||||
"run_as_user",
|
"run_as_user",
|
||||||
"env_vars",
|
"env_vars",
|
||||||
]
|
]
|
||||||
|
extra_kwargs = {"script_body": {"trim_whitespace": False}}
|
||||||
|
|
||||||
|
|
||||||
class ScriptCheckSerializer(ModelSerializer):
|
class ScriptCheckSerializer(ModelSerializer):
|
||||||
@@ -63,3 +64,4 @@ class ScriptSnippetSerializer(ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ScriptSnippet
|
model = ScriptSnippet
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
extra_kwargs = {"code": {"trim_whitespace": False}}
|
||||||
|
|||||||
@@ -54,12 +54,21 @@ def bulk_script_task(
|
|||||||
username: str,
|
username: str,
|
||||||
run_as_user: bool = False,
|
run_as_user: bool = False,
|
||||||
env_vars: list[str] = [],
|
env_vars: list[str] = [],
|
||||||
|
custom_field_pk: int | None,
|
||||||
|
collector_all_output: bool = False,
|
||||||
|
save_to_agent_note: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
script = Script.objects.get(pk=script_pk)
|
script = Script.objects.get(pk=script_pk)
|
||||||
# always override if set on script model
|
# always override if set on script model
|
||||||
if script.run_as_user:
|
if script.run_as_user:
|
||||||
run_as_user = True
|
run_as_user = True
|
||||||
|
|
||||||
|
custom_field = None
|
||||||
|
if custom_field_pk:
|
||||||
|
from core.models import CustomField
|
||||||
|
|
||||||
|
custom_field = CustomField.objects.get(pk=custom_field_pk)
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
agent: "Agent"
|
agent: "Agent"
|
||||||
for agent in Agent.objects.filter(pk__in=agent_pks):
|
for agent in Agent.objects.filter(pk__in=agent_pks):
|
||||||
@@ -68,6 +77,9 @@ def bulk_script_task(
|
|||||||
type=AgentHistoryType.SCRIPT_RUN,
|
type=AgentHistoryType.SCRIPT_RUN,
|
||||||
script=script,
|
script=script,
|
||||||
username=username,
|
username=username,
|
||||||
|
custom_field=custom_field,
|
||||||
|
collector_all_output=collector_all_output,
|
||||||
|
save_to_agent_note=save_to_agent_note,
|
||||||
)
|
)
|
||||||
data = {
|
data = {
|
||||||
"func": "runscriptfull",
|
"func": "runscriptfull",
|
||||||
|
|||||||
@@ -21,21 +21,21 @@ MAC_UNINSTALL = BASE_DIR / "core" / "mac_uninstall.sh"
|
|||||||
AUTH_USER_MODEL = "accounts.User"
|
AUTH_USER_MODEL = "accounts.User"
|
||||||
|
|
||||||
# latest release
|
# latest release
|
||||||
TRMM_VERSION = "0.19.3"
|
TRMM_VERSION = "0.19.4"
|
||||||
|
|
||||||
# https://github.com/amidaware/tacticalrmm-web
|
# https://github.com/amidaware/tacticalrmm-web
|
||||||
WEB_VERSION = "0.101.48"
|
WEB_VERSION = "0.101.49"
|
||||||
|
|
||||||
# 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.194"
|
APP_VER = "0.0.195"
|
||||||
|
|
||||||
# https://github.com/amidaware/rmmagent
|
# https://github.com/amidaware/rmmagent
|
||||||
LATEST_AGENT_VER = "2.8.0"
|
LATEST_AGENT_VER = "2.8.0"
|
||||||
|
|
||||||
MESH_VER = "1.1.21"
|
MESH_VER = "1.1.32"
|
||||||
|
|
||||||
NATS_SERVER_VER = "2.10.17"
|
NATS_SERVER_VER = "2.10.22"
|
||||||
|
|
||||||
# Install Nushell on the agent
|
# Install Nushell on the agent
|
||||||
# https://github.com/nushell/nushell
|
# https://github.com/nushell/nushell
|
||||||
@@ -81,10 +81,10 @@ INSTALL_DENO_URL = ""
|
|||||||
DENO_DEFAULT_PERMISSIONS = "--allow-all"
|
DENO_DEFAULT_PERMISSIONS = "--allow-all"
|
||||||
|
|
||||||
# for the update script, bump when need to recreate venv
|
# for the update script, bump when need to recreate venv
|
||||||
PIP_VER = "44"
|
PIP_VER = "45"
|
||||||
|
|
||||||
SETUPTOOLS_VER = "70.2.0"
|
SETUPTOOLS_VER = "75.1.0"
|
||||||
WHEEL_VER = "0.43.0"
|
WHEEL_VER = "0.44.0"
|
||||||
|
|
||||||
AGENT_BASE_URL = "https://agents.tacticalrmm.com"
|
AGENT_BASE_URL = "https://agents.tacticalrmm.com"
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ RUN MESH_VER=$(grep -o 'MESH_VER.*' /tmp/settings.py | cut -d'"' -f 2) && \
|
|||||||
cat > package.json <<EOF
|
cat > package.json <<EOF
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"archiver": "5.3.1",
|
"archiver": "7.0.1",
|
||||||
"meshcentral": "$MESH_VER",
|
"meshcentral": "$MESH_VER",
|
||||||
"mongodb": "4.13.0",
|
"mongodb": "4.13.0",
|
||||||
"otplib": "10.2.3",
|
"otplib": "10.2.3",
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ if [ ! -f "/home/node/app/meshcentral-data/config.json" ] || [[ "${MESH_PERSISTE
|
|||||||
|
|
||||||
encoded_uri=$(node -p "encodeURI('mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@${MONGODB_HOST}:${MONGODB_PORT}')")
|
encoded_uri=$(node -p "encodeURI('mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@${MONGODB_HOST}:${MONGODB_PORT}')")
|
||||||
|
|
||||||
mesh_config="$(cat << EOF
|
mesh_config="$(
|
||||||
|
cat <<EOF
|
||||||
{
|
{
|
||||||
"settings": {
|
"settings": {
|
||||||
"mongodb": "${encoded_uri}",
|
"mongodb": "${encoded_uri}",
|
||||||
@@ -39,8 +40,7 @@ if [ ! -f "/home/node/app/meshcentral-data/config.json" ] || [[ "${MESH_PERSISTE
|
|||||||
"aliasPort": 443,
|
"aliasPort": 443,
|
||||||
"allowLoginToken": true,
|
"allowLoginToken": true,
|
||||||
"allowFraming": true,
|
"allowFraming": true,
|
||||||
"_agentPing": 60,
|
"agentPing": 35,
|
||||||
"agentPong": 300,
|
|
||||||
"allowHighQualityDesktop": true,
|
"allowHighQualityDesktop": true,
|
||||||
"agentCoreDump": false,
|
"agentCoreDump": false,
|
||||||
"compression": ${MESH_COMPRESSION_ENABLED},
|
"compression": ${MESH_COMPRESSION_ENABLED},
|
||||||
@@ -74,26 +74,26 @@ if [ ! -f "/home/node/app/meshcentral-data/config.json" ] || [[ "${MESH_PERSISTE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
|
|
||||||
echo "${mesh_config}" > /home/node/app/meshcentral-data/config.json
|
echo "${mesh_config}" >/home/node/app/meshcentral-data/config.json
|
||||||
fi
|
fi
|
||||||
|
|
||||||
node node_modules/meshcentral --createaccount ${MESH_USER} --pass ${MESH_PASS} --email example@example.com
|
node node_modules/meshcentral --createaccount ${MESH_USER} --pass ${MESH_PASS} --email example@example.com
|
||||||
node node_modules/meshcentral --adminaccount ${MESH_USER}
|
node node_modules/meshcentral --adminaccount ${MESH_USER}
|
||||||
|
|
||||||
if [ ! -f "${TACTICAL_DIR}/tmp/mesh_token" ]; then
|
if [ ! -f "${TACTICAL_DIR}/tmp/mesh_token" ]; then
|
||||||
mesh_token=$(node node_modules/meshcentral --logintokenkey)
|
mesh_token=$(node node_modules/meshcentral --logintokenkey)
|
||||||
|
|
||||||
if [[ ${#mesh_token} -eq 160 ]]; then
|
if [[ ${#mesh_token} -eq 160 ]]; then
|
||||||
echo ${mesh_token} > /opt/tactical/tmp/mesh_token
|
echo ${mesh_token} >/opt/tactical/tmp/mesh_token
|
||||||
else
|
else
|
||||||
echo "Failed to generate mesh token. Fix the error and restart the mesh container"
|
echo "Failed to generate mesh token. Fix the error and restart the mesh container"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# wait for nginx container
|
# wait for nginx container
|
||||||
until (echo > /dev/tcp/"${NGINX_HOST_IP}"/${NGINX_HOST_PORT}) &> /dev/null; do
|
until (echo >/dev/tcp/"${NGINX_HOST_IP}"/${NGINX_HOST_PORT}) &>/dev/null; do
|
||||||
echo "waiting for nginx to start..."
|
echo "waiting for nginx to start..."
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM nats:2.10.17-alpine
|
FROM nats:2.10.22-alpine
|
||||||
|
|
||||||
ENV TACTICAL_DIR /opt/tactical
|
ENV TACTICAL_DIR /opt/tactical
|
||||||
ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready
|
ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
SCRIPT_VERSION="85"
|
SCRIPT_VERSION="86"
|
||||||
SCRIPT_URL="https://raw.githubusercontent.com/amidaware/tacticalrmm/master/install.sh"
|
SCRIPT_URL="https://raw.githubusercontent.com/amidaware/tacticalrmm/master/install.sh"
|
||||||
|
|
||||||
sudo apt install -y curl wget dirmngr gnupg lsb-release ca-certificates
|
sudo apt install -y curl wget dirmngr gnupg lsb-release ca-certificates
|
||||||
@@ -420,7 +420,7 @@ mesh_pkg="$(
|
|||||||
cat <<EOF
|
cat <<EOF
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"archiver": "5.3.1",
|
"archiver": "7.0.1",
|
||||||
"meshcentral": "${MESH_VER}",
|
"meshcentral": "${MESH_VER}",
|
||||||
"otplib": "10.2.3",
|
"otplib": "10.2.3",
|
||||||
"pg": "8.7.1",
|
"pg": "8.7.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
SCRIPT_VERSION="59"
|
SCRIPT_VERSION="60"
|
||||||
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/restore.sh'
|
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/restore.sh'
|
||||||
|
|
||||||
sudo apt update
|
sudo apt update
|
||||||
@@ -413,7 +413,7 @@ mesh_pkg="$(
|
|||||||
cat <<EOF
|
cat <<EOF
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"archiver": "5.3.1",
|
"archiver": "7.0.1",
|
||||||
"meshcentral": "${MESH_VER}",
|
"meshcentral": "${MESH_VER}",
|
||||||
"otplib": "10.2.3",
|
"otplib": "10.2.3",
|
||||||
"pg": "8.7.1",
|
"pg": "8.7.1",
|
||||||
@@ -494,7 +494,6 @@ echo "Running management commands...please wait..."
|
|||||||
API=$(python manage.py get_config api)
|
API=$(python manage.py get_config api)
|
||||||
WEB_VERSION=$(python manage.py get_config webversion)
|
WEB_VERSION=$(python manage.py get_config webversion)
|
||||||
FRONTEND=$(python manage.py get_config webdomain)
|
FRONTEND=$(python manage.py get_config webdomain)
|
||||||
webdomain=$(python manage.py get_config webdomain)
|
|
||||||
meshdomain=$(python manage.py get_config meshdomain)
|
meshdomain=$(python manage.py get_config meshdomain)
|
||||||
WEBTAR_URL=$(python manage.py get_webtar_url)
|
WEBTAR_URL=$(python manage.py get_webtar_url)
|
||||||
CERT_PUB_KEY=$(python manage.py get_config certfile)
|
CERT_PUB_KEY=$(python manage.py get_config certfile)
|
||||||
@@ -620,9 +619,9 @@ sudo ln -s /etc/nginx/sites-available/rmm.conf /etc/nginx/sites-enabled/rmm.conf
|
|||||||
|
|
||||||
HAS_11=$(grep 127.0.1.1 /etc/hosts)
|
HAS_11=$(grep 127.0.1.1 /etc/hosts)
|
||||||
if [[ $HAS_11 ]]; then
|
if [[ $HAS_11 ]]; then
|
||||||
sudo sed -i "/127.0.1.1/s/$/ ${API} ${webdomain} ${meshdomain}/" /etc/hosts
|
sudo sed -i "/127.0.1.1/s/$/ ${API} ${FRONTEND} ${meshdomain}/" /etc/hosts
|
||||||
else
|
else
|
||||||
echo "127.0.1.1 ${API} ${webdomain} ${meshdomain}" | sudo tee --append /etc/hosts >/dev/null
|
echo "127.0.1.1 ${API} ${FRONTEND} ${meshdomain}" | sudo tee --append /etc/hosts >/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo systemctl enable nats.service
|
sudo systemctl enable nats.service
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
SCRIPT_VERSION="153"
|
SCRIPT_VERSION="154"
|
||||||
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/update.sh'
|
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/update.sh'
|
||||||
LATEST_SETTINGS_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/api/tacticalrmm/tacticalrmm/settings.py'
|
LATEST_SETTINGS_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/api/tacticalrmm/tacticalrmm/settings.py'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
@@ -319,7 +319,7 @@ if [[ "${CURRENT_MESH_VER}" != "${LATEST_MESH_VER}" ]] || [[ "$force" = true ]];
|
|||||||
cat <<EOF
|
cat <<EOF
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"archiver": "5.3.1",
|
"archiver": "7.0.1",
|
||||||
"meshcentral": "${LATEST_MESH_VER}",
|
"meshcentral": "${LATEST_MESH_VER}",
|
||||||
"otplib": "10.2.3",
|
"otplib": "10.2.3",
|
||||||
"pg": "8.7.1",
|
"pg": "8.7.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user