Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d8c783a7d | ||
|
|
a2e996b550 | ||
|
|
cfc1c31050 | ||
|
|
45106bf6f9 | ||
|
|
6e3cfe491b | ||
|
|
12f2158afd | ||
|
|
6d78773c55 | ||
|
|
43a62d4eb6 | ||
|
|
cc08dfda96 | ||
|
|
622e33588e | ||
|
|
67980b58a0 | ||
|
|
027e444955 | ||
|
|
d838750389 | ||
|
|
71d8bd5266 | ||
|
|
ec4ae24bbd | ||
|
|
1128149359 | ||
|
|
bdfc6634ec | ||
|
|
ca4d19667b | ||
|
|
c71aa7baa7 | ||
|
|
fd80ccd2c5 | ||
|
|
9dc0b24399 | ||
|
|
747954e6fb | ||
|
|
274f4f227e | ||
|
|
92197d8d49 | ||
|
|
aee06920eb | ||
|
|
5111b17d3c | ||
|
|
2849d8f45d | ||
|
|
bac60d9bd4 | ||
|
|
9c797162f4 | ||
|
|
09d184e2f8 | ||
|
|
7bca618906 | ||
|
|
67607103e9 | ||
|
|
73c9956fe4 | ||
|
|
b42f2ffe33 | ||
|
|
30a3f185ef |
@@ -556,6 +556,7 @@ class Agent(BaseAuditModel):
|
|||||||
run_as_user = True
|
run_as_user = True
|
||||||
|
|
||||||
parsed_args = script.parse_script_args(self, script.shell, args)
|
parsed_args = script.parse_script_args(self, script.shell, args)
|
||||||
|
parsed_env_vars = script.parse_script_env_vars(self, script.shell, env_vars)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"func": "runscriptfull" if full else "runscript",
|
"func": "runscriptfull" if full else "runscript",
|
||||||
@@ -566,7 +567,7 @@ class Agent(BaseAuditModel):
|
|||||||
"shell": script.shell,
|
"shell": script.shell,
|
||||||
},
|
},
|
||||||
"run_as_user": run_as_user,
|
"run_as_user": run_as_user,
|
||||||
"env_vars": env_vars,
|
"env_vars": parsed_env_vars,
|
||||||
}
|
}
|
||||||
|
|
||||||
if history_pk != 0:
|
if history_pk != 0:
|
||||||
|
|||||||
@@ -570,6 +570,13 @@ def install_agent(request):
|
|||||||
from agents.utils import get_agent_url
|
from agents.utils import get_agent_url
|
||||||
from core.utils import token_is_valid
|
from core.utils import token_is_valid
|
||||||
|
|
||||||
|
insecure = getattr(settings, "TRMM_INSECURE", False)
|
||||||
|
|
||||||
|
if insecure and request.data["installMethod"] in {"exe", "powershell"}:
|
||||||
|
return notify_error(
|
||||||
|
"Not available in insecure mode. Please use the 'Manual' method."
|
||||||
|
)
|
||||||
|
|
||||||
# TODO rework this ghetto validation hack
|
# TODO rework this ghetto validation hack
|
||||||
# https://github.com/amidaware/tacticalrmm/issues/1461
|
# https://github.com/amidaware/tacticalrmm/issues/1461
|
||||||
try:
|
try:
|
||||||
@@ -672,6 +679,9 @@ def install_agent(request):
|
|||||||
if int(request.data["power"]):
|
if int(request.data["power"]):
|
||||||
cmd.append("--power")
|
cmd.append("--power")
|
||||||
|
|
||||||
|
if insecure:
|
||||||
|
cmd.append("--insecure")
|
||||||
|
|
||||||
resp["cmd"] = " ".join(str(i) for i in cmd)
|
resp["cmd"] = " ".join(str(i) for i in cmd)
|
||||||
else:
|
else:
|
||||||
install_flags.insert(0, f"sudo ./{inno}")
|
install_flags.insert(0, f"sudo ./{inno}")
|
||||||
@@ -680,6 +690,8 @@ def install_agent(request):
|
|||||||
resp["cmd"] = (
|
resp["cmd"] = (
|
||||||
dl + f" && chmod +x {inno} && " + " ".join(str(i) for i in cmd)
|
dl + f" && chmod +x {inno} && " + " ".join(str(i) for i in cmd)
|
||||||
)
|
)
|
||||||
|
if insecure:
|
||||||
|
resp["cmd"] += " --insecure"
|
||||||
|
|
||||||
resp["url"] = download_url
|
resp["url"] = download_url
|
||||||
|
|
||||||
|
|||||||
@@ -627,8 +627,7 @@ class Alert(models.Model):
|
|||||||
pattern = re.compile(".*\\{\\{alert\\.(.*)\\}\\}.*")
|
pattern = re.compile(".*\\{\\{alert\\.(.*)\\}\\}.*")
|
||||||
|
|
||||||
for arg in args:
|
for arg in args:
|
||||||
match = pattern.match(arg)
|
if match := pattern.match(arg):
|
||||||
if match:
|
|
||||||
name = match.group(1)
|
name = match.group(1)
|
||||||
|
|
||||||
# check if attr exists and isn't a function
|
# check if attr exists and isn't a function
|
||||||
|
|||||||
@@ -252,7 +252,11 @@ class TaskGOGetSerializer(serializers.ModelSerializer):
|
|||||||
"shell": script.shell,
|
"shell": script.shell,
|
||||||
"timeout": action["timeout"],
|
"timeout": action["timeout"],
|
||||||
"run_as_user": script.run_as_user,
|
"run_as_user": script.run_as_user,
|
||||||
"env_vars": env_vars,
|
"env_vars": Script.parse_script_env_vars(
|
||||||
|
agent=agent,
|
||||||
|
shell=script.shell,
|
||||||
|
env_vars=env_vars,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if actions_to_remove:
|
if actions_to_remove:
|
||||||
|
|||||||
0
api/tacticalrmm/beta/v1/__init__.py
Normal file
0
api/tacticalrmm/beta/v1/__init__.py
Normal file
0
api/tacticalrmm/beta/v1/agent/__init__.py
Normal file
0
api/tacticalrmm/beta/v1/agent/__init__.py
Normal file
37
api/tacticalrmm/beta/v1/agent/filter.py
Normal file
37
api/tacticalrmm/beta/v1/agent/filter.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import django_filters
|
||||||
|
from agents.models import Agent
|
||||||
|
|
||||||
|
|
||||||
|
class AgentFilter(django_filters.FilterSet):
|
||||||
|
last_seen_range = django_filters.DateTimeFromToRangeFilter(field_name="last_seen")
|
||||||
|
total_ram_range = django_filters.NumericRangeFilter(field_name="total_ram")
|
||||||
|
patches_last_installed_range = django_filters.DateTimeFromToRangeFilter(
|
||||||
|
field_name="patches_last_installed"
|
||||||
|
)
|
||||||
|
|
||||||
|
client_id = django_filters.NumberFilter(method="client_id_filter")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Agent
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"hostname",
|
||||||
|
"agent_id",
|
||||||
|
"operating_system",
|
||||||
|
"plat",
|
||||||
|
"monitoring_type",
|
||||||
|
"needs_reboot",
|
||||||
|
"logged_in_username",
|
||||||
|
"last_logged_in_user",
|
||||||
|
"alert_template",
|
||||||
|
"site",
|
||||||
|
"policy",
|
||||||
|
"last_seen_range",
|
||||||
|
"total_ram_range",
|
||||||
|
"patches_last_installed_range",
|
||||||
|
]
|
||||||
|
|
||||||
|
def client_id_filter(self, queryset, name, value):
|
||||||
|
if value:
|
||||||
|
return queryset.filter(site__client__id=value)
|
||||||
|
return queryset
|
||||||
40
api/tacticalrmm/beta/v1/agent/views.py
Normal file
40
api/tacticalrmm/beta/v1/agent/views.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||||
|
from rest_framework.request import Request
|
||||||
|
from rest_framework.serializers import BaseSerializer
|
||||||
|
|
||||||
|
from agents.models import Agent
|
||||||
|
from agents.permissions import AgentPerms
|
||||||
|
from beta.v1.agent.filter import AgentFilter
|
||||||
|
from beta.v1.pagination import StandardResultsSetPagination
|
||||||
|
from ..serializers import DetailAgentSerializer, ListAgentSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class AgentViewSet(viewsets.ModelViewSet):
|
||||||
|
permission_classes = [IsAuthenticated, AgentPerms]
|
||||||
|
queryset = Agent.objects.all()
|
||||||
|
pagination_class = StandardResultsSetPagination
|
||||||
|
http_method_names = ["get", "put"]
|
||||||
|
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||||
|
filterset_class = AgentFilter
|
||||||
|
search_fields = ["hostname", "services"]
|
||||||
|
ordering_fields = ["id"]
|
||||||
|
ordering = ["id"]
|
||||||
|
|
||||||
|
def check_permissions(self, request: Request) -> None:
|
||||||
|
if "agent_id" in request.query_params:
|
||||||
|
self.kwargs["agent_id"] = request.query_params["agent_id"]
|
||||||
|
super().check_permissions(request)
|
||||||
|
|
||||||
|
def get_permissions(self):
|
||||||
|
if self.request.method == "POST":
|
||||||
|
self.permission_classes = [IsAuthenticated]
|
||||||
|
return super().get_permissions()
|
||||||
|
|
||||||
|
def get_serializer_class(self) -> type[BaseSerializer]:
|
||||||
|
if self.kwargs:
|
||||||
|
if self.kwargs["pk"]:
|
||||||
|
return DetailAgentSerializer
|
||||||
|
return ListAgentSerializer
|
||||||
0
api/tacticalrmm/beta/v1/client/__init__.py
Normal file
0
api/tacticalrmm/beta/v1/client/__init__.py
Normal file
13
api/tacticalrmm/beta/v1/client/views.py
Normal file
13
api/tacticalrmm/beta/v1/client/views.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
||||||
|
from clients.models import Client
|
||||||
|
from clients.permissions import ClientsPerms
|
||||||
|
from ..serializers import ClientSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ClientViewSet(viewsets.ModelViewSet):
|
||||||
|
permission_classes = [IsAuthenticated, ClientsPerms]
|
||||||
|
queryset = Client.objects.all()
|
||||||
|
serializer_class = ClientSerializer
|
||||||
|
http_method_names = ["get", "put"]
|
||||||
7
api/tacticalrmm/beta/v1/pagination.py
Normal file
7
api/tacticalrmm/beta/v1/pagination.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from rest_framework.pagination import PageNumberPagination
|
||||||
|
|
||||||
|
|
||||||
|
class StandardResultsSetPagination(PageNumberPagination):
|
||||||
|
page_size = 100
|
||||||
|
page_size_query_param = "page_size"
|
||||||
|
max_page_size = 1000
|
||||||
73
api/tacticalrmm/beta/v1/serializers.py
Normal file
73
api/tacticalrmm/beta/v1/serializers.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from agents.models import Agent
|
||||||
|
from clients.models import Client, Site
|
||||||
|
|
||||||
|
|
||||||
|
class ListAgentSerializer(serializers.ModelSerializer[Agent]):
|
||||||
|
class Meta:
|
||||||
|
model = Agent
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class DetailAgentSerializer(serializers.ModelSerializer[Agent]):
|
||||||
|
status = serializers.ReadOnlyField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Agent
|
||||||
|
fields = (
|
||||||
|
"version",
|
||||||
|
"operating_system",
|
||||||
|
"plat",
|
||||||
|
"goarch",
|
||||||
|
"hostname",
|
||||||
|
"agent_id",
|
||||||
|
"last_seen",
|
||||||
|
"services",
|
||||||
|
"public_ip",
|
||||||
|
"total_ram",
|
||||||
|
"disks",
|
||||||
|
"boot_time",
|
||||||
|
"logged_in_username",
|
||||||
|
"last_logged_in_user",
|
||||||
|
"monitoring_type",
|
||||||
|
"description",
|
||||||
|
"mesh_node_id",
|
||||||
|
"overdue_email_alert",
|
||||||
|
"overdue_text_alert",
|
||||||
|
"overdue_dashboard_alert",
|
||||||
|
"offline_time",
|
||||||
|
"overdue_time",
|
||||||
|
"check_interval",
|
||||||
|
"needs_reboot",
|
||||||
|
"choco_installed",
|
||||||
|
"wmi_detail",
|
||||||
|
"patches_last_installed",
|
||||||
|
"time_zone",
|
||||||
|
"maintenance_mode",
|
||||||
|
"block_policy_inheritance",
|
||||||
|
"alert_template",
|
||||||
|
"site",
|
||||||
|
"policy",
|
||||||
|
"status",
|
||||||
|
"checks",
|
||||||
|
"pending_actions_count",
|
||||||
|
"cpu_model",
|
||||||
|
"graphics",
|
||||||
|
"local_ips",
|
||||||
|
"make_model",
|
||||||
|
"physical_disks",
|
||||||
|
"serial_number",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ClientSerializer(serializers.ModelSerializer[Client]):
|
||||||
|
class Meta:
|
||||||
|
model = Client
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class SiteSerializer(serializers.ModelSerializer[Site]):
|
||||||
|
class Meta:
|
||||||
|
model = Site
|
||||||
|
fields = "__all__"
|
||||||
21
api/tacticalrmm/beta/v1/site/views.py
Normal file
21
api/tacticalrmm/beta/v1/site/views.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||||
|
|
||||||
|
from clients.models import Site
|
||||||
|
from clients.permissions import SitesPerms
|
||||||
|
from beta.v1.pagination import StandardResultsSetPagination
|
||||||
|
from ..serializers import SiteSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class SiteViewSet(viewsets.ModelViewSet):
|
||||||
|
permission_classes = [IsAuthenticated, SitesPerms]
|
||||||
|
queryset = Site.objects.all()
|
||||||
|
serializer_class = SiteSerializer
|
||||||
|
pagination_class = StandardResultsSetPagination
|
||||||
|
http_method_names = ["get", "put"]
|
||||||
|
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||||
|
search_fields = ["name"]
|
||||||
|
ordering_fields = ["id"]
|
||||||
|
ordering = ["id"]
|
||||||
12
api/tacticalrmm/beta/v1/urls.py
Normal file
12
api/tacticalrmm/beta/v1/urls.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from rest_framework import routers
|
||||||
|
from .agent import views as agent
|
||||||
|
from .client import views as client
|
||||||
|
from .site import views as site
|
||||||
|
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
|
||||||
|
router.register("agent", agent.AgentViewSet, basename="agent")
|
||||||
|
router.register("client", client.ClientViewSet, basename="client")
|
||||||
|
router.register("site", site.SiteViewSet, basename="site")
|
||||||
|
|
||||||
|
urlpatterns = router.urls
|
||||||
@@ -172,8 +172,14 @@ class CheckRunnerGetSerializer(serializers.ModelSerializer):
|
|||||||
if obj.check_type != CheckType.SCRIPT:
|
if obj.check_type != CheckType.SCRIPT:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# check's env_vars override the script's env vars
|
agent = self.context["agent"] if "agent" in self.context.keys() else obj.agent
|
||||||
return obj.env_vars or obj.script.env_vars
|
|
||||||
|
return Script.parse_script_env_vars(
|
||||||
|
agent=agent,
|
||||||
|
shell=obj.script.shell,
|
||||||
|
env_vars=obj.env_vars
|
||||||
|
or obj.script.env_vars, # check's env_vars override the script's env vars
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Check
|
model = Check
|
||||||
|
|||||||
@@ -172,6 +172,31 @@ class TestCheckViews(TacticalTestCase):
|
|||||||
|
|
||||||
self.check_not_authenticated("post", url)
|
self.check_not_authenticated("post", url)
|
||||||
|
|
||||||
|
def test_reset_all_checks_status(self):
|
||||||
|
# setup data
|
||||||
|
agent = baker.make_recipe("agents.agent")
|
||||||
|
check = baker.make_recipe("checks.diskspace_check", agent=agent)
|
||||||
|
baker.make("checks.CheckResult", assigned_check=check, agent=agent)
|
||||||
|
baker.make(
|
||||||
|
"checks.CheckHistory",
|
||||||
|
check_id=check.id,
|
||||||
|
agent_id=agent.agent_id,
|
||||||
|
_quantity=30,
|
||||||
|
)
|
||||||
|
baker.make(
|
||||||
|
"checks.CheckHistory",
|
||||||
|
check_id=check.id,
|
||||||
|
agent_id=agent.agent_id,
|
||||||
|
_quantity=30,
|
||||||
|
)
|
||||||
|
|
||||||
|
url = f"{base_url}/{agent.agent_id}/resetall/"
|
||||||
|
|
||||||
|
resp = self.client.post(url)
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
|
self.check_not_authenticated("post", url)
|
||||||
|
|
||||||
def test_add_memory_check(self):
|
def test_add_memory_check(self):
|
||||||
url = f"{base_url}/"
|
url = f"{base_url}/"
|
||||||
agent = baker.make_recipe("agents.agent")
|
agent = baker.make_recipe("agents.agent")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ urlpatterns = [
|
|||||||
path("", views.GetAddChecks.as_view()),
|
path("", views.GetAddChecks.as_view()),
|
||||||
path("<int:pk>/", views.GetUpdateDeleteCheck.as_view()),
|
path("<int:pk>/", views.GetUpdateDeleteCheck.as_view()),
|
||||||
path("<int:pk>/reset/", views.ResetCheck.as_view()),
|
path("<int:pk>/reset/", views.ResetCheck.as_view()),
|
||||||
|
path("<agent:agent_id>/resetall/", views.ResetAllChecksStatus.as_view()),
|
||||||
path("<agent:agent_id>/run/", views.run_checks),
|
path("<agent:agent_id>/run/", views.run_checks),
|
||||||
path("<int:pk>/history/", views.GetCheckHistory.as_view()),
|
path("<int:pk>/history/", views.GetCheckHistory.as_view()),
|
||||||
path("<str:target>/<int:pk>/csbulkrun/", views.bulk_run_checks),
|
path("<str:target>/<int:pk>/csbulkrun/", views.bulk_run_checks),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from datetime import datetime as dt
|
from datetime import datetime as dt
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Prefetch, Q
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils import timezone as djangotime
|
from django.utils import timezone as djangotime
|
||||||
from rest_framework.decorators import api_view, permission_classes
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
@@ -13,7 +13,7 @@ from rest_framework.views import APIView
|
|||||||
from agents.models import Agent
|
from agents.models import Agent
|
||||||
from alerts.models import Alert
|
from alerts.models import Alert
|
||||||
from automation.models import Policy
|
from automation.models import Policy
|
||||||
from tacticalrmm.constants import CheckStatus, CheckType
|
from tacticalrmm.constants import AGENT_DEFER, CheckStatus, CheckType
|
||||||
from tacticalrmm.exceptions import NatsDown
|
from tacticalrmm.exceptions import NatsDown
|
||||||
from tacticalrmm.helpers import notify_error
|
from tacticalrmm.helpers import notify_error
|
||||||
from tacticalrmm.nats_utils import abulk_nats_command
|
from tacticalrmm.nats_utils import abulk_nats_command
|
||||||
@@ -122,15 +122,54 @@ class ResetCheck(APIView):
|
|||||||
result.save()
|
result.save()
|
||||||
|
|
||||||
# resolve any alerts that are open
|
# resolve any alerts that are open
|
||||||
alert = Alert.create_or_return_check_alert(
|
if alert := Alert.create_or_return_check_alert(
|
||||||
result.assigned_check, agent=result.agent, skip_create=True
|
result.assigned_check, agent=result.agent, skip_create=True
|
||||||
)
|
):
|
||||||
if alert:
|
|
||||||
alert.resolve()
|
alert.resolve()
|
||||||
|
|
||||||
return Response("The check status was reset")
|
return Response("The check status was reset")
|
||||||
|
|
||||||
|
|
||||||
|
class ResetAllChecksStatus(APIView):
|
||||||
|
permission_classes = [IsAuthenticated, ChecksPerms]
|
||||||
|
|
||||||
|
def post(self, request, agent_id):
|
||||||
|
agent = get_object_or_404(
|
||||||
|
Agent.objects.defer(*AGENT_DEFER)
|
||||||
|
.select_related(
|
||||||
|
"policy",
|
||||||
|
"policy__alert_template",
|
||||||
|
"alert_template",
|
||||||
|
)
|
||||||
|
.prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
"checkresults",
|
||||||
|
queryset=CheckResult.objects.select_related("assigned_check"),
|
||||||
|
),
|
||||||
|
"agentchecks",
|
||||||
|
),
|
||||||
|
agent_id=agent_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not _has_perm_on_agent(request.user, agent.agent_id):
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
for check in agent.get_checks_with_policies():
|
||||||
|
try:
|
||||||
|
result = check.check_result
|
||||||
|
result.status = CheckStatus.PASSING
|
||||||
|
result.save()
|
||||||
|
if alert := Alert.create_or_return_check_alert(
|
||||||
|
result.assigned_check, agent=agent, skip_create=True
|
||||||
|
):
|
||||||
|
alert.resolve()
|
||||||
|
except:
|
||||||
|
# check hasn't run yet, no check result entry
|
||||||
|
continue
|
||||||
|
|
||||||
|
return Response("All checks status were reset")
|
||||||
|
|
||||||
|
|
||||||
class GetCheckHistory(APIView):
|
class GetCheckHistory(APIView):
|
||||||
permission_classes = [IsAuthenticated, ChecksPerms]
|
permission_classes = [IsAuthenticated, ChecksPerms]
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import re
|
|||||||
import uuid
|
import uuid
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.db.models import Count, Exists, OuterRef, Prefetch, prefetch_related_objects
|
from django.db.models import Count, Exists, OuterRef, Prefetch, prefetch_related_objects
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils import timezone as djangotime
|
from django.utils import timezone as djangotime
|
||||||
@@ -288,6 +289,9 @@ class AgentDeployment(APIView):
|
|||||||
return Response(DeploymentSerializer(deps, many=True).data)
|
return Response(DeploymentSerializer(deps, many=True).data)
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
|
if getattr(settings, "TRMM_INSECURE", False):
|
||||||
|
return notify_error("Not available in insecure mode")
|
||||||
|
|
||||||
from accounts.models import User
|
from accounts.models import User
|
||||||
|
|
||||||
site = get_object_or_404(Site, pk=request.data["site"])
|
site = get_object_or_404(Site, pk=request.data["site"])
|
||||||
@@ -343,6 +347,9 @@ class GenerateAgent(APIView):
|
|||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
|
|
||||||
def get(self, request, uid):
|
def get(self, request, uid):
|
||||||
|
if getattr(settings, "TRMM_INSECURE", False):
|
||||||
|
return notify_error("Not available in insecure mode")
|
||||||
|
|
||||||
from tacticalrmm.utils import generate_winagent_exe
|
from tacticalrmm.utils import generate_winagent_exe
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -12,6 +12,19 @@ if [ "${HAS_SYSTEMD}" != 'systemd' ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ $DISPLAY ]]; then
|
||||||
|
echo "ERROR: Display detected. Installer only supports running headless, i.e from ssh."
|
||||||
|
echo "If you cannot ssh in then please run 'sudo systemctl isolate multi-user.target' to switch to a non-graphical user session and run the installer again."
|
||||||
|
echo "If you are already running headless, then you are probably running with X forwarding which is setting DISPLAY, if so then simply run"
|
||||||
|
echo "unset DISPLAY"
|
||||||
|
echo "to unset the variable and then try running the installer again"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEBUG=0
|
||||||
|
INSECURE=0
|
||||||
|
NOMESH=0
|
||||||
|
|
||||||
agentDL='agentDLChange'
|
agentDL='agentDLChange'
|
||||||
meshDL='meshDLChange'
|
meshDL='meshDLChange'
|
||||||
|
|
||||||
@@ -124,6 +137,19 @@ if [ $# -ne 0 ] && [ $1 == 'uninstall' ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--debug) DEBUG=1 ;;
|
||||||
|
--insecure) INSECURE=1 ;;
|
||||||
|
--nomesh) NOMESH=1 ;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: Unknown parameter: $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
RemoveOldAgent
|
RemoveOldAgent
|
||||||
|
|
||||||
echo "Downloading tactical agent..."
|
echo "Downloading tactical agent..."
|
||||||
@@ -136,7 +162,7 @@ chmod +x ${agentBin}
|
|||||||
|
|
||||||
MESH_NODE_ID=""
|
MESH_NODE_ID=""
|
||||||
|
|
||||||
if [ $# -ne 0 ] && [ $1 == '--nomesh' ]; then
|
if [[ $NOMESH -eq 1 ]]; then
|
||||||
echo "Skipping mesh install"
|
echo "Skipping mesh install"
|
||||||
else
|
else
|
||||||
if [ -f "${meshSystemBin}" ]; then
|
if [ -f "${meshSystemBin}" ]; then
|
||||||
@@ -154,18 +180,22 @@ if [ ! -d "${agentBinPath}" ]; then
|
|||||||
mkdir -p ${agentBinPath}
|
mkdir -p ${agentBinPath}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $# -ne 0 ] && [ $1 == '--debug' ]; then
|
INSTALL_CMD="${agentBin} -m install -api ${apiURL} -client-id ${clientID} -site-id ${siteID} -agent-type ${agentType} -auth ${token}"
|
||||||
INSTALL_CMD="${agentBin} -m install -api ${apiURL} -client-id ${clientID} -site-id ${siteID} -agent-type ${agentType} -auth ${token} -log debug"
|
|
||||||
else
|
|
||||||
INSTALL_CMD="${agentBin} -m install -api ${apiURL} -client-id ${clientID} -site-id ${siteID} -agent-type ${agentType} -auth ${token}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${MESH_NODE_ID}" != '' ]; then
|
if [ "${MESH_NODE_ID}" != '' ]; then
|
||||||
INSTALL_CMD+=" -meshnodeid ${MESH_NODE_ID}"
|
INSTALL_CMD+=" --meshnodeid ${MESH_NODE_ID}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $DEBUG -eq 1 ]]; then
|
||||||
|
INSTALL_CMD+=" --log debug"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $INSECURE -eq 1 ]]; then
|
||||||
|
INSTALL_CMD+=" --insecure"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${proxy}" != '' ]; then
|
if [ "${proxy}" != '' ]; then
|
||||||
INSTALL_CMD+=" -proxy ${proxy}"
|
INSTALL_CMD+=" --proxy ${proxy}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
eval ${INSTALL_CMD}
|
eval ${INSTALL_CMD}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import os
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from tacticalrmm.helpers import get_nats_ports
|
from tacticalrmm.helpers import get_nats_internal_protocol, get_nats_ports
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
@@ -21,9 +21,10 @@ class Command(BaseCommand):
|
|||||||
ssl = "disable"
|
ssl = "disable"
|
||||||
|
|
||||||
nats_std_port, _ = get_nats_ports()
|
nats_std_port, _ = get_nats_ports()
|
||||||
|
proto = get_nats_internal_protocol()
|
||||||
config = {
|
config = {
|
||||||
"key": settings.SECRET_KEY,
|
"key": settings.SECRET_KEY,
|
||||||
"natsurl": f"tls://{settings.ALLOWED_HOSTS[0]}:{nats_std_port}",
|
"natsurl": f"{proto}://{settings.ALLOWED_HOSTS[0]}:{nats_std_port}",
|
||||||
"user": db["USER"],
|
"user": db["USER"],
|
||||||
"pass": db["PASSWORD"],
|
"pass": db["PASSWORD"],
|
||||||
"host": db["HOST"],
|
"host": db["HOST"],
|
||||||
|
|||||||
@@ -11,4 +11,7 @@ class Command(BaseCommand):
|
|||||||
self.stdout.write(self.style.WARNING("Cleaning the cache"))
|
self.stdout.write(self.style.WARNING("Cleaning the cache"))
|
||||||
clear_entire_cache()
|
clear_entire_cache()
|
||||||
self.stdout.write(self.style.SUCCESS("Cache was cleared!"))
|
self.stdout.write(self.style.SUCCESS("Cache was cleared!"))
|
||||||
call_command("fix_dupe_agent_customfields")
|
try:
|
||||||
|
call_command("fix_dupe_agent_customfields")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|||||||
@@ -502,3 +502,27 @@ class TestCoreUtils(TacticalTestCase):
|
|||||||
r,
|
r,
|
||||||
"http://tactical-meshcentral:4443/meshagents?id=4&meshid=abc123&installflags=0",
|
"http://tactical-meshcentral:4443/meshagents?id=4&meshid=abc123&installflags=0",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@override_settings(TRMM_INSECURE=True)
|
||||||
|
def test_get_meshagent_url_insecure(self):
|
||||||
|
r = get_meshagent_url(
|
||||||
|
ident=MeshAgentIdent.DARWIN_UNIVERSAL,
|
||||||
|
plat="darwin",
|
||||||
|
mesh_site="https://mesh.example.com",
|
||||||
|
mesh_device_id="abc123",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
r,
|
||||||
|
"http://mesh.example.com:4430/meshagents?id=abc123&installflags=2&meshinstall=10005",
|
||||||
|
)
|
||||||
|
|
||||||
|
r = get_meshagent_url(
|
||||||
|
ident=MeshAgentIdent.WIN64,
|
||||||
|
plat="windows",
|
||||||
|
mesh_site="https://mesh.example.com",
|
||||||
|
mesh_device_id="abc123",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
r,
|
||||||
|
"http://mesh.example.com:4430/meshagents?id=4&meshid=abc123&installflags=0",
|
||||||
|
)
|
||||||
|
|||||||
@@ -88,8 +88,12 @@ def get_mesh_ws_url() -> str:
|
|||||||
if settings.DOCKER_BUILD:
|
if settings.DOCKER_BUILD:
|
||||||
uri = f"{settings.MESH_WS_URL}/control.ashx?auth={token}"
|
uri = f"{settings.MESH_WS_URL}/control.ashx?auth={token}"
|
||||||
else:
|
else:
|
||||||
site = core.mesh_site.replace("https", "wss")
|
if getattr(settings, "TRMM_INSECURE", False):
|
||||||
uri = f"{site}/control.ashx?auth={token}"
|
site = core.mesh_site.replace("https", "ws")
|
||||||
|
uri = f"{site}:4430/control.ashx?auth={token}"
|
||||||
|
else:
|
||||||
|
site = core.mesh_site.replace("https", "wss")
|
||||||
|
uri = f"{site}/control.ashx?auth={token}"
|
||||||
|
|
||||||
return uri
|
return uri
|
||||||
|
|
||||||
@@ -181,6 +185,8 @@ def get_meshagent_url(
|
|||||||
) -> str:
|
) -> str:
|
||||||
if settings.DOCKER_BUILD:
|
if settings.DOCKER_BUILD:
|
||||||
base = settings.MESH_WS_URL.replace("ws://", "http://")
|
base = settings.MESH_WS_URL.replace("ws://", "http://")
|
||||||
|
elif getattr(settings, "TRMM_INSECURE", False):
|
||||||
|
base = mesh_site.replace("https", "http") + ":4430"
|
||||||
else:
|
else:
|
||||||
base = mesh_site
|
base = mesh_site
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
adrf==0.1.1
|
adrf==0.1.2
|
||||||
asgiref==3.7.2
|
asgiref==3.7.2
|
||||||
celery==5.3.1
|
celery==5.3.1
|
||||||
certifi==2023.7.22
|
certifi==2023.7.22
|
||||||
cffi==1.15.1
|
cffi==1.15.1
|
||||||
channels==4.0.0
|
channels==4.0.0
|
||||||
channels_redis==4.1.0
|
channels_redis==4.1.0
|
||||||
cryptography==41.0.3
|
cryptography==41.0.4
|
||||||
daphne==4.0.0
|
daphne==4.0.0
|
||||||
Django==4.2.4
|
Django==4.2.5
|
||||||
django-cors-headers==4.2.0
|
django-cors-headers==4.2.0
|
||||||
|
django-filter==23.3
|
||||||
django-ipware==5.0.0
|
django-ipware==5.0.0
|
||||||
django-rest-knox==4.2.0
|
django-rest-knox==4.2.0
|
||||||
djangorestframework==3.14.0
|
djangorestframework==3.14.0
|
||||||
drf-spectacular==0.26.4
|
drf-spectacular==0.26.5
|
||||||
hiredis==2.2.3
|
hiredis==2.2.3
|
||||||
meshctrl==0.1.15
|
meshctrl==0.1.15
|
||||||
msgpack==1.0.5
|
msgpack==1.0.7
|
||||||
nats-py==2.3.1
|
nats-py==2.4.0
|
||||||
packaging==23.1
|
packaging==23.1
|
||||||
psutil==5.9.5
|
psutil==5.9.5
|
||||||
psycopg[binary]==3.1.10
|
psycopg[binary]==3.1.12
|
||||||
pycparser==2.21
|
pycparser==2.21
|
||||||
pycryptodome==3.18.0
|
pycryptodome==3.19.0
|
||||||
pyotp==2.9.0
|
pyotp==2.9.0
|
||||||
pyparsing==3.1.1
|
pyparsing==3.1.1
|
||||||
pytz==2023.3
|
pytz==2023.3
|
||||||
@@ -30,10 +31,10 @@ redis==4.5.5
|
|||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
sqlparse==0.4.4
|
sqlparse==0.4.4
|
||||||
twilio==8.5.0
|
twilio==8.9.0
|
||||||
urllib3==2.0.4
|
urllib3==2.0.5
|
||||||
uWSGI==2.0.22
|
uWSGI==2.0.22
|
||||||
validators==0.20.0
|
validators==0.20.0
|
||||||
vine==5.0.0
|
vine==5.0.0
|
||||||
websockets==11.0.3
|
websockets==11.0.3
|
||||||
zipp==3.16.2
|
zipp==3.17.0
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ class Script(BaseAuditModel):
|
|||||||
return ScriptSerializer(script).data
|
return ScriptSerializer(script).data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
# TODO refactor common functionality of parse functions
|
||||||
def parse_script_args(cls, agent, shell: str, args: List[str] = []) -> list:
|
def parse_script_args(cls, agent, shell: str, args: List[str] = []) -> list:
|
||||||
if not args:
|
if not args:
|
||||||
return []
|
return []
|
||||||
@@ -204,8 +205,7 @@ class Script(BaseAuditModel):
|
|||||||
pattern = re.compile(".*\\{\\{(.*)\\}\\}.*")
|
pattern = re.compile(".*\\{\\{(.*)\\}\\}.*")
|
||||||
|
|
||||||
for arg in args:
|
for arg in args:
|
||||||
match = pattern.match(arg)
|
if match := pattern.match(arg):
|
||||||
if match:
|
|
||||||
# only get the match between the () in regex
|
# only get the match between the () in regex
|
||||||
string = match.group(1)
|
string = match.group(1)
|
||||||
value = replace_db_values(
|
value = replace_db_values(
|
||||||
@@ -231,6 +231,42 @@ class Script(BaseAuditModel):
|
|||||||
|
|
||||||
return temp_args
|
return temp_args
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
# TODO refactor common functionality of parse functions
|
||||||
|
def parse_script_env_vars(cls, agent, shell: str, env_vars: list[str] = []) -> list:
|
||||||
|
if not env_vars:
|
||||||
|
return []
|
||||||
|
|
||||||
|
temp_env_vars = []
|
||||||
|
pattern = re.compile(".*\\{\\{(.*)\\}\\}.*")
|
||||||
|
for env_var in env_vars:
|
||||||
|
# must be in format KEY=VALUE
|
||||||
|
try:
|
||||||
|
env_key = env_var.split("=")[0]
|
||||||
|
env_val = env_var.split("=")[1]
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
if match := pattern.match(env_val):
|
||||||
|
string = match.group(1)
|
||||||
|
value = replace_db_values(
|
||||||
|
string=string,
|
||||||
|
instance=agent,
|
||||||
|
shell=shell,
|
||||||
|
quotes=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
if value:
|
||||||
|
try:
|
||||||
|
new_val = re.sub("\\{\\{.*\\}\\}", value, env_val)
|
||||||
|
except re.error:
|
||||||
|
new_val = re.sub("\\{\\{.*\\}\\}", re.escape(value), env_val)
|
||||||
|
temp_env_vars.append(f"{env_key}={new_val}")
|
||||||
|
else:
|
||||||
|
# pass parameter unaltered
|
||||||
|
temp_env_vars.append(env_var)
|
||||||
|
|
||||||
|
return temp_env_vars
|
||||||
|
|
||||||
|
|
||||||
class ScriptSnippet(models.Model):
|
class ScriptSnippet(models.Model):
|
||||||
name = CharField(max_length=40, unique=True)
|
name = CharField(max_length=40, unique=True)
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ def bulk_script_task(
|
|||||||
"shell": script.shell,
|
"shell": script.shell,
|
||||||
},
|
},
|
||||||
"run_as_user": run_as_user,
|
"run_as_user": run_as_user,
|
||||||
"env_vars": env_vars,
|
"env_vars": script.parse_script_env_vars(agent, script.shell, env_vars),
|
||||||
}
|
}
|
||||||
tup = (agent.agent_id, data)
|
tup = (agent.agent_id, data)
|
||||||
items.append(tup)
|
items.append(tup)
|
||||||
|
|||||||
@@ -242,6 +242,25 @@ class TestScriptViews(TacticalTestCase):
|
|||||||
Script.parse_script_args(agent=agent, shell=ScriptShell.PYTHON, args=args),
|
Script.parse_script_args(agent=agent, shell=ScriptShell.PYTHON, args=args),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_script_env_vars_variable_replacement(self):
|
||||||
|
agent = baker.make_recipe("agents.agent", public_ip="12.12.12.12")
|
||||||
|
env_vars = [
|
||||||
|
"PUBIP={{agent.public_ip}}",
|
||||||
|
"123CLIENT={{client.name}}",
|
||||||
|
"FOOBARSITE={{site.name}}",
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
"PUBIP=12.12.12.12",
|
||||||
|
f"123CLIENT={agent.client.name}",
|
||||||
|
f"FOOBARSITE={agent.site.name}",
|
||||||
|
],
|
||||||
|
Script.parse_script_env_vars(
|
||||||
|
agent=agent, shell=ScriptShell.POWERSHELL, env_vars=env_vars
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def test_script_arg_replacement_custom_field(self):
|
def test_script_arg_replacement_custom_field(self):
|
||||||
agent = baker.make_recipe("agents.agent")
|
agent = baker.make_recipe("agents.agent")
|
||||||
field = baker.make(
|
field = baker.make(
|
||||||
@@ -272,6 +291,40 @@ class TestScriptViews(TacticalTestCase):
|
|||||||
Script.parse_script_args(agent=agent, shell=ScriptShell.PYTHON, args=args),
|
Script.parse_script_args(agent=agent, shell=ScriptShell.PYTHON, args=args),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_script_env_vars_replacement_custom_field(self):
|
||||||
|
agent = baker.make_recipe("agents.agent")
|
||||||
|
field = baker.make(
|
||||||
|
"core.CustomField",
|
||||||
|
name="Test Field",
|
||||||
|
model=CustomFieldModel.AGENT,
|
||||||
|
type=CustomFieldType.TEXT,
|
||||||
|
default_value_string="DEFAULT",
|
||||||
|
)
|
||||||
|
|
||||||
|
env_vars = ["FOOBAR={{agent.Test Field}}"]
|
||||||
|
|
||||||
|
# test default value
|
||||||
|
self.assertEqual(
|
||||||
|
["FOOBAR=DEFAULT"],
|
||||||
|
Script.parse_script_env_vars(
|
||||||
|
agent=agent, shell=ScriptShell.POWERSHELL, env_vars=env_vars
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# test with set value
|
||||||
|
baker.make(
|
||||||
|
"agents.AgentCustomField",
|
||||||
|
field=field,
|
||||||
|
agent=agent,
|
||||||
|
string_value="CUSTOM VALUE",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
["FOOBAR=CUSTOM VALUE"],
|
||||||
|
Script.parse_script_env_vars(
|
||||||
|
agent=agent, shell=ScriptShell.POWERSHELL, env_vars=env_vars
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def test_script_arg_replacement_client_custom_fields(self):
|
def test_script_arg_replacement_client_custom_fields(self):
|
||||||
agent = baker.make_recipe("agents.agent")
|
agent = baker.make_recipe("agents.agent")
|
||||||
field = baker.make(
|
field = baker.make(
|
||||||
@@ -302,6 +355,42 @@ class TestScriptViews(TacticalTestCase):
|
|||||||
Script.parse_script_args(agent=agent, shell=ScriptShell.PYTHON, args=args),
|
Script.parse_script_args(agent=agent, shell=ScriptShell.PYTHON, args=args),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_script_env_vars_replacement_client_custom_fields(self):
|
||||||
|
agent = baker.make_recipe("agents.agent")
|
||||||
|
field = baker.make(
|
||||||
|
"core.CustomField",
|
||||||
|
name="test123",
|
||||||
|
model=CustomFieldModel.CLIENT,
|
||||||
|
type=CustomFieldType.TEXT,
|
||||||
|
default_value_string="https://a1234lkasd.asdinasd234.com/ask2348uASDlk234@!#$@#asd1dsf",
|
||||||
|
)
|
||||||
|
|
||||||
|
env_vars = ["FOOBAR={{client.test123}}"]
|
||||||
|
|
||||||
|
# test default value
|
||||||
|
self.assertEqual(
|
||||||
|
["FOOBAR=https://a1234lkasd.asdinasd234.com/ask2348uASDlk234@!#$@#asd1dsf"],
|
||||||
|
Script.parse_script_env_vars(
|
||||||
|
agent=agent, shell=ScriptShell.POWERSHELL, env_vars=env_vars
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# test with set value
|
||||||
|
baker.make(
|
||||||
|
"clients.ClientCustomField",
|
||||||
|
field=field,
|
||||||
|
client=agent.client,
|
||||||
|
string_value="uASdklj23487ASDkjhr345il987UASXK<DFOIul32oi454329837492384512342134!@#!@#ADSFW45X",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
"FOOBAR=uASdklj23487ASDkjhr345il987UASXK<DFOIul32oi454329837492384512342134!@#!@#ADSFW45X"
|
||||||
|
],
|
||||||
|
Script.parse_script_env_vars(
|
||||||
|
agent=agent, shell=ScriptShell.POWERSHELL, env_vars=env_vars
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def test_script_arg_replacement_site_custom_fields(self):
|
def test_script_arg_replacement_site_custom_fields(self):
|
||||||
agent = baker.make_recipe("agents.agent")
|
agent = baker.make_recipe("agents.agent")
|
||||||
field = baker.make(
|
field = baker.make(
|
||||||
@@ -350,6 +439,51 @@ class TestScriptViews(TacticalTestCase):
|
|||||||
Script.parse_script_args(agent=agent, shell=ScriptShell.PYTHON, args=args),
|
Script.parse_script_args(agent=agent, shell=ScriptShell.PYTHON, args=args),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_script_env_vars_replacement_site_custom_fields(self):
|
||||||
|
agent = baker.make_recipe("agents.agent")
|
||||||
|
field = baker.make(
|
||||||
|
"core.CustomField",
|
||||||
|
name="ffas2345asdasasdWEdd",
|
||||||
|
model=CustomFieldModel.SITE,
|
||||||
|
type=CustomFieldType.TEXT,
|
||||||
|
default_value_string="https://site.easkdjas.com/asik2348aSDH234RJKADBCA%123SAD",
|
||||||
|
)
|
||||||
|
|
||||||
|
env_vars = ["ASD45ASDKJASHD={{site.ffas2345asdasasdWEdd}}"]
|
||||||
|
|
||||||
|
# test default value
|
||||||
|
self.assertEqual(
|
||||||
|
["ASD45ASDKJASHD=https://site.easkdjas.com/asik2348aSDH234RJKADBCA%123SAD"],
|
||||||
|
Script.parse_script_env_vars(
|
||||||
|
agent=agent, shell=ScriptShell.POWERSHELL, env_vars=env_vars
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# test with set value
|
||||||
|
value = baker.make(
|
||||||
|
"clients.SiteCustomField",
|
||||||
|
field=field,
|
||||||
|
site=agent.site,
|
||||||
|
string_value="g435asdASD2354SDFasdfsdf",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
["ASD45ASDKJASHD=g435asdASD2354SDFasdfsdf"],
|
||||||
|
Script.parse_script_env_vars(
|
||||||
|
agent=agent, shell=ScriptShell.POWERSHELL, env_vars=env_vars
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# test with set but empty field value
|
||||||
|
value.string_value = ""
|
||||||
|
value.save()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
["ASD45ASDKJASHD=https://site.easkdjas.com/asik2348aSDH234RJKADBCA%123SAD"],
|
||||||
|
Script.parse_script_env_vars(
|
||||||
|
agent=agent, shell=ScriptShell.POWERSHELL, env_vars=env_vars
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def test_script_arg_replacement_array_fields(self):
|
def test_script_arg_replacement_array_fields(self):
|
||||||
agent = baker.make_recipe("agents.agent")
|
agent = baker.make_recipe("agents.agent")
|
||||||
field = baker.make(
|
field = baker.make(
|
||||||
|
|||||||
@@ -148,6 +148,9 @@ class TestScript(APIView):
|
|||||||
parsed_args = Script.parse_script_args(
|
parsed_args = Script.parse_script_args(
|
||||||
agent, request.data["shell"], request.data["args"]
|
agent, request.data["shell"], request.data["args"]
|
||||||
)
|
)
|
||||||
|
parsed_env_vars = Script.parse_script_env_vars(
|
||||||
|
agent, request.data["shell"], request.data["env_vars"]
|
||||||
|
)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"func": "runscript",
|
"func": "runscript",
|
||||||
@@ -158,7 +161,7 @@ class TestScript(APIView):
|
|||||||
"shell": request.data["shell"],
|
"shell": request.data["shell"],
|
||||||
},
|
},
|
||||||
"run_as_user": request.data["run_as_user"],
|
"run_as_user": request.data["run_as_user"],
|
||||||
"env_vars": request.data["env_vars"],
|
"env_vars": parsed_env_vars,
|
||||||
}
|
}
|
||||||
|
|
||||||
r = asyncio.run(
|
r = asyncio.run(
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -42,6 +42,13 @@ def get_nats_ports() -> tuple[int, int]:
|
|||||||
return nats_standard_port, nats_websocket_port
|
return nats_standard_port, nats_websocket_port
|
||||||
|
|
||||||
|
|
||||||
|
def get_nats_internal_protocol() -> str:
|
||||||
|
if getattr(settings, "TRMM_INSECURE", False):
|
||||||
|
return "nats"
|
||||||
|
|
||||||
|
return "tls"
|
||||||
|
|
||||||
|
|
||||||
def date_is_in_past(*, datetime_obj: "datetime", agent_tz: str) -> bool:
|
def date_is_in_past(*, datetime_obj: "datetime", agent_tz: str) -> bool:
|
||||||
"""
|
"""
|
||||||
datetime_obj must be a naive datetime
|
datetime_obj must be a naive datetime
|
||||||
@@ -66,8 +73,9 @@ def rand_range(min: int, max: int) -> float:
|
|||||||
|
|
||||||
def setup_nats_options() -> dict[str, Any]:
|
def setup_nats_options() -> dict[str, Any]:
|
||||||
nats_std_port, _ = get_nats_ports()
|
nats_std_port, _ = get_nats_ports()
|
||||||
|
proto = get_nats_internal_protocol()
|
||||||
opts = {
|
opts = {
|
||||||
"servers": f"tls://{settings.ALLOWED_HOSTS[0]}:{nats_std_port}",
|
"servers": f"{proto}://{settings.ALLOWED_HOSTS[0]}:{nats_std_port}",
|
||||||
"user": "tacticalrmm",
|
"user": "tacticalrmm",
|
||||||
"name": "trmm-django",
|
"name": "trmm-django",
|
||||||
"password": settings.SECRET_KEY,
|
"password": settings.SECRET_KEY,
|
||||||
|
|||||||
@@ -20,21 +20,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.16.2"
|
TRMM_VERSION = "0.16.5"
|
||||||
|
|
||||||
# https://github.com/amidaware/tacticalrmm-web
|
# https://github.com/amidaware/tacticalrmm-web
|
||||||
WEB_VERSION = "0.101.28"
|
WEB_VERSION = "0.101.31"
|
||||||
|
|
||||||
# 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.183"
|
APP_VER = "0.0.185"
|
||||||
|
|
||||||
# https://github.com/amidaware/rmmagent
|
# https://github.com/amidaware/rmmagent
|
||||||
LATEST_AGENT_VER = "2.4.11"
|
LATEST_AGENT_VER = "2.5.0"
|
||||||
|
|
||||||
MESH_VER = "1.1.9"
|
MESH_VER = "1.1.9"
|
||||||
|
|
||||||
NATS_SERVER_VER = "2.9.21"
|
NATS_SERVER_VER = "2.10.1"
|
||||||
|
|
||||||
# for the update script, bump when need to recreate venv
|
# for the update script, bump when need to recreate venv
|
||||||
PIP_VER = "38"
|
PIP_VER = "38"
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ urlpatterns = [
|
|||||||
path("accounts/", include("accounts.urls")),
|
path("accounts/", include("accounts.urls")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if getattr(settings, "BETA_API_ENABLED", False):
|
||||||
|
urlpatterns += (path("beta/v1/", include("beta.v1.urls")),)
|
||||||
|
|
||||||
if getattr(settings, "ADMIN_ENABLED", False):
|
if getattr(settings, "ADMIN_ENABLED", False):
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,12 @@ from tacticalrmm.constants import (
|
|||||||
DebugLogType,
|
DebugLogType,
|
||||||
ScriptShell,
|
ScriptShell,
|
||||||
)
|
)
|
||||||
from tacticalrmm.helpers import get_certs, get_nats_ports, notify_error
|
from tacticalrmm.helpers import (
|
||||||
|
get_certs,
|
||||||
|
get_nats_internal_protocol,
|
||||||
|
get_nats_ports,
|
||||||
|
notify_error,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def generate_winagent_exe(
|
def generate_winagent_exe(
|
||||||
@@ -204,10 +209,6 @@ def reload_nats() -> None:
|
|||||||
nats_std_port, nats_ws_port = get_nats_ports()
|
nats_std_port, nats_ws_port = get_nats_ports()
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
"tls": {
|
|
||||||
"cert_file": cert_file,
|
|
||||||
"key_file": key_file,
|
|
||||||
},
|
|
||||||
"authorization": {"users": users},
|
"authorization": {"users": users},
|
||||||
"max_payload": 67108864,
|
"max_payload": 67108864,
|
||||||
"port": nats_std_port, # internal only
|
"port": nats_std_port, # internal only
|
||||||
@@ -217,6 +218,12 @@ def reload_nats() -> None:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if get_nats_internal_protocol() == "tls":
|
||||||
|
config["tls"] = {
|
||||||
|
"cert_file": cert_file,
|
||||||
|
"key_file": key_file,
|
||||||
|
}
|
||||||
|
|
||||||
if "NATS_HTTP_PORT" in os.environ:
|
if "NATS_HTTP_PORT" in os.environ:
|
||||||
config["http_port"] = int(os.getenv("NATS_HTTP_PORT")) # type: ignore
|
config["http_port"] = int(os.getenv("NATS_HTTP_PORT")) # type: ignore
|
||||||
elif hasattr(settings, "NATS_HTTP_PORT"):
|
elif hasattr(settings, "NATS_HTTP_PORT"):
|
||||||
|
|||||||
12
backup.sh
12
backup.sh
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
SCRIPT_VERSION="28"
|
SCRIPT_VERSION="30"
|
||||||
|
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
@@ -72,7 +72,7 @@ mkdir ${tmp_dir}/confd
|
|||||||
POSTGRES_USER=$(/rmm/api/env/bin/python /rmm/api/tacticalrmm/manage.py get_config dbuser)
|
POSTGRES_USER=$(/rmm/api/env/bin/python /rmm/api/tacticalrmm/manage.py get_config dbuser)
|
||||||
POSTGRES_PW=$(/rmm/api/env/bin/python /rmm/api/tacticalrmm/manage.py get_config dbpw)
|
POSTGRES_PW=$(/rmm/api/env/bin/python /rmm/api/tacticalrmm/manage.py get_config dbpw)
|
||||||
|
|
||||||
pg_dump --dbname=postgresql://"${POSTGRES_USER}":"${POSTGRES_PW}"@127.0.0.1:5432/tacticalrmm | gzip -9 >${tmp_dir}/postgres/db-${dt_now}.psql.gz
|
pg_dump --dbname=postgresql://"${POSTGRES_USER}":"${POSTGRES_PW}"@localhost:5432/tacticalrmm | gzip -9 >${tmp_dir}/postgres/db-${dt_now}.psql.gz
|
||||||
|
|
||||||
node /meshcentral/node_modules/meshcentral --dbexport # for import to postgres
|
node /meshcentral/node_modules/meshcentral --dbexport # for import to postgres
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ if grep -q postgres "/meshcentral/meshcentral-data/config.json"; then
|
|||||||
fi
|
fi
|
||||||
MESH_POSTGRES_USER=$(jq '.settings.postgres.user' /meshcentral/meshcentral-data/config.json -r)
|
MESH_POSTGRES_USER=$(jq '.settings.postgres.user' /meshcentral/meshcentral-data/config.json -r)
|
||||||
MESH_POSTGRES_PW=$(jq '.settings.postgres.password' /meshcentral/meshcentral-data/config.json -r)
|
MESH_POSTGRES_PW=$(jq '.settings.postgres.password' /meshcentral/meshcentral-data/config.json -r)
|
||||||
pg_dump --dbname=postgresql://"${MESH_POSTGRES_USER}":"${MESH_POSTGRES_PW}"@127.0.0.1:5432/meshcentral | gzip -9 >${tmp_dir}/postgres/mesh-db-${dt_now}.psql.gz
|
pg_dump --dbname=postgresql://"${MESH_POSTGRES_USER}":"${MESH_POSTGRES_PW}"@localhost:5432/meshcentral | gzip -9 >${tmp_dir}/postgres/mesh-db-${dt_now}.psql.gz
|
||||||
else
|
else
|
||||||
mongodump --gzip --out=${tmp_dir}/meshcentral/mongo
|
mongodump --gzip --out=${tmp_dir}/meshcentral/mongo
|
||||||
fi
|
fi
|
||||||
@@ -101,6 +101,11 @@ if grep -q CERT_FILE "$local_settings"; then
|
|||||||
KEY_FILE=$(grep "^KEY_FILE" "$local_settings" | awk -F'[= "]' '{print $5}')
|
KEY_FILE=$(grep "^KEY_FILE" "$local_settings" | awk -F'[= "]' '{print $5}')
|
||||||
cp -p $CERT_FILE ${tmp_dir}/certs/custom/cert
|
cp -p $CERT_FILE ${tmp_dir}/certs/custom/cert
|
||||||
cp -p $KEY_FILE ${tmp_dir}/certs/custom/key
|
cp -p $KEY_FILE ${tmp_dir}/certs/custom/key
|
||||||
|
elif grep -q TRMM_INSECURE "$local_settings"; then
|
||||||
|
mkdir -p ${tmp_dir}/certs/selfsigned
|
||||||
|
certdir='/etc/ssl/tactical'
|
||||||
|
cp -p ${certdir}/key.pem ${tmp_dir}/certs/selfsigned/
|
||||||
|
cp -p ${certdir}/cert.pem ${tmp_dir}/certs/selfsigned/
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for i in rmm frontend meshcentral; do
|
for i in rmm frontend meshcentral; do
|
||||||
@@ -138,6 +143,7 @@ if [[ $* == *--auto* ]]; then
|
|||||||
|
|
||||||
else
|
else
|
||||||
tar -cf /rmmbackups/rmm-backup-${dt_now}.tar -C ${tmp_dir} .
|
tar -cf /rmmbackups/rmm-backup-${dt_now}.tar -C ${tmp_dir} .
|
||||||
|
rm -rf ${tmp_dir}
|
||||||
|
|
||||||
echo -ne "${GREEN}Backup saved to /rmmbackups/rmm-backup-${dt_now}.tar${NC}\n"
|
echo -ne "${GREEN}Backup saved to /rmmbackups/rmm-backup-${dt_now}.tar${NC}\n"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -10,7 +10,22 @@ SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
|
|||||||
|
|
||||||
COPY api/tacticalrmm/tacticalrmm/settings.py /tmp/settings.py
|
COPY api/tacticalrmm/tacticalrmm/settings.py /tmp/settings.py
|
||||||
|
|
||||||
RUN npm install meshcentral@$(grep -o 'MESH_VER.*' /tmp/settings.py | cut -d'"' -f 2)
|
RUN MESH_VER=$(grep -o 'MESH_VER.*' /tmp/settings.py | cut -d'"' -f 2) && \
|
||||||
|
cat > package.json <<EOF
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"archiver": "5.3.1",
|
||||||
|
"meshcentral": "$MESH_VER",
|
||||||
|
"mongodb": "4.13.0",
|
||||||
|
"otplib": "10.2.3",
|
||||||
|
"pg": "8.7.1",
|
||||||
|
"pgtools": "0.3.2",
|
||||||
|
"saslprep": "1.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
RUN chown -R node:node /home/node
|
RUN chown -R node:node /home/node
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM nats:2.9.20-alpine
|
FROM nats:2.10.1-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
|
||||||
|
|||||||
90
install.sh
90
install.sh
@@ -1,9 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
SCRIPT_VERSION="75"
|
SCRIPT_VERSION="78"
|
||||||
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
|
sudo apt install -y curl wget dirmngr gnupg lsb-release ca-certificates
|
||||||
|
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
@@ -14,6 +14,7 @@ NC='\033[0m'
|
|||||||
SCRIPTS_DIR='/opt/trmm-community-scripts'
|
SCRIPTS_DIR='/opt/trmm-community-scripts'
|
||||||
PYTHON_VER='3.11.4'
|
PYTHON_VER='3.11.4'
|
||||||
SETTINGS_FILE='/rmm/api/tacticalrmm/tacticalrmm/settings.py'
|
SETTINGS_FILE='/rmm/api/tacticalrmm/tacticalrmm/settings.py'
|
||||||
|
local_settings='/rmm/api/tacticalrmm/tacticalrmm/local_settings.py'
|
||||||
|
|
||||||
TMP_FILE=$(mktemp -p "" "rmminstall_XXXXXXXXXX")
|
TMP_FILE=$(mktemp -p "" "rmminstall_XXXXXXXXXX")
|
||||||
curl -s -L "${SCRIPT_URL}" >${TMP_FILE}
|
curl -s -L "${SCRIPT_URL}" >${TMP_FILE}
|
||||||
@@ -86,7 +87,7 @@ if [ "$arch" = "x86_64" ]; then
|
|||||||
else
|
else
|
||||||
pgarch='arm64'
|
pgarch='arm64'
|
||||||
fi
|
fi
|
||||||
postgresql_repo="deb [arch=${pgarch}] https://apt.postgresql.org/pub/repos/apt/ $codename-pgdg main"
|
postgresql_repo="deb [arch=${pgarch} signed-by=/etc/apt/keyrings/postgresql-archive-keyring.gpg] https://apt.postgresql.org/pub/repos/apt/ $codename-pgdg main"
|
||||||
|
|
||||||
# prevents logging issues with some VPS providers like Vultr if this is a freshly provisioned instance that hasn't been rebooted yet
|
# prevents logging issues with some VPS providers like Vultr if this is a freshly provisioned instance that hasn't been rebooted yet
|
||||||
sudo systemctl restart systemd-journald.service
|
sudo systemctl restart systemd-journald.service
|
||||||
@@ -161,30 +162,50 @@ if echo "$IPV4" | grep -qE '^(10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.|192
|
|||||||
BEHIND_NAT=true
|
BEHIND_NAT=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
insecure=false
|
||||||
|
if [[ $* == *--insecure* ]]; then
|
||||||
|
insecure=true
|
||||||
|
fi
|
||||||
|
|
||||||
sudo apt install -y software-properties-common
|
sudo apt install -y software-properties-common
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y certbot openssl
|
sudo apt install -y openssl
|
||||||
|
|
||||||
print_green 'Getting wildcard cert'
|
if [[ "$insecure" = true ]]; then
|
||||||
|
print_green 'Generating self-signed cert'
|
||||||
|
certdir='/etc/ssl/tactical'
|
||||||
|
sudo mkdir -p $certdir
|
||||||
|
sudo chown ${USER}:${USER} $certdir
|
||||||
|
sudo chmod 770 $certdir
|
||||||
|
CERT_PRIV_KEY=${certdir}/key.pem
|
||||||
|
CERT_PUB_KEY=${certdir}/cert.pem
|
||||||
|
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 \
|
||||||
|
-nodes -keyout ${CERT_PRIV_KEY} -out ${CERT_PUB_KEY} -subj "/CN=${rootdomain}" \
|
||||||
|
-addext "subjectAltName=DNS:${rootdomain},DNS:*.${rootdomain}"
|
||||||
|
|
||||||
|
else
|
||||||
|
sudo apt install -y certbot
|
||||||
|
print_green 'Getting wildcard cert'
|
||||||
|
|
||||||
sudo certbot certonly --manual -d *.${rootdomain} --agree-tos --no-bootstrap --preferred-challenges dns -m ${letsemail} --no-eff-email
|
|
||||||
while [[ $? -ne 0 ]]; do
|
|
||||||
sudo certbot certonly --manual -d *.${rootdomain} --agree-tos --no-bootstrap --preferred-challenges dns -m ${letsemail} --no-eff-email
|
sudo certbot certonly --manual -d *.${rootdomain} --agree-tos --no-bootstrap --preferred-challenges dns -m ${letsemail} --no-eff-email
|
||||||
done
|
while [[ $? -ne 0 ]]; do
|
||||||
|
sudo certbot certonly --manual -d *.${rootdomain} --agree-tos --no-bootstrap --preferred-challenges dns -m ${letsemail} --no-eff-email
|
||||||
CERT_PRIV_KEY=/etc/letsencrypt/live/${rootdomain}/privkey.pem
|
done
|
||||||
CERT_PUB_KEY=/etc/letsencrypt/live/${rootdomain}/fullchain.pem
|
CERT_PRIV_KEY=/etc/letsencrypt/live/${rootdomain}/privkey.pem
|
||||||
|
CERT_PUB_KEY=/etc/letsencrypt/live/${rootdomain}/fullchain.pem
|
||||||
|
fi
|
||||||
|
|
||||||
sudo chown ${USER}:${USER} -R /etc/letsencrypt
|
sudo chown ${USER}:${USER} -R /etc/letsencrypt
|
||||||
|
|
||||||
print_green 'Installing Nginx'
|
print_green 'Installing Nginx'
|
||||||
|
|
||||||
wget -qO - https://nginx.org/packages/keys/nginx_signing.key | sudo apt-key add -
|
sudo mkdir -p /etc/apt/keyrings
|
||||||
|
|
||||||
|
wget -qO - https://nginx.org/packages/keys/nginx_signing.key | sudo gpg --dearmor -o /etc/apt/keyrings/nginx-archive-keyring.gpg
|
||||||
|
|
||||||
nginxrepo="$(
|
nginxrepo="$(
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
deb https://nginx.org/packages/$osname/ $codename nginx
|
deb [signed-by=/etc/apt/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/$osname $codename nginx
|
||||||
deb-src https://nginx.org/packages/$osname/ $codename nginx
|
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
echo "${nginxrepo}" | sudo tee /etc/apt/sources.list.d/nginx.list >/dev/null
|
echo "${nginxrepo}" | sudo tee /etc/apt/sources.list.d/nginx.list >/dev/null
|
||||||
@@ -232,7 +253,9 @@ done
|
|||||||
|
|
||||||
print_green 'Installing NodeJS'
|
print_green 'Installing NodeJS'
|
||||||
|
|
||||||
curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||||
|
NODE_MAJOR=18
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y gcc g++ make
|
sudo apt install -y gcc g++ make
|
||||||
sudo apt install -y nodejs
|
sudo apt install -y nodejs
|
||||||
@@ -253,13 +276,13 @@ cd ~
|
|||||||
sudo rm -rf Python-${PYTHON_VER} Python-${PYTHON_VER}.tgz
|
sudo rm -rf Python-${PYTHON_VER} Python-${PYTHON_VER}.tgz
|
||||||
|
|
||||||
print_green 'Installing redis and git'
|
print_green 'Installing redis and git'
|
||||||
sudo apt install -y ca-certificates redis git
|
sudo apt install -y redis git
|
||||||
|
|
||||||
print_green 'Installing postgresql'
|
print_green 'Installing postgresql'
|
||||||
|
|
||||||
echo "$postgresql_repo" | sudo tee /etc/apt/sources.list.d/pgdg.list
|
echo "$postgresql_repo" | sudo tee /etc/apt/sources.list.d/pgdg.list
|
||||||
|
|
||||||
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
|
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /etc/apt/keyrings/postgresql-archive-keyring.gpg
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y postgresql-15
|
sudo apt install -y postgresql-15
|
||||||
sleep 2
|
sleep 2
|
||||||
@@ -336,9 +359,23 @@ MESH_VER=$(grep "^MESH_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
|
|||||||
sudo mkdir -p /meshcentral/meshcentral-data
|
sudo mkdir -p /meshcentral/meshcentral-data
|
||||||
sudo chown ${USER}:${USER} -R /meshcentral
|
sudo chown ${USER}:${USER} -R /meshcentral
|
||||||
cd /meshcentral
|
cd /meshcentral
|
||||||
npm install meshcentral@${MESH_VER}
|
|
||||||
sudo chown ${USER}:${USER} -R /meshcentral
|
sudo chown ${USER}:${USER} -R /meshcentral
|
||||||
|
|
||||||
|
mesh_pkg="$(
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"archiver": "5.3.1",
|
||||||
|
"meshcentral": "${MESH_VER}",
|
||||||
|
"otplib": "10.2.3",
|
||||||
|
"pg": "8.7.1",
|
||||||
|
"pgtools": "0.3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
echo "${mesh_pkg}" >/meshcentral/package.json
|
||||||
|
|
||||||
meshcfg="$(
|
meshcfg="$(
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
{
|
{
|
||||||
@@ -382,6 +419,8 @@ EOF
|
|||||||
)"
|
)"
|
||||||
echo "${meshcfg}" >/meshcentral/meshcentral-data/config.json
|
echo "${meshcfg}" >/meshcentral/meshcentral-data/config.json
|
||||||
|
|
||||||
|
npm install
|
||||||
|
|
||||||
localvars="$(
|
localvars="$(
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
SECRET_KEY = "${DJANGO_SEKRET}"
|
SECRET_KEY = "${DJANGO_SEKRET}"
|
||||||
@@ -413,7 +452,11 @@ REDIS_HOST = "localhost"
|
|||||||
ADMIN_ENABLED = True
|
ADMIN_ENABLED = True
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
echo "${localvars}" >/rmm/api/tacticalrmm/tacticalrmm/local_settings.py
|
echo "${localvars}" >$local_settings
|
||||||
|
|
||||||
|
if [[ "$insecure" = true ]]; then
|
||||||
|
echo "TRMM_INSECURE = True" | tee --append $local_settings >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$arch" = "x86_64" ]; then
|
if [ "$arch" = "x86_64" ]; then
|
||||||
natsapi='nats-api'
|
natsapi='nats-api'
|
||||||
@@ -446,7 +489,7 @@ python manage.py load_community_scripts
|
|||||||
WEB_VERSION=$(python manage.py get_config webversion)
|
WEB_VERSION=$(python manage.py get_config webversion)
|
||||||
printf >&2 "${YELLOW}%0.s*${NC}" {1..80}
|
printf >&2 "${YELLOW}%0.s*${NC}" {1..80}
|
||||||
printf >&2 "\n"
|
printf >&2 "\n"
|
||||||
printf >&2 "${YELLOW}Please create your login for the RMM website and django admin${NC}\n"
|
printf >&2 "${YELLOW}Please create your login for the RMM website${NC}\n"
|
||||||
printf >&2 "${YELLOW}%0.s*${NC}" {1..80}
|
printf >&2 "${YELLOW}%0.s*${NC}" {1..80}
|
||||||
printf >&2 "\n"
|
printf >&2 "\n"
|
||||||
echo -ne "Username: "
|
echo -ne "Username: "
|
||||||
@@ -856,7 +899,7 @@ done
|
|||||||
sleep 5
|
sleep 5
|
||||||
sudo systemctl enable meshcentral
|
sudo systemctl enable meshcentral
|
||||||
|
|
||||||
print_green 'Starting meshcentral and waiting for it to install plugins'
|
print_green 'Starting meshcentral and waiting for it to be ready'
|
||||||
|
|
||||||
sudo systemctl restart meshcentral
|
sudo systemctl restart meshcentral
|
||||||
|
|
||||||
@@ -880,7 +923,7 @@ meshtoken="$(
|
|||||||
MESH_TOKEN_KEY = "${MESHTOKENKEY}"
|
MESH_TOKEN_KEY = "${MESHTOKENKEY}"
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
echo "${meshtoken}" | tee --append /rmm/api/tacticalrmm/tacticalrmm/local_settings.py >/dev/null
|
echo "${meshtoken}" | tee --append $local_settings >/dev/null
|
||||||
|
|
||||||
print_green 'Creating meshcentral account and group'
|
print_green 'Creating meshcentral account and group'
|
||||||
|
|
||||||
@@ -917,7 +960,7 @@ sudo systemctl enable nats-api.service
|
|||||||
sudo systemctl start nats-api.service
|
sudo systemctl start nats-api.service
|
||||||
|
|
||||||
## disable django admin
|
## disable django admin
|
||||||
sed -i 's/ADMIN_ENABLED = True/ADMIN_ENABLED = False/g' /rmm/api/tacticalrmm/tacticalrmm/local_settings.py
|
sed -i 's/ADMIN_ENABLED = True/ADMIN_ENABLED = False/g' $local_settings
|
||||||
|
|
||||||
print_green 'Restarting services'
|
print_green 'Restarting services'
|
||||||
for i in rmm.service daphne.service celery.service celerybeat.service; do
|
for i in rmm.service daphne.service celery.service celerybeat.service; do
|
||||||
@@ -929,7 +972,6 @@ printf >&2 "${YELLOW}%0.s*${NC}" {1..80}
|
|||||||
printf >&2 "\n\n"
|
printf >&2 "\n\n"
|
||||||
printf >&2 "${YELLOW}Installation complete!${NC}\n\n"
|
printf >&2 "${YELLOW}Installation complete!${NC}\n\n"
|
||||||
printf >&2 "${YELLOW}Access your rmm at: ${GREEN}https://${frontenddomain}${NC}\n\n"
|
printf >&2 "${YELLOW}Access your rmm at: ${GREEN}https://${frontenddomain}${NC}\n\n"
|
||||||
printf >&2 "${YELLOW}Django admin url (disabled by default): ${GREEN}https://${rmmdomain}/${ADMINURL}/${NC}\n\n"
|
|
||||||
printf >&2 "${YELLOW}MeshCentral username: ${GREEN}${meshusername}${NC}\n"
|
printf >&2 "${YELLOW}MeshCentral username: ${GREEN}${meshusername}${NC}\n"
|
||||||
printf >&2 "${YELLOW}MeshCentral password: ${GREEN}${MESHPASSWD}${NC}\n\n"
|
printf >&2 "${YELLOW}MeshCentral password: ${GREEN}${MESHPASSWD}${NC}\n\n"
|
||||||
|
|
||||||
|
|||||||
44
restore.sh
44
restore.sh
@@ -1,10 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
SCRIPT_VERSION="50"
|
SCRIPT_VERSION="53"
|
||||||
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
|
||||||
sudo apt install -y curl wget dirmngr gnupg lsb-release
|
sudo apt install -y curl wget dirmngr gnupg lsb-release ca-certificates
|
||||||
|
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
@@ -86,7 +86,7 @@ if [ "$arch" = "x86_64" ]; then
|
|||||||
else
|
else
|
||||||
pgarch='arm64'
|
pgarch='arm64'
|
||||||
fi
|
fi
|
||||||
postgresql_repo="deb [arch=${pgarch}] https://apt.postgresql.org/pub/repos/apt/ $codename-pgdg main"
|
postgresql_repo="deb [arch=${pgarch} signed-by=/etc/apt/keyrings/postgresql-archive-keyring.gpg] https://apt.postgresql.org/pub/repos/apt/ $codename-pgdg main"
|
||||||
|
|
||||||
if [ ! -f "${1}" ]; then
|
if [ ! -f "${1}" ]; then
|
||||||
echo -ne "\n${RED}usage: ./restore.sh rmm-backup-xxxx.tar${NC}\n"
|
echo -ne "\n${RED}usage: ./restore.sh rmm-backup-xxxx.tar${NC}\n"
|
||||||
@@ -122,7 +122,10 @@ sudo apt update
|
|||||||
|
|
||||||
print_green 'Installing NodeJS'
|
print_green 'Installing NodeJS'
|
||||||
|
|
||||||
curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
sudo mkdir -p /etc/apt/keyrings
|
||||||
|
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||||
|
NODE_MAJOR=18
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y gcc g++ make
|
sudo apt install -y gcc g++ make
|
||||||
sudo apt install -y nodejs
|
sudo apt install -y nodejs
|
||||||
@@ -130,12 +133,11 @@ sudo npm install -g npm
|
|||||||
|
|
||||||
print_green 'Restoring Nginx'
|
print_green 'Restoring Nginx'
|
||||||
|
|
||||||
wget -qO - https://nginx.org/packages/keys/nginx_signing.key | sudo apt-key add -
|
wget -qO - https://nginx.org/packages/keys/nginx_signing.key | sudo gpg --dearmor -o /etc/apt/keyrings/nginx-archive-keyring.gpg
|
||||||
|
|
||||||
nginxrepo="$(
|
nginxrepo="$(
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
deb https://nginx.org/packages/$osname/ $codename nginx
|
deb [signed-by=/etc/apt/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/$osname $codename nginx
|
||||||
deb-src https://nginx.org/packages/$osname/ $codename nginx
|
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
echo "${nginxrepo}" | sudo tee /etc/apt/sources.list.d/nginx.list >/dev/null
|
echo "${nginxrepo}" | sudo tee /etc/apt/sources.list.d/nginx.list >/dev/null
|
||||||
@@ -209,7 +211,13 @@ if [ -d "${tmp_dir}/certs/custom" ]; then
|
|||||||
|
|
||||||
cp -p ${tmp_dir}/certs/custom/cert $CERT_FILE
|
cp -p ${tmp_dir}/certs/custom/cert $CERT_FILE
|
||||||
cp -p ${tmp_dir}/certs/custom/key $KEY_FILE
|
cp -p ${tmp_dir}/certs/custom/key $KEY_FILE
|
||||||
|
elif [ -d "${tmp_dir}/certs/selfsigned" ]; then
|
||||||
|
certdir='/etc/ssl/tactical'
|
||||||
|
sudo mkdir -p $certdir
|
||||||
|
sudo chown ${USER}:${USER} $certdir
|
||||||
|
sudo chmod 770 $certdir
|
||||||
|
cp -p ${tmp_dir}/certs/selfsigned/key.pem $certdir
|
||||||
|
cp -p ${tmp_dir}/certs/selfsigned/cert.pem $certdir
|
||||||
fi
|
fi
|
||||||
|
|
||||||
print_green 'Restoring celery configs'
|
print_green 'Restoring celery configs'
|
||||||
@@ -238,12 +246,12 @@ cd ~
|
|||||||
sudo rm -rf Python-${PYTHON_VER} Python-${PYTHON_VER}.tgz
|
sudo rm -rf Python-${PYTHON_VER} Python-${PYTHON_VER}.tgz
|
||||||
|
|
||||||
print_green 'Installing redis and git'
|
print_green 'Installing redis and git'
|
||||||
sudo apt install -y ca-certificates redis git
|
sudo apt install -y redis git
|
||||||
|
|
||||||
print_green 'Installing postgresql'
|
print_green 'Installing postgresql'
|
||||||
|
|
||||||
echo "$postgresql_repo" | sudo tee /etc/apt/sources.list.d/pgdg.list
|
echo "$postgresql_repo" | sudo tee /etc/apt/sources.list.d/pgdg.list
|
||||||
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
|
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /etc/apt/keyrings/postgresql-archive-keyring.gpg
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y postgresql-15
|
sudo apt install -y postgresql-15
|
||||||
sleep 2
|
sleep 2
|
||||||
@@ -349,7 +357,21 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
cd /meshcentral
|
cd /meshcentral
|
||||||
npm install meshcentral@${MESH_VER}
|
mesh_pkg="$(
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"archiver": "5.3.1",
|
||||||
|
"meshcentral": "${MESH_VER}",
|
||||||
|
"otplib": "10.2.3",
|
||||||
|
"pg": "8.7.1",
|
||||||
|
"pgtools": "0.3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
echo "${mesh_pkg}" >/meshcentral/package.json
|
||||||
|
npm install
|
||||||
|
|
||||||
if [ "$FROM_MONGO" = true ]; then
|
if [ "$FROM_MONGO" = true ]; then
|
||||||
node node_modules/meshcentral --dbimport >/dev/null
|
node node_modules/meshcentral --dbimport >/dev/null
|
||||||
|
|||||||
29
update.sh
29
update.sh
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
SCRIPT_VERSION="146"
|
SCRIPT_VERSION="148"
|
||||||
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'
|
||||||
@@ -67,6 +67,10 @@ cls() {
|
|||||||
printf "\033c"
|
printf "\033c"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if [ ! -d /etc/apt/keyrings ]; then
|
||||||
|
sudo mkdir -p /etc/apt/keyrings
|
||||||
|
fi
|
||||||
|
|
||||||
CHECK_NATS_LIMITNOFILE=$(grep LimitNOFILE /etc/systemd/system/nats.service)
|
CHECK_NATS_LIMITNOFILE=$(grep LimitNOFILE /etc/systemd/system/nats.service)
|
||||||
if ! [[ $CHECK_NATS_LIMITNOFILE ]]; then
|
if ! [[ $CHECK_NATS_LIMITNOFILE ]]; then
|
||||||
|
|
||||||
@@ -167,12 +171,11 @@ if [ ! -f /etc/apt/sources.list.d/nginx.list ]; then
|
|||||||
codename=$(lsb_release -sc)
|
codename=$(lsb_release -sc)
|
||||||
nginxrepo="$(
|
nginxrepo="$(
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
deb https://nginx.org/packages/$osname/ $codename nginx
|
deb [signed-by=/etc/apt/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/$osname $codename nginx
|
||||||
deb-src https://nginx.org/packages/$osname/ $codename nginx
|
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
echo "${nginxrepo}" | sudo tee /etc/apt/sources.list.d/nginx.list >/dev/null
|
echo "${nginxrepo}" | sudo tee /etc/apt/sources.list.d/nginx.list >/dev/null
|
||||||
wget -qO - https://nginx.org/packages/keys/nginx_signing.key | sudo apt-key add -
|
wget -qO - https://nginx.org/packages/keys/nginx_signing.key | sudo gpg --dearmor -o /etc/apt/keyrings/nginx-archive-keyring.gpg
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y nginx
|
sudo apt install -y nginx
|
||||||
fi
|
fi
|
||||||
@@ -407,8 +410,22 @@ if [[ "${CURRENT_MESH_VER}" != "${LATEST_MESH_VER}" ]] || [[ "$force" = true ]];
|
|||||||
sudo systemctl stop meshcentral
|
sudo systemctl stop meshcentral
|
||||||
sudo chown ${USER}:${USER} -R /meshcentral
|
sudo chown ${USER}:${USER} -R /meshcentral
|
||||||
cd /meshcentral
|
cd /meshcentral
|
||||||
rm -rf node_modules/
|
rm -rf node_modules/ package.json package-lock.json
|
||||||
npm install meshcentral@${LATEST_MESH_VER}
|
mesh_pkg="$(
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"archiver": "5.3.1",
|
||||||
|
"meshcentral": "${LATEST_MESH_VER}",
|
||||||
|
"otplib": "10.2.3",
|
||||||
|
"pg": "8.7.1",
|
||||||
|
"pgtools": "0.3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
echo "${mesh_pkg}" >/meshcentral/package.json
|
||||||
|
npm install
|
||||||
sudo systemctl start meshcentral
|
sudo systemctl start meshcentral
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user