Compare commits

..

35 Commits

Author SHA1 Message Date
wh1te909
7d8c783a7d Release 0.16.5 2023-10-02 01:50:44 +00:00
wh1te909
a2e996b550 bump version 2023-10-02 01:49:57 +00:00
wh1te909
cfc1c31050 rename setting 2023-10-02 00:12:39 +00:00
wh1te909
45106bf6f9 remove apt-key [skip ci] 2023-10-01 15:59:14 +00:00
wh1te909
6e3cfe491b update chocos fixes #1538 2023-09-30 23:33:52 +00:00
wh1te909
12f2158afd feat: make env vars expand custom fields closes #1609 2023-09-30 22:05:52 +00:00
wh1te909
6d78773c55 bump web ver 2023-09-30 22:02:49 +00:00
wh1te909
43a62d4eb6 update reqs 2023-09-30 20:53:08 +00:00
wh1te909
cc08dfda96 make beta api optional 2023-09-30 19:33:22 +00:00
Dan
622e33588e Merge pull request #1636 from redanthrax/beta-api
beta api clients, agents, sites with paging
2023-09-30 12:16:54 -07:00
wh1te909
67980b58a0 fix docker 2023-09-29 08:29:59 +00:00
redanthrax
027e444955 beta api clients, agents, sites with paging
formatted with black

django filter requirement

updated beta api, restricted to get and put
2023-09-27 14:36:14 -07:00
wh1te909
d838750389 update reqs 2023-09-27 17:25:56 +00:00
wh1te909
71d8bd5266 update reqs 2023-09-20 03:24:40 +00:00
wh1te909
ec4ae24bbd add note about x forwarding 2023-09-20 03:21:15 +00:00
wh1te909
1128149359 fix docker mesh npm install 2023-09-20 03:20:15 +00:00
wh1te909
bdfc6634ec fix tempdir cleanup [skip ci] 2023-09-13 20:29:33 +00:00
wh1te909
ca4d19667b update reqs 2023-09-11 02:43:50 +00:00
wh1te909
c71aa7baa7 back to dev 2023-09-11 02:40:28 +00:00
wh1te909
fd80ccd2c5 Release 0.16.4 2023-09-02 00:20:54 +00:00
wh1te909
9dc0b24399 bump versions 2023-09-01 23:48:31 +00:00
wh1te909
747954e6fb wording 2023-09-01 22:03:51 +00:00
wh1te909
274f4f227e node install script is deprecated [skip ci] 2023-09-01 21:12:45 +00:00
wh1te909
92197d8d49 change to localhost 2023-09-01 18:56:09 +00:00
wh1te909
aee06920eb more self signed stuff 2023-09-01 18:55:34 +00:00
wh1te909
5111b17d3c bump web ver [skip ci] 2023-08-30 04:29:36 +00:00
wh1te909
2849d8f45d update scripts for self signed 2023-08-29 23:53:19 +00:00
wh1te909
bac60d9bd4 feat: reset all checks status closes amidaware/tacticalrmm#1615 2023-08-29 20:36:20 +00:00
wh1te909
9c797162f4 only Manual is supported in insecure mode 2023-08-29 20:33:58 +00:00
wh1te909
09d184e2f8 update installers 2023-08-25 18:25:09 +00:00
wh1te909
7bca618906 allow self-signed certs 2023-08-24 21:40:51 +00:00
wh1te909
67607103e9 back to dev [skip ci] 2023-08-24 21:05:50 +00:00
wh1te909
73c9956fe4 Release 0.16.3 2023-08-18 04:33:01 +00:00
wh1te909
b42f2ffe33 bump version [skip ci] 2023-08-18 04:29:41 +00:00
wh1te909
30a3f185ef fix npm #1604 [skip ci] 2023-08-18 04:28:58 +00:00
40 changed files with 752 additions and 97 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

View 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

View 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

View 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"]

View 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

View 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__"

View 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"]

View 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

View File

@@ -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

View File

@@ -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")

View File

@@ -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),

View File

@@ -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]

View File

@@ -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:

View File

@@ -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} -log debug"
else
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}"
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}

View File

@@ -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"],

View File

@@ -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!"))
try:
call_command("fix_dupe_agent_customfields") call_command("fix_dupe_agent_customfields")
except:
pass

View File

@@ -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",
)

View File

@@ -87,6 +87,10 @@ 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:
if getattr(settings, "TRMM_INSECURE", False):
site = core.mesh_site.replace("https", "ws")
uri = f"{site}:4430/control.ashx?auth={token}"
else: else:
site = core.mesh_site.replace("https", "wss") site = core.mesh_site.replace("https", "wss")
uri = f"{site}/control.ashx?auth={token}" uri = f"{site}/control.ashx?auth={token}"
@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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(

View File

@@ -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

View File

@@ -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,

View File

@@ -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"

View File

@@ -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

View File

@@ -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"):

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
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' print_green 'Getting wildcard cert'
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
while [[ $? -ne 0 ]]; do 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 done
CERT_PRIV_KEY=/etc/letsencrypt/live/${rootdomain}/privkey.pem CERT_PRIV_KEY=/etc/letsencrypt/live/${rootdomain}/privkey.pem
CERT_PUB_KEY=/etc/letsencrypt/live/${rootdomain}/fullchain.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"

View File

@@ -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

View File

@@ -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