Release 0.15.0

This commit is contained in:
wh1te909
2022-09-24 03:09:12 +00:00
13 changed files with 211 additions and 72 deletions

4
.github/FUNDING.yml vendored
View File

@@ -1,9 +1,9 @@
# These are supported funding model platforms # These are supported funding model platforms
github: wh1te909 github: amidaware
patreon: # Replace with a single Patreon username patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username open_collective: # Replace with a single Open Collective username
ko_fi: tacticalrmm ko_fi: # tacticalrmm
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username liberapay: # Replace with a single Liberapay username

View File

@@ -35,6 +35,9 @@ Demo database resets every hour. A lot of features are disabled for obvious reas
## Linux agent versions supported ## Linux agent versions supported
- Any distro with systemd which includes but is not limited to: Debian (10, 11), Ubuntu x86_64 (18.04, 20.04, 22.04), Synology 7, centos, freepbx and more! - Any distro with systemd which includes but is not limited to: Debian (10, 11), Ubuntu x86_64 (18.04, 20.04, 22.04), Synology 7, centos, freepbx and more!
## Mac agent versions supported
- 64 bit Intel and Apple Silicon (M1, M2)
## Installation / Backup / Restore / Usage ## Installation / Backup / Restore / Usage
### Refer to the [documentation](https://docs.tacticalrmm.com) ### Refer to the [documentation](https://docs.tacticalrmm.com)

View File

@@ -2,6 +2,10 @@ SECRET_KEY = "{{ django_secret }}"
DEBUG = True DEBUG = True
ALLOWED_HOSTS = ['{{ api }}'] ALLOWED_HOSTS = ['{{ api }}']
ADMIN_URL = "admin/" ADMIN_URL = "admin/"
CORS_ORIGIN_WHITELIST = [
"http://{{ rmm }}:8080",
"https://{{ rmm }}:8080",
]
CORS_ORIGIN_ALLOW_ALL = True CORS_ORIGIN_ALLOW_ALL = True
DATABASES = { DATABASES = {
'default': { 'default': {

View File

@@ -835,9 +835,12 @@ class Agent(BaseAuditModel):
Return type: tuple(message: str, error: bool) Return type: tuple(message: str, error: bool)
""" """
if mode == "tacagent": if mode == "tacagent":
if self.is_posix: if self.plat == AgentPlat.LINUX:
cmd = "systemctl restart tacticalagent.service" cmd = "systemctl restart tacticalagent.service"
shell = 3 shell = 3
elif self.plat == AgentPlat.DARWIN:
cmd = "launchctl kickstart -k system/tacticalagent"
shell = 3
else: else:
cmd = "net stop tacticalrmm & taskkill /F /IM tacticalrmm.exe & net start tacticalrmm" cmd = "net stop tacticalrmm & taskkill /F /IM tacticalrmm.exe & net start tacticalrmm"
shell = 1 shell = 1

View File

@@ -30,9 +30,9 @@ from scripts.models import Script
from scripts.tasks import handle_bulk_command_task, handle_bulk_script_task from scripts.tasks import handle_bulk_command_task, handle_bulk_script_task
from tacticalrmm.constants import ( from tacticalrmm.constants import (
AGENT_DEFER, AGENT_DEFER,
AGENT_TABLE_DEFER,
AGENT_STATUS_OFFLINE, AGENT_STATUS_OFFLINE,
AGENT_STATUS_ONLINE, AGENT_STATUS_ONLINE,
AGENT_TABLE_DEFER,
AgentHistoryType, AgentHistoryType,
AgentMonType, AgentMonType,
AgentPlat, AgentPlat,
@@ -174,6 +174,7 @@ class GetUpdateDeleteAgent(APIView):
fields = [ fields = [
"maintenance_mode", # TODO separate this "maintenance_mode", # TODO separate this
"policy", # TODO separate this "policy", # TODO separate this
"block_policy_inheritance", # TODO separate this
"monitoring_type", "monitoring_type",
"description", "description",
"overdue_email_alert", "overdue_email_alert",
@@ -236,10 +237,13 @@ class GetUpdateDeleteAgent(APIView):
def delete(self, request, agent_id): def delete(self, request, agent_id):
agent = get_object_or_404(Agent, agent_id=agent_id) agent = get_object_or_404(Agent, agent_id=agent_id)
code = "foo" code = "foo" # stub for windows
if agent.plat == AgentPlat.LINUX: if agent.plat == AgentPlat.LINUX:
with open(settings.LINUX_AGENT_SCRIPT, "r") as f: with open(settings.LINUX_AGENT_SCRIPT, "r") as f:
code = f.read() code = f.read()
elif agent.plat == AgentPlat.DARWIN:
with open(settings.MAC_UNINSTALL, "r") as f:
code = f.read()
asyncio.run(agent.nats_cmd({"func": "uninstall", "code": code}, wait=False)) asyncio.run(agent.nats_cmd({"func": "uninstall", "code": code}, wait=False))
name = agent.hostname name = agent.hostname
@@ -550,7 +554,15 @@ def install_agent(request):
codesign_token, is_valid = token_is_valid() codesign_token, is_valid = token_is_valid()
inno = f"tacticalagent-v{version}-{plat}-{goarch}.exe" if request.data["installMethod"] in {"bash", "mac"} and not is_valid:
return notify_error(
"Missing code signing token, or token is no longer valid. Please read the docs for more info."
)
inno = f"tacticalagent-v{version}-{plat}-{goarch}"
if plat == AgentPlat.WINDOWS:
inno += ".exe"
download_url = get_agent_url(goarch=goarch, plat=plat, token=codesign_token) download_url = get_agent_url(goarch=goarch, plat=plat, token=codesign_token)
installer_user = User.objects.filter(is_installer_user=True).first() installer_user = User.objects.filter(is_installer_user=True).first()
@@ -559,6 +571,21 @@ def install_agent(request):
user=installer_user, expiry=dt.timedelta(hours=request.data["expires"]) user=installer_user, expiry=dt.timedelta(hours=request.data["expires"])
) )
install_flags = [
"-m",
"install",
"--api",
request.data["api"],
"--client-id",
client_id,
"--site-id",
site_id,
"--agent-type",
request.data["agenttype"],
"--auth",
token,
]
if request.data["installMethod"] == "exe": if request.data["installMethod"] == "exe":
from tacticalrmm.utils import generate_winagent_exe from tacticalrmm.utils import generate_winagent_exe
@@ -576,14 +603,6 @@ def install_agent(request):
) )
elif request.data["installMethod"] == "bash": elif request.data["installMethod"] == "bash":
# TODO
# linux agents are in beta for now, only available for sponsors for testing
# remove this after it's out of beta
if not is_valid:
return notify_error(
"Missing code signing token, or token is no longer valid. Please read the docs for more info."
)
from agents.utils import generate_linux_install from agents.utils import generate_linux_install
@@ -597,7 +616,9 @@ def install_agent(request):
download_url=download_url, download_url=download_url,
) )
elif request.data["installMethod"] == "manual": elif request.data["installMethod"] in {"manual", "mac"}:
resp = {}
if request.data["installMethod"] == "manual":
cmd = [ cmd = [
inno, inno,
"/VERYSILENT", "/VERYSILENT",
@@ -609,19 +630,7 @@ def install_agent(request):
"5", "5",
"&&", "&&",
r'"C:\Program Files\TacticalAgent\tacticalrmm.exe"', r'"C:\Program Files\TacticalAgent\tacticalrmm.exe"',
"-m", ] + install_flags
"install",
"--api",
request.data["api"],
"--client-id",
client_id,
"--site-id",
site_id,
"--agent-type",
request.data["agenttype"],
"--auth",
token,
]
if int(request.data["rdp"]): if int(request.data["rdp"]):
cmd.append("--rdp") cmd.append("--rdp")
@@ -630,10 +639,16 @@ def install_agent(request):
if int(request.data["power"]): if int(request.data["power"]):
cmd.append("--power") cmd.append("--power")
resp = { resp["cmd"] = " ".join(str(i) for i in cmd)
"cmd": " ".join(str(i) for i in cmd), else:
"url": download_url, install_flags.insert(0, f"sudo ./{inno}")
} cmd = install_flags.copy()
dl = f"curl -L -o {inno} '{download_url}'"
resp["cmd"] = (
dl + f" && chmod +x {inno} && " + " ".join(str(i) for i in cmd)
)
resp["url"] = download_url
return Response(resp) return Response(resp)
@@ -912,6 +927,8 @@ def bulk(request):
q = q.filter(plat=AgentPlat.WINDOWS) q = q.filter(plat=AgentPlat.WINDOWS)
elif request.data["osType"] == AgentPlat.LINUX: elif request.data["osType"] == AgentPlat.LINUX:
q = q.filter(plat=AgentPlat.LINUX) q = q.filter(plat=AgentPlat.LINUX)
elif request.data["osType"] == AgentPlat.DARWIN:
q = q.filter(plat=AgentPlat.DARWIN)
agents: list[int] = [agent.pk for agent in q] agents: list[int] = [agent.pk for agent in q]

View File

@@ -24,6 +24,7 @@ from core.utils import (
get_core_settings, get_core_settings,
get_mesh_device_id, get_mesh_device_id,
get_mesh_ws_url, get_mesh_ws_url,
get_meshagent_url,
) )
from logs.models import DebugLog, PendingAction from logs.models import DebugLog, PendingAction
from software.models import InstalledSoftware from software.models import InstalledSoftware
@@ -398,25 +399,32 @@ class MeshExe(APIView):
def post(self, request): def post(self, request):
match request.data: match request.data:
case {"goarch": GoArch.AMD64, "plat": AgentPlat.WINDOWS}: case {"goarch": GoArch.AMD64, "plat": AgentPlat.WINDOWS}:
arch = MeshAgentIdent.WIN64 ident = MeshAgentIdent.WIN64
case {"goarch": GoArch.i386, "plat": AgentPlat.WINDOWS}: case {"goarch": GoArch.i386, "plat": AgentPlat.WINDOWS}:
arch = MeshAgentIdent.WIN32 ident = MeshAgentIdent.WIN32
case {"goarch": GoArch.AMD64, "plat": AgentPlat.DARWIN} | {
"goarch": GoArch.ARM64,
"plat": AgentPlat.DARWIN,
}:
ident = MeshAgentIdent.DARWIN_UNIVERSAL
case _: case _:
return notify_error("Arch not specified") return notify_error("Arch not supported")
core = get_core_settings() core = get_core_settings()
try: try:
uri = get_mesh_ws_url() uri = get_mesh_ws_url()
mesh_id = asyncio.run(get_mesh_device_id(uri, core.mesh_device_group)) mesh_device_id: str = asyncio.run(
get_mesh_device_id(uri, core.mesh_device_group)
)
except: except:
return notify_error("Unable to connect to mesh to get group id information") return notify_error("Unable to connect to mesh to get group id information")
if settings.DOCKER_BUILD: dl_url = get_meshagent_url(
dl_url = f"{settings.MESH_WS_URL.replace('ws://', 'http://')}/meshagents?id={arch}&meshid={mesh_id}&installflags=0" ident=ident,
else: plat=request.data["plat"],
dl_url = ( mesh_site=core.mesh_site,
f"{core.mesh_site}/meshagents?id={arch}&meshid={mesh_id}&installflags=0" mesh_device_id=mesh_device_id,
) )
try: try:

View File

@@ -0,0 +1,8 @@
#!/bin/bash
/usr/local/mesh_services/meshagent/meshagent -fulluninstall
launchctl bootout system /Library/LaunchDaemons/tacticalagent.plist
rm -rf /usr/local/mesh_services
rm -f /etc/tacticalagent
rm -rf /opt/tacticalagent
rm -f /Library/LaunchDaemons/tacticalagent.plist

View File

@@ -5,13 +5,20 @@ from channels.db import database_sync_to_async
from channels.testing import WebsocketCommunicator from channels.testing import WebsocketCommunicator
from django.conf import settings from django.conf import settings
from django.core.management import call_command from django.core.management import call_command
from django.test import override_settings
from model_bakery import baker from model_bakery import baker
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from agents.models import Agent from agents.models import Agent
from core.utils import get_core_settings from core.utils import get_core_settings, get_meshagent_url
from logs.models import PendingAction from logs.models import PendingAction
from tacticalrmm.constants import CONFIG_MGMT_CMDS, CustomFieldModel, PAAction, PAStatus from tacticalrmm.constants import (
CONFIG_MGMT_CMDS,
CustomFieldModel,
MeshAgentIdent,
PAAction,
PAStatus,
)
from tacticalrmm.test import TacticalTestCase from tacticalrmm.test import TacticalTestCase
from .consumers import DashInfo from .consumers import DashInfo
@@ -444,3 +451,58 @@ class TestCorePermissions(TacticalTestCase):
def setUp(self): def setUp(self):
self.setup_client() self.setup_client()
self.setup_coresettings() self.setup_coresettings()
class TestCoreUtils(TacticalTestCase):
def setUp(self):
self.setup_coresettings()
def test_get_meshagent_url_standard(self):
r = get_meshagent_url(
ident=MeshAgentIdent.DARWIN_UNIVERSAL,
plat="darwin",
mesh_site="https://mesh.example.com",
mesh_device_id="abc123",
)
self.assertEqual(
r,
"https://mesh.example.com/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,
"https://mesh.example.com/meshagents?id=4&meshid=abc123&installflags=0",
)
@override_settings(DOCKER_BUILD=True)
@override_settings(MESH_WS_URL="ws://tactical-meshcentral:4443")
def test_get_meshagent_url_docker(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://tactical-meshcentral:4443/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://tactical-meshcentral:4443/meshagents?id=4&meshid=abc123&installflags=0",
)

View File

@@ -1,6 +1,7 @@
import json import json
import subprocess import subprocess
import tempfile import tempfile
import urllib.parse
from base64 import b64encode from base64 import b64encode
from typing import TYPE_CHECKING, Optional, cast from typing import TYPE_CHECKING, Optional, cast
@@ -11,7 +12,12 @@ from django.core.cache import cache
from django.http import FileResponse from django.http import FileResponse
from meshctrl.utils import get_auth_token from meshctrl.utils import get_auth_token
from tacticalrmm.constants import CORESETTINGS_CACHE_KEY, ROLE_CACHE_PREFIX from tacticalrmm.constants import (
CORESETTINGS_CACHE_KEY,
ROLE_CACHE_PREFIX,
AgentPlat,
MeshAgentIdent,
)
if TYPE_CHECKING: if TYPE_CHECKING:
from core.models import CodeSignToken, CoreSettings from core.models import CodeSignToken, CoreSettings
@@ -142,3 +148,28 @@ def sysd_svc_is_running(svc: str) -> bool:
cmd = ["systemctl", "is-active", "--quiet", svc] cmd = ["systemctl", "is-active", "--quiet", svc]
r = subprocess.run(cmd, capture_output=True) r = subprocess.run(cmd, capture_output=True)
return not r.returncode return not r.returncode
def get_meshagent_url(
*, ident: "MeshAgentIdent", plat: str, mesh_site: str, mesh_device_id: str
) -> str:
if settings.DOCKER_BUILD:
base = settings.MESH_WS_URL.replace("ws://", "http://")
else:
base = mesh_site
if plat == AgentPlat.WINDOWS:
params = {
"id": ident,
"meshid": mesh_device_id,
"installflags": 0,
}
else:
params = {
"id": mesh_device_id,
"installflags": 2,
"meshinstall": ident,
}
return base + "/meshagents?" + urllib.parse.urlencode(params)

View File

@@ -1,6 +1,6 @@
asgiref==3.5.2 asgiref==3.5.2
celery==5.2.7 celery==5.2.7
certifi==2022.6.15.1 certifi==2022.9.14
cffi==1.15.1 cffi==1.15.1
channels==3.0.5 channels==3.0.5
channels_redis==3.4.1 channels_redis==3.4.1
@@ -11,7 +11,8 @@ Django==4.1.1
django-cors-headers==3.13.0 django-cors-headers==3.13.0
django-ipware==4.0.2 django-ipware==4.0.2
django-rest-knox==4.2.0 django-rest-knox==4.2.0
djangorestframework==3.13.1 djangorestframework==3.14.0
drf-spectacular==0.24.1
future==0.18.2 future==0.18.2
msgpack==1.0.4 msgpack==1.0.4
nats-py==2.1.7 nats-py==2.1.7
@@ -28,12 +29,11 @@ hiredis==2.0.0
requests==2.28.1 requests==2.28.1
six==1.16.0 six==1.16.0
sqlparse==0.4.2 sqlparse==0.4.2
twilio==7.14.0 twilio==7.14.1
urllib3==1.26.12 urllib3==1.26.12
uWSGI==2.0.20 uWSGI==2.0.20
validators==0.20.0 validators==0.20.0
vine==5.0.0 vine==5.0.0
websockets==10.3 websockets==10.3
zipp==3.8.1 zipp==3.8.1
drf-spectacular==0.23.1
meshctrl==0.1.15 meshctrl==0.1.15

View File

@@ -10,6 +10,7 @@ class MeshAgentIdent(Enum):
LINUX64 = 6 LINUX64 = 6
LINUX_ARM_64 = 26 LINUX_ARM_64 = 26
LINUX_ARM_HF = 25 LINUX_ARM_HF = 25
DARWIN_UNIVERSAL = 10005
def __str__(self): def __str__(self):
return str(self.value) return str(self.value)

View File

@@ -14,24 +14,26 @@ EXE_DIR = os.path.join(BASE_DIR, "tacticalrmm/private/exe")
LINUX_AGENT_SCRIPT = BASE_DIR / "core" / "agent_linux.sh" LINUX_AGENT_SCRIPT = BASE_DIR / "core" / "agent_linux.sh"
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.14.8" TRMM_VERSION = "0.15.0"
# https://github.com/amidaware/tacticalrmm-web # https://github.com/amidaware/tacticalrmm-web
WEB_VERSION = "0.100.9" WEB_VERSION = "0.101.0"
# 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.170" APP_VER = "0.0.171"
# https://github.com/amidaware/rmmagent # https://github.com/amidaware/rmmagent
LATEST_AGENT_VER = "2.3.1" LATEST_AGENT_VER = "2.4.0"
MESH_VER = "1.0.85" MESH_VER = "1.0.85"
NATS_SERVER_VER = "2.9.0" NATS_SERVER_VER = "2.9.1"
# for the update script, bump when need to recreate venv # for the update script, bump when need to recreate venv
PIP_VER = "32" PIP_VER = "32"

View File

@@ -1,4 +1,4 @@
FROM nats:2.9.0-alpine FROM nats:2.9.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