diff --git a/api/tacticalrmm/agents/models.py b/api/tacticalrmm/agents/models.py index fe2b4d7571..133d157119 100644 --- a/api/tacticalrmm/agents/models.py +++ b/api/tacticalrmm/agents/models.py @@ -556,6 +556,7 @@ def run_script( run_as_user = True parsed_args = script.parse_script_args(self, script.shell, args) + parsed_env_vars = script.parse_script_env_vars(self, script.shell, env_vars) data = { "func": "runscriptfull" if full else "runscript", @@ -566,7 +567,7 @@ def run_script( "shell": script.shell, }, "run_as_user": run_as_user, - "env_vars": env_vars, + "env_vars": parsed_env_vars, } if history_pk != 0: diff --git a/api/tacticalrmm/agents/views.py b/api/tacticalrmm/agents/views.py index 3153ae052b..45fd989847 100644 --- a/api/tacticalrmm/agents/views.py +++ b/api/tacticalrmm/agents/views.py @@ -570,10 +570,9 @@ def install_agent(request): from agents.utils import get_agent_url from core.utils import token_is_valid - if getattr(settings, "TRMM_INSECURE", False) and request.data["installMethod"] in { - "exe", - "powershell", - }: + 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." ) @@ -680,7 +679,7 @@ def install_agent(request): if int(request.data["power"]): cmd.append("--power") - if getattr(settings, "TRMM_INSECURE", False): + if insecure: cmd.append("--insecure") resp["cmd"] = " ".join(str(i) for i in cmd) @@ -691,6 +690,8 @@ def install_agent(request): resp["cmd"] = ( dl + f" && chmod +x {inno} && " + " ".join(str(i) for i in cmd) ) + if insecure: + resp["cmd"] += " --insecure" resp["url"] = download_url diff --git a/api/tacticalrmm/alerts/models.py b/api/tacticalrmm/alerts/models.py index 6bb9488eeb..f0485a6b67 100644 --- a/api/tacticalrmm/alerts/models.py +++ b/api/tacticalrmm/alerts/models.py @@ -627,8 +627,7 @@ def parse_script_args(self, args: List[str]) -> List[str]: pattern = re.compile(".*\\{\\{alert\\.(.*)\\}\\}.*") for arg in args: - match = pattern.match(arg) - if match: + if match := pattern.match(arg): name = match.group(1) # check if attr exists and isn't a function diff --git a/api/tacticalrmm/autotasks/serializers.py b/api/tacticalrmm/autotasks/serializers.py index ceb390a06c..c9444c1677 100644 --- a/api/tacticalrmm/autotasks/serializers.py +++ b/api/tacticalrmm/autotasks/serializers.py @@ -252,7 +252,11 @@ def get_task_actions(self, obj): "shell": script.shell, "timeout": action["timeout"], "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: diff --git a/api/tacticalrmm/beta/v1/__init__.py b/api/tacticalrmm/beta/v1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/tacticalrmm/beta/v1/agent/__init__.py b/api/tacticalrmm/beta/v1/agent/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/tacticalrmm/beta/v1/agent/filter.py b/api/tacticalrmm/beta/v1/agent/filter.py new file mode 100644 index 0000000000..4d1153a1b8 --- /dev/null +++ b/api/tacticalrmm/beta/v1/agent/filter.py @@ -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 diff --git a/api/tacticalrmm/beta/v1/agent/views.py b/api/tacticalrmm/beta/v1/agent/views.py new file mode 100644 index 0000000000..dfcc21e247 --- /dev/null +++ b/api/tacticalrmm/beta/v1/agent/views.py @@ -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 diff --git a/api/tacticalrmm/beta/v1/client/__init__.py b/api/tacticalrmm/beta/v1/client/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/tacticalrmm/beta/v1/client/views.py b/api/tacticalrmm/beta/v1/client/views.py new file mode 100644 index 0000000000..c0539fa8ae --- /dev/null +++ b/api/tacticalrmm/beta/v1/client/views.py @@ -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"] diff --git a/api/tacticalrmm/beta/v1/pagination.py b/api/tacticalrmm/beta/v1/pagination.py new file mode 100644 index 0000000000..072c14d01d --- /dev/null +++ b/api/tacticalrmm/beta/v1/pagination.py @@ -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 diff --git a/api/tacticalrmm/beta/v1/serializers.py b/api/tacticalrmm/beta/v1/serializers.py new file mode 100644 index 0000000000..595d41b1c5 --- /dev/null +++ b/api/tacticalrmm/beta/v1/serializers.py @@ -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__" diff --git a/api/tacticalrmm/beta/v1/site/views.py b/api/tacticalrmm/beta/v1/site/views.py new file mode 100644 index 0000000000..0d061979b7 --- /dev/null +++ b/api/tacticalrmm/beta/v1/site/views.py @@ -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"] diff --git a/api/tacticalrmm/beta/v1/urls.py b/api/tacticalrmm/beta/v1/urls.py new file mode 100644 index 0000000000..44e4c926ca --- /dev/null +++ b/api/tacticalrmm/beta/v1/urls.py @@ -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 diff --git a/api/tacticalrmm/checks/serializers.py b/api/tacticalrmm/checks/serializers.py index 5a516285bf..e56b3cc453 100644 --- a/api/tacticalrmm/checks/serializers.py +++ b/api/tacticalrmm/checks/serializers.py @@ -172,8 +172,14 @@ def get_env_vars(self, obj): if obj.check_type != CheckType.SCRIPT: return [] - # check's env_vars override the script's env vars - return obj.env_vars or obj.script.env_vars + agent = self.context["agent"] if "agent" in self.context.keys() else obj.agent + + 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: model = Check diff --git a/api/tacticalrmm/clients/views.py b/api/tacticalrmm/clients/views.py index e82ec76901..475e8ee876 100644 --- a/api/tacticalrmm/clients/views.py +++ b/api/tacticalrmm/clients/views.py @@ -3,6 +3,7 @@ import uuid from contextlib import suppress +from django.conf import settings from django.db.models import Count, Exists, OuterRef, Prefetch, prefetch_related_objects from django.shortcuts import get_object_or_404 from django.utils import timezone as djangotime @@ -288,6 +289,9 @@ def get(self, request): return Response(DeploymentSerializer(deps, many=True).data) def post(self, request): + if getattr(settings, "TRMM_INSECURE", False): + return notify_error("Not available in insecure mode") + from accounts.models import User site = get_object_or_404(Site, pk=request.data["site"]) @@ -343,6 +347,9 @@ class GenerateAgent(APIView): permission_classes = (AllowAny,) 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 try: diff --git a/api/tacticalrmm/core/agent_linux.sh b/api/tacticalrmm/core/agent_linux.sh index b5fbcb04d7..871b414e1e 100755 --- a/api/tacticalrmm/core/agent_linux.sh +++ b/api/tacticalrmm/core/agent_linux.sh @@ -15,6 +15,9 @@ 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 diff --git a/api/tacticalrmm/core/management/commands/pre_update_tasks.py b/api/tacticalrmm/core/management/commands/pre_update_tasks.py index 7e689beb6b..a10ef0a93a 100644 --- a/api/tacticalrmm/core/management/commands/pre_update_tasks.py +++ b/api/tacticalrmm/core/management/commands/pre_update_tasks.py @@ -11,4 +11,7 @@ def handle(self, *args, **kwargs): self.stdout.write(self.style.WARNING("Cleaning the cache")) clear_entire_cache() self.stdout.write(self.style.SUCCESS("Cache was cleared!")) - call_command("fix_dupe_agent_customfields") + try: + call_command("fix_dupe_agent_customfields") + except: + pass diff --git a/api/tacticalrmm/core/views.py b/api/tacticalrmm/core/views.py index e8e5aa22b9..1f88813c02 100644 --- a/api/tacticalrmm/core/views.py +++ b/api/tacticalrmm/core/views.py @@ -1,5 +1,6 @@ import json import re +from contextlib import suppress from pathlib import Path from zoneinfo import ZoneInfo @@ -11,6 +12,7 @@ from django.shortcuts import get_object_or_404 from django.utils import timezone as djangotime from django.views.decorators.csrf import csrf_exempt +from redis import from_url from rest_framework.decorators import api_view, permission_classes from rest_framework.exceptions import PermissionDenied from rest_framework.permissions import IsAuthenticated @@ -430,6 +432,13 @@ def status(request): now = djangotime.now() delta = expires - now + redis_url = f"redis://{settings.REDIS_HOST}" + redis_ping = False + with suppress(Exception): + with from_url(redis_url) as conn: + conn.ping() + redis_ping = True + ret = { "version": settings.TRMM_VERSION, "latest_agent_version": settings.LATEST_AGENT_VER, @@ -440,6 +449,7 @@ def status(request): "mem_usage_percent": mem_usage, "days_until_cert_expires": delta.days, "cert_expired": delta.days < 0, + "redis_ping": redis_ping, } if settings.DOCKER_BUILD: diff --git a/api/tacticalrmm/requirements.txt b/api/tacticalrmm/requirements.txt index bb791425a4..a1c9adeef3 100644 --- a/api/tacticalrmm/requirements.txt +++ b/api/tacticalrmm/requirements.txt @@ -1,27 +1,28 @@ -adrf==0.1.1 +adrf==0.1.2 asgiref==3.7.2 celery==5.3.1 certifi==2023.7.22 cffi==1.15.1 channels==4.0.0 channels_redis==4.1.0 -cryptography==41.0.3 +cryptography==41.0.4 daphne==4.0.0 -Django==4.2.4 +Django==4.2.5 django-cors-headers==4.2.0 +django-filter==23.3 django-ipware==5.0.0 django-rest-knox==4.2.0 djangorestframework==3.14.0 -drf-spectacular==0.26.4 +drf-spectacular==0.26.5 hiredis==2.2.3 meshctrl==0.1.15 -msgpack==1.0.5 -nats-py==2.3.1 +msgpack==1.0.7 +nats-py==2.4.0 packaging==23.1 psutil==5.9.5 -psycopg[binary]==3.1.10 +psycopg[binary]==3.1.12 pycparser==2.21 -pycryptodome==3.18.0 +pycryptodome==3.19.0 pyotp==2.9.0 pyparsing==3.1.1 pytz==2023.3 @@ -30,10 +31,10 @@ redis==4.5.5 requests==2.31.0 six==1.16.0 sqlparse==0.4.4 -twilio==8.5.0 -urllib3==2.0.4 +twilio==8.9.0 +urllib3==2.0.5 uWSGI==2.0.22 validators==0.20.0 vine==5.0.0 websockets==11.0.3 -zipp==3.16.2 +zipp==3.17.0 diff --git a/api/tacticalrmm/scripts/models.py b/api/tacticalrmm/scripts/models.py index 7886a3397b..20842769f8 100644 --- a/api/tacticalrmm/scripts/models.py +++ b/api/tacticalrmm/scripts/models.py @@ -194,6 +194,7 @@ def serialize(script): return ScriptSerializer(script).data @classmethod + # TODO refactor common functionality of parse functions def parse_script_args(cls, agent, shell: str, args: List[str] = []) -> list: if not args: return [] @@ -204,8 +205,7 @@ def parse_script_args(cls, agent, shell: str, args: List[str] = []) -> list: pattern = re.compile(".*\\{\\{(.*)\\}\\}.*") for arg in args: - match = pattern.match(arg) - if match: + if match := pattern.match(arg): # only get the match between the () in regex string = match.group(1) value = replace_db_values( @@ -231,6 +231,42 @@ def parse_script_args(cls, agent, shell: str, args: List[str] = []) -> list: 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): name = CharField(max_length=40, unique=True) diff --git a/api/tacticalrmm/scripts/tasks.py b/api/tacticalrmm/scripts/tasks.py index d5b8b18682..6181c91e91 100644 --- a/api/tacticalrmm/scripts/tasks.py +++ b/api/tacticalrmm/scripts/tasks.py @@ -77,7 +77,7 @@ def bulk_script_task( "shell": script.shell, }, "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) items.append(tup) diff --git a/api/tacticalrmm/scripts/tests.py b/api/tacticalrmm/scripts/tests.py index 8bb1b1b91a..aff124d1c0 100644 --- a/api/tacticalrmm/scripts/tests.py +++ b/api/tacticalrmm/scripts/tests.py @@ -242,6 +242,25 @@ def test_script_arg_variable_replacement(self): 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): agent = baker.make_recipe("agents.agent") field = baker.make( @@ -272,6 +291,40 @@ def test_script_arg_replacement_custom_field(self): 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): agent = baker.make_recipe("agents.agent") field = baker.make( @@ -302,6 +355,42 @@ def test_script_arg_replacement_client_custom_fields(self): 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${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 @@ -82,7 +82,7 @@ if grep -q postgres "/meshcentral/meshcentral-data/config.json"; then fi 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) - 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 mongodump --gzip --out=${tmp_dir}/meshcentral/mongo fi @@ -143,6 +143,7 @@ if [[ $* == *--auto* ]]; then else 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" fi diff --git a/docker/.env.example b/docker/.env.example index a24cb86b59..f46c5718ae 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -5,6 +5,10 @@ VERSION=latest TRMM_USER=tactical TRMM_PASS=tactical +# optional web port override settings +TRMM_HTTP_PORT=80 +TRMM_HTTPS_PORT=443 + # dns settings APP_HOST=rmm.example.com API_HOST=api.example.com diff --git a/docker/containers/tactical-meshcentral/dockerfile b/docker/containers/tactical-meshcentral/dockerfile index 2705daf5c8..8bbd587523 100644 --- a/docker/containers/tactical-meshcentral/dockerfile +++ b/docker/containers/tactical-meshcentral/dockerfile @@ -10,7 +10,22 @@ SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"] 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 </dev/null @@ -252,7 +253,9 @@ done 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 install -y gcc g++ make sudo apt install -y nodejs @@ -273,13 +276,13 @@ cd ~ sudo rm -rf Python-${PYTHON_VER} Python-${PYTHON_VER}.tgz print_green 'Installing redis and git' -sudo apt install -y ca-certificates redis git +sudo apt install -y redis git print_green 'Installing postgresql' 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 install -y postgresql-15 sleep 2 @@ -486,7 +489,7 @@ python manage.py load_community_scripts WEB_VERSION=$(python manage.py get_config webversion) printf >&2 "${YELLOW}%0.s*${NC}" {1..80} 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 "\n" echo -ne "Username: " @@ -896,7 +899,7 @@ done sleep 5 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 @@ -969,7 +972,6 @@ printf >&2 "${YELLOW}%0.s*${NC}" {1..80} printf >&2 "\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}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 password: ${GREEN}${MESHPASSWD}${NC}\n\n" diff --git a/restore.sh b/restore.sh index d76b591684..e91ef26a5b 100755 --- a/restore.sh +++ b/restore.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash -SCRIPT_VERSION="51" +SCRIPT_VERSION="53" SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/restore.sh' 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' YELLOW='\033[1;33m' @@ -86,7 +86,7 @@ if [ "$arch" = "x86_64" ]; then else pgarch='arm64' 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 echo -ne "\n${RED}usage: ./restore.sh rmm-backup-xxxx.tar${NC}\n" @@ -122,7 +122,10 @@ sudo apt update 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 install -y gcc g++ make sudo apt install -y nodejs @@ -130,12 +133,11 @@ sudo npm install -g npm 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="$( cat </dev/null @@ -244,12 +246,12 @@ cd ~ sudo rm -rf Python-${PYTHON_VER} Python-${PYTHON_VER}.tgz print_green 'Installing redis and git' -sudo apt install -y ca-certificates redis git +sudo apt install -y redis git print_green 'Installing postgresql' 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 install -y postgresql-15 sleep 2 diff --git a/update.sh b/update.sh index 0522d5dafa..9069fad795 100644 --- a/update.sh +++ b/update.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -SCRIPT_VERSION="147" +SCRIPT_VERSION="148" 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' YELLOW='\033[1;33m' @@ -67,6 +67,10 @@ cls() { 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) if ! [[ $CHECK_NATS_LIMITNOFILE ]]; then @@ -167,12 +171,11 @@ if [ ! -f /etc/apt/sources.list.d/nginx.list ]; then codename=$(lsb_release -sc) nginxrepo="$( cat </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 install -y nginx fi