diff --git a/README.md b/README.md index f0e19970fc..84cc354012 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Demo database resets every hour. A lot of features are disabled for obvious reas - Teamviewer-like remote desktop control - Real-time remote shell - Remote file browser (download and upload files) -- Remote command and script execution (batch, powershell and python scripts) +- Remote command and script execution (batch, powershell, python, nushell and deno scripts) - Event log viewer - Services management - Windows patch management diff --git a/api/tacticalrmm/agents/models.py b/api/tacticalrmm/agents/models.py index 21ddfc0f2e..5c51b6414d 100644 --- a/api/tacticalrmm/agents/models.py +++ b/api/tacticalrmm/agents/models.py @@ -570,6 +570,8 @@ def run_script( }, "run_as_user": run_as_user, "env_vars": parsed_env_vars, + "nushell_enable_config": settings.NUSHELL_ENABLE_CONFIG, + "deno_default_permissions": settings.DENO_DEFAULT_PERMISSIONS, } if history_pk != 0: diff --git a/api/tacticalrmm/alerts/tests.py b/api/tacticalrmm/alerts/tests.py index 4680050279..c287ffd05f 100644 --- a/api/tacticalrmm/alerts/tests.py +++ b/api/tacticalrmm/alerts/tests.py @@ -1429,6 +1429,8 @@ def test_alert_actions( "run_as_user": False, "env_vars": ["hello=world", "foo=bar"], "id": AgentHistory.objects.last().pk, # type: ignore + "nushell_enable_config": settings.NUSHELL_ENABLE_CONFIG, + "deno_default_permissions": settings.DENO_DEFAULT_PERMISSIONS, } nats_cmd.assert_called_with(data, timeout=30, wait=True) @@ -1460,6 +1462,8 @@ def test_alert_actions( "run_as_user": False, "env_vars": ["resolved=action", "env=vars"], "id": AgentHistory.objects.last().pk, # type: ignore + "nushell_enable_config": settings.NUSHELL_ENABLE_CONFIG, + "deno_default_permissions": settings.DENO_DEFAULT_PERMISSIONS, } nats_cmd.assert_called_with(data, timeout=35, wait=True) diff --git a/api/tacticalrmm/apiv3/utils.py b/api/tacticalrmm/apiv3/utils.py index 4464f633d8..8b8404ec42 100644 --- a/api/tacticalrmm/apiv3/utils.py +++ b/api/tacticalrmm/apiv3/utils.py @@ -22,4 +22,12 @@ def get_agent_config() -> AgentCheckInConfig: *getattr(settings, "CHECKIN_SYNCMESH", (800, 1200)) ), limit_data=getattr(settings, "LIMIT_DATA", False), + install_nushell=getattr(settings, "INSTALL_NUSHELL", False), + install_nushell_version=getattr(settings, "INSTALL_NUSHELL_VERSION", ""), + install_nushell_url=getattr(settings, "INSTALL_NUSHELL_URL", ""), + nushell_enable_config=getattr(settings, "NUSHELL_ENABLE_CONFIG", False), + install_deno=getattr(settings, "INSTALL_DENO", False), + install_deno_version=getattr(settings, "INSTALL_DENO_VERSION", ""), + install_deno_url=getattr(settings, "INSTALL_DENO_URL", ""), + deno_default_permissions=getattr(settings, "DENO_DEFAULT_PERMISSIONS", ""), ) diff --git a/api/tacticalrmm/autotasks/serializers.py b/api/tacticalrmm/autotasks/serializers.py index c9444c1677..fcf73d8277 100644 --- a/api/tacticalrmm/autotasks/serializers.py +++ b/api/tacticalrmm/autotasks/serializers.py @@ -2,6 +2,7 @@ from django.utils import timezone as djangotime from rest_framework import serializers +from django.conf import settings from scripts.models import Script from tacticalrmm.constants import TaskType @@ -257,6 +258,8 @@ def get_task_actions(self, obj): shell=script.shell, env_vars=env_vars, ), + "nushell_enable_config": settings.NUSHELL_ENABLE_CONFIG, + "deno_default_permissions": settings.DENO_DEFAULT_PERMISSIONS, } ) if actions_to_remove: diff --git a/api/tacticalrmm/core/agent_linux.sh b/api/tacticalrmm/core/agent_linux.sh index 871b414e1e..d17c2785b4 100755 --- a/api/tacticalrmm/core/agent_linux.sh +++ b/api/tacticalrmm/core/agent_linux.sh @@ -41,6 +41,7 @@ agentBin="${agentBinPath}/${binName}" agentConf='/etc/tacticalagent' agentSvcName='tacticalagent.service' agentSysD="/etc/systemd/system/${agentSvcName}" +agentDir='/opt/tacticalagent' meshDir='/opt/tacticalmesh' meshSystemBin="${meshDir}/meshagent" meshSvcName='meshagent.service' @@ -65,16 +66,20 @@ RemoveOldAgent() { if [ -f "${agentSysD}" ]; then systemctl disable ${agentSvcName} systemctl stop ${agentSvcName} - rm -f ${agentSysD} + rm -f "${agentSysD}" systemctl daemon-reload fi if [ -f "${agentConf}" ]; then - rm -f ${agentConf} + rm -f "${agentConf}" fi if [ -f "${agentBin}" ]; then - rm -f ${agentBin} + rm -f "${agentBin}" + fi + + if [ -d "${agentDir}" ]; then + rm -rf "${agentDir}" fi } @@ -134,6 +139,8 @@ Uninstall() { if [ $# -ne 0 ] && [ $1 == 'uninstall' ]; then Uninstall + # Remove the current script + rm "$0" exit 0 fi diff --git a/api/tacticalrmm/scripts/tasks.py b/api/tacticalrmm/scripts/tasks.py index 6181c91e91..81ff379ec7 100644 --- a/api/tacticalrmm/scripts/tasks.py +++ b/api/tacticalrmm/scripts/tasks.py @@ -1,5 +1,7 @@ import asyncio +from django.conf import settings + from agents.models import Agent, AgentHistory from scripts.models import Script from tacticalrmm.celery import app @@ -78,6 +80,8 @@ def bulk_script_task( }, "run_as_user": run_as_user, "env_vars": script.parse_script_env_vars(agent, script.shell, env_vars), + "nushell_enable_config": settings.NUSHELL_ENABLE_CONFIG, + "deno_default_permissions": settings.DENO_DEFAULT_PERMISSIONS, } tup = (agent.agent_id, data) items.append(tup) diff --git a/api/tacticalrmm/scripts/views.py b/api/tacticalrmm/scripts/views.py index ddd48a0fbf..0720c8e73a 100644 --- a/api/tacticalrmm/scripts/views.py +++ b/api/tacticalrmm/scripts/views.py @@ -5,6 +5,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView +from django.conf import settings from agents.permissions import RunScriptPerms from tacticalrmm.constants import ScriptShell, ScriptType @@ -162,6 +163,8 @@ def post(self, request, agent_id): }, "run_as_user": request.data["run_as_user"], "env_vars": parsed_env_vars, + "nushell_enable_config": settings.NUSHELL_ENABLE_CONFIG, + "deno_default_permissions": settings.DENO_DEFAULT_PERMISSIONS, } r = asyncio.run( @@ -190,6 +193,10 @@ def download(request, pk): ext = ".py" case ScriptShell.SHELL: ext = ".sh" + case ScriptShell.NUSHELL: + ext = ".nu" + case ScriptShell.DENO: + ext = ".ts" case _: ext = "" diff --git a/api/tacticalrmm/tacticalrmm/constants.py b/api/tacticalrmm/tacticalrmm/constants.py index 1ef41346e5..751b129846 100644 --- a/api/tacticalrmm/tacticalrmm/constants.py +++ b/api/tacticalrmm/tacticalrmm/constants.py @@ -132,6 +132,8 @@ class ScriptShell(models.TextChoices): CMD = "cmd", "Batch (CMD)" PYTHON = "python", "Python" SHELL = "shell", "Shell" + NUSHELL = "nushell", "Nushell" + DENO = "deno", "Deno" class ScriptType(models.TextChoices): diff --git a/api/tacticalrmm/tacticalrmm/settings.py b/api/tacticalrmm/tacticalrmm/settings.py index 26b4fa0bfa..e5704d30c2 100644 --- a/api/tacticalrmm/tacticalrmm/settings.py +++ b/api/tacticalrmm/tacticalrmm/settings.py @@ -36,6 +36,49 @@ NATS_SERVER_VER = "2.10.5" +# Install Nushell on the agent +# https://github.com/nushell/nushell +INSTALL_NUSHELL = False +# GitHub version to download. The file will be downloaded from GitHub, extracted and installed. +# Version to download. If INSTALL_NUSHELL_URL is not provided, the file will be downloaded from GitHub, +# extracted and installed. +INSTALL_NUSHELL_VERSION = "" +# URL to download directly. This is expected to be the direct URL, unauthenticated, uncompressed, ready to be installed. +# Use {OS}, {ARCH} and {VERSION} to specify the GOOS, GOARCH and INSTALL_NUSHELL_VERSION respectively. +# Windows: The ".exe" extension will be added automatically. +# Examples: +# https://examplle.com/download/nushell/{OS}/{ARCH}/{VERSION}/nu +# https://examplle.com/download/nushell/nu-{VERSION}-{OS}-{ARCH} +INSTALL_NUSHELL_URL = "" +# Enable Nushell config on the agent +# The default is to not enable the config because it could change how scripts run. +# However, disabling the config prevents plugins from being registered. +# https://github.com/nushell/nushell/issues/10754 +# False: --no-config-file option is added to the command line. +# True: --config and --env-config options are added to the command line and point to the Agent's directory. +NUSHELL_ENABLE_CONFIG = False + +# Install Deno on the agent +# https://github.com/denoland/deno +INSTALL_DENO = False +# Version to download. If INSTALL_DENO_URL is not provided, the file will be downloaded from GitHub, +# extracted and installed. +INSTALL_DENO_VERSION = "" +# URL to download directly. This is expected to be the direct URL, unauthenticated, uncompressed, ready to be installed. +# Use {OS}, {ARCH} and {VERSION} to specify the GOOS, GOARCH and INSTALL_DENO_VERSION respectively. +# Windows: The ".exe" extension will be added automatically. +# Examples: +# https://examplle.com/download/deno/{OS}/{ARCH}/{VERSION}/deno +# https://examplle.com/download/deno/deno-{VERSION}-{OS}-{ARCH} +INSTALL_DENO_URL = "" +# Default permissions for Deno +# Space separated list of permissions as listed in the documentation. +# https://docs.deno.com/runtime/manual/basics/permissions#permissions +# Examples: +# DENO_DEFAULT_PERMISSIONS = "--allow-sys --allow-net --allow-env" +# DENO_DEFAULT_PERMISSIONS = "--allow-all" +DENO_DEFAULT_PERMISSIONS = "" + # for the update script, bump when need to recreate venv PIP_VER = "40" diff --git a/api/tacticalrmm/tacticalrmm/structs.py b/api/tacticalrmm/tacticalrmm/structs.py index 481f180317..ddd7c08695 100644 --- a/api/tacticalrmm/tacticalrmm/structs.py +++ b/api/tacticalrmm/tacticalrmm/structs.py @@ -18,3 +18,11 @@ class AgentCheckInConfig(TRMMStruct): checkin_wmi: int checkin_syncmesh: int limit_data: bool + install_nushell: bool + install_nushell_version: str + install_nushell_url: str + nushell_enable_config: bool + install_deno: bool + install_deno_version: str + install_deno_url: str + deno_default_permissions: str