Release 0.15.0
This commit is contained in:
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1,9 +1,9 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: wh1te909
|
||||
github: amidaware
|
||||
patreon: # Replace with a single Patreon 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
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
|
||||
@@ -35,6 +35,9 @@ Demo database resets every hour. A lot of features are disabled for obvious reas
|
||||
## 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!
|
||||
|
||||
## Mac agent versions supported
|
||||
- 64 bit Intel and Apple Silicon (M1, M2)
|
||||
|
||||
## Installation / Backup / Restore / Usage
|
||||
|
||||
### Refer to the [documentation](https://docs.tacticalrmm.com)
|
||||
|
||||
@@ -2,6 +2,10 @@ SECRET_KEY = "{{ django_secret }}"
|
||||
DEBUG = True
|
||||
ALLOWED_HOSTS = ['{{ api }}']
|
||||
ADMIN_URL = "admin/"
|
||||
CORS_ORIGIN_WHITELIST = [
|
||||
"http://{{ rmm }}:8080",
|
||||
"https://{{ rmm }}:8080",
|
||||
]
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
DATABASES = {
|
||||
'default': {
|
||||
|
||||
@@ -835,9 +835,12 @@ class Agent(BaseAuditModel):
|
||||
Return type: tuple(message: str, error: bool)
|
||||
"""
|
||||
if mode == "tacagent":
|
||||
if self.is_posix:
|
||||
if self.plat == AgentPlat.LINUX:
|
||||
cmd = "systemctl restart tacticalagent.service"
|
||||
shell = 3
|
||||
elif self.plat == AgentPlat.DARWIN:
|
||||
cmd = "launchctl kickstart -k system/tacticalagent"
|
||||
shell = 3
|
||||
else:
|
||||
cmd = "net stop tacticalrmm & taskkill /F /IM tacticalrmm.exe & net start tacticalrmm"
|
||||
shell = 1
|
||||
|
||||
@@ -30,9 +30,9 @@ from scripts.models import Script
|
||||
from scripts.tasks import handle_bulk_command_task, handle_bulk_script_task
|
||||
from tacticalrmm.constants import (
|
||||
AGENT_DEFER,
|
||||
AGENT_TABLE_DEFER,
|
||||
AGENT_STATUS_OFFLINE,
|
||||
AGENT_STATUS_ONLINE,
|
||||
AGENT_TABLE_DEFER,
|
||||
AgentHistoryType,
|
||||
AgentMonType,
|
||||
AgentPlat,
|
||||
@@ -174,6 +174,7 @@ class GetUpdateDeleteAgent(APIView):
|
||||
fields = [
|
||||
"maintenance_mode", # TODO separate this
|
||||
"policy", # TODO separate this
|
||||
"block_policy_inheritance", # TODO separate this
|
||||
"monitoring_type",
|
||||
"description",
|
||||
"overdue_email_alert",
|
||||
@@ -236,10 +237,13 @@ class GetUpdateDeleteAgent(APIView):
|
||||
def delete(self, request, agent_id):
|
||||
agent = get_object_or_404(Agent, agent_id=agent_id)
|
||||
|
||||
code = "foo"
|
||||
code = "foo" # stub for windows
|
||||
if agent.plat == AgentPlat.LINUX:
|
||||
with open(settings.LINUX_AGENT_SCRIPT, "r") as f:
|
||||
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))
|
||||
name = agent.hostname
|
||||
@@ -550,7 +554,15 @@ def install_agent(request):
|
||||
|
||||
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)
|
||||
|
||||
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"])
|
||||
)
|
||||
|
||||
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":
|
||||
from tacticalrmm.utils import generate_winagent_exe
|
||||
|
||||
@@ -576,14 +603,6 @@ def install_agent(request):
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
@@ -597,43 +616,39 @@ def install_agent(request):
|
||||
download_url=download_url,
|
||||
)
|
||||
|
||||
elif request.data["installMethod"] == "manual":
|
||||
cmd = [
|
||||
inno,
|
||||
"/VERYSILENT",
|
||||
"/SUPPRESSMSGBOXES",
|
||||
"&&",
|
||||
"ping",
|
||||
"127.0.0.1",
|
||||
"-n",
|
||||
"5",
|
||||
"&&",
|
||||
r'"C:\Program Files\TacticalAgent\tacticalrmm.exe"',
|
||||
"-m",
|
||||
"install",
|
||||
"--api",
|
||||
request.data["api"],
|
||||
"--client-id",
|
||||
client_id,
|
||||
"--site-id",
|
||||
site_id,
|
||||
"--agent-type",
|
||||
request.data["agenttype"],
|
||||
"--auth",
|
||||
token,
|
||||
]
|
||||
elif request.data["installMethod"] in {"manual", "mac"}:
|
||||
resp = {}
|
||||
if request.data["installMethod"] == "manual":
|
||||
cmd = [
|
||||
inno,
|
||||
"/VERYSILENT",
|
||||
"/SUPPRESSMSGBOXES",
|
||||
"&&",
|
||||
"ping",
|
||||
"127.0.0.1",
|
||||
"-n",
|
||||
"5",
|
||||
"&&",
|
||||
r'"C:\Program Files\TacticalAgent\tacticalrmm.exe"',
|
||||
] + install_flags
|
||||
|
||||
if int(request.data["rdp"]):
|
||||
cmd.append("--rdp")
|
||||
if int(request.data["ping"]):
|
||||
cmd.append("--ping")
|
||||
if int(request.data["power"]):
|
||||
cmd.append("--power")
|
||||
if int(request.data["rdp"]):
|
||||
cmd.append("--rdp")
|
||||
if int(request.data["ping"]):
|
||||
cmd.append("--ping")
|
||||
if int(request.data["power"]):
|
||||
cmd.append("--power")
|
||||
|
||||
resp = {
|
||||
"cmd": " ".join(str(i) for i in cmd),
|
||||
"url": download_url,
|
||||
}
|
||||
resp["cmd"] = " ".join(str(i) for i in cmd)
|
||||
else:
|
||||
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)
|
||||
|
||||
@@ -912,6 +927,8 @@ def bulk(request):
|
||||
q = q.filter(plat=AgentPlat.WINDOWS)
|
||||
elif request.data["osType"] == 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]
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ from core.utils import (
|
||||
get_core_settings,
|
||||
get_mesh_device_id,
|
||||
get_mesh_ws_url,
|
||||
get_meshagent_url,
|
||||
)
|
||||
from logs.models import DebugLog, PendingAction
|
||||
from software.models import InstalledSoftware
|
||||
@@ -398,26 +399,33 @@ class MeshExe(APIView):
|
||||
def post(self, request):
|
||||
match request.data:
|
||||
case {"goarch": GoArch.AMD64, "plat": AgentPlat.WINDOWS}:
|
||||
arch = MeshAgentIdent.WIN64
|
||||
ident = MeshAgentIdent.WIN64
|
||||
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 _:
|
||||
return notify_error("Arch not specified")
|
||||
return notify_error("Arch not supported")
|
||||
|
||||
core = get_core_settings()
|
||||
|
||||
try:
|
||||
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:
|
||||
return notify_error("Unable to connect to mesh to get group id information")
|
||||
|
||||
if settings.DOCKER_BUILD:
|
||||
dl_url = f"{settings.MESH_WS_URL.replace('ws://', 'http://')}/meshagents?id={arch}&meshid={mesh_id}&installflags=0"
|
||||
else:
|
||||
dl_url = (
|
||||
f"{core.mesh_site}/meshagents?id={arch}&meshid={mesh_id}&installflags=0"
|
||||
)
|
||||
dl_url = get_meshagent_url(
|
||||
ident=ident,
|
||||
plat=request.data["plat"],
|
||||
mesh_site=core.mesh_site,
|
||||
mesh_device_id=mesh_device_id,
|
||||
)
|
||||
|
||||
try:
|
||||
return download_mesh_agent(dl_url)
|
||||
|
||||
8
api/tacticalrmm/core/mac_uninstall.sh
Executable file
8
api/tacticalrmm/core/mac_uninstall.sh
Executable 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
|
||||
@@ -5,13 +5,20 @@ from channels.db import database_sync_to_async
|
||||
from channels.testing import WebsocketCommunicator
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.test import override_settings
|
||||
from model_bakery import baker
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
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 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 .consumers import DashInfo
|
||||
@@ -444,3 +451,58 @@ class TestCorePermissions(TacticalTestCase):
|
||||
def setUp(self):
|
||||
self.setup_client()
|
||||
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",
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import json
|
||||
import subprocess
|
||||
import tempfile
|
||||
import urllib.parse
|
||||
from base64 import b64encode
|
||||
from typing import TYPE_CHECKING, Optional, cast
|
||||
|
||||
@@ -11,7 +12,12 @@ from django.core.cache import cache
|
||||
from django.http import FileResponse
|
||||
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:
|
||||
from core.models import CodeSignToken, CoreSettings
|
||||
@@ -142,3 +148,28 @@ def sysd_svc_is_running(svc: str) -> bool:
|
||||
cmd = ["systemctl", "is-active", "--quiet", svc]
|
||||
r = subprocess.run(cmd, capture_output=True)
|
||||
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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
asgiref==3.5.2
|
||||
celery==5.2.7
|
||||
certifi==2022.6.15.1
|
||||
certifi==2022.9.14
|
||||
cffi==1.15.1
|
||||
channels==3.0.5
|
||||
channels_redis==3.4.1
|
||||
@@ -11,7 +11,8 @@ Django==4.1.1
|
||||
django-cors-headers==3.13.0
|
||||
django-ipware==4.0.2
|
||||
django-rest-knox==4.2.0
|
||||
djangorestframework==3.13.1
|
||||
djangorestframework==3.14.0
|
||||
drf-spectacular==0.24.1
|
||||
future==0.18.2
|
||||
msgpack==1.0.4
|
||||
nats-py==2.1.7
|
||||
@@ -28,12 +29,11 @@ hiredis==2.0.0
|
||||
requests==2.28.1
|
||||
six==1.16.0
|
||||
sqlparse==0.4.2
|
||||
twilio==7.14.0
|
||||
twilio==7.14.1
|
||||
urllib3==1.26.12
|
||||
uWSGI==2.0.20
|
||||
validators==0.20.0
|
||||
vine==5.0.0
|
||||
websockets==10.3
|
||||
zipp==3.8.1
|
||||
drf-spectacular==0.23.1
|
||||
meshctrl==0.1.15
|
||||
|
||||
@@ -10,6 +10,7 @@ class MeshAgentIdent(Enum):
|
||||
LINUX64 = 6
|
||||
LINUX_ARM_64 = 26
|
||||
LINUX_ARM_HF = 25
|
||||
DARWIN_UNIVERSAL = 10005
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
@@ -14,24 +14,26 @@ EXE_DIR = os.path.join(BASE_DIR, "tacticalrmm/private/exe")
|
||||
|
||||
LINUX_AGENT_SCRIPT = BASE_DIR / "core" / "agent_linux.sh"
|
||||
|
||||
MAC_UNINSTALL = BASE_DIR / "core" / "mac_uninstall.sh"
|
||||
|
||||
AUTH_USER_MODEL = "accounts.User"
|
||||
|
||||
# latest release
|
||||
TRMM_VERSION = "0.14.8"
|
||||
TRMM_VERSION = "0.15.0"
|
||||
|
||||
# https://github.com/amidaware/tacticalrmm-web
|
||||
WEB_VERSION = "0.100.9"
|
||||
WEB_VERSION = "0.101.0"
|
||||
|
||||
# bump this version everytime vue code is changed
|
||||
# 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
|
||||
LATEST_AGENT_VER = "2.3.1"
|
||||
LATEST_AGENT_VER = "2.4.0"
|
||||
|
||||
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
|
||||
PIP_VER = "32"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM nats:2.9.0-alpine
|
||||
FROM nats:2.9.1-alpine
|
||||
|
||||
ENV TACTICAL_DIR /opt/tactical
|
||||
ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready
|
||||
|
||||
Reference in New Issue
Block a user