Compare commits
129 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00c5f1365a | ||
|
|
f7d317328a | ||
|
|
3ccd705225 | ||
|
|
9e439fffaa | ||
|
|
859dc170e7 | ||
|
|
1932d8fad9 | ||
|
|
0c814ae436 | ||
|
|
89313d8a37 | ||
|
|
2b85722222 | ||
|
|
57e5b0188c | ||
|
|
2d7c830e70 | ||
|
|
ccaa1790a9 | ||
|
|
f6531d905e | ||
|
|
64a31879d3 | ||
|
|
0c6a4b1ed2 | ||
|
|
67801f39fe | ||
|
|
892a0d67bf | ||
|
|
9fc0b7d5cc | ||
|
|
22a614ef54 | ||
|
|
cd257b8e4d | ||
|
|
fa1ee2ca14 | ||
|
|
34ea1adde6 | ||
|
|
41cf8abb1f | ||
|
|
c0ffec1a4c | ||
|
|
65779b8eaf | ||
|
|
c47bdb2d56 | ||
|
|
d47ae642e7 | ||
|
|
39c4609cc6 | ||
|
|
3ebba02a10 | ||
|
|
4dc7a96e79 | ||
|
|
5a49a29110 | ||
|
|
1e2a56c5e9 | ||
|
|
8011773af4 | ||
|
|
ddc69c692e | ||
|
|
df925c9744 | ||
|
|
1726341aad | ||
|
|
63b1ccc7a7 | ||
|
|
e80397c857 | ||
|
|
81aa7ca1a4 | ||
|
|
f0f7695890 | ||
|
|
e7e8ce2f7a | ||
|
|
ba37a3f18d | ||
|
|
60b11a7a5d | ||
|
|
29461c20a7 | ||
|
|
2ff1f34543 | ||
|
|
b75d7f970f | ||
|
|
204681f097 | ||
|
|
e239fe95a4 | ||
|
|
0a101f061a | ||
|
|
f112a17afa | ||
|
|
54658a66d2 | ||
|
|
6b8f5a76e4 | ||
|
|
623a5d338d | ||
|
|
9c5565cfd5 | ||
|
|
722f2efaee | ||
|
|
4928264204 | ||
|
|
12d62ddc2a | ||
|
|
da54e97217 | ||
|
|
9c0993dac8 | ||
|
|
175486b7c4 | ||
|
|
4760a287f6 | ||
|
|
0237b48c87 | ||
|
|
95c9f22e6c | ||
|
|
9b001219d5 | ||
|
|
6ff15efc7b | ||
|
|
6fe1dccc7e | ||
|
|
1c80f6f3fa | ||
|
|
54d3177fdd | ||
|
|
a24ad245d2 | ||
|
|
f38cfdcadf | ||
|
|
92e4ad8ccd | ||
|
|
3f3ab088d2 | ||
|
|
2c2cbaa175 | ||
|
|
911b6bf863 | ||
|
|
31462cab64 | ||
|
|
1ee35da62d | ||
|
|
edf4815595 | ||
|
|
06ccee5d18 | ||
|
|
d5ad85725f | ||
|
|
4d5bddb413 | ||
|
|
2f4da7c381 | ||
|
|
8b845fce03 | ||
|
|
9fd15c38a9 | ||
|
|
ec1573d01f | ||
|
|
92ec1cc9e7 | ||
|
|
8b2f9665ce | ||
|
|
cb388a5a78 | ||
|
|
7f4389ae08 | ||
|
|
76d71beaa2 | ||
|
|
31bb9c2197 | ||
|
|
6a2cd5c45a | ||
|
|
520632514b | ||
|
|
f998b28d0b | ||
|
|
1a6587e9e6 | ||
|
|
9b4b729d19 | ||
|
|
e80345295e | ||
|
|
026c259a2e | ||
|
|
63474c2269 | ||
|
|
faa1a9312f | ||
|
|
23fa0726d5 | ||
|
|
22210eaf7d | ||
|
|
dcd8bee676 | ||
|
|
06f0fa8f0e | ||
|
|
6d0f9e2cd5 | ||
|
|
732afdb65d | ||
|
|
1a9e8742f7 | ||
|
|
b8eda37339 | ||
|
|
5107db6169 | ||
|
|
2c8f207454 | ||
|
|
489bc9c3b3 | ||
|
|
514713e883 | ||
|
|
17cc0cd09c | ||
|
|
4475df1295 | ||
|
|
fdad267cfd | ||
|
|
3684fc80f0 | ||
|
|
e97a5fef94 | ||
|
|
de2972631f | ||
|
|
e5b8fd67c8 | ||
|
|
5fade89e2d | ||
|
|
2eefedadb3 | ||
|
|
e63d7a0b8a | ||
|
|
2a1b1849fa | ||
|
|
0461cb7f19 | ||
|
|
0932e0be03 | ||
|
|
4638ac9474 | ||
|
|
d8d7255029 | ||
|
|
fa05276c3f | ||
|
|
e50a5d51d8 | ||
|
|
c03ba78587 |
@@ -35,3 +35,4 @@ Pygments
|
||||
mypy
|
||||
pysnooper
|
||||
isort
|
||||
drf_spectacular
|
||||
|
||||
70
.github/workflows/codeql-analysis.yml
vendored
Normal file
70
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ develop ]
|
||||
schedule:
|
||||
- cron: '19 14 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go', 'javascript', 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
34
.github/workflows/devskim-analysis.yml
vendored
Normal file
34
.github/workflows/devskim-analysis.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
name: DevSkim
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
pull_request:
|
||||
branches: [ develop ]
|
||||
schedule:
|
||||
- cron: '19 5 * * 0'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: DevSkim
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Run DevSkim scanner
|
||||
uses: microsoft/DevSkim-Action@v1
|
||||
|
||||
- name: Upload DevSkim scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v1
|
||||
with:
|
||||
sarif_file: devskim-results.sarif
|
||||
19
SECURITY.md
Normal file
19
SECURITY.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Use this section to tell people about which versions of your project are
|
||||
currently being supported with security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 0.10.4 | :white_check_mark: |
|
||||
| < 0.10.4| :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Use this section to tell people how to report a vulnerability.
|
||||
|
||||
Tell them where to go, how often they can expect to get an update on a
|
||||
reported vulnerability, what to expect if the vulnerability is accepted or
|
||||
declined, etc.
|
||||
@@ -0,0 +1,81 @@
|
||||
import asyncio
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone as djangotime
|
||||
from packaging import version as pyver
|
||||
|
||||
from agents.models import Agent
|
||||
from tacticalrmm.utils import AGENT_DEFER, reload_nats
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Delete old agents"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--days",
|
||||
type=int,
|
||||
help="Delete agents that have not checked in for this many days",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--agentver",
|
||||
type=str,
|
||||
help="Delete agents that equal to or less than this version",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--delete",
|
||||
action="store_true",
|
||||
help="This will delete agents",
|
||||
)
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
days = kwargs["days"]
|
||||
agentver = kwargs["agentver"]
|
||||
delete = kwargs["delete"]
|
||||
|
||||
if not days and not agentver:
|
||||
self.stdout.write(
|
||||
self.style.ERROR("Must have at least one parameter: days or agentver")
|
||||
)
|
||||
return
|
||||
|
||||
q = Agent.objects.defer(*AGENT_DEFER)
|
||||
|
||||
agents = []
|
||||
if days:
|
||||
overdue = djangotime.now() - djangotime.timedelta(days=days)
|
||||
agents = [i for i in q if i.last_seen < overdue]
|
||||
|
||||
if agentver:
|
||||
agents = [i for i in q if pyver.parse(i.version) <= pyver.parse(agentver)]
|
||||
|
||||
if not agents:
|
||||
self.stdout.write(self.style.ERROR("No agents matched"))
|
||||
return
|
||||
|
||||
deleted_count = 0
|
||||
for agent in agents:
|
||||
s = f"{agent.hostname} | Version {agent.version} | Last Seen {agent.last_seen} | {agent.client} > {agent.site}"
|
||||
if delete:
|
||||
s = "Deleting " + s
|
||||
self.stdout.write(self.style.SUCCESS(s))
|
||||
asyncio.run(agent.nats_cmd({"func": "uninstall"}, wait=False))
|
||||
try:
|
||||
agent.delete()
|
||||
except Exception as e:
|
||||
err = f"Failed to delete agent {agent.hostname}: {str(e)}"
|
||||
self.stdout.write(self.style.ERROR(err))
|
||||
else:
|
||||
deleted_count += 1
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING(s))
|
||||
|
||||
if delete:
|
||||
reload_nats()
|
||||
self.stdout.write(self.style.SUCCESS(f"Deleted {deleted_count} agents"))
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
"The above agents would be deleted. Run again with --delete to actually delete them."
|
||||
)
|
||||
)
|
||||
@@ -3,6 +3,7 @@ from django.core.management.base import BaseCommand
|
||||
from packaging import version as pyver
|
||||
|
||||
from agents.models import Agent
|
||||
from core.models import CoreSettings
|
||||
from agents.tasks import send_agent_update_task
|
||||
from tacticalrmm.utils import AGENT_DEFER
|
||||
|
||||
@@ -11,6 +12,10 @@ class Command(BaseCommand):
|
||||
help = "Triggers an agent update task to run"
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
core = CoreSettings.objects.first()
|
||||
if not core.agent_auto_update: # type: ignore
|
||||
return
|
||||
|
||||
q = Agent.objects.defer(*AGENT_DEFER).exclude(version=settings.LATEST_AGENT_VER)
|
||||
agent_ids: list[str] = [
|
||||
i.agent_id
|
||||
|
||||
@@ -98,7 +98,7 @@ class Agent(BaseAuditModel):
|
||||
|
||||
# check if new agent has been created
|
||||
# or check if policy have changed on agent
|
||||
# or if site has changed on agent and if so generate-policies
|
||||
# or if site has changed on agent and if so generate policies
|
||||
# or if agent was changed from server or workstation
|
||||
if (
|
||||
not old_agent
|
||||
@@ -109,10 +109,6 @@ class Agent(BaseAuditModel):
|
||||
):
|
||||
generate_agent_checks_task.delay(agents=[self.pk], create_tasks=True)
|
||||
|
||||
# calculate alert template for new agents
|
||||
if not old_agent:
|
||||
self.set_alert_template()
|
||||
|
||||
def __str__(self):
|
||||
return self.hostname
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from tacticalrmm.celery import app
|
||||
|
||||
from agents.models import Agent
|
||||
from agents.utils import get_winagent_url
|
||||
from tacticalrmm.utils import AGENT_DEFER
|
||||
|
||||
|
||||
def agent_update(agent_id: str, force: bool = False) -> str:
|
||||
@@ -311,9 +312,7 @@ def prune_agent_history(older_than_days: int) -> str:
|
||||
|
||||
@app.task
|
||||
def handle_agents_task() -> None:
|
||||
q = Agent.objects.prefetch_related("pendingactions", "autotasks").only(
|
||||
"pk", "agent_id", "version", "last_seen", "overdue_time", "offline_time"
|
||||
)
|
||||
q = Agent.objects.defer(*AGENT_DEFER)
|
||||
agents = [
|
||||
i
|
||||
for i in q
|
||||
|
||||
@@ -456,7 +456,8 @@ class Alert(models.Model):
|
||||
if match:
|
||||
name = match.group(1)
|
||||
|
||||
if hasattr(self, name):
|
||||
# check if attr exists and isn't a function
|
||||
if hasattr(self, name) and not callable(getattr(self, name)):
|
||||
value = f"'{getattr(self, name)}'"
|
||||
else:
|
||||
continue
|
||||
|
||||
@@ -121,18 +121,18 @@ class WinUpdates(APIView):
|
||||
|
||||
def put(self, request):
|
||||
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
|
||||
|
||||
needs_reboot: bool = request.data["needs_reboot"]
|
||||
agent.needs_reboot = needs_reboot
|
||||
agent.save(update_fields=["needs_reboot"])
|
||||
|
||||
reboot_policy: str = agent.get_patch_policy().reboot_after_install
|
||||
reboot = False
|
||||
|
||||
if reboot_policy == "always":
|
||||
reboot = True
|
||||
|
||||
if request.data["needs_reboot"]:
|
||||
if reboot_policy == "required":
|
||||
reboot = True
|
||||
elif reboot_policy == "never":
|
||||
agent.needs_reboot = True
|
||||
agent.save(update_fields=["needs_reboot"])
|
||||
elif needs_reboot and reboot_policy == "required":
|
||||
reboot = True
|
||||
|
||||
if reboot:
|
||||
asyncio.run(agent.nats_cmd({"func": "rebootnow"}, wait=False))
|
||||
|
||||
@@ -54,6 +54,8 @@ def generate_agent_checks_task(
|
||||
if create_tasks:
|
||||
agent.generate_tasks_from_policies()
|
||||
|
||||
agent.set_alert_template()
|
||||
|
||||
return "ok"
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import base64
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from logs.models import PendingAction
|
||||
@@ -20,3 +21,15 @@ class Command(BaseCommand):
|
||||
for user in User.objects.filter(is_installer_user=True):
|
||||
user.block_dashboard_login = True
|
||||
user.save()
|
||||
|
||||
# convert script base64 field to text field
|
||||
user_scripts = Script.objects.exclude(script_type="builtin").filter(
|
||||
script_body=""
|
||||
)
|
||||
for script in user_scripts:
|
||||
# decode base64 string
|
||||
script.script_body = base64.b64decode(
|
||||
script.code_base64.encode("ascii", "ignore")
|
||||
).decode("ascii", "ignore")
|
||||
# script.hash_script_body() # also saves script
|
||||
script.save(update_fields=["script_body"])
|
||||
|
||||
@@ -9,6 +9,7 @@ from alerts.tasks import prune_resolved_alerts
|
||||
from core.models import CoreSettings
|
||||
from logs.tasks import prune_debug_log, prune_audit_log
|
||||
from tacticalrmm.celery import app
|
||||
from tacticalrmm.utils import AGENT_DEFER
|
||||
|
||||
|
||||
@app.task
|
||||
@@ -58,9 +59,7 @@ def core_maintenance_tasks():
|
||||
def cache_db_fields_task():
|
||||
from agents.models import Agent
|
||||
|
||||
for agent in Agent.objects.prefetch_related("winupdates", "pendingactions").only(
|
||||
"pending_actions_count", "has_patches_pending", "pk"
|
||||
):
|
||||
for agent in Agent.objects.defer(*AGENT_DEFER):
|
||||
agent.pending_actions_count = agent.pendingactions.filter(
|
||||
status="pending"
|
||||
).count()
|
||||
|
||||
@@ -98,7 +98,7 @@ def dashboard_info(request):
|
||||
"client_tree_splitter": request.user.client_tree_splitter,
|
||||
"loading_bar_color": request.user.loading_bar_color,
|
||||
"clear_search_when_switching": request.user.clear_search_when_switching,
|
||||
"hosted": hasattr(settings, "HOSTED") and settings.HOSTED,
|
||||
"hosted": getattr(settings, "HOSTED", False),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from tacticalrmm.utils import notify_error, get_default_timezone
|
||||
from tacticalrmm.utils import notify_error, get_default_timezone, AGENT_DEFER
|
||||
from tacticalrmm.permissions import _audit_log_filter, _has_perm_on_agent
|
||||
|
||||
from .models import AuditLog, PendingAction, DebugLog
|
||||
@@ -93,10 +93,16 @@ class PendingActions(APIView):
|
||||
|
||||
def get(self, request, agent_id=None):
|
||||
if agent_id:
|
||||
agent = get_object_or_404(Agent, agent_id=agent_id)
|
||||
agent = get_object_or_404(
|
||||
Agent.objects.defer(*AGENT_DEFER), agent_id=agent_id
|
||||
)
|
||||
actions = PendingAction.objects.filter(agent=agent)
|
||||
else:
|
||||
actions = PendingAction.objects.filter_by_role(request.user)
|
||||
actions = (
|
||||
PendingAction.objects.select_related("agent")
|
||||
.defer("agent__services", "agent__wmi_detail")
|
||||
.filter_by_role(request.user) # type: ignore
|
||||
)
|
||||
|
||||
return Response(PendingActionSerializer(actions, many=True).data)
|
||||
|
||||
|
||||
@@ -8,4 +8,3 @@ Pygments
|
||||
isort
|
||||
mypy
|
||||
types-pytz
|
||||
types-pytz
|
||||
@@ -1,34 +1,34 @@
|
||||
asgiref==3.4.1
|
||||
asyncio-nats-client==0.11.4
|
||||
asyncio-nats-client==0.11.5
|
||||
celery==5.2.1
|
||||
certifi==2021.10.8
|
||||
cffi==1.15.0
|
||||
channels==3.0.4
|
||||
channels_redis==3.3.1
|
||||
chardet==4.0.0
|
||||
cryptography==35.0.0
|
||||
cryptography==36.0.1
|
||||
daphne==3.0.2
|
||||
Django==3.2.9
|
||||
django-cors-headers==3.10.0
|
||||
django-ipware==4.0.0
|
||||
Django==3.2.10
|
||||
django-cors-headers==3.10.1
|
||||
django-ipware==4.0.2
|
||||
django-rest-knox==4.1.0
|
||||
djangorestframework==3.12.4
|
||||
djangorestframework==3.13.1
|
||||
future==0.18.2
|
||||
loguru==0.5.3
|
||||
msgpack==1.0.2
|
||||
msgpack==1.0.3
|
||||
packaging==21.3
|
||||
psycopg2-binary==2.9.2
|
||||
pycparser==2.21
|
||||
pycryptodome==3.11.0
|
||||
pycryptodome==3.12.0
|
||||
pyotp==2.6.0
|
||||
pyparsing==2.4.7
|
||||
pyparsing==3.0.6
|
||||
pytz==2021.3
|
||||
qrcode==6.1
|
||||
redis==3.5.3
|
||||
redis==4.0.2
|
||||
requests==2.26.0
|
||||
six==1.16.0
|
||||
sqlparse==0.4.2
|
||||
twilio==7.3.1
|
||||
twilio==7.4.0
|
||||
urllib3==1.26.7
|
||||
uWSGI==2.0.20
|
||||
validators==0.18.2
|
||||
|
||||
@@ -9,6 +9,16 @@
|
||||
"category": "TRMM (Win):Browsers",
|
||||
"default_timeout": "300"
|
||||
},
|
||||
{
|
||||
"guid": "720edbb7-8faf-4a77-9283-29935e8880d0",
|
||||
"filename": "Win_Printer_ClearandRestart.bat",
|
||||
"submittedBy": "https://github.com/wh1te909",
|
||||
"name": "Printers - Clear all print jobs",
|
||||
"description": "This script will stop the spooler, delete all pending print jobs and restart the spooler",
|
||||
"shell": "cmd",
|
||||
"category": "TRMM (Win):Printing",
|
||||
"default_timeout": "300"
|
||||
},
|
||||
{
|
||||
"guid": "3ff6a386-11d1-4f9d-8cca-1b0563bb6443",
|
||||
"filename": "Win_Google_Chrome_Clear_Cache.ps1",
|
||||
@@ -19,6 +29,16 @@
|
||||
"category": "TRMM (Win):Browsers",
|
||||
"default_timeout": "300"
|
||||
},
|
||||
{
|
||||
"guid": "d3c74105-d1e5-40d8-94ff-b4d6b216fe0f",
|
||||
"filename": "Win_Chocolatey_List_Installed.bat",
|
||||
"submittedBy": "https://github.com/silversword411",
|
||||
"name": "Chocolatey - List Installed apps",
|
||||
"description": "Lists apps locally installed by chocolatey",
|
||||
"shell": "cmd",
|
||||
"category": "TRMM (Win):3rd Party Software>Chocolatey",
|
||||
"default_timeout": "90"
|
||||
},
|
||||
{
|
||||
"guid": "be1de837-f677-4ac5-aa0c-37a0fc9991fc",
|
||||
"filename": "Win_Install_Adobe_Reader.ps1",
|
||||
@@ -48,6 +68,16 @@
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):3rd Party Software>Monitoring"
|
||||
},
|
||||
{
|
||||
"guid": "5a60c13b-1882-4a92-bdfb-6dd1f6a11dd14",
|
||||
"filename": "Win_Windows_Update_RevertToDefault.ps1",
|
||||
"submittedBy": "https://github.com/silversword411",
|
||||
"name": "Windows Update - Re-enable Microsoft managed Windows Update",
|
||||
"description": "TRMM agent will set registry key to disable Windows Auto Updates. This will re-enable Windows standard update settings",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Updates",
|
||||
"default_timeout": "90"
|
||||
},
|
||||
{
|
||||
"guid": "81cc5bcb-01bf-4b0c-89b9-0ac0f3fe0c04",
|
||||
"filename": "Win_Windows_Update_Reset.ps1",
|
||||
@@ -63,7 +93,7 @@
|
||||
"filename": "Win_Start_Cleanup.ps1",
|
||||
"submittedBy": "https://github.com/Omnicef",
|
||||
"name": "Disk - Cleanup C: drive",
|
||||
"description": "Cleans the C: drive's Window Temperary files, Windows SoftwareDistribution folder, the local users Temperary folder, IIS logs (if applicable) and empties the recycling bin. All deleted files will go into a log transcript in $env:TEMP. By default this script leaves files that are newer than 7 days old however this variable can be edited.",
|
||||
"description": "Cleans the C: drive's Window Temporary files, Windows SoftwareDistribution folder, the local users Temperary folder, IIS logs (if applicable) and empties the recycling bin. All deleted files will go into a log transcript in $env:TEMP. By default this script leaves files that are newer than 7 days old however this variable can be edited.",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Maintenance",
|
||||
"default_timeout": "25000"
|
||||
@@ -143,6 +173,15 @@
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Storage"
|
||||
},
|
||||
{
|
||||
"guid": "11be7136-0416-47b4-a6dd-9776fa857dca",
|
||||
"filename": "Win_Storage_CheckPools.ps1",
|
||||
"submittedBy": "https://github.com/wh1te909",
|
||||
"name": "Storage Pools - Check Health",
|
||||
"description": "Checks all storage pools for health, returns error 1 if unhealthy",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Monitoring"
|
||||
},
|
||||
{
|
||||
"guid": "cfa14c28-4dfc-4d4e-95ee-a380652e058d",
|
||||
"filename": "Win_Bios_Check.ps1",
|
||||
@@ -184,19 +223,31 @@
|
||||
"filename": "Win_Screenconnect_GetGUID.ps1",
|
||||
"submittedBy": "https://github.com/silversword411",
|
||||
"name": "Screenconnect - Get GUID for client",
|
||||
"description": "Returns Screenconnect GUID for client - Use with Custom Fields for later use. ",
|
||||
"description": "Returns Screenconnect GUID for client - Use with Custom Fields for later use.",
|
||||
"args": [
|
||||
"{{client.ScreenConnectService}}"
|
||||
],
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Collectors"
|
||||
},
|
||||
{
|
||||
"guid": "bbe5645f-c8d8-4d86-bddd-c8dbea45c974",
|
||||
"filename": "Win_Splashtop_Get_ID.ps1",
|
||||
"submittedBy": "https://github.com/r3die",
|
||||
"name": "Splashtop - Get SUUID for client",
|
||||
"description": "Returns Splashtop SUUID for client - Use with Custom Fields for later use.",
|
||||
"args": [
|
||||
"{{agent.SplashtopSUUID}}"
|
||||
],
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Collectors"
|
||||
},
|
||||
{
|
||||
"guid": "9cfdfe8f-82bf-4081-a59f-576d694f4649",
|
||||
"filename": "Win_Teamviewer_Get_ID.ps1",
|
||||
"submittedBy": "https://github.com/silversword411",
|
||||
"name": "TeamViewer - Get ClientID for client",
|
||||
"description": "Returns Teamviwer ClientID for client - Use with Custom Fields for later use. ",
|
||||
"description": "Returns Teamviwer ClientID for client - Use with Custom Fields for later use.",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Collectors"
|
||||
},
|
||||
@@ -205,7 +256,7 @@
|
||||
"filename": "Win_AnyDesk_Get_Anynet_ID.ps1",
|
||||
"submittedBy": "https://github.com/meuchels",
|
||||
"name": "AnyDesk - Get AnyNetID for client",
|
||||
"description": "Returns AnyNetID for client - Use with Custom Fields for later use. ",
|
||||
"description": "Returns AnyNetID for client - Use with Custom Fields for later use.",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Collectors"
|
||||
},
|
||||
@@ -256,12 +307,24 @@
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Hardware"
|
||||
},
|
||||
{
|
||||
"guid": "4ace28ee-98f7-4931-9ac9-0adaf1a757ed",
|
||||
"filename": "Win_Software_Install_Report.ps1",
|
||||
"submittedBy": "https://github.com/silversword",
|
||||
"name": "Software Install - Reports new installs",
|
||||
"description": "This will check for software install events in the application Event Viewer log. If a number is provided as a command parameter it will search that number of days back.",
|
||||
"syntax": "[<int>]",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Monitoring",
|
||||
"default_timeout": "90"
|
||||
},
|
||||
{
|
||||
"guid": "907652a5-9ec1-4759-9871-a7743f805ff2",
|
||||
"filename": "Win_Software_Uninstall.ps1",
|
||||
"submittedBy": "https://github.com/subzdev",
|
||||
"name": "Software Uninstaller - list, find, and uninstall most software",
|
||||
"description": "Allows listing, finding and uninstalling most software on Windows. There will be a best effort to uninstall silently if the silent uninstall string is not provided.",
|
||||
"syntax": "-list <string>\n[-u <uninstall string>]\n[-u quiet <uninstall string>]",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):3rd Party Software",
|
||||
"default_timeout": "600"
|
||||
@@ -272,6 +335,7 @@
|
||||
"submittedBy": "https://github.com/jhtechIL/",
|
||||
"name": "BitDefender Gravity Zone Install",
|
||||
"description": "Installs BitDefender Gravity Zone, requires client custom field setup. See script comments for details",
|
||||
"syntax": "[-log]",
|
||||
"args": [
|
||||
"-url {{client.bdurl}}",
|
||||
"-exe {{client.bdexe}}"
|
||||
@@ -280,10 +344,37 @@
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):3rd Party Software"
|
||||
},
|
||||
{
|
||||
"guid": "bfd61545-839b-45da-8b3d-75ffc4d43272",
|
||||
"filename": "Win_Sophos_EndpointProtection_Install.ps1",
|
||||
"submittedBy": "https://github.com/bc24fl/",
|
||||
"name": "Sophos Endpoint Protection Install",
|
||||
"description": "Installs Sophos Endpoint Protection via the Sophos API. Products include Antivirus, InterceptX, MDR, Device Encryption. The script requires API credentials, Custom Fields, and Arguments passed to script. See script comments for details",
|
||||
"args": [
|
||||
"-ClientId {{client.SophosClientId}}",
|
||||
"-ClientSecret {{client.SophosClientSecret}}",
|
||||
"-TenantName {{client.SophosTenantName}}",
|
||||
"-Products antivirus,intercept"
|
||||
],
|
||||
"default_timeout": "3600",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):3rd Party Software"
|
||||
},
|
||||
{
|
||||
"guid": "a9d2a6c0-8afa-4d69-8faf-f83b49c11702",
|
||||
"filename": "Win_Printer_Restart_Jobs.ps1",
|
||||
"submittedBy": "https://github.com/bc24fl/",
|
||||
"name": "Printers - Restarts stuck printer jobs.",
|
||||
"description": "Cycles through each printer and restarts any jobs that are stuck with error status.",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Printing",
|
||||
"default_timeout": "90"
|
||||
},
|
||||
{
|
||||
"guid": "da51111c-aff6-4d87-9d76-0608e1f67fe5",
|
||||
"filename": "Win_Defender_Enable.ps1",
|
||||
"submittedBy": "https://github.com/dinger1986",
|
||||
"syntax": "[-NoControlledFolders]",
|
||||
"name": "Defender - Enable",
|
||||
"description": "Enables Windows Defender and sets preferences",
|
||||
"shell": "powershell",
|
||||
@@ -374,6 +465,7 @@
|
||||
"submittedBy": "https://github.com/dinger1986",
|
||||
"name": "Defender - Status Report",
|
||||
"description": "This will check for Malware and Antispyware within the last 24 hours and display, otherwise will report as Healthy. Command Parameter: (number) if provided will check that number of days back in the log.",
|
||||
"syntax": "[<int>]",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Security>Antivirus"
|
||||
},
|
||||
@@ -409,6 +501,7 @@
|
||||
"filename": "Win_Display_Message_To_User.ps1",
|
||||
"submittedBy": "https://github.com/bradhawkins85",
|
||||
"name": "Message Popup To User",
|
||||
"syntax": "<string>",
|
||||
"description": "Displays a popup message to the currently logged on user",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Other"
|
||||
@@ -418,6 +511,7 @@
|
||||
"filename": "Win_Antivirus_Verify.ps1",
|
||||
"submittedBy": "https://github.com/beejayzed",
|
||||
"name": "Antivirus - Verify Status",
|
||||
"syntax": "[-antivirusName <string>]",
|
||||
"description": "Verify and display status for all installed Antiviruses",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Security>Antivirus"
|
||||
@@ -462,10 +556,11 @@
|
||||
},
|
||||
{
|
||||
"guid": "71090fc4-faa6-460b-adb0-95d7863544e1",
|
||||
"filename": "Win_Check_Events_for_Bluescreens.ps1",
|
||||
"submittedBy": "https://github.com/dinger1986",
|
||||
"filename": "Win_Bluescreen_Report.ps1",
|
||||
"submittedBy": "https://github.com/bbrendon",
|
||||
"name": "Event Viewer - Bluescreen Notification",
|
||||
"description": "Event Viewer Monitor - Notify Bluescreen events on your system",
|
||||
"syntax": "[<int>]",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Monitoring"
|
||||
},
|
||||
@@ -474,7 +569,8 @@
|
||||
"filename": "Win_Local_User_Created_Monitor.ps1",
|
||||
"submittedBy": "https://github.com/dinger1986",
|
||||
"name": "Event Viewer - New User Notification",
|
||||
"description": "Event Viewer Monitor - Notify when new Local user is created",
|
||||
"description": "Event Viewer Monitor - Notify when new Local user is created. If parameter provided will search back that number of days",
|
||||
"syntax": "[<int>]",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Monitoring"
|
||||
},
|
||||
@@ -484,6 +580,7 @@
|
||||
"submittedBy": "https://github.com/dinger1986",
|
||||
"name": "Event Viewer - Task Scheduler New Item Notification",
|
||||
"description": "Event Viewer Monitor - Notify when new Task Scheduler item is created",
|
||||
"syntax": "[<int>]",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Monitoring"
|
||||
},
|
||||
@@ -692,6 +789,15 @@
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Security"
|
||||
},
|
||||
{
|
||||
"guid": "43a3206d-f1cb-44ef-8405-aae4d33a0bad",
|
||||
"filename": "Win_Security_Audit.ps1",
|
||||
"submittedBy": "theinterwebs",
|
||||
"name": "Windows Security - Security Audit",
|
||||
"description": "Runs an Audit on many components of windows to check for security issues",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Security"
|
||||
},
|
||||
{
|
||||
"guid": "7ea6a11a-05c0-4151-b5c1-cb8af029299f",
|
||||
"filename": "Win_AzureAD_Check_Connection_Status.ps1",
|
||||
@@ -756,6 +862,16 @@
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):User Management"
|
||||
},
|
||||
{
|
||||
"guid": "6e27d5341-88fa-4c2f-9c91-c3aeb1740e85",
|
||||
"filename": "Win_User_EnableDisable.ps1",
|
||||
"submittedBy": "https://github.com/silversword411",
|
||||
"name": "User - Enable or disable a user",
|
||||
"description": "Used to enable or disable local user",
|
||||
"syntax": "-Name <string>\n-Enabled { yes | no }",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):User Management"
|
||||
},
|
||||
{
|
||||
"guid": "57997ec7-b293-4fd5-9f90-a25426d0eb90",
|
||||
"filename": "Win_Users_List.ps1",
|
||||
@@ -799,6 +915,7 @@
|
||||
"submittedBy": "https://github.com/tremor021",
|
||||
"name": "EXAMPLE File Copying using powershell",
|
||||
"description": "Reference Script: Will need manual tweaking, for copying files/folders from paths/websites to local",
|
||||
"syntax": "-source <string>\n-destination <string>\n[-recursive {True | False}]",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Misc>Reference",
|
||||
"default_timeout": "1"
|
||||
@@ -818,6 +935,7 @@
|
||||
"filename": "Win_AD_Join_Computer.ps1",
|
||||
"submittedBy": "https://github.com/rfost52",
|
||||
"name": "AD - Join Computer to Domain",
|
||||
"syntax": "-domain <string>\n-password <string>\n-UserAccount ADMINaccount\n[-OUPath <OU=testOU,DC=test,DC=local>]",
|
||||
"description": "Join computer to a domain in Active Directory",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Active Directory",
|
||||
@@ -828,6 +946,7 @@
|
||||
"filename": "Win_Collect_System_Report_And_Email.ps1",
|
||||
"submittedBy": "https://github.com/rfost52",
|
||||
"name": "Collect System Report and Email",
|
||||
"syntax": "-agentname <string>\n-file <string enter file name with the extension .HTM or .HTML>\n-fromaddress <string>\n-toaddress <string>\n-smtpserver <string>\n-password <string>\n-port <int 587 is the standard port for sending mail over TLS>",
|
||||
"description": "Generates a system report in HTML format, then emails it",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Other",
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 3.2.9 on 2021-11-28 16:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('scripts', '0014_alter_script_filename'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='script',
|
||||
name='script_body',
|
||||
field=models.TextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='script',
|
||||
name='script_hash',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='script',
|
||||
name='code_base64',
|
||||
field=models.TextField(blank=True, default=''),
|
||||
),
|
||||
]
|
||||
@@ -1,5 +1,6 @@
|
||||
import base64
|
||||
import re
|
||||
import hmac
|
||||
import hashlib
|
||||
from typing import List
|
||||
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
@@ -40,7 +41,9 @@ class Script(BaseAuditModel):
|
||||
syntax = TextField(null=True, blank=True)
|
||||
favorite = models.BooleanField(default=False)
|
||||
category = models.CharField(max_length=100, null=True, blank=True)
|
||||
code_base64 = models.TextField(null=True, blank=True, default="")
|
||||
script_body = models.TextField(blank=True, default="")
|
||||
script_hash = models.CharField(max_length=100, null=True, blank=True)
|
||||
code_base64 = models.TextField(blank=True, default="") # deprecated
|
||||
default_timeout = models.PositiveIntegerField(default=90)
|
||||
|
||||
def __str__(self):
|
||||
@@ -48,12 +51,7 @@ class Script(BaseAuditModel):
|
||||
|
||||
@property
|
||||
def code_no_snippets(self):
|
||||
if self.code_base64:
|
||||
return base64.b64decode(self.code_base64.encode("ascii", "ignore")).decode(
|
||||
"ascii", "ignore"
|
||||
)
|
||||
else:
|
||||
return ""
|
||||
return self.script_body if self.script_body else ""
|
||||
|
||||
@property
|
||||
def code(self):
|
||||
@@ -78,6 +76,15 @@ class Script(BaseAuditModel):
|
||||
else:
|
||||
return code
|
||||
|
||||
def hash_script_body(self):
|
||||
from django.conf import settings
|
||||
|
||||
msg = self.code.encode()
|
||||
self.script_hash = hmac.new(
|
||||
settings.SECRET_KEY.encode(), msg, hashlib.sha256
|
||||
).hexdigest()
|
||||
self.save()
|
||||
|
||||
@classmethod
|
||||
def load_community_scripts(cls):
|
||||
import json
|
||||
@@ -100,6 +107,9 @@ class Script(BaseAuditModel):
|
||||
) as f:
|
||||
info = json.load(f)
|
||||
|
||||
# used to remove scripts from DB that are removed from the json file and file system
|
||||
community_scripts_processed = [] # list of script guids
|
||||
|
||||
for script in info:
|
||||
if os.path.exists(os.path.join(scripts_dir, script["filename"])):
|
||||
s = cls.objects.filter(script_type="builtin", guid=script["guid"])
|
||||
@@ -118,91 +128,34 @@ class Script(BaseAuditModel):
|
||||
|
||||
syntax = script["syntax"] if "syntax" in script.keys() else ""
|
||||
|
||||
# if community script exists update it
|
||||
if s.exists():
|
||||
i = s.first()
|
||||
i.name = script["name"] # type: ignore
|
||||
i.description = script["description"] # type: ignore
|
||||
i.category = category # type: ignore
|
||||
i.shell = script["shell"] # type: ignore
|
||||
i.default_timeout = default_timeout # type: ignore
|
||||
i.args = args # type: ignore
|
||||
i.syntax = syntax # type: ignore
|
||||
i.filename = script["filename"] # type: ignore
|
||||
i: Script = s.get()
|
||||
i.name = script["name"]
|
||||
i.description = script["description"]
|
||||
i.category = category
|
||||
i.shell = script["shell"]
|
||||
i.default_timeout = default_timeout
|
||||
i.args = args
|
||||
i.syntax = syntax
|
||||
i.filename = script["filename"]
|
||||
|
||||
with open(os.path.join(scripts_dir, script["filename"]), "rb") as f:
|
||||
script_bytes = (
|
||||
f.read().decode("utf-8").encode("ascii", "ignore")
|
||||
)
|
||||
i.code_base64 = base64.b64encode(script_bytes).decode("ascii") # type: ignore
|
||||
i.script_body = f.read().decode("utf-8")
|
||||
# i.hash_script_body()
|
||||
i.save()
|
||||
|
||||
i.save( # type: ignore
|
||||
update_fields=[
|
||||
"name",
|
||||
"description",
|
||||
"category",
|
||||
"default_timeout",
|
||||
"code_base64",
|
||||
"shell",
|
||||
"args",
|
||||
"filename",
|
||||
"syntax",
|
||||
]
|
||||
)
|
||||
|
||||
# check if script was added without a guid
|
||||
elif cls.objects.filter(
|
||||
script_type="builtin", name=script["name"]
|
||||
).exists():
|
||||
s = cls.objects.get(script_type="builtin", name=script["name"])
|
||||
|
||||
if not s.guid:
|
||||
print(f"Updating GUID for: {script['name']}")
|
||||
s.guid = script["guid"]
|
||||
s.name = script["name"]
|
||||
s.description = script["description"]
|
||||
s.category = category
|
||||
s.shell = script["shell"]
|
||||
s.default_timeout = default_timeout
|
||||
s.args = args
|
||||
s.filename = script["filename"]
|
||||
s.syntax = syntax
|
||||
|
||||
with open(
|
||||
os.path.join(scripts_dir, script["filename"]), "rb"
|
||||
) as f:
|
||||
script_bytes = (
|
||||
f.read().decode("utf-8").encode("ascii", "ignore")
|
||||
)
|
||||
s.code_base64 = base64.b64encode(script_bytes).decode(
|
||||
"ascii"
|
||||
)
|
||||
|
||||
s.save(
|
||||
update_fields=[
|
||||
"guid",
|
||||
"name",
|
||||
"description",
|
||||
"category",
|
||||
"default_timeout",
|
||||
"code_base64",
|
||||
"shell",
|
||||
"args",
|
||||
"filename",
|
||||
"syntax",
|
||||
]
|
||||
)
|
||||
community_scripts_processed.append(i.guid)
|
||||
|
||||
# doesn't exist in database so create it
|
||||
else:
|
||||
print(f"Adding new community script: {script['name']}")
|
||||
|
||||
with open(os.path.join(scripts_dir, script["filename"]), "rb") as f:
|
||||
script_bytes = (
|
||||
f.read().decode("utf-8").encode("ascii", "ignore")
|
||||
)
|
||||
code_base64 = base64.b64encode(script_bytes).decode("ascii")
|
||||
script_body = f.read().decode("utf-8")
|
||||
|
||||
cls(
|
||||
code_base64=code_base64,
|
||||
new_script: Script = cls(
|
||||
script_body=script_body,
|
||||
guid=script["guid"],
|
||||
name=script["name"],
|
||||
description=script["description"],
|
||||
@@ -213,10 +166,22 @@ class Script(BaseAuditModel):
|
||||
args=args,
|
||||
filename=script["filename"],
|
||||
syntax=syntax,
|
||||
).save()
|
||||
)
|
||||
# new_script.hash_script_body() # also saves script
|
||||
new_script.save()
|
||||
|
||||
# delete community scripts that had their name changed
|
||||
cls.objects.filter(script_type="builtin", guid=None).delete()
|
||||
community_scripts_processed.append(new_script.guid)
|
||||
|
||||
# check for community scripts that were deleted from json and scripts folder
|
||||
count, _ = (
|
||||
Script.objects.filter(script_type="builtin")
|
||||
.exclude(guid__in=community_scripts_processed)
|
||||
.delete()
|
||||
)
|
||||
if count:
|
||||
print(
|
||||
f"Removing {count} community scripts that was removed from source repo"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def serialize(script):
|
||||
|
||||
@@ -22,6 +22,8 @@ class ScriptTableSerializer(ModelSerializer):
|
||||
|
||||
|
||||
class ScriptSerializer(ModelSerializer):
|
||||
script_hash = ReadOnlyField()
|
||||
|
||||
class Meta:
|
||||
model = Script
|
||||
fields = [
|
||||
@@ -32,7 +34,8 @@ class ScriptSerializer(ModelSerializer):
|
||||
"args",
|
||||
"category",
|
||||
"favorite",
|
||||
"code_base64",
|
||||
"script_body",
|
||||
"script_hash",
|
||||
"default_timeout",
|
||||
"syntax",
|
||||
"filename",
|
||||
@@ -41,10 +44,11 @@ class ScriptSerializer(ModelSerializer):
|
||||
|
||||
class ScriptCheckSerializer(ModelSerializer):
|
||||
code = ReadOnlyField()
|
||||
script_hash = ReadOnlyField
|
||||
|
||||
class Meta:
|
||||
model = Script
|
||||
fields = ["code", "shell"]
|
||||
fields = ["code", "shell", "script_hash"]
|
||||
|
||||
|
||||
class ScriptSnippetSerializer(ModelSerializer):
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import json
|
||||
import os
|
||||
import hmac
|
||||
import hashlib
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import override_settings
|
||||
from django.conf import settings
|
||||
from model_bakery import baker
|
||||
from tacticalrmm.test import TacticalTestCase
|
||||
@@ -31,6 +35,7 @@ class TestScriptViews(TacticalTestCase):
|
||||
|
||||
self.check_not_authenticated("get", url)
|
||||
|
||||
@override_settings(SECRET_KEY="Test Secret Key")
|
||||
def test_add_script(self):
|
||||
url = f"/scripts/"
|
||||
|
||||
@@ -39,7 +44,7 @@ class TestScriptViews(TacticalTestCase):
|
||||
"description": "Description",
|
||||
"shell": "powershell",
|
||||
"category": "New",
|
||||
"code_base64": "VGVzdA==", # Test
|
||||
"script_body": "Test Script",
|
||||
"default_timeout": 99,
|
||||
"args": ["hello", "world", r"{{agent.public_ip}}"],
|
||||
"favorite": False,
|
||||
@@ -48,11 +53,18 @@ class TestScriptViews(TacticalTestCase):
|
||||
# test without file upload
|
||||
resp = self.client.post(url, data, format="json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTrue(Script.objects.filter(name="Name").exists())
|
||||
self.assertEqual(Script.objects.get(name="Name").code, "Test")
|
||||
|
||||
new_script = Script.objects.filter(name="Name").get()
|
||||
self.assertTrue(new_script)
|
||||
|
||||
# correct_hash = hmac.new(
|
||||
# settings.SECRET_KEY.encode(), data["script_body"].encode(), hashlib.sha256
|
||||
# ).hexdigest()
|
||||
# self.assertEqual(new_script.script_hash, correct_hash)
|
||||
|
||||
self.check_not_authenticated("post", url)
|
||||
|
||||
@override_settings(SECRET_KEY="Test Secret Key")
|
||||
def test_modify_script(self):
|
||||
# test a call where script doesn't exist
|
||||
resp = self.client.put("/scripts/500/", format="json")
|
||||
@@ -66,7 +78,7 @@ class TestScriptViews(TacticalTestCase):
|
||||
"name": script.name,
|
||||
"description": "Description Change",
|
||||
"shell": script.shell,
|
||||
"code_base64": "VGVzdA==", # Test
|
||||
"script_body": "Test Script Body", # Test
|
||||
"default_timeout": 13344556,
|
||||
}
|
||||
|
||||
@@ -75,14 +87,17 @@ class TestScriptViews(TacticalTestCase):
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
script = Script.objects.get(pk=script.pk)
|
||||
self.assertEquals(script.description, "Description Change")
|
||||
self.assertEquals(script.code, "Test")
|
||||
|
||||
# correct_hash = hmac.new(
|
||||
# settings.SECRET_KEY.encode(), data["script_body"].encode(), hashlib.sha256
|
||||
# ).hexdigest()
|
||||
# self.assertEqual(script.script_hash, correct_hash)
|
||||
|
||||
# test edit a builtin script
|
||||
|
||||
data = {
|
||||
"name": "New Name",
|
||||
"description": "New Desc",
|
||||
"code_base64": "VGVzdA==",
|
||||
"script_body": "aasdfdsf",
|
||||
} # Test
|
||||
builtin_script = baker.make_recipe("scripts.script", script_type="builtin")
|
||||
|
||||
@@ -94,7 +109,7 @@ class TestScriptViews(TacticalTestCase):
|
||||
"description": "Description Change",
|
||||
"shell": script.shell,
|
||||
"favorite": True,
|
||||
"code_base64": "VGVzdA==", # Test
|
||||
"script_body": "Test Script Body", # Test
|
||||
"default_timeout": 54345,
|
||||
}
|
||||
# test marking a builtin script as favorite
|
||||
@@ -166,29 +181,33 @@ class TestScriptViews(TacticalTestCase):
|
||||
|
||||
# test powershell file
|
||||
script = baker.make(
|
||||
"scripts.Script", code_base64="VGVzdA==", shell="powershell"
|
||||
"scripts.Script", script_body="Test Script Body", shell="powershell"
|
||||
)
|
||||
url = f"/scripts/{script.pk}/download/" # type: ignore
|
||||
|
||||
resp = self.client.get(url, format="json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual(resp.data, {"filename": f"{script.name}.ps1", "code": "Test"}) # type: ignore
|
||||
self.assertEqual(resp.data, {"filename": f"{script.name}.ps1", "code": "Test Script Body"}) # type: ignore
|
||||
|
||||
# test batch file
|
||||
script = baker.make("scripts.Script", code_base64="VGVzdA==", shell="cmd")
|
||||
script = baker.make(
|
||||
"scripts.Script", script_body="Test Script Body", shell="cmd"
|
||||
)
|
||||
url = f"/scripts/{script.pk}/download/" # type: ignore
|
||||
|
||||
resp = self.client.get(url, format="json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual(resp.data, {"filename": f"{script.name}.bat", "code": "Test"}) # type: ignore
|
||||
self.assertEqual(resp.data, {"filename": f"{script.name}.bat", "code": "Test Script Body"}) # type: ignore
|
||||
|
||||
# test python file
|
||||
script = baker.make("scripts.Script", code_base64="VGVzdA==", shell="python")
|
||||
script = baker.make(
|
||||
"scripts.Script", script_body="Test Script Body", shell="python"
|
||||
)
|
||||
url = f"/scripts/{script.pk}/download/" # type: ignore
|
||||
|
||||
resp = self.client.get(url, format="json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual(resp.data, {"filename": f"{script.name}.py", "code": "Test"}) # type: ignore
|
||||
self.assertEqual(resp.data, {"filename": f"{script.name}.py", "code": "Test Script Body"}) # type: ignore
|
||||
|
||||
self.check_not_authenticated("get", url)
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import base64
|
||||
import asyncio
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
@@ -37,6 +36,8 @@ class GetAddScripts(APIView):
|
||||
serializer.is_valid(raise_exception=True)
|
||||
obj = serializer.save()
|
||||
|
||||
# obj.hash_script_body()
|
||||
|
||||
return Response(f"{obj.name} was added!")
|
||||
|
||||
|
||||
@@ -64,6 +65,8 @@ class GetUpdateDeleteScript(APIView):
|
||||
serializer.is_valid(raise_exception=True)
|
||||
obj = serializer.save()
|
||||
|
||||
# obj.hash_script_body()
|
||||
|
||||
return Response(f"{obj.name} was edited!")
|
||||
|
||||
def delete(self, request, pk):
|
||||
|
||||
@@ -20,8 +20,9 @@ app.accept_content = ["application/json"] # type: ignore
|
||||
app.result_serializer = "json" # type: ignore
|
||||
app.task_serializer = "json" # type: ignore
|
||||
app.conf.task_track_started = True
|
||||
app.autodiscover_tasks()
|
||||
app.conf.worker_proc_alive_timeout = 30
|
||||
app.conf.worker_max_tasks_per_child = 2
|
||||
app.autodiscover_tasks()
|
||||
|
||||
app.conf.beat_schedule = {
|
||||
"auto-approve-win-updates": {
|
||||
|
||||
@@ -21,6 +21,7 @@ EXCLUDE_PATHS = (
|
||||
f"/{settings.ADMIN_URL}",
|
||||
"/logout",
|
||||
"/agents/installer",
|
||||
"/api/schema",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -15,24 +15,24 @@ EXE_DIR = os.path.join(BASE_DIR, "tacticalrmm/private/exe")
|
||||
AUTH_USER_MODEL = "accounts.User"
|
||||
|
||||
# latest release
|
||||
TRMM_VERSION = "0.10.1"
|
||||
TRMM_VERSION = "0.10.5"
|
||||
|
||||
# bump this version everytime vue code is changed
|
||||
# to alert user they need to manually refresh their browser
|
||||
APP_VER = "0.0.152"
|
||||
APP_VER = "0.0.155"
|
||||
|
||||
# https://github.com/wh1te909/rmmagent
|
||||
LATEST_AGENT_VER = "1.7.0"
|
||||
LATEST_AGENT_VER = "1.7.2"
|
||||
|
||||
MESH_VER = "0.9.51"
|
||||
MESH_VER = "0.9.61"
|
||||
|
||||
NATS_SERVER_VER = "2.3.3"
|
||||
|
||||
# for the update script, bump when need to recreate venv or npm install
|
||||
PIP_VER = "24"
|
||||
NPM_VER = "25"
|
||||
PIP_VER = "25"
|
||||
NPM_VER = "27"
|
||||
|
||||
SETUPTOOLS_VER = "58.5.3"
|
||||
SETUPTOOLS_VER = "59.6.0"
|
||||
WHEEL_VER = "0.37.0"
|
||||
|
||||
DL_64 = f"https://github.com/wh1te909/rmmagent/releases/download/v{LATEST_AGENT_VER}/winagent-v{LATEST_AGENT_VER}.exe"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from django.conf import settings
|
||||
from django.urls import include, path, register_converter
|
||||
from knox import views as knox_views
|
||||
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
||||
|
||||
from accounts.views import CheckCreds, LoginView
|
||||
from core.consumers import DashInfo
|
||||
@@ -38,19 +37,25 @@ urlpatterns = [
|
||||
path("scripts/", include("scripts.urls")),
|
||||
path("alerts/", include("alerts.urls")),
|
||||
path("accounts/", include("accounts.urls")),
|
||||
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
|
||||
path(
|
||||
"api/schema/swagger-ui/",
|
||||
SpectacularSwaggerView.as_view(url_name="schema"),
|
||||
name="swagger-ui",
|
||||
),
|
||||
]
|
||||
|
||||
if hasattr(settings, "ADMIN_ENABLED") and settings.ADMIN_ENABLED:
|
||||
if getattr(settings, "ADMIN_ENABLED", False):
|
||||
from django.contrib import admin
|
||||
|
||||
urlpatterns += (path(settings.ADMIN_URL, admin.site.urls),)
|
||||
|
||||
if getattr(settings, "SWAGGER_ENABLED", False):
|
||||
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
||||
|
||||
urlpatterns += (
|
||||
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
|
||||
path(
|
||||
"api/schema/swagger-ui/",
|
||||
SpectacularSwaggerView.as_view(url_name="schema"),
|
||||
name="swagger-ui",
|
||||
),
|
||||
)
|
||||
|
||||
ws_urlpatterns = [
|
||||
path("ws/dashinfo/", DashInfo.as_asgi()), # type: ignore
|
||||
]
|
||||
|
||||
@@ -299,7 +299,8 @@ def replace_db_values(
|
||||
if not obj:
|
||||
return ""
|
||||
|
||||
if hasattr(obj, temp[1]):
|
||||
# check if attr exists and isn't a function
|
||||
if hasattr(obj, temp[1]) and not callable(getattr(obj, temp[1])):
|
||||
value = f"'{getattr(obj, temp[1])}'" if quotes else getattr(obj, temp[1])
|
||||
|
||||
elif CustomField.objects.filter(model=model, name=temp[1]).exists():
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_VERSION="16"
|
||||
SCRIPT_VERSION="17"
|
||||
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/backup.sh'
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
@@ -89,4 +89,4 @@ 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"
|
||||
|
||||
@@ -131,6 +131,7 @@ EOF
|
||||
python manage.py reload_nats
|
||||
python manage.py create_natsapi_conf
|
||||
python manage.py create_installer_user
|
||||
python manage.py post_update_tasks
|
||||
|
||||
# create super user
|
||||
echo "from accounts.models import User; User.objects.create_superuser('${TRMM_USER}', 'admin@example.com', '${TRMM_PASS}') if not User.objects.filter(username='${TRMM_USER}').exists() else 0;" | python manage.py shell
|
||||
@@ -168,4 +169,4 @@ if [ "$1" = 'tactical-websockets' ]; then
|
||||
export DJANGO_SETTINGS_MODULE=tacticalrmm.settings
|
||||
|
||||
daphne tacticalrmm.asgi:application --port 8383 -b 0.0.0.0
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -8,17 +8,16 @@ networks:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.20.0.0/24
|
||||
api-db:
|
||||
redis:
|
||||
mesh-db:
|
||||
api-db: null
|
||||
redis: null
|
||||
mesh-db: null # docker managed persistent volumes
|
||||
|
||||
# docker managed persistent volumes
|
||||
volumes:
|
||||
tactical_data:
|
||||
postgres_data:
|
||||
mongo_data:
|
||||
mesh_data:
|
||||
redis_data:
|
||||
tactical_data: null
|
||||
postgres_data: null
|
||||
mongo_data: null
|
||||
mesh_data: null
|
||||
redis_data: null
|
||||
|
||||
services:
|
||||
# postgres database for api service
|
||||
@@ -41,7 +40,7 @@ services:
|
||||
image: redis:6.0-alpine
|
||||
command: redis-server --appendonly yes
|
||||
restart: always
|
||||
volumes:
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
networks:
|
||||
- redis
|
||||
@@ -51,7 +50,7 @@ services:
|
||||
container_name: trmm-init
|
||||
image: ${IMAGE_REPO}tactical:${VERSION}
|
||||
restart: on-failure
|
||||
command: ["tactical-init"]
|
||||
command: [ "tactical-init" ]
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASS: ${POSTGRES_PASS}
|
||||
@@ -63,13 +62,13 @@ services:
|
||||
TRMM_PASS: ${TRMM_PASS}
|
||||
depends_on:
|
||||
- tactical-postgres
|
||||
- tactical-meshcentral
|
||||
- tactical-meshcentral
|
||||
networks:
|
||||
- api-db
|
||||
- proxy
|
||||
volumes:
|
||||
- tactical_data:/opt/tactical
|
||||
|
||||
|
||||
# nats
|
||||
tactical-nats:
|
||||
container_name: trmm-nats
|
||||
@@ -82,6 +81,7 @@ services:
|
||||
volumes:
|
||||
- tactical_data:/opt/tactical
|
||||
networks:
|
||||
api-db: null
|
||||
proxy:
|
||||
aliases:
|
||||
- ${API_HOST}
|
||||
@@ -91,7 +91,7 @@ services:
|
||||
container_name: trmm-meshcentral
|
||||
image: ${IMAGE_REPO}tactical-meshcentral:${VERSION}
|
||||
restart: always
|
||||
environment:
|
||||
environment:
|
||||
MESH_HOST: ${MESH_HOST}
|
||||
MESH_USER: ${MESH_USER}
|
||||
MESH_PASS: ${MESH_PASS}
|
||||
@@ -102,7 +102,7 @@ services:
|
||||
proxy:
|
||||
aliases:
|
||||
- ${MESH_HOST}
|
||||
mesh-db:
|
||||
mesh-db: null
|
||||
volumes:
|
||||
- tactical_data:/opt/tactical
|
||||
- mesh_data:/home/node/app/meshcentral-data
|
||||
@@ -137,7 +137,7 @@ services:
|
||||
tactical-backend:
|
||||
container_name: trmm-backend
|
||||
image: ${IMAGE_REPO}tactical:${VERSION}
|
||||
command: ["tactical-backend"]
|
||||
command: [ "tactical-backend" ]
|
||||
restart: always
|
||||
networks:
|
||||
- proxy
|
||||
@@ -152,7 +152,7 @@ services:
|
||||
tactical-websockets:
|
||||
container_name: trmm-websockets
|
||||
image: ${IMAGE_REPO}tactical:${VERSION}
|
||||
command: ["tactical-websockets"]
|
||||
command: [ "tactical-websockets" ]
|
||||
restart: always
|
||||
networks:
|
||||
- proxy
|
||||
@@ -188,7 +188,7 @@ services:
|
||||
tactical-celery:
|
||||
container_name: trmm-celery
|
||||
image: ${IMAGE_REPO}tactical:${VERSION}
|
||||
command: ["tactical-celery"]
|
||||
command: [ "tactical-celery" ]
|
||||
restart: always
|
||||
networks:
|
||||
- redis
|
||||
@@ -204,7 +204,7 @@ services:
|
||||
tactical-celerybeat:
|
||||
container_name: trmm-celerybeat
|
||||
image: ${IMAGE_REPO}tactical:${VERSION}
|
||||
command: ["tactical-celerybeat"]
|
||||
command: [ "tactical-celerybeat" ]
|
||||
restart: always
|
||||
networks:
|
||||
- proxy
|
||||
|
||||
@@ -61,6 +61,12 @@ It should ask you to sign into your Connectwise Control server if you are not al
|
||||
|
||||
*****
|
||||
|
||||
## Install Screenconnect via Tactical
|
||||
|
||||
Use the [Screenconnect AIO script](https://github.com/wh1te909/tacticalrmm/blob/develop/scripts/Win_ScreenConnectAIO.ps1)
|
||||
|
||||

|
||||
|
||||
## Install Tactical RMM via Screeconnect commands window
|
||||
|
||||
1. Create a Deplopment under **Agents > Manage Deployments**
|
||||
|
||||
42
docs/docs/3rdparty_splashtop.md
Normal file
42
docs/docs/3rdparty_splashtop.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Splashtop
|
||||
|
||||
## Splashtop Integration
|
||||
|
||||
|
||||
From the UI go to **Settings > Global Settings > CUSTOM FIELDS > Agents**
|
||||
|
||||
Add Custom Field</br>
|
||||
**Target** = `Agent`</br>
|
||||
**Name** = `SplashtopSUUID`</br>
|
||||
**Field Type** = `Text`</br>
|
||||
|
||||

|
||||
|
||||
While in Global Settings go to **URL ACTIONS**
|
||||
|
||||
Add a URL Action</br>
|
||||
**Name** = `Splashtop`</br>
|
||||
**Description** = `Connect to a Splashtop client`</br>
|
||||
**URL Pattern** =
|
||||
|
||||
```html
|
||||
st-business://com.splashtop.business?account=&uuid={{agent.SplashtopSUUID}}&sessiontype=remote
|
||||
```
|
||||
|
||||
Navigate to an agent with Splashtop running (or apply using **Settings > Automation Manager**).</br>
|
||||
Go to Tasks.</br>
|
||||
Add Task</br>
|
||||
**Select Script** = `Splashtop - Get SUUID for client` (this is a builtin script from script library)</br>
|
||||
**Descriptive name of task** = `Obtain Splashtop SUUID from device registry.`</br>
|
||||
**Collector Task** = `CHECKED`</br>
|
||||
**Custom Field to update** = `SplashtopSUUID`</br>
|
||||
|
||||

|
||||
|
||||
Click **Next**</br>
|
||||
Check **Manual**</br>
|
||||
Click **Add Task**
|
||||
|
||||
Right click on the newly created task and click **Run Task Now**.
|
||||
|
||||
Give it a second to execute then right click the agent that you are working with and go to **Run URL Action > Splashtop**
|
||||
@@ -27,3 +27,21 @@ chmod +x backup.sh
|
||||
The backup tar file will be saved in `/rmmbackups` with the following format:
|
||||
|
||||
`rmm-backup-CURRENTDATETIME.tar`
|
||||
|
||||
# Schedule to run daily via cron
|
||||
|
||||
Make a symlink in `/etc/cron.d` (daily cron jobs) with these contents `00 18 * * * tactical /rmm/backup.sh` to run at 6pm daily.
|
||||
|
||||
```bash
|
||||
echo -e "\n" >> /rmm/backup.sh
|
||||
sudo ln -s /rmm/backup.sh /etc/cron.daily/
|
||||
```
|
||||
|
||||
!!!warning
|
||||
Currently the backup script doesn't have any pruning functions so the folder will grow forever without periodic cleanup
|
||||
|
||||
# Video Walkthru
|
||||
|
||||
<div class="video-wrapper">
|
||||
<iframe width="320" height="180" src="https://www.youtube.com/embed/rC0NgYJUf_8" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
Tactical RMM agents are now [code signed](https://comodosslstore.com/resources/what-is-microsoft-authenticode-code-signing-certificate/)!
|
||||
|
||||
To get access to code signed agents, you must be a [Github Sponsor](https://github.com/sponsors/wh1te909) with a minumum monthly donation of $50.00
|
||||
To get access to code signed agents, you must be a [Github Sponsor](https://github.com/sponsors/wh1te909) with a minumum **monthly** donation of $50.00. If you signup for the $50, and then downgrade your auth token _**will be**_ invalidated and stop working.
|
||||
|
||||
Once you have become a sponsor, please email **support@amidaware.com** with your Github username (and Discord username if you're on our [Discord](https://discord.gg/upGTkWp))
|
||||
|
||||
|
||||
@@ -87,7 +87,8 @@ npm install -g @quasar/cli
|
||||
quasar dev
|
||||
```
|
||||
|
||||
!!!info If you receive a CORS error when trying to log into your server via localhost or IP, try the following
|
||||
!!!info
|
||||
If you receive a CORS error when trying to log into your server via localhost or IP, try the following
|
||||
```bash
|
||||
rm -rf node_modules .quasar
|
||||
npm install
|
||||
|
||||
@@ -78,6 +78,12 @@ mkdocs is Exposed on Port: 8005
|
||||
|
||||
Open: [http://rmm.example.com:8005/](http://rmm.example.com:8005/)
|
||||
|
||||
!!!note
|
||||
If you add new mkdocs extensions you might need to:<br>
|
||||
- docker-compose down.<br>
|
||||
- Then delete the `/api/tacticalrmm/env/` folder.<br>
|
||||
- Then docker-compose up and it will download/rebuild new extensions
|
||||
|
||||
### View django administration
|
||||
|
||||
Open: [http://rmm.example.com:8000/admin/](http://rmm.example.com:8000/admin/)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# FAQ
|
||||
|
||||
## Is Tactical RMM vulnerable to Log4j
|
||||
|
||||
No
|
||||
|
||||
## Is it possible to use XXX with Tactical RMM
|
||||
|
||||
While it _may be possible_ to use XXX, we have not configured it and therefore it is [Unsupported](../unsupported_guidelines). We cannot help you configure XXX as it pertains to **your environment**.
|
||||
|
||||
25
docs/docs/guide_gettingstarted.md
Normal file
25
docs/docs/guide_gettingstarted.md
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
# TLRD Version
|
||||
|
||||
## At Install
|
||||
|
||||
Setup Email Alerts
|
||||
Setup SMS Alerts
|
||||
Setup Server Preferences
|
||||
General
|
||||
Time Zone
|
||||
Clear faults on agents that haven't checked in after (days)
|
||||
|
||||
Setup Automation Manager
|
||||
Default Profile for workstations
|
||||
|
||||
|
||||
## Every 75 days
|
||||
|
||||
OS updates
|
||||
reboot
|
||||
Backup
|
||||
TRMM Update
|
||||
|
||||
## Biannually
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# How It All Works
|
||||
|
||||

|
||||
[](images/TacticalRMM-Network.png)
|
||||
|
||||
1. Agent installer steps
|
||||
Still need graphics for
|
||||
|
||||
2. Agent checks/tasks and how they work on the workstation/interact with server
|
||||
1. Agent installer steps
|
||||
|
||||
2. Agent checks/tasks and how they work on the workstation/interact with server
|
||||
|
||||
## Server
|
||||
|
||||
@@ -15,41 +17,308 @@ Has a postgres database located here:
|
||||
!!!description
|
||||
A web interface for the postgres database
|
||||
|
||||
### Services
|
||||
All Tactical RMM dependencies are listed [here](https://github.com/wh1te909/tacticalrmm/blob/develop/api/tacticalrmm/requirements.txt)
|
||||
|
||||
nginx
|
||||
### System Services
|
||||
|
||||
!!!description
|
||||
Web server that handles https traffic
|
||||
This lists the system services used by the server.
|
||||
|
||||
Log located at `/var/log/nginx`
|
||||
#### nginx web server
|
||||
|
||||
```bash
|
||||
tail /var/log/nginx
|
||||
```
|
||||
Nginx is the web server for the `rmm`, `api`, and `mesh` domains. All sites redirect port 80 (HTTP) to port 443 (HTTPS).
|
||||
|
||||
### Dependencies from [here](https://github.com/wh1te909/tacticalrmm/blob/develop/api/tacticalrmm/requirements.txt)
|
||||
!!! warning
|
||||
|
||||
[nats](https://nats.io/)
|
||||
nginx does not serve the NATS service on port 4222.
|
||||
|
||||
How communication between client and server bride NAT (Network Address Translation)
|
||||
???+ abstract "nginx configuration (a.k.a. sites available)"
|
||||
|
||||
[celery](https://github.com/celery/celery)
|
||||
- [nginx configuration docs](https://docs.nginx.com/nginx/admin-guide/basic-functionality/managing-configuration-files/)
|
||||
|
||||
!!!description
|
||||
Used to schedule tasks to be sent to Agent
|
||||
=== ":material-web: `rmm.example.com`"
|
||||
|
||||
This serves the frontend website that you intereact with.
|
||||
|
||||
- Config: `/etc/nginx/sites-enabled/frontend.conf`
|
||||
- root: `/var/www/rmm/dist`
|
||||
- Access log: `/var/log/nginx/frontend-access.log`
|
||||
- Error log: `/var/log/nginx/frontend-error.log`
|
||||
- TLS certificate: `/etc/letsencrypt/live/example.com/fullchain.pem`
|
||||
|
||||
=== ":material-web: `api.example.com`"
|
||||
|
||||
This serves the TRMM API for the frontend and agents.
|
||||
|
||||
- Config: `/etc/nginx/sites-enabled/rmm.conf`
|
||||
- roots:
|
||||
- `/rmm/api/tacticalrmm/static/`
|
||||
- `/rmm/api/tacticalrmm/tacticalrmm/private/`
|
||||
- Upstreams:
|
||||
- `unix://rmm/api/tacticalrmm/tacticalrmm.sock`
|
||||
- `unix://rmm/daphne.sock`
|
||||
- Access log: `/rmm/api/tacticalrmm/tacticalrmm/private/log/access.log`
|
||||
- Error log: `/rmm/api/tacticalrmm/tacticalrmm/private/log/error.log`
|
||||
- TLS certificate: `/etc/letsencrypt/live/example.com/fullchain.pem`
|
||||
|
||||
=== ":material-web: `mesh.example.com`"
|
||||
|
||||
This serves MeshCentral for remote access.
|
||||
|
||||
- Config: `/etc/nginx/sites-enabled/meshcentral.conf`
|
||||
- Upstream: `http://127.0.0.1:4430/`
|
||||
- Access log: `/var/log/nginx/access.log` (uses deafult)
|
||||
- Error log: `/var/log/nginx/error.log` (uses deafult)
|
||||
- TLS certificate: `/etc/letsencrypt/live/example.com/fullchain.pem`
|
||||
|
||||
=== ":material-web: default"
|
||||
|
||||
This is the default site installed with nginx. This listens on port 80 only.
|
||||
|
||||
- Config: `/etc/nginx/sites-enabled/default`
|
||||
- root: `/var/www/rmm/dist`
|
||||
- Access log: `/var/log/nginx/access.log` (uses deafult)
|
||||
- Error log: `/var/log/nginx/error.log` (uses deafult)
|
||||
|
||||
???+ note "systemd config"
|
||||
|
||||
=== ":material-console-line: status commands"
|
||||
|
||||
- Status: `systemctl status --full nginx.service`
|
||||
- Stop: `systemctl stop nginx.service`
|
||||
- Start: `systemctl start nginx.service`
|
||||
- Restart: `systemctl restart nginx.service`
|
||||
- Restart: `systemctl reload nginx.service` reloads the config without restarting
|
||||
- Test config: `nginx -t`
|
||||
- Listening process: `ss -tulnp | grep nginx`
|
||||
|
||||
=== ":material-ubuntu: standard"
|
||||
|
||||
- Service: `nginx.service`
|
||||
- Address: `0.0.0.0`
|
||||
- Port: 443
|
||||
- Exec: `/usr/sbin/nginx -g 'daemon on; master_process on;'`
|
||||
- Version: 1.18.0
|
||||
|
||||
=== ":material-docker: docker"
|
||||
|
||||
TBD - To Be Documented
|
||||
|
||||
#### Tactical RMM (Django uWSGI) service
|
||||
|
||||
Built on the Django framework, the Tactical RMM service is the heart of system by serving the API for the frontend and agents.
|
||||
|
||||
???+ note "systemd config"
|
||||
|
||||
- [uWSGI docs](https://uwsgi-docs.readthedocs.io/en/latest/index.html)
|
||||
|
||||
=== ":material-console-line: status commands"
|
||||
|
||||
- Status: `systemctl status --full rmm.service`
|
||||
- Stop: `systemctl stop rmm.service`
|
||||
- Start: `systemctl start rmm.service`
|
||||
- Restart: `systemctl restart rmm.service`
|
||||
- journalctl:
|
||||
- "tail" the logs: `journalctl --identifier uwsgi --follow`
|
||||
- View the logs: `journalctl --identifier uwsgi --since "30 minutes ago" | less`
|
||||
|
||||
=== ":material-ubuntu: standard"
|
||||
|
||||
- Service: `rmm.service`
|
||||
- Socket: `/rmm/api/tacticalrmm/tacticalrmm.sock`
|
||||
- uWSGI config: `/rmm/api/tacticalrmm/app.ini`
|
||||
- Log: None
|
||||
- Journal identifier: `uwsgi`
|
||||
- Version: 2.0.18
|
||||
|
||||
=== ":material-docker: docker"
|
||||
|
||||
TBD - To Be Documented
|
||||
|
||||
#### Daphne: Django channels daemon
|
||||
|
||||
[Daphne](https://github.com/django/daphne) is the official ASGI HTTP/WebSocket server maintained by the [Channels project](https://channels.readthedocs.io/en/stable/index.html).
|
||||
|
||||
???+ note "systemd config"
|
||||
|
||||
- Django [Channels configuration docs](https://channels.readthedocs.io/en/stable/topics/channel_layers.html)
|
||||
|
||||
=== ":material-console-line: status commands"
|
||||
|
||||
- Status: `systemctl status --full daphne.service`
|
||||
- Stop: `systemctl stop daphne.service`
|
||||
- Start: `systemctl start daphne.service`
|
||||
- Restart: `systemctl restart daphne.service`
|
||||
- journalctl (this provides only system start/stop logs, not the actual logs):
|
||||
- "tail" the logs: `journalctl --identifier daphne --follow`
|
||||
- View the logs: `journalctl --identifier daphne --since "30 minutes ago" | less`
|
||||
|
||||
=== ":material-ubuntu: standard"
|
||||
|
||||
- Service: `daphne.service`
|
||||
- Socket: `/rmm/daphne.sock`
|
||||
- Exec: `/rmm/api/env/bin/daphne -u /rmm/daphne.sock tacticalrmm.asgi:application`
|
||||
- Config: `/rmm/api/tacticalrmm/tacticalrmm/local_settings.py`
|
||||
- Log: `/rmm/api/tacticalrmm/tacticalrmm/private/log/debug.log`
|
||||
|
||||
=== ":material-docker: docker"
|
||||
|
||||
TBD - To Be Documented
|
||||
|
||||
#### NATS server service
|
||||
|
||||
[NATS](https://nats.io/) is a messaging bus for "live" communication between the agent and server. NATS provides the framework for the server to push commands to the agent and receive information back.
|
||||
|
||||
???+ note "systemd config"
|
||||
|
||||
- [NATS server configuration docs](https://docs.nats.io/running-a-nats-service/configuration)
|
||||
|
||||
=== ":material-console-line: status commands"
|
||||
|
||||
- Status: `systemctl status --full nats.service`
|
||||
- Stop: `systemctl stop nats.service`
|
||||
- Start: `systemctl start nats.service`
|
||||
- Restart: `systemctl restart nats.service`
|
||||
- Restart: `systemctl reload nats.service` reloads the config without restarting
|
||||
- journalctl:
|
||||
- "tail" the logs: `journalctl --identifier nats-server --follow`
|
||||
- View the logs: `journalctl --identifier nats-server --since "30 minutes ago" | less`
|
||||
- Listening process: `ss -tulnp | grep nats-server`
|
||||
|
||||
=== ":material-ubuntu: standard"
|
||||
|
||||
- Service: `nats.service`
|
||||
- Address: `0.0.0.0`
|
||||
- Port: `4222`
|
||||
- Exec: `/usr/local/bin/nats-server --config /rmm/api/tacticalrmm/nats-rmm.conf`
|
||||
- Config: `/rmm/api/tacticalrmm/nats-rmm.conf`
|
||||
- TLS: `/etc/letsencrypt/live/example.com/fullchain.pem`
|
||||
- Log: None
|
||||
- Version: v2.3.3
|
||||
|
||||
=== ":material-docker: docker"
|
||||
|
||||
- Get into bash in your docker with: `sudo docker-compose exec tactical-nats /bin/bash`
|
||||
- Log: `nats-api -log debug`
|
||||
|
||||
#### NATS API service
|
||||
|
||||
The NATS API service appears to bridge the connection between the NATS server and database, allowing the agent to save (i.e. push) information in the database.
|
||||
|
||||
???+ note "systemd config"
|
||||
|
||||
=== ":material-console-line: status commands"
|
||||
|
||||
- Status: `systemctl status --full nats-api.service`
|
||||
- Stop: `systemctl stop nats-api.service`
|
||||
- Start: `systemctl start nats-api.service`
|
||||
- Restart: `systemctl restart nats-api.service`
|
||||
- journalctl: This application does not appear to log anything.
|
||||
|
||||
=== ":material-ubuntu: standard"
|
||||
|
||||
- Service: `nats-api.service`
|
||||
- Exec: `/usr/local/bin/nats-api --config /rmm/api/tacticalrmm/nats-api.conf`
|
||||
- Config: `/rmm/api/tacticalrmm/nats-api.conf`
|
||||
- TLS: `/etc/letsencrypt/live/example.com/fullchain.pem`
|
||||
- Log: None
|
||||
|
||||
=== ":material-docker: docker"
|
||||
|
||||
TBD - To Be Documented
|
||||
|
||||
#### Celery service
|
||||
|
||||
[Celery](https://github.com/celery/celery) is a task queue focused on real-time processing and is responsible for scheduling tasks to be sent to agents.
|
||||
|
||||
Log located at `/var/log/celery`
|
||||
|
||||
```bash
|
||||
tail /var/log/celery
|
||||
```
|
||||
???+ note "systemd config"
|
||||
|
||||
[Django](https://www.djangoproject.com/)
|
||||
- [Celery docs](https://docs.celeryproject.org/en/stable/index.html)
|
||||
- [Celery configuration docs](https://docs.celeryproject.org/en/stable/userguide/configuration.html)
|
||||
|
||||
!!!description
|
||||
Framework to integrate the server to interact with browser
|
||||
=== ":material-console-line: status commands"
|
||||
|
||||
- Status: `systemctl status --full celery.service`
|
||||
- Stop: `systemctl stop celery.service`
|
||||
- Start: `systemctl start celery.service`
|
||||
- Restart: `systemctl restart celery.service`
|
||||
- journalctl: Celery executes `sh` causing the systemd identifier to be `sh`, thus mixing the `celery` and `celerybeat` logs together.
|
||||
- "tail" the logs: `journalctl --identifier sh --follow`
|
||||
- View the logs: `journalctl --identifier sh --since "30 minutes ago" | less`
|
||||
- Tail logs: `tail -F /var/log/celery/w*-*.log`
|
||||
|
||||
=== ":material-ubuntu: standard"
|
||||
|
||||
- Service: `celery.service`
|
||||
- Exec: `/bin/sh -c '${CELERY_BIN} -A $CELERY_APP multi start $CELERYD_NODES --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel="${CELERYD_LOG_LEVEL}" $CELERYD_OPTS'`
|
||||
- Config: `/etc/conf.d/celery.conf`
|
||||
- Log: `/var/log/celery/w*-*.log`
|
||||
|
||||
=== ":material-docker: docker"
|
||||
|
||||
TBD - To Be Documented
|
||||
|
||||
#### Celery Beat service
|
||||
|
||||
[celery beat](https://github.com/celery/django-celery-beat) is a scheduler; It kicks off tasks at regular intervals, that are then executed by available worker nodes in the cluster.
|
||||
|
||||
???+ note "systemd config"
|
||||
|
||||
- [Celery beat docs](https://docs.celeryproject.org/en/stable/userguide/periodic-tasks.html)
|
||||
|
||||
=== ":material-console-line: status commands"
|
||||
|
||||
- Status: `systemctl status --full celerybeat.service`
|
||||
- Stop: `systemctl stop celerybeat.service`
|
||||
- Start: `systemctl start celerybeat.service`
|
||||
- Restart: `systemctl restart celerybeat.service`
|
||||
- journalctl: Celery executes `sh` causing the systemd identifier to be `sh`, thus mixing the `celery` and `celerybeat` logs together.
|
||||
- "tail" the logs: `journalctl --identifier sh --follow`
|
||||
- View the logs: `journalctl --identifier sh --since "30 minutes ago" | less`
|
||||
- Tail logs: `tail -F /var/log/celery/beat.log`
|
||||
|
||||
=== ":material-ubuntu: standard"
|
||||
|
||||
- Service: `celerybeat.service`
|
||||
- Exec: `/bin/sh -c '${CELERY_BIN} -A ${CELERY_APP} beat --pidfile=${CELERYBEAT_PID_FILE} --logfile=${CELERYBEAT_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL}'`
|
||||
- Config: `/etc/conf.d/celery.conf`
|
||||
- Log: `/var/log/celery/beat.log`
|
||||
|
||||
=== ":material-docker: docker"
|
||||
|
||||
TBD - To Be Documented
|
||||
|
||||
#### MeshCentral
|
||||
|
||||
[MeshCentral](https://github.com/Ylianst/MeshCentral) is used for: "Take Control" (connecting to machine for remote access), and 2 screens of the "Remote Background" (Terminal, and File Browser).
|
||||
|
||||
???+ note "meshcentral"
|
||||
|
||||
- [MeshCentral docs](https://info.meshcentral.com/downloads/MeshCentral2/MeshCentral2UserGuide.pdf)
|
||||
|
||||
=== ":material-console-line: status commands"
|
||||
|
||||
- Status: `systemctl status --full meshcentral`
|
||||
- Stop: `systemctl stop meshcentral`
|
||||
- Start: `systemctl start meshcentral`
|
||||
- Restart: `systemctl restart meshcentral`
|
||||
|
||||
=== ":material-remote-desktop: Debugging"
|
||||
|
||||
- Open either "Take Control" or "Remote Background" to get mesh login token
|
||||
- Open https://mesh.example.com to open native mesh admin interface
|
||||
- Left-side "My Server" > Choose "Console" > type `agentstats`
|
||||
- To view detailed logging goto "Trace" > click Tracing button and choose categories
|
||||
|
||||
### Other Dependencies
|
||||
|
||||
[Django](https://www.djangoproject.com/) - Framework to integrate the server to interact with browser.
|
||||
|
||||
<details>
|
||||
<summary>Django dependencies</summary>
|
||||
|
||||
```text
|
||||
future==0.18.2
|
||||
loguru==0.5.3
|
||||
msgpack==1.0.2
|
||||
@@ -60,33 +329,58 @@ pycryptodome==3.10.1
|
||||
pyotp==2.6.0
|
||||
pyparsing==2.4.7
|
||||
pytz==2021.1
|
||||
```
|
||||
</details>
|
||||
|
||||
[qrcode](https://pypi.org/project/qrcode/)
|
||||
[qrcode](https://pypi.org/project/qrcode/) - Creating QR codes for 2FA.
|
||||
|
||||
!!!description
|
||||
For creating QR codes for 2FA
|
||||
<details>
|
||||
<summary>qrcode dependencies</summary>
|
||||
|
||||
```text
|
||||
redis==3.5.3
|
||||
requests==2.25.1
|
||||
six==1.16.0
|
||||
sqlparse==0.4.1
|
||||
```
|
||||
</details>
|
||||
|
||||
[twilio](https://www.twilio.com/)
|
||||
[twilio](https://www.twilio.com/) - Python SMS notification integration.
|
||||
|
||||
!!!description
|
||||
Python SMS notification integration
|
||||
<details>
|
||||
<summary>twilio dependencies</summary>
|
||||
|
||||
```text
|
||||
urllib3==1.26.5
|
||||
uWSGI==2.0.19.1
|
||||
validators==0.18.2
|
||||
vine==5.0.0
|
||||
websockets==9.1
|
||||
zipp==3.4.1
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
## Windows Agent
|
||||
|
||||
Found in `%programfiles%\TacticalAgent`
|
||||
|
||||
### Outbound Firewall Rules
|
||||
|
||||
If you have strict firewall rules these are the only outbound rules from the agent needed for all functionality:
|
||||
|
||||
1. All agents have to be able to connect outbound to TRMM server on the 3 domain names on ports: 443 (agent and mesh) and 4222 (nats for checks/tasks/data)
|
||||
|
||||
2. The agent uses `https://icanhazip.tacticalrmm.io/` to get public IP info. If this site is down for whatever reason, the agent will fallback to `https://icanhazip.com` and then `https://ifconfig.co/ip`
|
||||
|
||||
#### Unsigned Agents
|
||||
|
||||
Unsigned agents require access to: `https://github.com/wh1te909/rmmagent/releases/*`
|
||||
|
||||
#### Signed Agents
|
||||
|
||||
Signed agents will require: `https://exe.tacticalrmm.io/` and `https://exe2.tacticalrmm.io/` for downloading/updating agents
|
||||
|
||||
### Services
|
||||
|
||||
3 services exist on all clients
|
||||
@@ -101,7 +395,7 @@ Found in `%programfiles%\TacticalAgent`
|
||||

|
||||

|
||||
|
||||
The [MeshCentral](https://meshcentral.com/) system which is accessible from <https://mesh.example.com> and is used
|
||||
The [MeshCentral](https://meshcentral.com/) system which is accessible from `https://mesh.example.com` and is used
|
||||
|
||||
* It runs 2 goroutines
|
||||
* one is the checkrunner which runs all the checks and then just sleeps until it's time to run more checks
|
||||
|
||||
BIN
docs/docs/images/3rdparty_sc_aio.png
Normal file
BIN
docs/docs/images/3rdparty_sc_aio.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
docs/docs/images/3rdparty_splashtop1.png
Normal file
BIN
docs/docs/images/3rdparty_splashtop1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/docs/images/3rdparty_splashtop2.png
Normal file
BIN
docs/docs/images/3rdparty_splashtop2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/docs/images/mesh_agent_onlineoffline.png
Normal file
BIN
docs/docs/images/mesh_agent_onlineoffline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/docs/images/tipsntricks_filters.png
Normal file
BIN
docs/docs/images/tipsntricks_filters.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/docs/images/tipsntricks_script_syntaxhelp.png
Normal file
BIN
docs/docs/images/tipsntricks_script_syntaxhelp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
@@ -93,18 +93,45 @@ If you want to deploy the TRMM agent using AD, intune, mesh, teamviewer, Group P
|
||||
You will need to replace `deployment url` with your custom deployment URL
|
||||
|
||||
```bat
|
||||
if not exist C:\TEMP\TRMM md C:\TEMP\TRMM
|
||||
powershell Set-ExecutionPolicy -ExecutionPolicy Unrestricted
|
||||
powershell Add-MpPreference -ExclusionPath C:\TEMP\TRMM
|
||||
powershell Add-MpPreference -ExclusionPath "C:\Program Files\TacticalAgent\*"
|
||||
powershell Add-MpPreference -ExclusionPath C:\Windows\Temp\winagent-v*.exe
|
||||
powershell Add-MpPreference -ExclusionPath "C:\Program Files\Mesh Agent\*"
|
||||
powershell Add-MpPreference -ExclusionPath C:\Windows\Temp\TRMM\*
|
||||
cd c:\temp\trmm
|
||||
powershell Invoke-WebRequest "deployment url" -Outfile tactical.exe
|
||||
"C:\Program Files\TacticalAgent\unins000.exe" /VERYSILENT /SUPPRESSMSGBOXES /FORCECLOSEAPPLICATIONS
|
||||
start tactical.exe
|
||||
powershell Remove-MpPreference -ExclusionPath C:\TEMP\TRMM
|
||||
@echo off
|
||||
|
||||
REM Setup deployment URL
|
||||
set "DeploymentURL="
|
||||
|
||||
set "Name="
|
||||
for /f "usebackq tokens=* delims=" %%# in (
|
||||
`wmic service where "name like 'tacticalagent'" get Name /Format:Value`
|
||||
) do (
|
||||
for /f "tokens=* delims=" %%g in ("%%#") do set "%%g"
|
||||
)
|
||||
|
||||
if not defined Name (
|
||||
echo Tactical RMM not found, installing now.
|
||||
if not exist C:\TEMP\TRMM md C:\TEMP\TRMM
|
||||
powershell Set-ExecutionPolicy -ExecutionPolicy Unrestricted
|
||||
powershell Add-MpPreference -ExclusionPath C:\TEMP\TRMM
|
||||
powershell Add-MpPreference -ExclusionPath "C:\Program Files\TacticalAgent\*"
|
||||
powershell Add-MpPreference -ExclusionPath C:\Windows\Temp\winagent-v*.exe
|
||||
powershell Add-MpPreference -ExclusionPath "C:\Program Files\Mesh Agent\*"
|
||||
powershell Add-MpPreference -ExclusionPath C:\Windows\Temp\TRMM\*
|
||||
cd c:\temp\trmm
|
||||
powershell Invoke-WebRequest "%DeploymentURL%" -Outfile tactical.exe
|
||||
REM"C:\Program Files\TacticalAgent\unins000.exe" /VERYSILENT
|
||||
tactical.exe
|
||||
powershell Remove-MpPreference -ExclusionPath C:\TEMP\TRMM
|
||||
rem exit /b 1
|
||||
) else (
|
||||
echo Tactical RMM already installed Exiting
|
||||
Exit 0
|
||||
)
|
||||
```
|
||||
|
||||
There is also a full powershell version [here](https://wh1te909.github.io/tacticalrmm/3rdparty_screenconnect/#install-tactical-rmm-via-screeconnect-commands-window)
|
||||
|
||||
## Script for full agent uninstall
|
||||
|
||||
You can always use this to silently uninstall agent on workstations
|
||||
|
||||
```cmd
|
||||
"C:\Program Files\TacticalAgent\unins000.exe" /VERYSILENT
|
||||
```
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
There's pluses and minuses to each install type. Be aware that:
|
||||
|
||||
- There is no migration script, once you've installed with one type there is no "conversion". You'll be installing a new server and migrating agents if you decide to go another way.
|
||||
- There is no migration script, once you've installed with one type there is no "conversion". You'll be installing a new server and migrating agents manually if you decide to go another way.
|
||||
|
||||
## Traditional Install
|
||||
|
||||
@@ -13,5 +13,5 @@ There's pluses and minuses to each install type. Be aware that:
|
||||
|
||||
- Docker is more complicated in concept: has volumes and images
|
||||
- If you're running multiple apps it uses less resources in the long run because you only have one OS base files underlying many Containers/Apps
|
||||
- Backup/restore is by via Docker methods only
|
||||
- Backup/restore is via Docker methods only
|
||||
- Docker has container replication/mirroring options for redundancy/multiple servers
|
||||
|
||||
@@ -9,7 +9,7 @@ Install docker
|
||||
We'll be using `example.com` as our domain for this example.
|
||||
|
||||
!!!info
|
||||
The RMM uses 3 different sites. The Vue frontend e.g. `rmm.example.com` which is where you'll be accesing your RMM from the browser, the REST backend e.g. `api.example.com` and Meshcentral e.g. `mesh.example.com`
|
||||
The RMM uses 3 different sites. The Vue frontend e.g. `rmm.example.com` which is where you'll be accessing your RMM from the browser, the REST backend e.g. `api.example.com` and Meshcentral e.g. `mesh.example.com`
|
||||
|
||||
1. Get the public IP of your server with `curl https://icanhazip.tacticalrmm.io`
|
||||
2. Open the DNS manager of wherever the domain you purchased is hosted.
|
||||
@@ -34,7 +34,7 @@ We're using the [DNS-01 challenge method](https://letsencrypt.org/docs/challenge
|
||||
#### a. Deploy the TXT record in your DNS manager
|
||||
|
||||
!!!warning
|
||||
TXT records can take anywhere from 1 minute to a few hours to propogate depending on your DNS provider.<br/>
|
||||
TXT records can take anywhere from 1 minute to a few hours to propagate depending on your DNS provider.<br/>
|
||||
You should verify the TXT record has been deployed first before pressing Enter.<br/>
|
||||
A quick way to check is with the following command:<br/> `dig -t txt _acme-challenge.example.com`<br/>
|
||||
or test using: <https://viewdns.info/dnsrecord/> Enter: `_acme-challenge.example.com`
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#### Hardware / OS
|
||||
|
||||
A fresh linux VM running either Ubuntu 20.04 LTS or Debian 10 with 3GB RAM
|
||||
A fresh linux VM running either Ubuntu 20.04 LTS or Debian 10/11 with 2GB RAM
|
||||
|
||||
!!!warning
|
||||
The provided install script assumes a fresh server with no software installed on it. Attempting to run it on an existing server with other services **will** break things and the install will fail.
|
||||
@@ -14,6 +14,10 @@ A fresh linux VM running either Ubuntu 20.04 LTS or Debian 10 with 3GB RAM
|
||||
!!!note
|
||||
The install script has been tested on the following public cloud providers: DigitalOcean, Linode, Vultr, BuyVM (highly recommended), Hetzner, AWS, Google Cloud and Azure, as well as behind NAT on Hyper-V, Proxmox and ESXi.
|
||||
|
||||
!!!note
|
||||
CPU: 1 core is fine for < 200 agents with limited checks/tasks.<br><br>
|
||||
Disk space and speed are dependent on your use case. Of course faster is better SSD/NVMe. Space is dependent on how long you're keeping historical data, and how many checks/script runs and their output size. 50GB should be fine for < 12months of history on < 200 agents with < 30 checks/tasks run at reasonable time intervals.
|
||||
|
||||
#### Network Requirements
|
||||
|
||||
- A real (internet resolvable) domain is needed to generate a Let's Encrypt wildcard cert. _If you cannot afford to purchase a domain ($12 a year) then you can get one for free at [freenom.com](https://www.freenom.com/)_
|
||||
@@ -32,7 +36,7 @@ Install on a VPS: DigitalOcean, Linode, Vultr, BuyVM (highly recommended), Hetzn
|
||||
|
||||
Use something that meets [minimum specs](install_server.md#hardware-os)
|
||||
|
||||
### Run updates and setup the linux user
|
||||
### Run Updates on OS
|
||||
|
||||
SSH into the server as **root**.
|
||||
|
||||
@@ -46,6 +50,8 @@ apt -y upgrade
|
||||
|
||||
If a new kernel is installed, then reboot the server with the `reboot` command
|
||||
|
||||
### Create a linux user
|
||||
|
||||
Create a linux user named `tactical` to run the rmm and add it to the sudoers group.
|
||||
|
||||
**For Ubuntu**:
|
||||
@@ -63,7 +69,7 @@ usermod -a -G sudo tactical
|
||||
```
|
||||
|
||||
!!!tip
|
||||
[Enable passwordless sudo to make your life easier](https://linuxconfig.org/configure-sudo-without-password-on-ubuntu-20-04-focal-fossa-linux)
|
||||
[Enable passwordless sudo to make your life easier in the future](https://linuxconfig.org/configure-sudo-without-password-on-ubuntu-20-04-focal-fossa-linux)
|
||||
|
||||
### Setup the firewall (optional but highly recommended)
|
||||
|
||||
@@ -98,12 +104,15 @@ Enable and activate the firewall
|
||||
ufw enable && ufw reload
|
||||
```
|
||||
|
||||
!!!note
|
||||
You will never login to the server again as `root` again unless something has gone horribly wrong, and you're working with the developers.
|
||||
|
||||
### Create the A records
|
||||
|
||||
We'll be using `example.com` as our domain for this example.
|
||||
|
||||
!!!info
|
||||
The RMM uses 3 different sites. The Vue frontend e.g. `rmm.example.com` which is where you'll be accesing your RMM from the browser, the REST backend e.g. `api.example.com` and Meshcentral e.g. `mesh.example.com`
|
||||
The RMM uses 3 different sites. The Vue frontend e.g. `rmm.example.com` which is where you'll be accessing your RMM from the browser, the REST backend e.g. `api.example.com` and Meshcentral e.g. `mesh.example.com`
|
||||
|
||||
1. Get the public IP of your server with `curl https://icanhazip.tacticalrmm.io`
|
||||
2. Open the DNS manager of wherever the domain you purchased is hosted.
|
||||
@@ -134,7 +143,7 @@ Answer the initial questions when prompted. Replace `example.com` with your doma
|
||||
### Deploy the TXT record in your DNS manager for Lets Encrypt wildcard certs
|
||||
|
||||
!!!warning
|
||||
TXT records can take anywhere from 1 minute to a few hours to propogate depending on your DNS provider.<br/>
|
||||
TXT records can take anywhere from 1 minute to a few hours to propagate depending on your DNS provider.<br/>
|
||||
You should verify the TXT record has been deployed first before pressing Enter.<br/>
|
||||
A quick way to check is with the following command:<br/> `dig -t txt _acme-challenge.example.com`<br/>
|
||||
or test using: <https://viewdns.info/dnsrecord/> Enter: `_acme-challenge.example.com`
|
||||
@@ -181,7 +190,7 @@ If you have agents outside your local network: Make sure the public DNS servers
|
||||
|
||||
Login to your router/NAT device.
|
||||
|
||||
1. Set your TRMM server as a static IP (Use a DHCP reservation is usually safer)
|
||||
1. Set your TRMM server as a static IP (Using a DHCP reservation is usually safer)
|
||||
2. Create 2 port forwarding rules. `TCP Port 443` and `TCP Port 4222` to your TRMM servers private IP address.
|
||||
|
||||
!!!note
|
||||
|
||||
@@ -7,6 +7,32 @@ cd /rmm/api/tacticalrmm
|
||||
source ../env/bin/activate
|
||||
```
|
||||
|
||||
or docker version:
|
||||
|
||||
```bash
|
||||
docker exec -it trmm-backend /bin/bash
|
||||
/opt/venv/bin/python /opt/tactical/api/manage.py shell
|
||||
```
|
||||
|
||||
!!!tip
|
||||
The Dev Docker version it would be `docker exec -it trmm-api-dev env/bin/python manage.py shell`
|
||||
|
||||
## Bulk Delete old agents by last checkin date or agent version
|
||||
|
||||
Test to see what will happen
|
||||
|
||||
```bash
|
||||
python manage.py bulk_delete_agents --days 60
|
||||
python manage.py bulk_delete_agents --agentver 1.5.0
|
||||
```
|
||||
|
||||
Do the delete
|
||||
|
||||
```bash
|
||||
python manage.py bulk_delete_agents --days 60 --delete
|
||||
python manage.py bulk_delete_agents --agentver 1.5.0 --delete
|
||||
```
|
||||
|
||||
## Reset a user's password
|
||||
|
||||
```bash
|
||||
@@ -25,6 +51,13 @@ python manage.py reset_2fa <username>
|
||||
python manage.py find_software "adobe"
|
||||
```
|
||||
|
||||
## Set specific Windows update to not install
|
||||
|
||||
```bash
|
||||
from winupdate.models import WinUpdate
|
||||
WinUpdate.objects.filter(kb="KB5007186").update(action="ignore", date_installed=None)
|
||||
```
|
||||
|
||||
## Show outdated online agents
|
||||
|
||||
```bash
|
||||
|
||||
@@ -8,13 +8,79 @@
|
||||
|
||||
Make sure you update your old RMM to the latest version using the `update.sh` script and then run a fresh backup to use with this restore script.
|
||||
|
||||
## Prepare the new server
|
||||
## Install the new server
|
||||
|
||||
Create the same exact linux user account as you did when you installed the original server.
|
||||
### Run Updates on OS
|
||||
|
||||
Add it to the sudoers group and setup the firewall.
|
||||
SSH into the server as **root**.
|
||||
|
||||
Refer to the [installation instructions](install_server.md) for steps on how to do all of the above.
|
||||
Download and run the prereqs and latest updates
|
||||
|
||||
```bash
|
||||
apt update
|
||||
apt install -y wget curl sudo
|
||||
apt -y upgrade
|
||||
```
|
||||
|
||||
If a new kernel is installed, then reboot the server with the `reboot` command
|
||||
|
||||
### Create a linux user
|
||||
|
||||
Create a linux user named `tactical` to run the rmm and add it to the sudoers group.
|
||||
|
||||
**For Ubuntu**:
|
||||
|
||||
```bash
|
||||
adduser tactical
|
||||
usermod -a -G sudo tactical
|
||||
```
|
||||
|
||||
**For Debian**:
|
||||
|
||||
```bash
|
||||
useradd -m -s /bin/bash tactical
|
||||
usermod -a -G sudo tactical
|
||||
```
|
||||
|
||||
!!!tip
|
||||
[Enable passwordless sudo to make your life easier in the future](https://linuxconfig.org/configure-sudo-without-password-on-ubuntu-20-04-focal-fossa-linux)
|
||||
|
||||
### Setup the firewall (optional but highly recommended)
|
||||
|
||||
!!!info
|
||||
Skip this step if your VM is __not__ publicly exposed to the world e.g. running behind NAT. You should setup the firewall rules in your router instead (ports 22, 443 and 4222 TCP).
|
||||
|
||||
```bash
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
ufw allow https
|
||||
ufw allow proto tcp from any to any port 4222
|
||||
```
|
||||
|
||||
!!!info
|
||||
SSH (port 22 tcp) is only required for you to remotely login and do basic linux server administration for your rmm. It is not needed for any agent communication.<br/>
|
||||
Allow ssh from everywhere (__not__ recommended)
|
||||
|
||||
```bash
|
||||
ufw allow ssh
|
||||
```
|
||||
|
||||
Allow ssh from only allowed IP's (__highly__ recommended)
|
||||
|
||||
```bash
|
||||
ufw allow proto tcp from X.X.X.X to any port 22
|
||||
ufw allow proto tcp from X.X.X.X to any port 22
|
||||
```
|
||||
|
||||
Enable and activate the firewall
|
||||
|
||||
```bash
|
||||
ufw enable && ufw reload
|
||||
```
|
||||
|
||||
!!!note
|
||||
You will never login to the server again as `root` again unless something has gone horribly wrong, and you're working with the developers.
|
||||
|
||||
|
||||
## Change DNS A records
|
||||
|
||||
@@ -24,16 +90,16 @@ Change the 3 A records `rmm`, `api` and `mesh` and point them to the public IP o
|
||||
|
||||
## Run the restore script
|
||||
|
||||
Copy the backup tar file you created during [backup](backup.md) to the new server.
|
||||
1. Make sure you're logged in with the non-root user (eg `tactical`)
|
||||
|
||||
Download the restore script.
|
||||
2. Copy the backup tar file you created during [backup](backup.md) to the new server.
|
||||
|
||||
```bash
|
||||
wget https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/restore.sh
|
||||
chmod +x restore.sh
|
||||
```
|
||||
3. Download the restore script.
|
||||
|
||||
Call the restore script, passing it the backup file as the first argument:
|
||||
wget https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/restore.sh
|
||||
chmod +x restore.sh
|
||||
|
||||
4. Call the restore script, passing it the backup file as the first argument:
|
||||
|
||||
```bash
|
||||
./restore.sh rmm-backup-XXXXXXXXX.tar
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Tips and Tricks
|
||||
|
||||
## Server Monitoring
|
||||
|
||||
Monitor Network usage: <https://humdi.net/vnstat/>
|
||||
|
||||
Realtime Everything Usage: (_only run when needed because it uses a lot of resources_): <https://learn.netdata.cloud/docs/agent/packaging/installer/methods/kickstart>
|
||||
|
||||
## Customize User Interface
|
||||
|
||||
At the top right of your web administration interface, click your Username > preferences. Set default tab: Servers|Workstations|Mixed
|
||||
@@ -8,10 +14,19 @@ At the top right of your web administration interface, click your Username > pre
|
||||
|
||||
*****
|
||||
|
||||
## Use the filters in the agent list
|
||||
|
||||

|
||||
|
||||
*****
|
||||
## MeshCentral
|
||||
|
||||
Tactical RMM is actually 2 products: An RMM service with agent, and a secondary [MeshCentral](https://github.com/Ylianst/MeshCentral) install that handles the `Take Control` and `Remote Background` stuff.
|
||||
|
||||
Want to download multiple files?
|
||||
|
||||
> ZIP zip's the currently selected file(s) and saves it in the current directory. Then you can download the ZIP. It doesn't download and ZIP on the fly.
|
||||
|
||||
### Adjust Settings
|
||||
|
||||
Right-click the connect button in *Remote Background | Terminal* for shell options
|
||||
@@ -25,7 +40,7 @@ Right-click the connect button in *Take Control* for connect options
|
||||
### Enable Remote Control options
|
||||
|
||||
!!!note
|
||||
These settings are independant of Tactical RMM. Enable features (like auto remove inactive devices) with caution
|
||||
These settings are independent of Tactical RMM. Enable features (like auto remove inactive devices) with caution
|
||||
|
||||
1. Remote background a machine then go to mesh.EXAMPLE.COM
|
||||
2. Click on My Account
|
||||
@@ -34,6 +49,20 @@ Right-click the connect button in *Take Control* for connect options
|
||||

|
||||
5. You can also change features by ticking whatever boxes you want in there (Features: Sync server device name to hostname, Automatically remove inactive devices, Notify/Prompt for Consent/Connection Toolbar settings)<br>
|
||||

|
||||
|
||||
6. Ok your way out
|
||||
|
||||
### Agent online/offline logs
|
||||
|
||||
In mesh from the agent | General Tab
|
||||
|
||||

|
||||
## Scripts
|
||||
|
||||
### When Running Scripts
|
||||
|
||||
Use the (i) at the end of the script name to:
|
||||
|
||||
- Hover: see script parameter syntax help
|
||||
- Left Click: Opens the script source in Github
|
||||
|
||||

|
||||
|
||||
@@ -63,9 +63,44 @@ If you have agents that are relatively old, you will need to uninstall them manu
|
||||
|
||||
## Agents not checking in or showing up / General agent issues
|
||||
|
||||
These are nats problems. Try quickfix first:
|
||||
|
||||
### from Admin Web Interface
|
||||
|
||||
First, reload NATS from tactical's web UI:<br>
|
||||
*Tools > Server Maintenance > Reload Nats Configuration*
|
||||
|
||||
If that doesn't work, check each part starting with the server:
|
||||
|
||||
### Server SSH login
|
||||
|
||||
Reload NATS:
|
||||
|
||||
```bash
|
||||
/rmm/api/env/bin/python /rmm/api/tacticalrmm/manage.py reload_nats
|
||||
sudo systemctl restart nats
|
||||
```
|
||||
|
||||
Look at nats service errors (make sure it's running)
|
||||
|
||||
```bash
|
||||
sudo systemctl status nats
|
||||
```
|
||||
|
||||
If nats isn't running see detailed reason why it isn't:
|
||||
|
||||
```bash
|
||||
sudo systemctl stop nats
|
||||
nats-server -DVV -c /rmm/api/tacticalrmm/nats-rmm.conf
|
||||
```
|
||||
|
||||
Fix the problem, then restart nats.
|
||||
```
|
||||
sudo systemctl restart nats
|
||||
```
|
||||
|
||||
### From Agent Install
|
||||
|
||||
Open CMD as admin on the problem computer and stop the agent services:
|
||||
|
||||
```cmd
|
||||
|
||||
@@ -869,3 +869,71 @@ Limit access to Tactical RMM's administration panel in nginx to specific locatio
|
||||
server_name rmm.example.com;
|
||||
return 404;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Apache Proxy
|
||||
howto - proxy on apache
|
||||
### TRMM SERVER
|
||||
edit file /etc/nginx/sites-available/rmm.conf
|
||||
add the lines from 'real_ip' module inside server tag:
|
||||
|
||||
|
||||
set_real_ip_from 192.168.0.200; #IP Address of your apache proxy
|
||||
real_ip_header X-Forwarded-For;
|
||||
|
||||
restart nginx
|
||||
|
||||
systemctl restart nginx
|
||||
|
||||
### APACHE
|
||||
enable ssl proxy, rewriteEngine.
|
||||
set proxy to preserve host.
|
||||
set upgrade rule to websocket.
|
||||
set proxypass rules redirecting to rmm location
|
||||
|
||||
on your apache ssl config
|
||||
example:
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName rmm.blablabla.com.br:443
|
||||
ServerAlias mesh.blablabla.com.br:443 api.blablabla.com.br:443
|
||||
SSLEngine on
|
||||
|
||||
SSLCertificateFile "C:/Apache24/conf/ssl-rmm.blablabla.com.br/_.blablabla.com.br-chain.pem"
|
||||
SSLCertificateKeyFile "C:/Apache24/conf/ssl-rmm.blablabla.com.br/_.blablabla.com.br-key.pem"
|
||||
|
||||
SSLProxyEngine on
|
||||
|
||||
RewriteEngine On
|
||||
ProxyPreserveHost On
|
||||
|
||||
# When Upgrade:websocket header is present, redirect to ws
|
||||
# Using NC flag (case-insensitive) as some browsers will pass Websocket
|
||||
RewriteCond %{HTTP:Upgrade} =websocket [NC]
|
||||
RewriteRule ^/(.*) wss://192.168.0.212/$1 [P,L]
|
||||
|
||||
ProxyPass "/" "https://192.168.0..212/" retry=3
|
||||
ProxyPassReverse "/" "https://192.168.0.212/" retry=3
|
||||
|
||||
BrowserMatch "MSIE [2-5]" \
|
||||
nokeepalive ssl-unclean-shutdown \
|
||||
downgrade-1.0 force-response-1.0
|
||||
|
||||
</VirtualHost>
|
||||
|
||||
### Updating certificate:
|
||||
Im my case, auto DNS Challenge from apache, so every time we get new cert files, it must be copied inside rmm too.
|
||||
just overwrite default location:
|
||||
/etc/letsencrypt/archive/blablablabla
|
||||
or change certs location on nginx conf to whatever you want.
|
||||
|
||||
## nginx Proxy
|
||||
|
||||
Having mesh connection issues?
|
||||
|
||||
See <https://info.meshcentral.com/downloads/MeshCentral2/MeshCentral2UserGuide.pdf> page 30.
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
## Updating to the latest RMM version
|
||||
|
||||
!!!question
|
||||
You have a [backup](https://docs.docker.com/desktop/backup-and-restore/) right?
|
||||
|
||||
Tactical RMM updates the docker images on every release and should be available within a few minutes
|
||||
|
||||
SSH into your server as a root user and run the below commands:
|
||||
@@ -23,7 +26,7 @@ To renew your Let's Encrypt wildcard cert, run the following command, replacing
|
||||
sudo certbot certonly --manual -d *.example.com --agree-tos --no-bootstrap --manual-public-ip-logging-ok --preferred-challenges dns -m admin@example.com --no-eff-email
|
||||
```
|
||||
|
||||
Verify the domain with the TXT record. Once issued, run the below commands to base64 encode the certificates and add then to the .env file
|
||||
Verify the domain with the TXT record. Once issued, run the below commands to base64 encode the certificates and add them to the .env file
|
||||
|
||||
```bash
|
||||
echo "CERT_PUB_KEY=$(sudo base64 -w 0 /etc/letsencrypt/live/${rootdomain}/fullchain.pem)" >> .env
|
||||
|
||||
@@ -19,13 +19,16 @@ Other than this, you should avoid making any changes to your server and let the
|
||||
|
||||
Sometimes, manual intervention will be required during an update in the form of yes/no prompts, so attempting to automate this will ignore these prompts and cause your installation to break.
|
||||
|
||||
SSH into your server as the linux user you created during install.
|
||||
SSH into your server as the linux user you created during install (eg `tactical`).
|
||||
|
||||
!!!danger
|
||||
__Never__ run any update scripts or commands as the `root` user.
|
||||
|
||||
This will mess up permissions and break your installation.
|
||||
|
||||
!!!question
|
||||
You have a [backup](backup.md) right?
|
||||
|
||||
Download the update script and run it:
|
||||
|
||||
```bash
|
||||
@@ -42,7 +45,7 @@ You can pass the optional `--force` flag to the update script to forcefully run
|
||||
./update.sh --force
|
||||
```
|
||||
|
||||
This is usefull for a botched update that might have not completed fully.
|
||||
This is useful for a botched update that might have not completed fully.
|
||||
|
||||
The update script will also fix any permissions that might have gotten messed up during a botched update, or if you accidentally ran the update script as the `root` user.
|
||||
|
||||
@@ -64,7 +67,7 @@ To renew your Let's Encrypt wildcard cert, run the following command, replacing
|
||||
sudo certbot certonly --manual -d *.example.com --agree-tos --no-bootstrap --manual-public-ip-logging-ok --preferred-challenges dns -m admin@example.com --no-eff-email
|
||||
```
|
||||
|
||||
Same instructions as during install for [verifying the TXT record](install_server.md#deploy-the-txt-record-in-your-dns-manager) has propogated before hitting Enter.
|
||||
Same instructions as during install for [verifying the TXT record](install_server.md#deploy-the-txt-record-in-your-dns-manager) has propagated before hitting Enter.
|
||||
|
||||
After this you have renewed the cert, simply run the `update.sh` script, passing it the `--force` flag.
|
||||
|
||||
@@ -84,3 +87,9 @@ If you're running low, shrink you database
|
||||
sudo -u postgres psql -d tacticalrmm -c "vacuum full logs_auditlog"
|
||||
sudo -u postgres psql -d tacticalrmm -c "vacuum full logs_auditlog"
|
||||
```
|
||||
|
||||
## Video Walkthru
|
||||
|
||||
<div class="video-wrapper">
|
||||
<iframe width="320" height="180" src="https://www.youtube.com/embed/ElUfQgesYs0" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ nav:
|
||||
- "Agent Installation": install_agent.md
|
||||
- "Updating Agents": update_agents.md
|
||||
- Functionality:
|
||||
- "How it all Works": howitallworks.md
|
||||
- "Alerting": functions/alerting.md
|
||||
- "API Access": functions/api.md
|
||||
- "Automated Tasks": functions/automated_tasks.md
|
||||
@@ -39,11 +40,12 @@ nav:
|
||||
- "Grafana": 3rdparty_grafana.md
|
||||
- "AnyDesk": 3rdparty_anydesk.md
|
||||
- "Connectwise Control / Screenconnect": 3rdparty_screenconnect.md
|
||||
- "Splashtop": 3rdparty_splashtop.md
|
||||
- "TeamViewer": 3rdparty_teamviewer.md
|
||||
- "BitDefender GravityZone": 3rdparty_bitdefender_gravityzone.md
|
||||
- Unsupported Extras:
|
||||
- "Unsupported Guidelines": unsupported_guidelines.md
|
||||
- "Unsupported Scripts": unsupported_scripts.md
|
||||
- "Unsupported Configs": unsupported_scripts.md
|
||||
- "Securing nginx": securing_nginx.md
|
||||
- "Installing in Synology docker": unsupported_synology_docker_install.md
|
||||
- Tips n' Tricks: tipsntricks.md
|
||||
@@ -74,16 +76,25 @@ theme:
|
||||
palette:
|
||||
primary: "white"
|
||||
accent: "indigo"
|
||||
features:
|
||||
extra_css:
|
||||
- stylesheets/extra.css
|
||||
extra:
|
||||
social:
|
||||
- icon: fontawesome/brands/github
|
||||
link: "https://github.com/wh1te909/tacticalrmm"
|
||||
|
||||
markdown_extensions:
|
||||
- pymdownx.inlinehilite
|
||||
- admonition
|
||||
- pymdownx.details
|
||||
- codehilite:
|
||||
guess_lang: false
|
||||
- toc:
|
||||
permalink: true
|
||||
permalink: true
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:materialx.emoji.twemoji
|
||||
emoji_generator: !!python/name:materialx.emoji.to_svg
|
||||
- pymdownx.superfences
|
||||
- pymdownx.tabbed:
|
||||
alternate_style: true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_VERSION="56"
|
||||
SCRIPT_VERSION="57"
|
||||
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/install.sh'
|
||||
|
||||
sudo apt install -y curl wget dirmngr gnupg lsb-release
|
||||
@@ -454,6 +454,7 @@ User=${USER}
|
||||
Group=www-data
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
LimitNOFILE=1000000
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
37
scripts/Win_Bluescreen_Report.ps1
Normal file
37
scripts/Win_Bluescreen_Report.ps1
Normal file
@@ -0,0 +1,37 @@
|
||||
<#
|
||||
.Synopsis
|
||||
Bluescreen - Reports bluescreens
|
||||
.DESCRIPTION
|
||||
This will check for Bluescreen events on your system. If parameter provided, goes back that number of days
|
||||
.EXAMPLE
|
||||
365
|
||||
.NOTES
|
||||
v1 bbrendon 2/2021
|
||||
v1.1 silversword updating with parameters 11/2021
|
||||
#>
|
||||
|
||||
|
||||
$param1 = $args[0]
|
||||
|
||||
$ErrorActionPreference = 'silentlycontinue'
|
||||
if ($Args.Count -eq 0) {
|
||||
$TimeSpan = (Get-Date) - (New-TimeSpan -Day 1)
|
||||
}
|
||||
else {
|
||||
$TimeSpan = (Get-Date) - (New-TimeSpan -Day $param1)
|
||||
}
|
||||
|
||||
|
||||
if (Get-WinEvent -FilterHashtable @{LogName = 'application'; ID = '1001'; ProviderName = 'Windows Error Reporting'; Level = 4; Data = 'BlueScreen'; StartTime = $TimeSpan }) {
|
||||
Write-Output "There has been bluescreen events detected on your system"
|
||||
Get-WinEvent -FilterHashtable @{LogName = 'application'; ID = '1001'; ProviderName = 'Windows Error Reporting'; Level = 4; Data = 'BlueScreen'; StartTime = $TimeSpan }
|
||||
exit 1
|
||||
}
|
||||
|
||||
{
|
||||
else
|
||||
Write-Output "No bluescreen events detected in the past 24 hours."
|
||||
exit 0
|
||||
}
|
||||
|
||||
Exit $LASTEXITCODE
|
||||
@@ -1,20 +0,0 @@
|
||||
# This will check for Bluescreen events on your system
|
||||
|
||||
$ErrorActionPreference= 'silentlycontinue'
|
||||
$TimeSpan = (Get-Date) - (New-TimeSpan -Day 1)
|
||||
|
||||
if (Get-WinEvent -FilterHashtable @{LogName='application';ID='1001';ProviderName='Windows Error Reporting';Level=4;Data='BlueScreen';StartTime=$TimeSpan})
|
||||
|
||||
{
|
||||
Write-Output "There has been bluescreen events detected on your system"
|
||||
Get-WinEvent -FilterHashtable @{LogName='application';ID='1001';ProviderName='Windows Error Reporting';Level=4;Data='BlueScreen';StartTime=$TimeSpan}
|
||||
exit 1
|
||||
}
|
||||
|
||||
{
|
||||
else
|
||||
Write-Output "No bluescreen events detected in the past 24 hours."
|
||||
exit 0
|
||||
}
|
||||
|
||||
Exit $LASTEXITCODE
|
||||
3
scripts/Win_Chocolatey_List_Installed.bat
Normal file
3
scripts/Win_Chocolatey_List_Installed.bat
Normal file
@@ -0,0 +1,3 @@
|
||||
rem List apps installed by Chocolatey
|
||||
|
||||
choco list --localonly
|
||||
@@ -1,3 +1,21 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Enables Windows Defender and sets preferences to lock Defender down
|
||||
.DESCRIPTION
|
||||
Windows Defender in its default configuration does basic protections. Running this script will enable many additional settings to increase security.
|
||||
.PARAMETER NoControlledFolders
|
||||
Adding this parameter will not enable Controlled Folders
|
||||
.EXAMPLE
|
||||
-NoControlledFolders
|
||||
.NOTES
|
||||
9/2021 v1 Initial release dinger1986
|
||||
11/24/2021 v1.1 adding command parameters for Controller Folder access by Tremor and silversword
|
||||
#>
|
||||
|
||||
param (
|
||||
[switch] $NoControlledFolders
|
||||
)
|
||||
|
||||
# Verifies that script is running on Windows 10 or greater
|
||||
function Check-IsWindows10
|
||||
{
|
||||
@@ -92,8 +110,14 @@ if (!(Check-IsWindows10-1709))
|
||||
|
||||
Write-Host # `nUpdating Windows Defender Exploit Guard settings`n# -ForegroundColor Green
|
||||
|
||||
Write-Host # Enabling Controlled Folder Access and setting to block mode#
|
||||
Set-MpPreference -EnableControlledFolderAccess Enabled
|
||||
if ($NoControlledFolders) # Check if user has run with -NoControlledFolders parameter
|
||||
{
|
||||
Write-Host "Skipping enabling Controlled folders"
|
||||
}
|
||||
else {
|
||||
Write-Host "Enabling Controlled folders"
|
||||
Set-MpPreference -EnableControlledFolderAccess Enabled
|
||||
}
|
||||
|
||||
Write-Host # Enabling Network Protection and setting to block mode#
|
||||
Set-MpPreference -EnableNetworkProtection Enabled
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
Write-Host "Running Windows Defender Full Scan in Background" -ForegroundColor Green
|
||||
Write-Host "Running Windows Defender Full Scan in Background" -ForegroundColor Green
|
||||
Start-MpScan -ScanPath C:\ -ScanType FullScan -AsJob
|
||||
@@ -1,2 +1,2 @@
|
||||
Write-Host "Running Windows Defender Quick Scan in Background" -ForegroundColor Green
|
||||
Write-Host "Running Windows Defender Quick Scan in Background" -ForegroundColor Green
|
||||
Start-MpScan -ScanType QuickScan -AsJob
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
This will check Event Log for Windows Defender Malware and Antispyware reports, otherwise will report as Healthy. By default if no command parameter is provided it will check the last 1 day (good for a scheduled daily task).
|
||||
If a number is provided as a command parameter it will search back that number of days back provided (good for collecting all AV alerts on the computer).
|
||||
.EXAMPLE
|
||||
Win_Defender_Status_reports.ps1 365
|
||||
365
|
||||
.NOTES
|
||||
v1 dinger initial release 2021
|
||||
v1.1 bdrayer Adding full message output if items found
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#Install Adobe Reader DC
|
||||
#Install Adobe Reader DC
|
||||
choco install adobereader -params '"/EnableUpdateService /UpdateMode:3 /DesktopIcon"' --yes --no-progress --force
|
||||
@@ -1,16 +1,32 @@
|
||||
$ErrorActionPreference= 'silentlycontinue'
|
||||
$TimeSpan = (Get-Date) - (New-TimeSpan -Day 1)
|
||||
if (Get-WinEvent -FilterHashtable @{LogName='security';ID='4720','4720','4728','4732','4756','4767';StartTime=$TimeSpan})
|
||||
{
|
||||
Write-Output "A change has been made to local users"
|
||||
Get-WinEvent -FilterHashtable @{LogName='security';ID='4720','4720','4728','4732','4756','4767';StartTime=$TimeSpan}
|
||||
exit 1
|
||||
<#
|
||||
.Synopsis
|
||||
Event Viewer - New User Notification
|
||||
.DESCRIPTION
|
||||
Event Viewer Monitor - Notify when new Local user is created
|
||||
.EXAMPLE
|
||||
365
|
||||
.NOTES
|
||||
v1 dinger initial release
|
||||
v1.1 silversword adding parameter options 11/2021
|
||||
#>
|
||||
|
||||
$ErrorActionPreference = 'silentlycontinue'
|
||||
if ($Args.Count -eq 0) {
|
||||
$TimeSpan = (Get-Date) - (New-TimeSpan -Day 1)
|
||||
}
|
||||
else {
|
||||
$TimeSpan = (Get-Date) - (New-TimeSpan -Day $param1)
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Write-Output "No changes all looks fine"
|
||||
exit 0
|
||||
if (Get-WinEvent -FilterHashtable @{LogName = 'security'; ID = '4720', '4720', '4728', '4732', '4756', '4767'; StartTime = $TimeSpan }) {
|
||||
Write-Output "A change has been made to local users"
|
||||
Get-WinEvent -FilterHashtable @{LogName = 'security'; ID = '4720', '4720', '4728', '4732', '4756', '4767'; StartTime = $TimeSpan }
|
||||
exit 1
|
||||
}
|
||||
|
||||
else {
|
||||
Write-Output "No changes all looks fine"
|
||||
exit 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
9
scripts/Win_Printer_ClearandRestart.bat
Normal file
9
scripts/Win_Printer_ClearandRestart.bat
Normal file
@@ -0,0 +1,9 @@
|
||||
@echo off
|
||||
|
||||
sc stop spooler
|
||||
|
||||
ping 127.0.0.1 -n 6 > nul
|
||||
|
||||
del C:\Windows\System32\spool\printers\* /Q /F /S
|
||||
|
||||
sc start spooler
|
||||
28
scripts/Win_Printer_Restart_Jobs.ps1
Normal file
28
scripts/Win_Printer_Restart_Jobs.ps1
Normal file
@@ -0,0 +1,28 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Restarts stuck printer jobs.
|
||||
|
||||
.DESCRIPTION
|
||||
Cycles through each printer and restarts any jobs that are stuck with error status.
|
||||
|
||||
.NOTES
|
||||
Change Log
|
||||
----------------------------------------------------------------------------------
|
||||
V1.0 Initial Release by https://github.com/bc24fl/tacticalrmm-scripts/
|
||||
|
||||
#>
|
||||
|
||||
$allPrinters = Get-Printer
|
||||
foreach ($printer in $allPrinters) {
|
||||
$printJobs = Get-PrintJob -PrinterName $($printer.Name)
|
||||
if ($printJobs) {
|
||||
foreach ($job in $printJobs) {
|
||||
if ($job.JobStatus -match 'Error') {
|
||||
$stuckPrinterName = $job.PrinterName
|
||||
$stuckPrinterJob = $job.Id
|
||||
Write-Host "Restarting Job Id $stuckPrinterJob on printer $stuckPrinterName"
|
||||
Restart-PrintJob -InputObject $job
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
scripts/Win_Software_Install_Report.ps1
Normal file
35
scripts/Win_Software_Install_Report.ps1
Normal file
@@ -0,0 +1,35 @@
|
||||
<#
|
||||
.Synopsis
|
||||
Software Install - Reports new installs
|
||||
.DESCRIPTION
|
||||
This will check for software install events in the application Event Viewer log
|
||||
If a number is provided as a command parameter it will search that number of days back.
|
||||
.EXAMPLE
|
||||
365
|
||||
.NOTES
|
||||
v1 silversword initial release 11/2021
|
||||
#>
|
||||
|
||||
$param1 = $args[0]
|
||||
|
||||
$ErrorActionPreference = 'silentlycontinue'
|
||||
if ($Args.Count -eq 0) {
|
||||
$TimeSpan = (Get-Date) - (New-TimeSpan -Day 1)
|
||||
}
|
||||
else {
|
||||
$TimeSpan = (Get-Date) - (New-TimeSpan -Day $param1)
|
||||
}
|
||||
|
||||
if (Get-WinEvent -FilterHashtable @{LogName = 'application'; ID = '11707'; StartTime = $TimeSpan }) {
|
||||
Write-Output "Software installed"
|
||||
Get-WinEvent -FilterHashtable @{LogName = 'application'; ID = '11707'; StartTime = $TimeSpan }
|
||||
exit 1
|
||||
}
|
||||
|
||||
{
|
||||
else
|
||||
Write-Output "No Software install events detected in the past 24 hours."
|
||||
exit 0
|
||||
}
|
||||
|
||||
Exit $LASTEXITCODE
|
||||
202
scripts/Win_Sophos_EndpointProtection_Install.ps1
Normal file
202
scripts/Win_Sophos_EndpointProtection_Install.ps1
Normal file
@@ -0,0 +1,202 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Installs Sophos Endpoint via the Sophos API https://developer.sophos.com/apis
|
||||
|
||||
.REQUIREMENTS
|
||||
You will need API credentials to use this script. The instructions are slightly different depending who you are.
|
||||
For Partners : https://developer.sophos.com/getting-started (Only Step 1 Required For API Credentials)
|
||||
For Organizations: https://developer.sophos.com/getting-started-organization (Only Step 1 Required For API Credentials)
|
||||
For Tenants : https://developer.sophos.com/getting-started-tenant (Only Step 1 Required For API Credentials)
|
||||
|
||||
.INSTRUCTIONS
|
||||
1. Get your API Credentials (Client Id, Client Secret) using the steps in the Requirements section
|
||||
2. In Tactical RMM, Go to Settings >> Global Settings >> Custom Fields and under Clients, create the following custom fields:
|
||||
a) SophosTenantName as type text
|
||||
b) SophosClientId as type text
|
||||
c) SophosClientSecret as type text
|
||||
3. In Tactical RMM, Right-click on each client and select Edit. Fill in the SophosTenantName, SophosClientId, and SophosClientSecret.
|
||||
Make sure the SophosTenantName is EXACTLY how it is displayed in your Sophos Partner / Central Dashboard. A partner can find the list of tenants on the left menu under Sophos Central Customers
|
||||
4. Create the follow script arguments
|
||||
a) -ClientId {{client.SophosClientId}}
|
||||
b) -ClientSecret {{client.SophosClientSecret}}
|
||||
c) -TenantName {{client.SophosTenantName}}
|
||||
d) -Products (Optional Parameter) - A list of products to install, comma-separated. Available options are: antivirus, intercept, mdr, deviceEncryption or all. Example - To install Antivirus, Intercept, and Device encryption you would pass "antivirus,intercept,deviceEncryption".
|
||||
|
||||
.NOTES
|
||||
V1.0 Initial Release by https://github.com/bc24fl/tacticalrmm-scripts/
|
||||
V1.1 Added error handling for each Invoke-Rest Call for easier troubleshooting and graceful exit.
|
||||
V1.2 Added support for more than 100 tenants.
|
||||
|
||||
#>
|
||||
|
||||
param(
|
||||
$ClientId,
|
||||
$ClientSecret,
|
||||
$TenantName,
|
||||
$Products
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrEmpty($ClientId)) {
|
||||
Write-Output "ClientId must be defined. Use -ClientId <value> to pass it."
|
||||
Exit 1
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($ClientSecret)) {
|
||||
Write-Output "ClientSecret must be defined. Use -ClientSecret <value> to pass it."
|
||||
Exit 1
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($TenantName)) {
|
||||
Write-Output "TenantName must be defined. Use -TenantName <value> to pass it."
|
||||
Exit 1
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($Products)) {
|
||||
Write-Output "No product options specified installing default antivirus and intercept."
|
||||
$Products = "antivirus,intercept"
|
||||
}
|
||||
|
||||
Write-Host "Running Sophos Endpoint Installation Script On: $env:COMPUTERNAME"
|
||||
|
||||
# Find if workstation or server. osInfo.ProductType returns 1 = workstation, 2 = domain controller, 3 = server
|
||||
$osInfo = Get-CimInstance -ClassName Win32_OperatingSystem
|
||||
|
||||
$urlAuth = "https://id.sophos.com/api/v2/oauth2/token"
|
||||
$urlWhoami = "https://api.central.sophos.com/whoami/v1"
|
||||
$urlTenant = "https://api.central.sophos.com/partner/v1/tenants?pageTotal=true"
|
||||
|
||||
$authBody = @{
|
||||
"grant_type"="client_credentials"
|
||||
"client_id"=$ClientId
|
||||
"client_secret"=$ClientSecret
|
||||
"scope"="token"
|
||||
}
|
||||
|
||||
$authResponse = (Invoke-RestMethod -Method 'post' -Uri $urlAuth -Body $authBody)
|
||||
$authToken = $authResponse.access_token
|
||||
$authHeaders = @{Authorization = "Bearer $authToken"}
|
||||
|
||||
if ($authToken.length -eq 0){
|
||||
Write-Host "Error, no authentication token received. Please check your api credentials. Exiting script."
|
||||
Exit 1
|
||||
}
|
||||
|
||||
$whoAmIResponse = (Invoke-RestMethod -Method 'Get' -headers $authHeaders -Uri $urlWhoami)
|
||||
$myId = $whoAmIResponse.Id
|
||||
$myIdType = $whoAmIResponse.idType
|
||||
|
||||
if ($myIdType.length -eq 0){
|
||||
Write-Host "Error, no Whoami Id Type received. Please check your api credentials or network connections. Exiting script."
|
||||
Exit 1
|
||||
}
|
||||
|
||||
if($myIdType -eq 'partner'){
|
||||
$requestHeaders =@{
|
||||
'Authorization'="Bearer $authToken"
|
||||
'X-Partner-ID'=$myId
|
||||
}
|
||||
}
|
||||
elseif($myIdType -eq 'organization') {
|
||||
$requestHeaders =@{
|
||||
'Authorization' = "Bearer $authToken"
|
||||
'X-Organization-ID' = $myId
|
||||
}
|
||||
}
|
||||
elseif($myIdType -eq 'tenant'){
|
||||
$requestHeaders =@{
|
||||
'Authorization' = "Bearer $authToken"
|
||||
'X-Tenant-ID' = $myId
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host "Error finding id type. This script only supports Partner, Organization, and Tenant API's."
|
||||
Exit 1
|
||||
}
|
||||
|
||||
# Cycle through all tenants until a tenant match, or all pages have exhausted.
|
||||
$currentPage = 1
|
||||
do {
|
||||
Write-Output "Looking for tenant on page $currentPage. Please wait..."
|
||||
|
||||
if ($currentPage -ge 2){
|
||||
Start-Sleep -s 5
|
||||
$urlTenant = "https://api.central.sophos.com/partner/v1/tenants?page=$currentPage"
|
||||
}
|
||||
|
||||
$tenantResponse = (Invoke-RestMethod -Method 'Get' -headers $requestHeaders -Uri $urlTenant)
|
||||
$tenants = $tenantResponse.items
|
||||
$totalPages = [int]$tenantResponse.pages.total
|
||||
|
||||
foreach ($tenant in $tenants) {
|
||||
if ($tenant.name -eq $TenantName){
|
||||
$tenantRegion = $tenant.dataRegion
|
||||
$tenantId = $tenant.id
|
||||
}
|
||||
}
|
||||
$currentPage += 1
|
||||
} until( $currentPage -gt $totalPages -Or ($tenantId.length -gt 1 ) )
|
||||
|
||||
if ($tenantId.length -eq 0){
|
||||
Write-Host "Error, no tenant found with the provided name. Please check the name and try again. Exiting script."
|
||||
Exit 1
|
||||
}
|
||||
|
||||
$requestHeaders =@{
|
||||
'Authorization' = "Bearer $authToken"
|
||||
'X-Tenant-ID' = $tenantId
|
||||
}
|
||||
|
||||
$urlEndpoint = "https://api-" + $tenantRegion + ".central.sophos.com/endpoint/v1/downloads"
|
||||
$endpointDownloadResponse = (Invoke-RestMethod -Method 'Get' -headers $requestHeaders -Uri $urlEndpoint)
|
||||
$endpointInstallers = $endpointDownloadResponse.installers
|
||||
|
||||
if ($endpointInstallers.length -eq 0){
|
||||
Write-Host "Error, no installers received. Please check your api credentials or network connections. Exiting script."
|
||||
Exit 1
|
||||
}
|
||||
|
||||
foreach ($installer in $endpointInstallers){
|
||||
|
||||
if ( ($installer.platform -eq "windows") -And ($installer.productName = "Sophos Endpoint Protection") ){
|
||||
|
||||
if ( ($osInfo.ProductType -eq 1) -And ($installer.type = "computer") ){
|
||||
# Workstation Install
|
||||
$installUrl = $installer.downloadUrl
|
||||
}
|
||||
elseif ( ( ($osInfo.ProductType -eq 2) -Or ($osInfo.ProductType -eq 3) ) -And ($installer.type = "server") ){
|
||||
# Server Install
|
||||
$installUrl = $installer.downloadUrl
|
||||
}
|
||||
else{
|
||||
Write-Host "Error, this script only supports producttype of 1) Work Station, 2) Domain Controller, or 3) Server."
|
||||
Exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try{
|
||||
Write-Host "Checking if Sophos Endpoint installed. Please wait..."
|
||||
$AppInstalled = & "choco" "list" "-li"
|
||||
|
||||
if ($AppInstalled -like '*Sophos Endpoint Agent*'){
|
||||
Write-Host "Sophos Endpoint is installed. Skipping installation."
|
||||
} else {
|
||||
Write-Host "Sophos Endpoint is NOT installed. Installing now..."
|
||||
|
||||
Write-Host "Downloading Sophos from " + $installUrl + " Please wait..."
|
||||
$tmpDir = [System.IO.Path]::GetTempPath()
|
||||
|
||||
$outpath = $tmpDir + "SophosSetup.exe"
|
||||
|
||||
Write-Host "Saving file to " + $outpath
|
||||
|
||||
Invoke-WebRequest -Uri $installUrl -OutFile $outpath
|
||||
|
||||
Write-Host "Running Sophos Setup... Please wait up to 20 minutes for install to complete."
|
||||
$appArgs = @("--products=" + $Products + " --quiet ")
|
||||
Start-Process -Filepath $outpath -ArgumentList $appArgs
|
||||
}
|
||||
}
|
||||
catch{
|
||||
Write-Host "Installation failed with error message: $($PSItem.ToString())"
|
||||
}
|
||||
10
scripts/Win_Splashtop_Get_ID.ps1
Normal file
10
scripts/Win_Splashtop_Get_ID.ps1
Normal file
@@ -0,0 +1,10 @@
|
||||
# Retrieve Splashtop SUUID from device registry.
|
||||
|
||||
if (!$ErrorCount -eq 0) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
$key = 'HKLM:\SOFTWARE\WOW6432Node\Splashtop Inc.\Splashtop Remote Server'
|
||||
(Get-ItemProperty -Path $key -Name SUUID).SUUID
|
||||
Write-Output $key.SUUID
|
||||
@@ -1,4 +1,4 @@
|
||||
Function Start-Cleanup {
|
||||
Function Start-Cleanup {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Automate cleaning up a C:\ drive with low disk space
|
||||
|
||||
16
scripts/Win_Storage_CheckPools.ps1
Normal file
16
scripts/Win_Storage_CheckPools.ps1
Normal file
@@ -0,0 +1,16 @@
|
||||
$pools = Get-VirtualDisk | select -ExpandProperty HealthStatus
|
||||
|
||||
$err = $False
|
||||
|
||||
ForEach ($pool in $pools) {
|
||||
if ($pool -ne "Healthy") {
|
||||
$err = $True
|
||||
}
|
||||
}
|
||||
|
||||
if ($err) {
|
||||
exit 1
|
||||
}
|
||||
else {
|
||||
exit 0
|
||||
}
|
||||
@@ -1,17 +1,34 @@
|
||||
$ErrorActionPreference= 'silentlycontinue'
|
||||
$TimeSpan = (Get-Date) - (New-TimeSpan -Day 1)
|
||||
if (Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-TaskScheduler/Operational';ID='106';StartTime=$TimeSpan} | Where-Object -Property Message -notlike *$env:COMPUTERNAME*)
|
||||
{
|
||||
Write-Output "New Task Has Been Added"
|
||||
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-TaskScheduler/Operational';ID='106';StartTime=$TimeSpan}
|
||||
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-TaskScheduler/Operational';ID='141';StartTime=$TimeSpan}
|
||||
exit 1
|
||||
<#
|
||||
.Synopsis
|
||||
Event Viewer - Task Scheduler New Item Notification
|
||||
.DESCRIPTION
|
||||
Event Viewer Monitor - Notify when new Task Scheduler item is created
|
||||
.EXAMPLE
|
||||
365
|
||||
.NOTES
|
||||
v1 dinger initial release
|
||||
v1.1 silversword adding command parameters 11/2021
|
||||
#>
|
||||
|
||||
|
||||
$ErrorActionPreference = 'silentlycontinue'
|
||||
if ($Args.Count -eq 0) {
|
||||
$TimeSpan = (Get-Date) - (New-TimeSpan -Day 1)
|
||||
}
|
||||
else {
|
||||
$TimeSpan = (Get-Date) - (New-TimeSpan -Day $param1)
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Write-Output "No changes with Task Scheduler"
|
||||
exit 0
|
||||
if (Get-WinEvent -FilterHashtable @{LogName = 'Microsoft-Windows-TaskScheduler/Operational'; ID = '106'; StartTime = $TimeSpan } | Where-Object -Property Message -notlike *$env:COMPUTERNAME*) {
|
||||
Write-Output "New Task Has Been Added"
|
||||
Get-WinEvent -FilterHashtable @{LogName = 'Microsoft-Windows-TaskScheduler/Operational'; ID = '106'; StartTime = $TimeSpan }
|
||||
Get-WinEvent -FilterHashtable @{LogName = 'Microsoft-Windows-TaskScheduler/Operational'; ID = '141'; StartTime = $TimeSpan }
|
||||
exit 1
|
||||
}
|
||||
|
||||
else {
|
||||
Write-Output "No changes with Task Scheduler"
|
||||
exit 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Used to enable or disable users
|
||||
User - Enable or disable a user
|
||||
.DESCRIPTION
|
||||
For installing packages using chocolatey. If you're running against more than 10, include the Hosts parameter to limit the speed. If running on more than 30 agents at a time make sure you also change the script timeout setting.
|
||||
Used to enable or disable local user
|
||||
.PARAMETER Name
|
||||
Required: Username
|
||||
.PARAMETER Enabled
|
||||
@@ -1,4 +1,4 @@
|
||||
<#
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Reset-WindowsUpdate.ps1 - Resets the Windows Update components
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Tactical RMM Patch management disables Windows Automatic Update settings by setting the registry key below to 1.
|
||||
# Run this to revert back to default
|
||||
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "AUOptions" -Value "0"
|
||||
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "AUOptions"
|
||||
1
scripts_wip/Win_Dell_Command_RunUpdate.bat
Normal file
1
scripts_wip/Win_Dell_Command_RunUpdate.bat
Normal file
@@ -0,0 +1 @@
|
||||
"c:\Program Files (x86)\Dell\CommandUpdate\dcu-cli.exe" /applyUpdates
|
||||
@@ -9,6 +9,6 @@ powershell Add-MpPreference -ExclusionPath "C:\Program Files\Mesh Agent\*"
|
||||
powershell Add-MpPreference -ExclusionPath C:\Windows\Temp\TRMM\*
|
||||
cd c:\temp\trmm
|
||||
powershell Invoke-WebRequest "deployment url" -Outfile tactical.exe
|
||||
"C:\Program Files\TacticalAgent\unins000.exe" /VERYSILENT /SUPPRESSMSGBOXES /FORCECLOSEAPPLICATIONS
|
||||
"C:\Program Files\TacticalAgent\unins000.exe" /VERYSILENT
|
||||
start tactical.exe
|
||||
powershell Remove-MpPreference -ExclusionPath C:\TEMP\TRMM
|
||||
@@ -65,7 +65,7 @@ if ($install -eq $NULL) {
|
||||
|
||||
If (Get-Service $serviceName -ErrorAction SilentlyContinue) {
|
||||
write-host ('Tactical RMM Is Already Installed')
|
||||
& 'C:\Program Files\TacticalAgent\unins000.exe' /VERYSILENT /SUPPRESSMSGBOXES /FORCECLOSEAPPLICATIONS
|
||||
& 'C:\Program Files\TacticalAgent\unins000.exe' /VERYSILENT
|
||||
Start-Sleep -s 20
|
||||
}
|
||||
Invoke-Expression ((new-object System.Net.WebClient).DownloadString($rmmURI))
|
||||
|
||||
70
scripts_wip/Win_TRMM_New_AgentviaAPI.ps1
Normal file
70
scripts_wip/Win_TRMM_New_AgentviaAPI.ps1
Normal file
@@ -0,0 +1,70 @@
|
||||
# From Chase 12/14/2021
|
||||
|
||||
function Create-TacticalRMMClient {
|
||||
|
||||
param(
|
||||
$APIKey,
|
||||
$Customer,
|
||||
$Site,
|
||||
$URL
|
||||
)
|
||||
|
||||
$Headers = @{
|
||||
"Content-Type" = "application/json"
|
||||
"X-API-KEY" = $APIKey
|
||||
}
|
||||
|
||||
$AllClients = Invoke-RestMethod -Uri "$URL/clients/" -Headers $Headers -Method GET -UseBasicParsing
|
||||
|
||||
$ClientCheck = ($AllClients | Where name -eq $Customer)
|
||||
|
||||
if (!$ClientCheck) {
|
||||
|
||||
$CreateClientBody = @"
|
||||
{
|
||||
"client": {"name": "$Customer"},
|
||||
"site": {"name": "Unspecified"},
|
||||
"custom_fields": []
|
||||
}
|
||||
"@
|
||||
|
||||
Invoke-RestMethod -URI "$URL/clients/" -Headers $Headers -Method Post -Body $CreateClientBody -UseBasicParsing | Out-Null
|
||||
|
||||
$NewSearch = Invoke-RestMethod -Uri "$URL/clients/" -Headers $Headers -Method GET -UseBasicParsing
|
||||
|
||||
$ClientID = ($NewSearch | Where { $_.name -eq $Customer }).id
|
||||
$ClientName = ($NewSearch | Where { $_.name -eq $Customer }).name
|
||||
$ClientSites = ($NewSearch | Where { $_.name -eq $Customer }).sites
|
||||
|
||||
}
|
||||
else {
|
||||
$ClientID = $ClientCheck.ID
|
||||
$ClientName = $ClientCheck.Name
|
||||
$ClientSites = $ClientCheck.Sites
|
||||
}
|
||||
|
||||
$SiteCheck = ($ClientSites | Where Name -eq $Site)
|
||||
|
||||
if (!$SiteCheck) {
|
||||
|
||||
$CreateSiteBody = @"
|
||||
{
|
||||
"site": {"client": $ClientID, "name": "$Site"},
|
||||
"custom_fields": []
|
||||
}
|
||||
"@
|
||||
|
||||
Invoke-RestMethod -Uri "$URL/clients/sites/" -Headers $Headers -Method POST -Body $CreateSiteBody -UseBasicParsing | Out-Null
|
||||
|
||||
$SiteNewSearch = (Invoke-RestMethod -Uri "$URL/clients/$ClientID/" -Headers $Headers -Method GET -UseBasicParsing).sites
|
||||
$SiteID = ($SiteNewSearch | Where name -eq $Site).id
|
||||
}
|
||||
|
||||
|
||||
|
||||
$Output = @()
|
||||
$Output += New-Object -TypeName psobject -Property @{ClientID = "$ClientID"; SiteID = "$SiteID" }
|
||||
|
||||
Write-Output $Output
|
||||
|
||||
}
|
||||
@@ -9,7 +9,7 @@ else {
|
||||
$ChkReg = Test-Path 'HKLM:\SOFTWARE\TacticalRMM\'
|
||||
If ($ChkReg -eq $True) {
|
||||
$regrmm = Get-ItemProperty -Path HKLM:\SOFTWARE\TacticalRMM\
|
||||
& 'C:\Program Files\TacticalAgent\unins000.exe' /VERYSILENT /SUPPRESSMSGBOXES /FORCECLOSEAPPLICATIONS
|
||||
& 'C:\Program Files\TacticalAgent\unins000.exe' /VERYSILENT
|
||||
start-sleep -s 20
|
||||
}
|
||||
dsregcmd.exe /debug /leave
|
||||
|
||||
78
update.sh
78
update.sh
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_VERSION="126"
|
||||
SCRIPT_VERSION="127"
|
||||
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/update.sh'
|
||||
LATEST_SETTINGS_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/api/tacticalrmm/tacticalrmm/settings.py'
|
||||
YELLOW='\033[1;33m'
|
||||
@@ -41,12 +41,6 @@ if [ "$ORIGUSER" != "$USER" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CHECK_TOO_OLD=$(grep natsapi /etc/nginx/sites-available/rmm.conf)
|
||||
if ! [[ $CHECK_TOO_OLD ]]; then
|
||||
printf >&2 "${RED}Your version of TRMM is no longer supported. Refusing to update.${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMP_SETTINGS=$(mktemp -p "" "rmmsettings_XXXXXXXXXX")
|
||||
curl -s -L "${LATEST_SETTINGS_URL}" > ${TMP_SETTINGS}
|
||||
SETTINGS_FILE="/rmm/api/tacticalrmm/tacticalrmm/settings.py"
|
||||
@@ -68,27 +62,39 @@ NATS_SERVER_VER=$(grep "^NATS_SERVER_VER" "$TMP_SETTINGS" | awk -F'[= "]' '{prin
|
||||
CURRENT_PIP_VER=$(grep "^PIP_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
|
||||
CURRENT_NPM_VER=$(grep "^NPM_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
|
||||
|
||||
|
||||
if [ -f /etc/systemd/system/natsapi.service ]; then
|
||||
printf >&2 "${GREEN}Removing natsapi.service${NC}\n"
|
||||
sudo systemctl stop natsapi.service
|
||||
sudo systemctl disable natsapi.service
|
||||
sudo rm -f /etc/systemd/system/natsapi.service
|
||||
sudo systemctl daemon-reload
|
||||
fi
|
||||
|
||||
cls() {
|
||||
printf "\033c"
|
||||
}
|
||||
|
||||
CHECK_HAS_DAPHNE=$(grep daphne.sock /etc/nginx/sites-available/rmm.conf)
|
||||
if ! [[ $CHECK_HAS_DAPHNE ]]; then
|
||||
cls
|
||||
echo -ne "${RED}Nginx config changes required before continuing.${NC}\n"
|
||||
echo -ne "${RED}Please check the v0.5.0 release notes on github for instructions, then re-run this script.${NC}\n"
|
||||
echo -ne "${YELLOW}https://github.com/wh1te909/tacticalrmm/releases/tag/v0.5.0${NC}\n"
|
||||
echo -ne "${RED}Aborting...${NC}\n"
|
||||
exit 1
|
||||
|
||||
CHECK_NATS_LIMITNOFILE=$(grep LimitNOFILE /etc/systemd/system/nats.service)
|
||||
if ! [[ $CHECK_NATS_LIMITNOFILE ]]; then
|
||||
|
||||
sudo rm -f /etc/systemd/system/nats.service
|
||||
|
||||
natsservice="$(cat << EOF
|
||||
[Unit]
|
||||
Description=NATS Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
PrivateTmp=true
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/nats-server -c /rmm/api/tacticalrmm/nats-rmm.conf
|
||||
ExecReload=/usr/bin/kill -s HUP \$MAINPID
|
||||
ExecStop=/usr/bin/kill -s SIGINT \$MAINPID
|
||||
User=${USER}
|
||||
Group=www-data
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
LimitNOFILE=1000000
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
)"
|
||||
echo "${natsservice}" | sudo tee /etc/systemd/system/nats.service > /dev/null
|
||||
sudo systemctl daemon-reload
|
||||
fi
|
||||
|
||||
if ! sudo nginx -t > /dev/null 2>&1; then
|
||||
@@ -99,30 +105,6 @@ if ! sudo nginx -t > /dev/null 2>&1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -f /etc/systemd/system/daphne.service ]; then
|
||||
daphneservice="$(cat << EOF
|
||||
[Unit]
|
||||
Description=django channels daemon
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=${USER}
|
||||
Group=www-data
|
||||
WorkingDirectory=/rmm/api/tacticalrmm
|
||||
Environment="PATH=/rmm/api/env/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
ExecStart=/rmm/api/env/bin/daphne -u /rmm/daphne.sock tacticalrmm.asgi:application
|
||||
Restart=always
|
||||
RestartSec=3s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
)"
|
||||
echo "${daphneservice}" | sudo tee /etc/systemd/system/daphne.service > /dev/null
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable daphne.service
|
||||
fi
|
||||
|
||||
if [ ! -f /etc/systemd/system/nats-api.service ]; then
|
||||
natsapi="$(cat << EOF
|
||||
[Unit]
|
||||
|
||||
2168
web/package-lock.json
generated
2168
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,18 +10,18 @@
|
||||
"test:e2e:ci": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.12.0",
|
||||
"@quasar/extras": "^1.12.2",
|
||||
"apexcharts": "^3.27.1",
|
||||
"axios": "^0.24.0",
|
||||
"dotenv": "^8.6.0",
|
||||
"qrcode.vue": "^3.2.2",
|
||||
"quasar": "^2.3.2",
|
||||
"quasar": "^2.3.4",
|
||||
"vue3-ace-editor": "^2.2.1",
|
||||
"vue3-apexcharts": "^1.4.0",
|
||||
"vuex": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@quasar/app": "^3.2.3",
|
||||
"@quasar/app": "^3.2.5",
|
||||
"@quasar/cli": "^1.2.2"
|
||||
},
|
||||
"browserslist": [
|
||||
@@ -30,4 +30,4 @@
|
||||
"last 2 Edge versions",
|
||||
"last 1 Safari versions"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,11 +564,11 @@ export default {
|
||||
removeAgent(agent) {
|
||||
this.$q
|
||||
.dialog({
|
||||
title: `Please type <code style="color:red">${agent.hostname}</code> to confirm deletion.`,
|
||||
title: `Please type <code style="color:red">yes</code> in the box below to confirm deletion.`,
|
||||
prompt: {
|
||||
model: "",
|
||||
type: "text",
|
||||
isValid: val => val === agent.hostname,
|
||||
isValid: val => val === "yes",
|
||||
},
|
||||
cancel: true,
|
||||
ok: { label: "Uninstall", color: "negative" },
|
||||
|
||||
@@ -127,6 +127,9 @@
|
||||
<q-item clickable v-close-popup @click="openHelp('docs')">
|
||||
<q-item-section>Documentation</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="openHelp('github')">
|
||||
<q-item-section>GitHub Repo</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="openHelp('bug')">
|
||||
<q-item-section>Bug Report</q-item-section>
|
||||
</q-item>
|
||||
@@ -227,6 +230,9 @@ export default {
|
||||
openHelp(mode) {
|
||||
let url;
|
||||
switch (mode) {
|
||||
case "github":
|
||||
url = "https://github.com/wh1te909/tacticalrmm/";
|
||||
break;
|
||||
case "docs":
|
||||
url = "https://wh1te909.github.io/tacticalrmm/";
|
||||
break;
|
||||
|
||||
@@ -95,7 +95,7 @@ const columns = [
|
||||
field: "cpu_percent",
|
||||
align: "left",
|
||||
sortable: true,
|
||||
sort: (a, b, rowA, rowB) => parseInt(b) < parseInt(a),
|
||||
sort: (a, b, rowA, rowB) => parseFloat(b) < parseFloat(a),
|
||||
},
|
||||
{
|
||||
name: "membytes",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<template v-slot:before>
|
||||
<q-tabs dense v-model="tab" vertical class="text-primary">
|
||||
<q-tab name="general" label="General" />
|
||||
<q-tab name="customfields" label="Custom Fields" />
|
||||
<q-tab name="patch" label="Patches" />
|
||||
</q-tabs>
|
||||
</template>
|
||||
@@ -15,7 +16,7 @@
|
||||
<q-space />
|
||||
<q-btn icon="close" flat round dense v-close-popup />
|
||||
</q-card-section>
|
||||
<div class="scroll" style="max-height: 65vh">
|
||||
<div class="scroll" style="height: 65vh; max-height: 65vh">
|
||||
<q-tab-panels v-model="tab" animated transition-prev="jump-up" transition-next="jump-up">
|
||||
<!-- general -->
|
||||
<q-tab-panel name="general">
|
||||
@@ -105,11 +106,18 @@
|
||||
<q-checkbox v-model="agent.overdue_text_alert" label="Get overdue sms alerts" />
|
||||
<q-checkbox v-model="agent.overdue_dashboard_alert" label="Get overdue dashboard alerts" />
|
||||
</q-card-section>
|
||||
<div class="text-h6" v-if="customFields.length > 0">Custom Fields</div>
|
||||
</q-tab-panel>
|
||||
|
||||
<!-- custom fields -->
|
||||
<q-tab-panel name="customfields">
|
||||
<div class="text-subtitle" v-if="customFields.length === 0">
|
||||
No agent custom fields found. Go to **Settings > Global Settings > Custom Settings**
|
||||
</div>
|
||||
<q-card-section v-for="field in customFields" :key="field.id">
|
||||
<CustomField v-model="custom_fields[field.name]" :field="field" />
|
||||
</q-card-section>
|
||||
</q-tab-panel>
|
||||
|
||||
<!-- patch -->
|
||||
<q-tab-panel name="patch">
|
||||
<PatchPolicyForm :agent="agent" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<q-dialog ref="dialogRef" @hide="onDialogHide" persistent @keydown.esc="onDialogHide" :maximized="maximized">
|
||||
<q-card class="q-dialog-plugin" :style="maximized ? '' : 'width: 80vw; max-width: 90vw'">
|
||||
<q-card class="q-dialog-plugin" :style="maximized ? '' : 'width: 90vw; max-width: 90vw'">
|
||||
<q-bar>
|
||||
{{ title }}
|
||||
<q-space />
|
||||
@@ -15,81 +15,83 @@
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
<q-form @submit="submitForm">
|
||||
<div class="q-pt-sm q-px-sm row">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
class="col-2"
|
||||
:readonly="readonly"
|
||||
v-model="formScript.name"
|
||||
label="Name"
|
||||
:rules="[val => !!val || '*Required']"
|
||||
/>
|
||||
<q-select
|
||||
class="q-pl-sm col-2"
|
||||
:readonly="readonly"
|
||||
options-dense
|
||||
filled
|
||||
dense
|
||||
v-model="formScript.shell"
|
||||
:options="shellOptions"
|
||||
emit-value
|
||||
map-options
|
||||
label="Shell Type"
|
||||
/>
|
||||
<q-input
|
||||
type="number"
|
||||
class="q-pl-sm col-2"
|
||||
filled
|
||||
dense
|
||||
:readonly="readonly"
|
||||
v-model.number="formScript.default_timeout"
|
||||
label="Timeout (seconds)"
|
||||
:rules="[val => val >= 5 || 'Minimum is 5']"
|
||||
/>
|
||||
<tactical-dropdown
|
||||
class="q-pl-sm col-3"
|
||||
filled
|
||||
v-model="formScript.category"
|
||||
:options="categories"
|
||||
use-input
|
||||
clearable
|
||||
new-value-mode="add-unique"
|
||||
filterable
|
||||
label="Category"
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
/>
|
||||
<q-input
|
||||
class="q-pl-sm col-3"
|
||||
filled
|
||||
dense
|
||||
:readonly="readonly"
|
||||
v-model="formScript.description"
|
||||
label="Description"
|
||||
/>
|
||||
<tactical-dropdown
|
||||
v-model="formScript.args"
|
||||
label="Script Arguments (press Enter after typing each argument)"
|
||||
class="q-pb-sm col-12 row"
|
||||
filled
|
||||
use-input
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
:readonly="readonly"
|
||||
<div class="row q-pa-sm">
|
||||
<div class="col-4 q-gutter-sm q-pr-sm">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
:readonly="readonly"
|
||||
v-model="formScript.name"
|
||||
label="Name"
|
||||
:rules="[val => !!val || '*Required']"
|
||||
hide-bottom-space
|
||||
/>
|
||||
<q-input filled dense :readonly="readonly" v-model="formScript.description" label="Description" />
|
||||
<q-select
|
||||
:readonly="readonly"
|
||||
options-dense
|
||||
filled
|
||||
dense
|
||||
v-model="formScript.shell"
|
||||
:options="shellOptions"
|
||||
emit-value
|
||||
map-options
|
||||
label="Shell Type"
|
||||
/>
|
||||
<tactical-dropdown
|
||||
filled
|
||||
v-model="formScript.category"
|
||||
:options="categories"
|
||||
use-input
|
||||
clearable
|
||||
new-value-mode="add-unique"
|
||||
filterable
|
||||
label="Category"
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
/>
|
||||
<tactical-dropdown
|
||||
v-model="formScript.args"
|
||||
label="Script Arguments (press Enter after typing each argument)"
|
||||
filled
|
||||
use-input
|
||||
multiple
|
||||
hide-dropdown-icon
|
||||
input-debounce="0"
|
||||
new-value-mode="add"
|
||||
:readonly="readonly"
|
||||
/>
|
||||
<q-input
|
||||
type="number"
|
||||
filled
|
||||
dense
|
||||
:readonly="readonly"
|
||||
v-model.number="formScript.default_timeout"
|
||||
label="Timeout (seconds)"
|
||||
:rules="[val => val >= 5 || 'Minimum is 5']"
|
||||
hide-bottom-space
|
||||
/>
|
||||
<q-input
|
||||
label="Syntax"
|
||||
type="textarea"
|
||||
style="height: 150px; overflow-y: auto; resize: none"
|
||||
v-model="formScript.syntax"
|
||||
dense
|
||||
filled
|
||||
:readonly="readonly"
|
||||
/>
|
||||
</div>
|
||||
<v-ace-editor
|
||||
v-model:value="formScript.script_body"
|
||||
class="col-8"
|
||||
:lang="formScript.shell === 'cmd' ? 'batchfile' : formScript.shell"
|
||||
:theme="$q.dark.isActive ? 'tomorrow_night_eighties' : 'tomorrow'"
|
||||
:style="{ height: `${maximized ? '87vh' : '64vh'}` }"
|
||||
wrap
|
||||
:printMargin="false"
|
||||
:options="{ fontSize: '14px' }"
|
||||
/>
|
||||
</div>
|
||||
<v-ace-editor
|
||||
v-model:value="code"
|
||||
:lang="formScript.shell === 'cmd' ? 'batchfile' : formScript.shell"
|
||||
:theme="$q.dark.isActive ? 'tomorrow_night_eighties' : 'tomorrow'"
|
||||
:style="{ height: `${maximized ? '72vh' : '64vh'}` }"
|
||||
wrap
|
||||
:printMargin="false"
|
||||
:options="{ fontSize: '14px' }"
|
||||
/>
|
||||
<q-card-actions>
|
||||
<tactical-dropdown
|
||||
style="width: 350px"
|
||||
@@ -109,7 +111,7 @@
|
||||
dense
|
||||
flat
|
||||
label="Test Script"
|
||||
:disable="!agent || !code || !formScript.default_timeout"
|
||||
:disable="!agent || !formScript.script_body || !formScript.default_timeout"
|
||||
@click="openTestScriptModal"
|
||||
/>
|
||||
</template>
|
||||
@@ -175,11 +177,10 @@ export default {
|
||||
|
||||
// script form logic
|
||||
const script = props.script
|
||||
? ref(Object.assign({}, props.script))
|
||||
: ref({ shell: "powershell", default_timeout: 90, args: [] });
|
||||
? ref(Object.assign({}, { ...props.script, script_body: "" }))
|
||||
: ref({ shell: "powershell", default_timeout: 90, args: [], script_body: "" });
|
||||
|
||||
if (props.clone) script.value.name = `(Copy) ${script.value.name}`;
|
||||
const code = ref("");
|
||||
const maximized = ref(false);
|
||||
const loading = ref(false);
|
||||
const agentLoading = ref(false);
|
||||
@@ -199,16 +200,13 @@ export default {
|
||||
// get code if editing or cloning script
|
||||
if (props.script)
|
||||
downloadScript(script.value.id, { with_snippets: props.readonly }).then(r => {
|
||||
code.value = r.code;
|
||||
script.value.script_body = r.code;
|
||||
});
|
||||
|
||||
async function submitForm() {
|
||||
loading.value = true;
|
||||
let result = "";
|
||||
try {
|
||||
// base64 encode the script text
|
||||
script.value.code_base64 = btoa(code.value);
|
||||
|
||||
// edit existing script
|
||||
if (props.script && !props.clone) {
|
||||
result = await editScript(script.value);
|
||||
@@ -229,7 +227,7 @@ export default {
|
||||
$q.dialog({
|
||||
component: TestScriptModal,
|
||||
componentProps: {
|
||||
script: { ...script.value, code: code.value },
|
||||
script: { ...script.value },
|
||||
agent: agent.value,
|
||||
},
|
||||
});
|
||||
@@ -245,7 +243,6 @@ export default {
|
||||
return {
|
||||
// reactive data
|
||||
formScript: script.value,
|
||||
code,
|
||||
maximized,
|
||||
loading,
|
||||
agentOptions,
|
||||
|
||||
@@ -118,12 +118,12 @@ export default {
|
||||
// base64 encode the script and delete file
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
script.value.code_base64 = reader.result.replace(/^data:.+;base64,/, "");
|
||||
script.value.script_body = reader.result;
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file.value);
|
||||
reader.readAsText(file.value);
|
||||
} else {
|
||||
script.value.code_base64 = "";
|
||||
script.value.script_body = "";
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export default {
|
||||
async function runTestScript() {
|
||||
loading.value = true;
|
||||
const data = {
|
||||
code: props.script.code,
|
||||
code: props.script.script_body,
|
||||
timeout: props.script.default_timeout,
|
||||
args: props.script.args,
|
||||
shell: props.script.shell,
|
||||
|
||||
@@ -65,7 +65,9 @@ export function useCheckModal({ editCheck, initialState }) {
|
||||
|
||||
async function getAgentServiceOptions() {
|
||||
const { services } = await fetchAgent(check.value.agent)
|
||||
serviceOptions.value = Object.freeze(services.map(service => ({ label: service.display_name, value: service.name })))
|
||||
|
||||
const tmp = services.map(service => ({ label: service.display_name, value: service.name }))
|
||||
serviceOptions.value = Object.freeze(tmp.sort((a, b) => a.label.localeCompare(b.label)))
|
||||
check.value.svc_name = serviceOptions.value[0].value
|
||||
check.value.svc_display_name = serviceOptions.value[0].label
|
||||
}
|
||||
@@ -1194,4 +1196,4 @@ export const defaultServiceOptions = [
|
||||
value: "tacticalagent",
|
||||
label: "Tactical RMM Agent"
|
||||
}
|
||||
]
|
||||
].sort((a, b) => a.label.localeCompare(b.label))
|
||||
Reference in New Issue
Block a user