Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd80ccd2c5 | ||
|
|
9dc0b24399 | ||
|
|
747954e6fb | ||
|
|
274f4f227e | ||
|
|
92197d8d49 | ||
|
|
aee06920eb | ||
|
|
5111b17d3c | ||
|
|
2849d8f45d | ||
|
|
bac60d9bd4 | ||
|
|
9c797162f4 | ||
|
|
09d184e2f8 | ||
|
|
7bca618906 | ||
|
|
67607103e9 | ||
|
|
73c9956fe4 | ||
|
|
b42f2ffe33 | ||
|
|
30a3f185ef | ||
|
|
4f1b41227f | ||
|
|
83b9d13ec9 | ||
|
|
cee7896c37 | ||
|
|
0377009d2b | ||
|
|
b472f3644e | ||
|
|
5d8ea837c8 | ||
|
|
82de6bc849 | ||
|
|
cb4bc68c48 | ||
|
|
3ce6b38247 | ||
|
|
716c0fe979 | ||
|
|
c993790b7a | ||
|
|
aa32286531 | ||
|
|
6f94abde00 | ||
|
|
fa19538c9d | ||
|
|
84c858b878 | ||
|
|
865de142d4 | ||
|
|
9118162553 | ||
|
|
f4fc6ee9b4 | ||
|
|
108c38d57b | ||
|
|
a1d73eb830 | ||
|
|
997906a610 | ||
|
|
b6e5d120d3 | ||
|
|
d469d0b435 | ||
|
|
e9f823e000 | ||
|
|
d7fb76ba74 | ||
|
|
b7dde1a0d9 | ||
|
|
15095d8c23 | ||
|
|
dfbebc7606 | ||
|
|
895309d93d | ||
|
|
bcf50e821a | ||
|
|
30195800dd | ||
|
|
6532b0f149 | ||
|
|
5e108e4057 | ||
|
|
c2b2f4d222 | ||
|
|
bc4329ad21 | ||
|
|
aec6d1b2f6 |
2
.github/workflows/ci-tests.yml
vendored
2
.github/workflows/ci-tests.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: harmon758/postgresql-action@v1
|
- uses: harmon758/postgresql-action@v1
|
||||||
with:
|
with:
|
||||||
postgresql version: "14"
|
postgresql version: "15"
|
||||||
postgresql db: "pipeline"
|
postgresql db: "pipeline"
|
||||||
postgresql user: "pipeline"
|
postgresql user: "pipeline"
|
||||||
postgresql password: "pipeline123456"
|
postgresql password: "pipeline123456"
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
deb https://nginx.org/packages/debian/ bullseye nginx
|
|
||||||
deb-src https://nginx.org/packages/debian/ bullseye nginx
|
|
||||||
@@ -41,11 +41,15 @@
|
|||||||
with_items:
|
with_items:
|
||||||
- "{{ base_pkgs }}"
|
- "{{ base_pkgs }}"
|
||||||
|
|
||||||
|
- name: set arch fact
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
goarch: "{{ 'amd64' if ansible_architecture == 'x86_64' else 'arm64' }}"
|
||||||
|
|
||||||
- name: download and install golang
|
- name: download and install golang
|
||||||
tags: golang
|
tags: golang
|
||||||
become: yes
|
become: yes
|
||||||
ansible.builtin.unarchive:
|
ansible.builtin.unarchive:
|
||||||
src: "https://go.dev/dl/go{{ go_ver }}.linux-amd64.tar.gz"
|
src: "https://go.dev/dl/go{{ go_ver }}.linux-{{ goarch }}.tar.gz"
|
||||||
dest: /usr/local
|
dest: /usr/local
|
||||||
remote_src: yes
|
remote_src: yes
|
||||||
|
|
||||||
@@ -111,7 +115,7 @@
|
|||||||
tags: postgres
|
tags: postgres
|
||||||
become: yes
|
become: yes
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
content: "deb http://apt.postgresql.org/pub/repos/apt bullseye-pgdg main"
|
content: "deb http://apt.postgresql.org/pub/repos/apt {{ ansible_distribution_release }}-pgdg main"
|
||||||
dest: /etc/apt/sources.list.d/pgdg.list
|
dest: /etc/apt/sources.list.d/pgdg.list
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
@@ -128,7 +132,7 @@
|
|||||||
tags: postgres
|
tags: postgres
|
||||||
become: yes
|
become: yes
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
pkg: postgresql-14
|
pkg: postgresql-15
|
||||||
state: present
|
state: present
|
||||||
update_cache: yes
|
update_cache: yes
|
||||||
|
|
||||||
@@ -140,7 +144,7 @@
|
|||||||
enabled: yes
|
enabled: yes
|
||||||
state: started
|
state: started
|
||||||
|
|
||||||
- name: setup database
|
- name: setup trmm database
|
||||||
tags: postgres
|
tags: postgres
|
||||||
become: yes
|
become: yes
|
||||||
become_user: postgres
|
become_user: postgres
|
||||||
@@ -153,6 +157,23 @@
|
|||||||
psql -c "ALTER ROLE {{ db_user }} SET timezone TO 'UTC'"
|
psql -c "ALTER ROLE {{ db_user }} SET timezone TO 'UTC'"
|
||||||
psql -c "ALTER ROLE {{ db_user }} CREATEDB"
|
psql -c "ALTER ROLE {{ db_user }} CREATEDB"
|
||||||
psql -c "GRANT ALL PRIVILEGES ON DATABASE tacticalrmm TO {{ db_user }}"
|
psql -c "GRANT ALL PRIVILEGES ON DATABASE tacticalrmm TO {{ db_user }}"
|
||||||
|
psql -c "ALTER DATABASE tacticalrmm OWNER TO {{ db_user }}"
|
||||||
|
psql -c "GRANT USAGE, CREATE ON SCHEMA PUBLIC TO {{ db_user }}"
|
||||||
|
|
||||||
|
- name: setup mesh database
|
||||||
|
tags: postgres
|
||||||
|
become: yes
|
||||||
|
become_user: postgres
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: |
|
||||||
|
psql -c "CREATE DATABASE meshcentral"
|
||||||
|
psql -c "CREATE USER {{ mesh_db_user }} WITH PASSWORD '{{ mesh_db_passwd }}'"
|
||||||
|
psql -c "ALTER ROLE {{ mesh_db_user }} SET client_encoding TO 'utf8'"
|
||||||
|
psql -c "ALTER ROLE {{ mesh_db_user }} SET default_transaction_isolation TO 'read committed'"
|
||||||
|
psql -c "ALTER ROLE {{ mesh_db_user }} SET timezone TO 'UTC'"
|
||||||
|
psql -c "GRANT ALL PRIVILEGES ON DATABASE meshcentral TO {{ mesh_db_user }}"
|
||||||
|
psql -c "ALTER DATABASE meshcentral OWNER TO {{ mesh_db_user }}"
|
||||||
|
psql -c "GRANT USAGE, CREATE ON SCHEMA PUBLIC TO {{ mesh_db_user }}"
|
||||||
|
|
||||||
- name: create repo dirs
|
- name: create repo dirs
|
||||||
become: yes
|
become: yes
|
||||||
@@ -202,7 +223,7 @@
|
|||||||
- name: download and extract nats
|
- name: download and extract nats
|
||||||
tags: nats
|
tags: nats
|
||||||
ansible.builtin.unarchive:
|
ansible.builtin.unarchive:
|
||||||
src: "https://github.com/nats-io/nats-server/releases/download/v{{ nats_server_ver.stdout }}/nats-server-v{{ nats_server_ver.stdout }}-linux-amd64.tar.gz"
|
src: "https://github.com/nats-io/nats-server/releases/download/v{{ nats_server_ver.stdout }}/nats-server-v{{ nats_server_ver.stdout }}-linux-{{ goarch }}.tar.gz"
|
||||||
dest: "{{ nats_tmp.path }}"
|
dest: "{{ nats_tmp.path }}"
|
||||||
remote_src: yes
|
remote_src: yes
|
||||||
|
|
||||||
@@ -211,7 +232,7 @@
|
|||||||
become: yes
|
become: yes
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
remote_src: yes
|
remote_src: yes
|
||||||
src: "{{ nats_tmp.path }}/nats-server-v{{ nats_server_ver.stdout }}-linux-amd64/nats-server"
|
src: "{{ nats_tmp.path }}/nats-server-v{{ nats_server_ver.stdout }}-linux-{{ goarch }}/nats-server"
|
||||||
dest: /usr/local/bin/nats-server
|
dest: /usr/local/bin/nats-server
|
||||||
owner: "{{ user }}"
|
owner: "{{ user }}"
|
||||||
group: "{{ user }}"
|
group: "{{ user }}"
|
||||||
@@ -227,7 +248,7 @@
|
|||||||
- name: download nodejs setup
|
- name: download nodejs setup
|
||||||
tags: nodejs
|
tags: nodejs
|
||||||
ansible.builtin.get_url:
|
ansible.builtin.get_url:
|
||||||
url: https://deb.nodesource.com/setup_16.x
|
url: https://deb.nodesource.com/setup_18.x
|
||||||
dest: "{{ nodejs_tmp.path }}/setup_node.sh"
|
dest: "{{ nodejs_tmp.path }}/setup_node.sh"
|
||||||
mode: "0755"
|
mode: "0755"
|
||||||
|
|
||||||
@@ -314,8 +335,8 @@
|
|||||||
- name: add nginx repo
|
- name: add nginx repo
|
||||||
tags: nginx
|
tags: nginx
|
||||||
become: yes
|
become: yes
|
||||||
ansible.builtin.copy:
|
ansible.builtin.template:
|
||||||
src: nginx.repo
|
src: nginx.repo.j2
|
||||||
dest: /etc/apt/sources.list.d/nginx.list
|
dest: /etc/apt/sources.list.d/nginx.list
|
||||||
owner: "root"
|
owner: "root"
|
||||||
group: "root"
|
group: "root"
|
||||||
@@ -391,12 +412,16 @@
|
|||||||
enabled: yes
|
enabled: yes
|
||||||
state: restarted
|
state: restarted
|
||||||
|
|
||||||
|
- name: set natsapi fact
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
natsapi: "{{ 'nats-api' if ansible_architecture == 'x86_64' else 'nats-api-arm64' }}"
|
||||||
|
|
||||||
- name: copy nats-api bin
|
- name: copy nats-api bin
|
||||||
tags: nats-api
|
tags: nats-api
|
||||||
become: yes
|
become: yes
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
remote_src: yes
|
remote_src: yes
|
||||||
src: "{{ backend_dir }}/natsapi/bin/nats-api"
|
src: "{{ backend_dir }}/natsapi/bin/{{ natsapi }}"
|
||||||
dest: /usr/local/bin/nats-api
|
dest: /usr/local/bin/nats-api
|
||||||
owner: "{{ user }}"
|
owner: "{{ user }}"
|
||||||
group: "{{ user }}"
|
group: "{{ user }}"
|
||||||
@@ -482,39 +507,6 @@
|
|||||||
- { src: nats-server.systemd.j2, dest: /etc/systemd/system/nats.service }
|
- { src: nats-server.systemd.j2, dest: /etc/systemd/system/nats.service }
|
||||||
- { src: mesh.systemd.j2, dest: /etc/systemd/system/meshcentral.service }
|
- { src: mesh.systemd.j2, dest: /etc/systemd/system/meshcentral.service }
|
||||||
|
|
||||||
- name: import mongodb repo signing key
|
|
||||||
tags: mongo
|
|
||||||
become: yes
|
|
||||||
ansible.builtin.apt_key:
|
|
||||||
url: https://www.mongodb.org/static/pgp/server-4.4.asc
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: setup mongodb repo
|
|
||||||
tags: mongo
|
|
||||||
become: yes
|
|
||||||
ansible.builtin.copy:
|
|
||||||
content: "deb https://repo.mongodb.org/apt/debian buster/mongodb-org/4.4 main"
|
|
||||||
dest: /etc/apt/sources.list.d/mongodb-org-4.4.list
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: install mongodb
|
|
||||||
tags: mongo
|
|
||||||
become: yes
|
|
||||||
ansible.builtin.apt:
|
|
||||||
pkg: mongodb-org
|
|
||||||
state: present
|
|
||||||
update_cache: yes
|
|
||||||
|
|
||||||
- name: ensure mongodb enabled and started
|
|
||||||
tags: mongo
|
|
||||||
become: yes
|
|
||||||
ansible.builtin.service:
|
|
||||||
name: mongod
|
|
||||||
enabled: yes
|
|
||||||
state: started
|
|
||||||
|
|
||||||
- name: get mesh_ver
|
- name: get mesh_ver
|
||||||
tags: mesh
|
tags: mesh
|
||||||
ansible.builtin.shell: grep "^MESH_VER" {{ settings_file }} | awk -F'[= "]' '{print $5}'
|
ansible.builtin.shell: grep "^MESH_VER" {{ settings_file }} | awk -F'[= "]' '{print $5}'
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
{
|
{
|
||||||
"settings": {
|
"settings": {
|
||||||
"Cert": "{{ mesh }}",
|
"Cert": "{{ mesh }}",
|
||||||
"MongoDb": "mongodb://127.0.0.1:27017",
|
|
||||||
"MongoDbName": "meshcentral",
|
|
||||||
"WANonly": true,
|
"WANonly": true,
|
||||||
"Minify": 1,
|
"Minify": 1,
|
||||||
"Port": 4430,
|
"Port": 4430,
|
||||||
@@ -10,19 +8,25 @@
|
|||||||
"RedirPort": 800,
|
"RedirPort": 800,
|
||||||
"AllowLoginToken": true,
|
"AllowLoginToken": true,
|
||||||
"AllowFraming": true,
|
"AllowFraming": true,
|
||||||
"AgentPong": 300,
|
"AgentPing": 35,
|
||||||
"AllowHighQualityDesktop": true,
|
"AllowHighQualityDesktop": true,
|
||||||
"TlsOffload": "127.0.0.1",
|
"TlsOffload": "127.0.0.1",
|
||||||
"agentCoreDump": false,
|
"agentCoreDump": false,
|
||||||
"Compression": true,
|
"Compression": true,
|
||||||
"WsCompression": true,
|
"WsCompression": true,
|
||||||
"AgentWsCompression": true,
|
"AgentWsCompression": true,
|
||||||
"MaxInvalidLogin": { "time": 5, "count": 5, "coolofftime": 30 }
|
"MaxInvalidLogin": { "time": 5, "count": 5, "coolofftime": 30 },
|
||||||
|
"postgres": {
|
||||||
|
"user": "{{ mesh_db_user }}",
|
||||||
|
"password": "{{ mesh_db_passwd }}",
|
||||||
|
"port": "5432",
|
||||||
|
"host": "localhost"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"domains": {
|
"domains": {
|
||||||
"": {
|
"": {
|
||||||
"Title": "Tactical RMM",
|
"Title": "Tactical RMM Dev",
|
||||||
"Title2": "Tactical RMM",
|
"Title2": "Tactical RMM Dev",
|
||||||
"NewAccounts": false,
|
"NewAccounts": false,
|
||||||
"CertUrl": "https://{{ mesh }}:443/",
|
"CertUrl": "https://{{ mesh }}:443/",
|
||||||
"GeoLocation": true,
|
"GeoLocation": true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=MeshCentral Server
|
Description=MeshCentral Server
|
||||||
After=network.target mongod.service nginx.service
|
After=network.target postgresql.service nginx.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
|
|||||||
2
ansible/roles/trmm_dev/templates/nginx.repo.j2
Normal file
2
ansible/roles/trmm_dev/templates/nginx.repo.j2
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
deb https://nginx.org/packages/debian/ {{ ansible_distribution_release }} nginx
|
||||||
|
deb-src https://nginx.org/packages/debian/ {{ ansible_distribution_release }} nginx
|
||||||
@@ -13,6 +13,8 @@
|
|||||||
mesh_password: "changeme"
|
mesh_password: "changeme"
|
||||||
db_user: "changeme"
|
db_user: "changeme"
|
||||||
db_passwd: "changeme"
|
db_passwd: "changeme"
|
||||||
|
mesh_db_user: "changeme"
|
||||||
|
mesh_db_passwd: "changeme"
|
||||||
django_secret: "changeme"
|
django_secret: "changeme"
|
||||||
django_user: "changeme"
|
django_user: "changeme"
|
||||||
django_password: "changeme"
|
django_password: "changeme"
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ from rest_framework.serializers import (
|
|||||||
SerializerMethodField,
|
SerializerMethodField,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from tacticalrmm.helpers import get_webdomain
|
||||||
|
|
||||||
from .models import APIKey, Role, User
|
from .models import APIKey, Role, User
|
||||||
|
|
||||||
|
|
||||||
@@ -61,7 +63,7 @@ class TOTPSetupSerializer(ModelSerializer):
|
|||||||
|
|
||||||
def get_qr_url(self, obj):
|
def get_qr_url(self, obj):
|
||||||
return pyotp.totp.TOTP(obj.totp_key).provisioning_uri(
|
return pyotp.totp.TOTP(obj.totp_key).provisioning_uri(
|
||||||
obj.username, issuer_name="Tactical RMM"
|
obj.username, issuer_name=get_webdomain()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from agents.models import Agent
|
||||||
|
from tacticalrmm.constants import AGENT_DEFER
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def find_duplicates(self, lst):
|
||||||
|
return list(set([item for item in lst if lst.count(item) > 1]))
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
for agent in Agent.objects.defer(*AGENT_DEFER).prefetch_related(
|
||||||
|
"custom_fields__field"
|
||||||
|
):
|
||||||
|
if dupes := self.find_duplicates(
|
||||||
|
[i.field.name for i in agent.custom_fields.all()]
|
||||||
|
):
|
||||||
|
for dupe in dupes:
|
||||||
|
cf = list(
|
||||||
|
agent.custom_fields.filter(field__name=dupe).order_by("id")
|
||||||
|
)
|
||||||
|
to_delete = cf[:-1]
|
||||||
|
for i in to_delete:
|
||||||
|
i.delete()
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 4.2.3 on 2023-07-18 01:15
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("core", "0037_coresettings_open_ai_model_and_more"),
|
||||||
|
("agents", "0056_alter_agent_time_zone"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="agentcustomfield",
|
||||||
|
unique_together={("agent", "field")},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1011,6 +1011,9 @@ class AgentCustomField(models.Model):
|
|||||||
default=list,
|
default=list,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = (("agent", "field"),)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.field.name
|
return self.field.name
|
||||||
|
|
||||||
|
|||||||
@@ -570,6 +570,13 @@ def install_agent(request):
|
|||||||
from agents.utils import get_agent_url
|
from agents.utils import get_agent_url
|
||||||
from core.utils import token_is_valid
|
from core.utils import token_is_valid
|
||||||
|
|
||||||
|
insecure = getattr(settings, "TRMM_INSECURE", False)
|
||||||
|
|
||||||
|
if insecure and request.data["installMethod"] in {"exe", "powershell"}:
|
||||||
|
return notify_error(
|
||||||
|
"Not available in insecure mode. Please use the 'Manual' method."
|
||||||
|
)
|
||||||
|
|
||||||
# TODO rework this ghetto validation hack
|
# TODO rework this ghetto validation hack
|
||||||
# https://github.com/amidaware/tacticalrmm/issues/1461
|
# https://github.com/amidaware/tacticalrmm/issues/1461
|
||||||
try:
|
try:
|
||||||
@@ -672,6 +679,9 @@ def install_agent(request):
|
|||||||
if int(request.data["power"]):
|
if int(request.data["power"]):
|
||||||
cmd.append("--power")
|
cmd.append("--power")
|
||||||
|
|
||||||
|
if insecure:
|
||||||
|
cmd.append("--insecure")
|
||||||
|
|
||||||
resp["cmd"] = " ".join(str(i) for i in cmd)
|
resp["cmd"] = " ".join(str(i) for i in cmd)
|
||||||
else:
|
else:
|
||||||
install_flags.insert(0, f"sudo ./{inno}")
|
install_flags.insert(0, f"sudo ./{inno}")
|
||||||
@@ -680,6 +690,8 @@ def install_agent(request):
|
|||||||
resp["cmd"] = (
|
resp["cmd"] = (
|
||||||
dl + f" && chmod +x {inno} && " + " ".join(str(i) for i in cmd)
|
dl + f" && chmod +x {inno} && " + " ".join(str(i) for i in cmd)
|
||||||
)
|
)
|
||||||
|
if insecure:
|
||||||
|
resp["cmd"] += " --insecure"
|
||||||
|
|
||||||
resp["url"] = download_url
|
resp["url"] = download_url
|
||||||
|
|
||||||
|
|||||||
@@ -25,12 +25,16 @@ class GetAddAlerts(APIView):
|
|||||||
def patch(self, request):
|
def patch(self, request):
|
||||||
# top 10 alerts for dashboard icon
|
# top 10 alerts for dashboard icon
|
||||||
if "top" in request.data.keys():
|
if "top" in request.data.keys():
|
||||||
alerts = Alert.objects.filter(
|
alerts = (
|
||||||
resolved=False, snoozed=False, hidden=False
|
Alert.objects.filter_by_role(request.user)
|
||||||
).order_by("alert_time")[: int(request.data["top"])]
|
.filter(resolved=False, snoozed=False, hidden=False)
|
||||||
count = Alert.objects.filter(
|
.order_by("alert_time")[: int(request.data["top"])]
|
||||||
resolved=False, snoozed=False, hidden=False
|
)
|
||||||
).count()
|
count = (
|
||||||
|
Alert.objects.filter_by_role(request.user)
|
||||||
|
.filter(resolved=False, snoozed=False, hidden=False)
|
||||||
|
.count()
|
||||||
|
)
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"alerts_count": count,
|
"alerts_count": count,
|
||||||
|
|||||||
@@ -172,6 +172,31 @@ class TestCheckViews(TacticalTestCase):
|
|||||||
|
|
||||||
self.check_not_authenticated("post", url)
|
self.check_not_authenticated("post", url)
|
||||||
|
|
||||||
|
def test_reset_all_checks_status(self):
|
||||||
|
# setup data
|
||||||
|
agent = baker.make_recipe("agents.agent")
|
||||||
|
check = baker.make_recipe("checks.diskspace_check", agent=agent)
|
||||||
|
baker.make("checks.CheckResult", assigned_check=check, agent=agent)
|
||||||
|
baker.make(
|
||||||
|
"checks.CheckHistory",
|
||||||
|
check_id=check.id,
|
||||||
|
agent_id=agent.agent_id,
|
||||||
|
_quantity=30,
|
||||||
|
)
|
||||||
|
baker.make(
|
||||||
|
"checks.CheckHistory",
|
||||||
|
check_id=check.id,
|
||||||
|
agent_id=agent.agent_id,
|
||||||
|
_quantity=30,
|
||||||
|
)
|
||||||
|
|
||||||
|
url = f"{base_url}/{agent.agent_id}/resetall/"
|
||||||
|
|
||||||
|
resp = self.client.post(url)
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
|
self.check_not_authenticated("post", url)
|
||||||
|
|
||||||
def test_add_memory_check(self):
|
def test_add_memory_check(self):
|
||||||
url = f"{base_url}/"
|
url = f"{base_url}/"
|
||||||
agent = baker.make_recipe("agents.agent")
|
agent = baker.make_recipe("agents.agent")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ urlpatterns = [
|
|||||||
path("", views.GetAddChecks.as_view()),
|
path("", views.GetAddChecks.as_view()),
|
||||||
path("<int:pk>/", views.GetUpdateDeleteCheck.as_view()),
|
path("<int:pk>/", views.GetUpdateDeleteCheck.as_view()),
|
||||||
path("<int:pk>/reset/", views.ResetCheck.as_view()),
|
path("<int:pk>/reset/", views.ResetCheck.as_view()),
|
||||||
|
path("<agent:agent_id>/resetall/", views.ResetAllChecksStatus.as_view()),
|
||||||
path("<agent:agent_id>/run/", views.run_checks),
|
path("<agent:agent_id>/run/", views.run_checks),
|
||||||
path("<int:pk>/history/", views.GetCheckHistory.as_view()),
|
path("<int:pk>/history/", views.GetCheckHistory.as_view()),
|
||||||
path("<str:target>/<int:pk>/csbulkrun/", views.bulk_run_checks),
|
path("<str:target>/<int:pk>/csbulkrun/", views.bulk_run_checks),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from datetime import datetime as dt
|
from datetime import datetime as dt
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Prefetch, Q
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils import timezone as djangotime
|
from django.utils import timezone as djangotime
|
||||||
from rest_framework.decorators import api_view, permission_classes
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
@@ -13,7 +13,7 @@ from rest_framework.views import APIView
|
|||||||
from agents.models import Agent
|
from agents.models import Agent
|
||||||
from alerts.models import Alert
|
from alerts.models import Alert
|
||||||
from automation.models import Policy
|
from automation.models import Policy
|
||||||
from tacticalrmm.constants import CheckStatus, CheckType
|
from tacticalrmm.constants import AGENT_DEFER, CheckStatus, CheckType
|
||||||
from tacticalrmm.exceptions import NatsDown
|
from tacticalrmm.exceptions import NatsDown
|
||||||
from tacticalrmm.helpers import notify_error
|
from tacticalrmm.helpers import notify_error
|
||||||
from tacticalrmm.nats_utils import abulk_nats_command
|
from tacticalrmm.nats_utils import abulk_nats_command
|
||||||
@@ -122,15 +122,54 @@ class ResetCheck(APIView):
|
|||||||
result.save()
|
result.save()
|
||||||
|
|
||||||
# resolve any alerts that are open
|
# resolve any alerts that are open
|
||||||
alert = Alert.create_or_return_check_alert(
|
if alert := Alert.create_or_return_check_alert(
|
||||||
result.assigned_check, agent=result.agent, skip_create=True
|
result.assigned_check, agent=result.agent, skip_create=True
|
||||||
)
|
):
|
||||||
if alert:
|
|
||||||
alert.resolve()
|
alert.resolve()
|
||||||
|
|
||||||
return Response("The check status was reset")
|
return Response("The check status was reset")
|
||||||
|
|
||||||
|
|
||||||
|
class ResetAllChecksStatus(APIView):
|
||||||
|
permission_classes = [IsAuthenticated, ChecksPerms]
|
||||||
|
|
||||||
|
def post(self, request, agent_id):
|
||||||
|
agent = get_object_or_404(
|
||||||
|
Agent.objects.defer(*AGENT_DEFER)
|
||||||
|
.select_related(
|
||||||
|
"policy",
|
||||||
|
"policy__alert_template",
|
||||||
|
"alert_template",
|
||||||
|
)
|
||||||
|
.prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
"checkresults",
|
||||||
|
queryset=CheckResult.objects.select_related("assigned_check"),
|
||||||
|
),
|
||||||
|
"agentchecks",
|
||||||
|
),
|
||||||
|
agent_id=agent_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not _has_perm_on_agent(request.user, agent.agent_id):
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
for check in agent.get_checks_with_policies():
|
||||||
|
try:
|
||||||
|
result = check.check_result
|
||||||
|
result.status = CheckStatus.PASSING
|
||||||
|
result.save()
|
||||||
|
if alert := Alert.create_or_return_check_alert(
|
||||||
|
result.assigned_check, agent=agent, skip_create=True
|
||||||
|
):
|
||||||
|
alert.resolve()
|
||||||
|
except:
|
||||||
|
# check hasn't run yet, no check result entry
|
||||||
|
continue
|
||||||
|
|
||||||
|
return Response("All checks status were reset")
|
||||||
|
|
||||||
|
|
||||||
class GetCheckHistory(APIView):
|
class GetCheckHistory(APIView):
|
||||||
permission_classes = [IsAuthenticated, ChecksPerms]
|
permission_classes = [IsAuthenticated, ChecksPerms]
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import re
|
|||||||
import uuid
|
import uuid
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.db.models import Count, Exists, OuterRef, Prefetch, prefetch_related_objects
|
from django.db.models import Count, Exists, OuterRef, Prefetch, prefetch_related_objects
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils import timezone as djangotime
|
from django.utils import timezone as djangotime
|
||||||
@@ -288,6 +289,9 @@ class AgentDeployment(APIView):
|
|||||||
return Response(DeploymentSerializer(deps, many=True).data)
|
return Response(DeploymentSerializer(deps, many=True).data)
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
|
if getattr(settings, "TRMM_INSECURE", False):
|
||||||
|
return notify_error("Not available in insecure mode")
|
||||||
|
|
||||||
from accounts.models import User
|
from accounts.models import User
|
||||||
|
|
||||||
site = get_object_or_404(Site, pk=request.data["site"])
|
site = get_object_or_404(Site, pk=request.data["site"])
|
||||||
@@ -343,6 +347,9 @@ class GenerateAgent(APIView):
|
|||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
|
|
||||||
def get(self, request, uid):
|
def get(self, request, uid):
|
||||||
|
if getattr(settings, "TRMM_INSECURE", False):
|
||||||
|
return notify_error("Not available in insecure mode")
|
||||||
|
|
||||||
from tacticalrmm.utils import generate_winagent_exe
|
from tacticalrmm.utils import generate_winagent_exe
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -12,6 +12,16 @@ if [ "${HAS_SYSTEMD}" != 'systemd' ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ $DISPLAY ]]; then
|
||||||
|
echo "ERROR: Display detected. Installer only supports running headless, i.e from ssh."
|
||||||
|
echo "If you cannot ssh in then please run 'sudo systemctl isolate multi-user.target' to switch to a non-graphical user session and run the installer again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEBUG=0
|
||||||
|
INSECURE=0
|
||||||
|
NOMESH=0
|
||||||
|
|
||||||
agentDL='agentDLChange'
|
agentDL='agentDLChange'
|
||||||
meshDL='meshDLChange'
|
meshDL='meshDLChange'
|
||||||
|
|
||||||
@@ -67,8 +77,14 @@ RemoveOldAgent() {
|
|||||||
|
|
||||||
InstallMesh() {
|
InstallMesh() {
|
||||||
if [ -f /etc/os-release ]; then
|
if [ -f /etc/os-release ]; then
|
||||||
distroID=$(. /etc/os-release; echo $ID)
|
distroID=$(
|
||||||
distroIDLIKE=$(. /etc/os-release; echo $ID_LIKE)
|
. /etc/os-release
|
||||||
|
echo $ID
|
||||||
|
)
|
||||||
|
distroIDLIKE=$(
|
||||||
|
. /etc/os-release
|
||||||
|
echo $ID_LIKE
|
||||||
|
)
|
||||||
if [[ " ${deb[*]} " =~ " ${distroID} " ]]; then
|
if [[ " ${deb[*]} " =~ " ${distroID} " ]]; then
|
||||||
set_locale_deb
|
set_locale_deb
|
||||||
elif [[ " ${deb[*]} " =~ " ${distroIDLIKE} " ]]; then
|
elif [[ " ${deb[*]} " =~ " ${distroIDLIKE} " ]]; then
|
||||||
@@ -80,11 +96,9 @@ InstallMesh() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
meshTmpDir=$(mktemp -d -t "mesh-XXXXXXXXX")
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
meshTmpDir='/root/meshtemp'
|
meshTmpDir='/root/meshtemp'
|
||||||
mkdir -p ${meshTmpDir}
|
mkdir -p $meshTmpDir
|
||||||
fi
|
|
||||||
meshTmpBin="${meshTmpDir}/meshagent"
|
meshTmpBin="${meshTmpDir}/meshagent"
|
||||||
wget --no-check-certificate -q -O ${meshTmpBin} ${meshDL}
|
wget --no-check-certificate -q -O ${meshTmpBin} ${meshDL}
|
||||||
chmod +x ${meshTmpBin}
|
chmod +x ${meshTmpBin}
|
||||||
@@ -120,6 +134,19 @@ if [ $# -ne 0 ] && [ $1 == 'uninstall' ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--debug) DEBUG=1 ;;
|
||||||
|
--insecure) INSECURE=1 ;;
|
||||||
|
--nomesh) NOMESH=1 ;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: Unknown parameter: $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
RemoveOldAgent
|
RemoveOldAgent
|
||||||
|
|
||||||
echo "Downloading tactical agent..."
|
echo "Downloading tactical agent..."
|
||||||
@@ -132,7 +159,7 @@ chmod +x ${agentBin}
|
|||||||
|
|
||||||
MESH_NODE_ID=""
|
MESH_NODE_ID=""
|
||||||
|
|
||||||
if [ $# -ne 0 ] && [ $1 == '--nomesh' ]; then
|
if [[ $NOMESH -eq 1 ]]; then
|
||||||
echo "Skipping mesh install"
|
echo "Skipping mesh install"
|
||||||
else
|
else
|
||||||
if [ -f "${meshSystemBin}" ]; then
|
if [ -f "${meshSystemBin}" ]; then
|
||||||
@@ -150,23 +177,28 @@ if [ ! -d "${agentBinPath}" ]; then
|
|||||||
mkdir -p ${agentBinPath}
|
mkdir -p ${agentBinPath}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $# -ne 0 ] && [ $1 == '--debug' ]; then
|
|
||||||
INSTALL_CMD="${agentBin} -m install -api ${apiURL} -client-id ${clientID} -site-id ${siteID} -agent-type ${agentType} -auth ${token} -log debug"
|
|
||||||
else
|
|
||||||
INSTALL_CMD="${agentBin} -m install -api ${apiURL} -client-id ${clientID} -site-id ${siteID} -agent-type ${agentType} -auth ${token}"
|
INSTALL_CMD="${agentBin} -m install -api ${apiURL} -client-id ${clientID} -site-id ${siteID} -agent-type ${agentType} -auth ${token}"
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${MESH_NODE_ID}" != '' ]; then
|
if [ "${MESH_NODE_ID}" != '' ]; then
|
||||||
INSTALL_CMD+=" -meshnodeid ${MESH_NODE_ID}"
|
INSTALL_CMD+=" --meshnodeid ${MESH_NODE_ID}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $DEBUG -eq 1 ]]; then
|
||||||
|
INSTALL_CMD+=" --log debug"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $INSECURE -eq 1 ]]; then
|
||||||
|
INSTALL_CMD+=" --insecure"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${proxy}" != '' ]; then
|
if [ "${proxy}" != '' ]; then
|
||||||
INSTALL_CMD+=" -proxy ${proxy}"
|
INSTALL_CMD+=" --proxy ${proxy}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
eval ${INSTALL_CMD}
|
eval ${INSTALL_CMD}
|
||||||
|
|
||||||
tacticalsvc="$(cat << EOF
|
tacticalsvc="$(
|
||||||
|
cat <<EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Tactical RMM Linux Agent
|
Description=Tactical RMM Linux Agent
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import os
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from tacticalrmm.helpers import get_nats_ports
|
from tacticalrmm.helpers import get_nats_internal_protocol, get_nats_ports
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
@@ -21,9 +21,10 @@ class Command(BaseCommand):
|
|||||||
ssl = "disable"
|
ssl = "disable"
|
||||||
|
|
||||||
nats_std_port, _ = get_nats_ports()
|
nats_std_port, _ = get_nats_ports()
|
||||||
|
proto = get_nats_internal_protocol()
|
||||||
config = {
|
config = {
|
||||||
"key": settings.SECRET_KEY,
|
"key": settings.SECRET_KEY,
|
||||||
"natsurl": f"tls://{settings.ALLOWED_HOSTS[0]}:{nats_std_port}",
|
"natsurl": f"{proto}://{settings.ALLOWED_HOSTS[0]}:{nats_std_port}",
|
||||||
"user": db["USER"],
|
"user": db["USER"],
|
||||||
"pass": db["PASSWORD"],
|
"pass": db["PASSWORD"],
|
||||||
"host": db["HOST"],
|
"host": db["HOST"],
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from django.core.management import call_command
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from core.utils import clear_entire_cache
|
from core.utils import clear_entire_cache
|
||||||
@@ -10,3 +11,4 @@ class Command(BaseCommand):
|
|||||||
self.stdout.write(self.style.WARNING("Cleaning the cache"))
|
self.stdout.write(self.style.WARNING("Cleaning the cache"))
|
||||||
clear_entire_cache()
|
clear_entire_cache()
|
||||||
self.stdout.write(self.style.SUCCESS("Cache was cleared!"))
|
self.stdout.write(self.style.SUCCESS("Cache was cleared!"))
|
||||||
|
call_command("fix_dupe_agent_customfields")
|
||||||
|
|||||||
@@ -502,3 +502,27 @@ class TestCoreUtils(TacticalTestCase):
|
|||||||
r,
|
r,
|
||||||
"http://tactical-meshcentral:4443/meshagents?id=4&meshid=abc123&installflags=0",
|
"http://tactical-meshcentral:4443/meshagents?id=4&meshid=abc123&installflags=0",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@override_settings(TRMM_INSECURE=True)
|
||||||
|
def test_get_meshagent_url_insecure(self):
|
||||||
|
r = get_meshagent_url(
|
||||||
|
ident=MeshAgentIdent.DARWIN_UNIVERSAL,
|
||||||
|
plat="darwin",
|
||||||
|
mesh_site="https://mesh.example.com",
|
||||||
|
mesh_device_id="abc123",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
r,
|
||||||
|
"http://mesh.example.com:4430/meshagents?id=abc123&installflags=2&meshinstall=10005",
|
||||||
|
)
|
||||||
|
|
||||||
|
r = get_meshagent_url(
|
||||||
|
ident=MeshAgentIdent.WIN64,
|
||||||
|
plat="windows",
|
||||||
|
mesh_site="https://mesh.example.com",
|
||||||
|
mesh_device_id="abc123",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
r,
|
||||||
|
"http://mesh.example.com:4430/meshagents?id=4&meshid=abc123&installflags=0",
|
||||||
|
)
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ def get_mesh_ws_url() -> str:
|
|||||||
|
|
||||||
if settings.DOCKER_BUILD:
|
if settings.DOCKER_BUILD:
|
||||||
uri = f"{settings.MESH_WS_URL}/control.ashx?auth={token}"
|
uri = f"{settings.MESH_WS_URL}/control.ashx?auth={token}"
|
||||||
|
else:
|
||||||
|
if getattr(settings, "TRMM_INSECURE", False):
|
||||||
|
site = core.mesh_site.replace("https", "ws")
|
||||||
|
uri = f"{site}:4430/control.ashx?auth={token}"
|
||||||
else:
|
else:
|
||||||
site = core.mesh_site.replace("https", "wss")
|
site = core.mesh_site.replace("https", "wss")
|
||||||
uri = f"{site}/control.ashx?auth={token}"
|
uri = f"{site}/control.ashx?auth={token}"
|
||||||
@@ -181,6 +185,8 @@ def get_meshagent_url(
|
|||||||
) -> str:
|
) -> str:
|
||||||
if settings.DOCKER_BUILD:
|
if settings.DOCKER_BUILD:
|
||||||
base = settings.MESH_WS_URL.replace("ws://", "http://")
|
base = settings.MESH_WS_URL.replace("ws://", "http://")
|
||||||
|
elif getattr(settings, "TRMM_INSECURE", False):
|
||||||
|
base = mesh_site.replace("https", "http") + ":4430"
|
||||||
else:
|
else:
|
||||||
base = mesh_site
|
base = mesh_site
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,39 @@
|
|||||||
adrf==0.1.1
|
adrf==0.1.1
|
||||||
asgiref==3.7.2
|
asgiref==3.7.2
|
||||||
celery==5.3.1
|
celery==5.3.1
|
||||||
certifi==2023.5.7
|
certifi==2023.7.22
|
||||||
cffi==1.15.1
|
cffi==1.15.1
|
||||||
channels==4.0.0
|
channels==4.0.0
|
||||||
channels_redis==4.1.0
|
channels_redis==4.1.0
|
||||||
cryptography==41.0.1
|
cryptography==41.0.3
|
||||||
daphne==4.0.0
|
daphne==4.0.0
|
||||||
Django==4.2.3
|
Django==4.2.4
|
||||||
django-cors-headers==4.1.0
|
django-cors-headers==4.2.0
|
||||||
django-ipware==5.0.0
|
django-ipware==5.0.0
|
||||||
django-rest-knox==4.2.0
|
django-rest-knox==4.2.0
|
||||||
djangorestframework==3.14.0
|
djangorestframework==3.14.0
|
||||||
drf-spectacular==0.26.3
|
drf-spectacular==0.26.4
|
||||||
hiredis==2.2.3
|
hiredis==2.2.3
|
||||||
meshctrl==0.1.15
|
meshctrl==0.1.15
|
||||||
msgpack==1.0.5
|
msgpack==1.0.5
|
||||||
nats-py==2.3.1
|
nats-py==2.3.1
|
||||||
packaging==23.1
|
packaging==23.1
|
||||||
psutil==5.9.5
|
psutil==5.9.5
|
||||||
psycopg2-binary==2.9.6
|
psycopg[binary]==3.1.10
|
||||||
pycparser==2.21
|
pycparser==2.21
|
||||||
pycryptodome==3.18.0
|
pycryptodome==3.18.0
|
||||||
pyotp==2.8.0
|
pyotp==2.9.0
|
||||||
pyparsing==3.1.0
|
pyparsing==3.1.1
|
||||||
pytz==2023.3
|
pytz==2023.3
|
||||||
qrcode==7.4.2
|
qrcode==7.4.2
|
||||||
redis==4.5.5
|
redis==4.5.5
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
sqlparse==0.4.4
|
sqlparse==0.4.4
|
||||||
twilio==8.3.0
|
twilio==8.5.0
|
||||||
urllib3==2.0.3
|
urllib3==2.0.4
|
||||||
uWSGI==2.0.21
|
uWSGI==2.0.22
|
||||||
validators==0.20.0
|
validators==0.20.0
|
||||||
vine==5.0.0
|
vine==5.0.0
|
||||||
websockets==11.0.3
|
websockets==11.0.3
|
||||||
zipp==3.15.0
|
zipp==3.16.2
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ class GetEditActionService(APIView):
|
|||||||
# win service action
|
# win service action
|
||||||
def post(self, request, agent_id, svcname):
|
def post(self, request, agent_id, svcname):
|
||||||
agent = get_object_or_404(Agent, agent_id=agent_id)
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
||||||
|
if agent.is_posix:
|
||||||
|
return notify_error("Please use 'Recover Connection' instead.")
|
||||||
action = request.data["sv_action"]
|
action = request.data["sv_action"]
|
||||||
data = {
|
data = {
|
||||||
"func": "winsvcaction",
|
"func": "winsvcaction",
|
||||||
|
|||||||
@@ -42,6 +42,13 @@ def get_nats_ports() -> tuple[int, int]:
|
|||||||
return nats_standard_port, nats_websocket_port
|
return nats_standard_port, nats_websocket_port
|
||||||
|
|
||||||
|
|
||||||
|
def get_nats_internal_protocol() -> str:
|
||||||
|
if getattr(settings, "TRMM_INSECURE", False):
|
||||||
|
return "nats"
|
||||||
|
|
||||||
|
return "tls"
|
||||||
|
|
||||||
|
|
||||||
def date_is_in_past(*, datetime_obj: "datetime", agent_tz: str) -> bool:
|
def date_is_in_past(*, datetime_obj: "datetime", agent_tz: str) -> bool:
|
||||||
"""
|
"""
|
||||||
datetime_obj must be a naive datetime
|
datetime_obj must be a naive datetime
|
||||||
@@ -66,8 +73,9 @@ def rand_range(min: int, max: int) -> float:
|
|||||||
|
|
||||||
def setup_nats_options() -> dict[str, Any]:
|
def setup_nats_options() -> dict[str, Any]:
|
||||||
nats_std_port, _ = get_nats_ports()
|
nats_std_port, _ = get_nats_ports()
|
||||||
|
proto = get_nats_internal_protocol()
|
||||||
opts = {
|
opts = {
|
||||||
"servers": f"tls://{settings.ALLOWED_HOSTS[0]}:{nats_std_port}",
|
"servers": f"{proto}://{settings.ALLOWED_HOSTS[0]}:{nats_std_port}",
|
||||||
"user": "tacticalrmm",
|
"user": "tacticalrmm",
|
||||||
"name": "trmm-django",
|
"name": "trmm-django",
|
||||||
"password": settings.SECRET_KEY,
|
"password": settings.SECRET_KEY,
|
||||||
|
|||||||
@@ -20,27 +20,27 @@ MAC_UNINSTALL = BASE_DIR / "core" / "mac_uninstall.sh"
|
|||||||
AUTH_USER_MODEL = "accounts.User"
|
AUTH_USER_MODEL = "accounts.User"
|
||||||
|
|
||||||
# latest release
|
# latest release
|
||||||
TRMM_VERSION = "0.16.0"
|
TRMM_VERSION = "0.16.4"
|
||||||
|
|
||||||
# https://github.com/amidaware/tacticalrmm-web
|
# https://github.com/amidaware/tacticalrmm-web
|
||||||
WEB_VERSION = "0.101.25"
|
WEB_VERSION = "0.101.29"
|
||||||
|
|
||||||
# bump this version everytime vue code is changed
|
# bump this version everytime vue code is changed
|
||||||
# to alert user they need to manually refresh their browser
|
# to alert user they need to manually refresh their browser
|
||||||
APP_VER = "0.0.182"
|
APP_VER = "0.0.184"
|
||||||
|
|
||||||
# https://github.com/amidaware/rmmagent
|
# https://github.com/amidaware/rmmagent
|
||||||
LATEST_AGENT_VER = "2.4.9"
|
LATEST_AGENT_VER = "2.5.0"
|
||||||
|
|
||||||
MESH_VER = "1.1.6"
|
MESH_VER = "1.1.9"
|
||||||
|
|
||||||
NATS_SERVER_VER = "2.9.19"
|
NATS_SERVER_VER = "2.9.21"
|
||||||
|
|
||||||
# for the update script, bump when need to recreate venv
|
# for the update script, bump when need to recreate venv
|
||||||
PIP_VER = "37"
|
PIP_VER = "38"
|
||||||
|
|
||||||
SETUPTOOLS_VER = "67.8.0"
|
SETUPTOOLS_VER = "68.0.0"
|
||||||
WHEEL_VER = "0.40.0"
|
WHEEL_VER = "0.41.1"
|
||||||
|
|
||||||
AGENT_BASE_URL = "https://agents.tacticalrmm.com"
|
AGENT_BASE_URL = "https://agents.tacticalrmm.com"
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,12 @@ from tacticalrmm.constants import (
|
|||||||
DebugLogType,
|
DebugLogType,
|
||||||
ScriptShell,
|
ScriptShell,
|
||||||
)
|
)
|
||||||
from tacticalrmm.helpers import get_certs, get_nats_ports, notify_error
|
from tacticalrmm.helpers import (
|
||||||
|
get_certs,
|
||||||
|
get_nats_internal_protocol,
|
||||||
|
get_nats_ports,
|
||||||
|
notify_error,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def generate_winagent_exe(
|
def generate_winagent_exe(
|
||||||
@@ -204,10 +209,6 @@ def reload_nats() -> None:
|
|||||||
nats_std_port, nats_ws_port = get_nats_ports()
|
nats_std_port, nats_ws_port = get_nats_ports()
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
"tls": {
|
|
||||||
"cert_file": cert_file,
|
|
||||||
"key_file": key_file,
|
|
||||||
},
|
|
||||||
"authorization": {"users": users},
|
"authorization": {"users": users},
|
||||||
"max_payload": 67108864,
|
"max_payload": 67108864,
|
||||||
"port": nats_std_port, # internal only
|
"port": nats_std_port, # internal only
|
||||||
@@ -217,6 +218,12 @@ def reload_nats() -> None:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if get_nats_internal_protocol() == "tls":
|
||||||
|
config["tls"] = {
|
||||||
|
"cert_file": cert_file,
|
||||||
|
"key_file": key_file,
|
||||||
|
}
|
||||||
|
|
||||||
if "NATS_HTTP_PORT" in os.environ:
|
if "NATS_HTTP_PORT" in os.environ:
|
||||||
config["http_port"] = int(os.getenv("NATS_HTTP_PORT")) # type: ignore
|
config["http_port"] = int(os.getenv("NATS_HTTP_PORT")) # type: ignore
|
||||||
elif hasattr(settings, "NATS_HTTP_PORT"):
|
elif hasattr(settings, "NATS_HTTP_PORT"):
|
||||||
|
|||||||
39
backup.sh
39
backup.sh
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
SCRIPT_VERSION="25"
|
SCRIPT_VERSION="29"
|
||||||
|
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
@@ -16,7 +16,7 @@ fi
|
|||||||
if [[ $* == *--schedule* ]]; then
|
if [[ $* == *--schedule* ]]; then
|
||||||
(
|
(
|
||||||
crontab -l 2>/dev/null
|
crontab -l 2>/dev/null
|
||||||
echo "0 0 * * * /rmm/backup.sh --auto"
|
echo "0 0 * * * /rmm/backup.sh --auto > /dev/null 2>&1"
|
||||||
) | crontab -
|
) | crontab -
|
||||||
|
|
||||||
if [ ! -d /rmmbackups ]; then
|
if [ ! -d /rmmbackups ]; then
|
||||||
@@ -49,6 +49,10 @@ if [ -d /meshcentral/meshcentral-backup ]; then
|
|||||||
rm -rf /meshcentral/meshcentral-backup/*
|
rm -rf /meshcentral/meshcentral-backup/*
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -d /meshcentral/meshcentral-backups ]; then
|
||||||
|
rm -rf /meshcentral/meshcentral-backups/*
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -d /meshcentral/meshcentral-coredumps ]; then
|
if [ -d /meshcentral/meshcentral-coredumps ]; then
|
||||||
rm -f /meshcentral/meshcentral-coredumps/*
|
rm -f /meshcentral/meshcentral-coredumps/*
|
||||||
fi
|
fi
|
||||||
@@ -68,24 +72,41 @@ mkdir ${tmp_dir}/confd
|
|||||||
POSTGRES_USER=$(/rmm/api/env/bin/python /rmm/api/tacticalrmm/manage.py get_config dbuser)
|
POSTGRES_USER=$(/rmm/api/env/bin/python /rmm/api/tacticalrmm/manage.py get_config dbuser)
|
||||||
POSTGRES_PW=$(/rmm/api/env/bin/python /rmm/api/tacticalrmm/manage.py get_config dbpw)
|
POSTGRES_PW=$(/rmm/api/env/bin/python /rmm/api/tacticalrmm/manage.py get_config dbpw)
|
||||||
|
|
||||||
pg_dump --dbname=postgresql://"${POSTGRES_USER}":"${POSTGRES_PW}"@127.0.0.1:5432/tacticalrmm | gzip -9 >${tmp_dir}/postgres/db-${dt_now}.psql.gz
|
pg_dump --dbname=postgresql://"${POSTGRES_USER}":"${POSTGRES_PW}"@localhost:5432/tacticalrmm | gzip -9 >${tmp_dir}/postgres/db-${dt_now}.psql.gz
|
||||||
|
|
||||||
node /meshcentral/node_modules/meshcentral --dbexport # for import to postgres
|
node /meshcentral/node_modules/meshcentral --dbexport # for import to postgres
|
||||||
|
|
||||||
if grep -q postgres "/meshcentral/meshcentral-data/config.json"; then
|
if grep -q postgres "/meshcentral/meshcentral-data/config.json"; then
|
||||||
if ! which jq >/dev/null; then
|
if ! which jq >/dev/null; then
|
||||||
sudo apt-get install -y jq >null
|
sudo apt-get install -y jq >/dev/null
|
||||||
fi
|
fi
|
||||||
MESH_POSTGRES_USER=$(jq '.settings.postgres.user' /meshcentral/meshcentral-data/config.json -r)
|
MESH_POSTGRES_USER=$(jq '.settings.postgres.user' /meshcentral/meshcentral-data/config.json -r)
|
||||||
MESH_POSTGRES_PW=$(jq '.settings.postgres.password' /meshcentral/meshcentral-data/config.json -r)
|
MESH_POSTGRES_PW=$(jq '.settings.postgres.password' /meshcentral/meshcentral-data/config.json -r)
|
||||||
pg_dump --dbname=postgresql://"${MESH_POSTGRES_USER}":"${MESH_POSTGRES_PW}"@127.0.0.1:5432/meshcentral | gzip -9 >${tmp_dir}/postgres/mesh-db-${dt_now}.psql.gz
|
pg_dump --dbname=postgresql://"${MESH_POSTGRES_USER}":"${MESH_POSTGRES_PW}"@localhost:5432/meshcentral | gzip -9 >${tmp_dir}/postgres/mesh-db-${dt_now}.psql.gz
|
||||||
else
|
else
|
||||||
mongodump --gzip --out=${tmp_dir}/meshcentral/mongo
|
mongodump --gzip --out=${tmp_dir}/meshcentral/mongo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
tar -czvf ${tmp_dir}/meshcentral/mesh.tar.gz --exclude=/meshcentral/node_modules /meshcentral
|
tar -czvf ${tmp_dir}/meshcentral/mesh.tar.gz --exclude=/meshcentral/node_modules /meshcentral
|
||||||
|
|
||||||
|
if [ -d /etc/letsencrypt ]; then
|
||||||
sudo tar -czvf ${tmp_dir}/certs/etc-letsencrypt.tar.gz -C /etc/letsencrypt .
|
sudo tar -czvf ${tmp_dir}/certs/etc-letsencrypt.tar.gz -C /etc/letsencrypt .
|
||||||
|
fi
|
||||||
|
|
||||||
|
local_settings='/rmm/api/tacticalrmm/tacticalrmm/local_settings.py'
|
||||||
|
|
||||||
|
if grep -q CERT_FILE "$local_settings"; then
|
||||||
|
mkdir -p ${tmp_dir}/certs/custom
|
||||||
|
CERT_FILE=$(grep "^CERT_FILE" "$local_settings" | awk -F'[= "]' '{print $5}')
|
||||||
|
KEY_FILE=$(grep "^KEY_FILE" "$local_settings" | awk -F'[= "]' '{print $5}')
|
||||||
|
cp -p $CERT_FILE ${tmp_dir}/certs/custom/cert
|
||||||
|
cp -p $KEY_FILE ${tmp_dir}/certs/custom/key
|
||||||
|
elif grep -q TRMM_INSECURE "$local_settings"; then
|
||||||
|
mkdir -p ${tmp_dir}/certs/selfsigned
|
||||||
|
certdir='/etc/ssl/tactical'
|
||||||
|
cp -p ${certdir}/key.pem ${tmp_dir}/certs/selfsigned/
|
||||||
|
cp -p ${certdir}/cert.pem ${tmp_dir}/certs/selfsigned/
|
||||||
|
fi
|
||||||
|
|
||||||
for i in rmm frontend meshcentral; do
|
for i in rmm frontend meshcentral; do
|
||||||
sudo cp /etc/nginx/sites-available/${i}.conf ${tmp_dir}/nginx/
|
sudo cp /etc/nginx/sites-available/${i}.conf ${tmp_dir}/nginx/
|
||||||
@@ -95,7 +116,7 @@ sudo tar -czvf ${tmp_dir}/confd/etc-confd.tar.gz -C /etc/conf.d .
|
|||||||
|
|
||||||
sudo cp ${sysd}/rmm.service ${sysd}/celery.service ${sysd}/celerybeat.service ${sysd}/meshcentral.service ${sysd}/nats.service ${sysd}/daphne.service ${sysd}/nats-api.service ${tmp_dir}/systemd/
|
sudo cp ${sysd}/rmm.service ${sysd}/celery.service ${sysd}/celerybeat.service ${sysd}/meshcentral.service ${sysd}/nats.service ${sysd}/daphne.service ${sysd}/nats-api.service ${tmp_dir}/systemd/
|
||||||
|
|
||||||
cp /rmm/api/tacticalrmm/tacticalrmm/local_settings.py ${tmp_dir}/rmm/
|
cp $local_settings ${tmp_dir}/rmm/
|
||||||
|
|
||||||
if [[ $* == *--auto* ]]; then
|
if [[ $* == *--auto* ]]; then
|
||||||
|
|
||||||
@@ -114,9 +135,9 @@ if [[ $* == *--auto* ]]; then
|
|||||||
|
|
||||||
rm -rf ${tmp_dir}
|
rm -rf ${tmp_dir}
|
||||||
|
|
||||||
find /rmmbackups/daily/ -maxdepth 1 -mtime +14 -type d -exec rm -rv {} \;
|
find /rmmbackups/daily/ -type f -mtime +14 -name '*.tar' -execdir rm -- '{}' \;
|
||||||
find /rmmbackups/weekly/ -maxdepth 1 -mtime +60 -type d -exec rm -rv {} \;
|
find /rmmbackups/weekly/ -type f -mtime +60 -name '*.tar' -execdir rm -- '{}' \;
|
||||||
find /rmmbackups/monthly/ -maxdepth 1 -mtime +380 -type d -exec rm -rv {} \;
|
find /rmmbackups/monthly/ -type f -mtime +380 -name '*.tar' -execdir rm -- '{}' \;
|
||||||
echo -ne "${GREEN}Backup Completed${NC}\n"
|
echo -ne "${GREEN}Backup Completed${NC}\n"
|
||||||
exit
|
exit
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM nats:2.9.19-alpine
|
FROM nats:2.9.20-alpine
|
||||||
|
|
||||||
ENV TACTICAL_DIR /opt/tactical
|
ENV TACTICAL_DIR /opt/tactical
|
||||||
ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready
|
ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready
|
||||||
|
|||||||
@@ -14,17 +14,17 @@ SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
|
|||||||
|
|
||||||
COPY api/tacticalrmm/requirements.txt ${TACTICAL_TMP_DIR}/api/requirements.txt
|
COPY api/tacticalrmm/requirements.txt ${TACTICAL_TMP_DIR}/api/requirements.txt
|
||||||
|
|
||||||
RUN apt-get update &&
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends gcc libc6-dev &&
|
apt-get install -y --no-install-recommends gcc libc6-dev && \
|
||||||
pip install --upgrade pip &&
|
pip install --upgrade pip && \
|
||||||
pip install --no-cache-dir setuptools wheel &&
|
pip install --no-cache-dir setuptools wheel && \
|
||||||
pip install --no-cache-dir -r ${TACTICAL_TMP_DIR}/api/requirements.txt
|
pip install --no-cache-dir -r ${TACTICAL_TMP_DIR}/api/requirements.txt
|
||||||
|
|
||||||
# pulls community scripts from git repo
|
# pulls community scripts from git repo
|
||||||
FROM python:3.11.4-slim AS GET_SCRIPTS_STAGE
|
FROM python:3.11.4-slim AS GET_SCRIPTS_STAGE
|
||||||
|
|
||||||
RUN apt-get update &&
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends git &&
|
apt-get install -y --no-install-recommends git && \
|
||||||
git clone https://github.com/amidaware/community-scripts.git /community-scripts
|
git clone https://github.com/amidaware/community-scripts.git /community-scripts
|
||||||
|
|
||||||
# runtime image
|
# runtime image
|
||||||
@@ -46,11 +46,11 @@ COPY --from=GET_SCRIPTS_STAGE /community-scripts ${TACTICAL_TMP_DIR}/community-s
|
|||||||
COPY --from=CREATE_VENV_STAGE ${VIRTUAL_ENV} ${VIRTUAL_ENV}
|
COPY --from=CREATE_VENV_STAGE ${VIRTUAL_ENV} ${VIRTUAL_ENV}
|
||||||
|
|
||||||
# install deps
|
# install deps
|
||||||
RUN apt-get update &&
|
RUN apt-get update && \
|
||||||
apt-get upgrade -y &&
|
apt-get upgrade -y && \
|
||||||
apt-get install -y --no-install-recommends rsync &&
|
apt-get install -y --no-install-recommends rsync && \
|
||||||
rm -rf /var/lib/apt/lists/* &&
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
groupadd -g 1000 "${TACTICAL_USER}" &&
|
groupadd -g 1000 "${TACTICAL_USER}" && \
|
||||||
useradd -M -d "${TACTICAL_DIR}" -s /bin/bash -u 1000 -g 1000 "${TACTICAL_USER}"
|
useradd -M -d "${TACTICAL_DIR}" -s /bin/bash -u 1000 -g 1000 "${TACTICAL_USER}"
|
||||||
|
|
||||||
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
|
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
|
||||||
|
|||||||
14
go.mod
14
go.mod
@@ -6,21 +6,21 @@ require (
|
|||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/jmoiron/sqlx v1.3.5
|
github.com/jmoiron/sqlx v1.3.5
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
github.com/nats-io/nats-server/v2 v2.9.18 // indirect
|
github.com/nats-io/nats-server/v2 v2.9.21 // indirect
|
||||||
github.com/nats-io/nats.go v1.27.0
|
github.com/nats-io/nats.go v1.28.0
|
||||||
github.com/ugorji/go/codec v1.2.11
|
github.com/ugorji/go/codec v1.2.11
|
||||||
github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139
|
github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139
|
||||||
google.golang.org/protobuf v1.28.0 // indirect
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require github.com/sirupsen/logrus v1.9.0
|
require github.com/sirupsen/logrus v1.9.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.16.5 // indirect
|
github.com/klauspost/compress v1.16.7 // indirect
|
||||||
github.com/nats-io/nkeys v0.4.4 // indirect
|
github.com/nats-io/nkeys v0.4.4 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/stretchr/testify v1.7.1 // indirect
|
github.com/stretchr/testify v1.7.1 // indirect
|
||||||
golang.org/x/crypto v0.9.0 // indirect
|
golang.org/x/crypto v0.11.0 // indirect
|
||||||
golang.org/x/sys v0.8.0 // indirect
|
golang.org/x/sys v0.10.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
26
go.sum
26
go.sum
@@ -9,8 +9,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
|
|||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||||
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||||
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
@@ -18,10 +18,10 @@ github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRU
|
|||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
|
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
|
||||||
github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4=
|
github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4=
|
||||||
github.com/nats-io/nats-server/v2 v2.9.18 h1:00muGH0qu/7NAw1b/2eFcpIvdHcTghj6PFjUVhy8zEo=
|
github.com/nats-io/nats-server/v2 v2.9.21 h1:2TBTh0UDE74eNXQmV4HofsmRSCiVN0TH2Wgrp6BD6fk=
|
||||||
github.com/nats-io/nats-server/v2 v2.9.18/go.mod h1:aTb/xtLCGKhfTFLxP591CMWfkdgBmcUUSkiSOe5A3gw=
|
github.com/nats-io/nats-server/v2 v2.9.21/go.mod h1:ozqMZc2vTHcNcblOiXMWIXkf8+0lDGAi5wQcG+O1mHU=
|
||||||
github.com/nats-io/nats.go v1.27.0 h1:3o9fsPhmoKm+yK7rekH2GtWoE+D9jFbw8N3/ayI1C00=
|
github.com/nats-io/nats.go v1.28.0 h1:Th4G6zdsz2d0OqXdfzKLClo6bOfoI/b1kInhRtFIy5c=
|
||||||
github.com/nats-io/nats.go v1.27.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc=
|
github.com/nats-io/nats.go v1.28.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc=
|
||||||
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
|
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
|
||||||
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
|
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
@@ -30,6 +30,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||||
@@ -38,11 +40,11 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d
|
|||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139 h1:PfOl03o+Y+svWrfXAAu1QWUDePu1yqTq0pf4rpnN8eA=
|
github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139 h1:PfOl03o+Y+svWrfXAAu1QWUDePu1yqTq0pf4rpnN8eA=
|
||||||
github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139/go.mod h1:ILUz1utl5KgwrxmNHv0RpgMtKeh8gPAABvK2MiXBqv8=
|
github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139/go.mod h1:ILUz1utl5KgwrxmNHv0RpgMtKeh8gPAABvK2MiXBqv8=
|
||||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
@@ -51,5 +53,5 @@ google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscL
|
|||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
74
install.sh
74
install.sh
@@ -1,9 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
SCRIPT_VERSION="74"
|
SCRIPT_VERSION="77"
|
||||||
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/install.sh'
|
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/install.sh'
|
||||||
|
|
||||||
sudo apt install -y curl wget dirmngr gnupg lsb-release
|
sudo apt install -y curl wget dirmngr gnupg lsb-release ca-certificates
|
||||||
|
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
@@ -14,6 +14,7 @@ NC='\033[0m'
|
|||||||
SCRIPTS_DIR='/opt/trmm-community-scripts'
|
SCRIPTS_DIR='/opt/trmm-community-scripts'
|
||||||
PYTHON_VER='3.11.4'
|
PYTHON_VER='3.11.4'
|
||||||
SETTINGS_FILE='/rmm/api/tacticalrmm/tacticalrmm/settings.py'
|
SETTINGS_FILE='/rmm/api/tacticalrmm/tacticalrmm/settings.py'
|
||||||
|
local_settings='/rmm/api/tacticalrmm/tacticalrmm/local_settings.py'
|
||||||
|
|
||||||
TMP_FILE=$(mktemp -p "" "rmminstall_XXXXXXXXXX")
|
TMP_FILE=$(mktemp -p "" "rmminstall_XXXXXXXXXX")
|
||||||
curl -s -L "${SCRIPT_URL}" >${TMP_FILE}
|
curl -s -L "${SCRIPT_URL}" >${TMP_FILE}
|
||||||
@@ -137,6 +138,12 @@ while [[ $letsemail != *[@]*[.]* ]]; do
|
|||||||
read letsemail
|
read letsemail
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if grep -q manage_etc_hosts /etc/hosts; then
|
||||||
|
sudo sed -i '/manage_etc_hosts: true/d' /etc/cloud/cloud.cfg >/dev/null
|
||||||
|
echo -e "\nmanage_etc_hosts: false" | sudo tee --append /etc/cloud/cloud.cfg >/dev/null
|
||||||
|
sudo systemctl restart cloud-init >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
CHECK_HOSTS=$(grep 127.0.1.1 /etc/hosts | grep "$rmmdomain" | grep "$meshdomain" | grep "$frontenddomain")
|
CHECK_HOSTS=$(grep 127.0.1.1 /etc/hosts | grep "$rmmdomain" | grep "$meshdomain" | grep "$frontenddomain")
|
||||||
HAS_11=$(grep 127.0.1.1 /etc/hosts)
|
HAS_11=$(grep 127.0.1.1 /etc/hosts)
|
||||||
|
|
||||||
@@ -155,19 +162,38 @@ if echo "$IPV4" | grep -qE '^(10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.|192
|
|||||||
BEHIND_NAT=true
|
BEHIND_NAT=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
insecure=false
|
||||||
|
if [[ $* == *--insecure* ]]; then
|
||||||
|
insecure=true
|
||||||
|
fi
|
||||||
|
|
||||||
sudo apt install -y software-properties-common
|
sudo apt install -y software-properties-common
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y certbot openssl
|
sudo apt install -y openssl
|
||||||
|
|
||||||
|
if [[ "$insecure" = true ]]; then
|
||||||
|
print_green 'Generating self-signed cert'
|
||||||
|
certdir='/etc/ssl/tactical'
|
||||||
|
sudo mkdir -p $certdir
|
||||||
|
sudo chown ${USER}:${USER} $certdir
|
||||||
|
sudo chmod 770 $certdir
|
||||||
|
CERT_PRIV_KEY=${certdir}/key.pem
|
||||||
|
CERT_PUB_KEY=${certdir}/cert.pem
|
||||||
|
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 \
|
||||||
|
-nodes -keyout ${CERT_PRIV_KEY} -out ${CERT_PUB_KEY} -subj "/CN=${rootdomain}" \
|
||||||
|
-addext "subjectAltName=DNS:${rootdomain},DNS:*.${rootdomain}"
|
||||||
|
|
||||||
|
else
|
||||||
|
sudo apt install -y certbot
|
||||||
print_green 'Getting wildcard cert'
|
print_green 'Getting wildcard cert'
|
||||||
|
|
||||||
sudo certbot certonly --manual -d *.${rootdomain} --agree-tos --no-bootstrap --preferred-challenges dns -m ${letsemail} --no-eff-email
|
sudo certbot certonly --manual -d *.${rootdomain} --agree-tos --no-bootstrap --preferred-challenges dns -m ${letsemail} --no-eff-email
|
||||||
while [[ $? -ne 0 ]]; do
|
while [[ $? -ne 0 ]]; do
|
||||||
sudo certbot certonly --manual -d *.${rootdomain} --agree-tos --no-bootstrap --preferred-challenges dns -m ${letsemail} --no-eff-email
|
sudo certbot certonly --manual -d *.${rootdomain} --agree-tos --no-bootstrap --preferred-challenges dns -m ${letsemail} --no-eff-email
|
||||||
done
|
done
|
||||||
|
|
||||||
CERT_PRIV_KEY=/etc/letsencrypt/live/${rootdomain}/privkey.pem
|
CERT_PRIV_KEY=/etc/letsencrypt/live/${rootdomain}/privkey.pem
|
||||||
CERT_PUB_KEY=/etc/letsencrypt/live/${rootdomain}/fullchain.pem
|
CERT_PUB_KEY=/etc/letsencrypt/live/${rootdomain}/fullchain.pem
|
||||||
|
fi
|
||||||
|
|
||||||
sudo chown ${USER}:${USER} -R /etc/letsencrypt
|
sudo chown ${USER}:${USER} -R /etc/letsencrypt
|
||||||
|
|
||||||
@@ -226,7 +252,10 @@ done
|
|||||||
|
|
||||||
print_green 'Installing NodeJS'
|
print_green 'Installing NodeJS'
|
||||||
|
|
||||||
curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
sudo mkdir -p /etc/apt/keyrings
|
||||||
|
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||||
|
NODE_MAJOR=18
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y gcc g++ make
|
sudo apt install -y gcc g++ make
|
||||||
sudo apt install -y nodejs
|
sudo apt install -y nodejs
|
||||||
@@ -247,7 +276,7 @@ cd ~
|
|||||||
sudo rm -rf Python-${PYTHON_VER} Python-${PYTHON_VER}.tgz
|
sudo rm -rf Python-${PYTHON_VER} Python-${PYTHON_VER}.tgz
|
||||||
|
|
||||||
print_green 'Installing redis and git'
|
print_green 'Installing redis and git'
|
||||||
sudo apt install -y ca-certificates redis git
|
sudo apt install -y redis git
|
||||||
|
|
||||||
print_green 'Installing postgresql'
|
print_green 'Installing postgresql'
|
||||||
|
|
||||||
@@ -330,9 +359,23 @@ MESH_VER=$(grep "^MESH_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
|
|||||||
sudo mkdir -p /meshcentral/meshcentral-data
|
sudo mkdir -p /meshcentral/meshcentral-data
|
||||||
sudo chown ${USER}:${USER} -R /meshcentral
|
sudo chown ${USER}:${USER} -R /meshcentral
|
||||||
cd /meshcentral
|
cd /meshcentral
|
||||||
npm install meshcentral@${MESH_VER}
|
|
||||||
sudo chown ${USER}:${USER} -R /meshcentral
|
sudo chown ${USER}:${USER} -R /meshcentral
|
||||||
|
|
||||||
|
mesh_pkg="$(
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"archiver": "5.3.1",
|
||||||
|
"meshcentral": "${MESH_VER}",
|
||||||
|
"otplib": "10.2.3",
|
||||||
|
"pg": "8.7.1",
|
||||||
|
"pgtools": "0.3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
echo "${mesh_pkg}" >/meshcentral/package.json
|
||||||
|
|
||||||
meshcfg="$(
|
meshcfg="$(
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
{
|
{
|
||||||
@@ -376,6 +419,8 @@ EOF
|
|||||||
)"
|
)"
|
||||||
echo "${meshcfg}" >/meshcentral/meshcentral-data/config.json
|
echo "${meshcfg}" >/meshcentral/meshcentral-data/config.json
|
||||||
|
|
||||||
|
npm install
|
||||||
|
|
||||||
localvars="$(
|
localvars="$(
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
SECRET_KEY = "${DJANGO_SEKRET}"
|
SECRET_KEY = "${DJANGO_SEKRET}"
|
||||||
@@ -407,7 +452,11 @@ REDIS_HOST = "localhost"
|
|||||||
ADMIN_ENABLED = True
|
ADMIN_ENABLED = True
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
echo "${localvars}" >/rmm/api/tacticalrmm/tacticalrmm/local_settings.py
|
echo "${localvars}" >$local_settings
|
||||||
|
|
||||||
|
if [[ "$insecure" = true ]]; then
|
||||||
|
echo "TRMM_INSECURE = True" | tee --append $local_settings >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$arch" = "x86_64" ]; then
|
if [ "$arch" = "x86_64" ]; then
|
||||||
natsapi='nats-api'
|
natsapi='nats-api'
|
||||||
@@ -440,7 +489,7 @@ python manage.py load_community_scripts
|
|||||||
WEB_VERSION=$(python manage.py get_config webversion)
|
WEB_VERSION=$(python manage.py get_config webversion)
|
||||||
printf >&2 "${YELLOW}%0.s*${NC}" {1..80}
|
printf >&2 "${YELLOW}%0.s*${NC}" {1..80}
|
||||||
printf >&2 "\n"
|
printf >&2 "\n"
|
||||||
printf >&2 "${YELLOW}Please create your login for the RMM website and django admin${NC}\n"
|
printf >&2 "${YELLOW}Please create your login for the RMM website${NC}\n"
|
||||||
printf >&2 "${YELLOW}%0.s*${NC}" {1..80}
|
printf >&2 "${YELLOW}%0.s*${NC}" {1..80}
|
||||||
printf >&2 "\n"
|
printf >&2 "\n"
|
||||||
echo -ne "Username: "
|
echo -ne "Username: "
|
||||||
@@ -850,7 +899,7 @@ done
|
|||||||
sleep 5
|
sleep 5
|
||||||
sudo systemctl enable meshcentral
|
sudo systemctl enable meshcentral
|
||||||
|
|
||||||
print_green 'Starting meshcentral and waiting for it to install plugins'
|
print_green 'Starting meshcentral and waiting for it to be ready'
|
||||||
|
|
||||||
sudo systemctl restart meshcentral
|
sudo systemctl restart meshcentral
|
||||||
|
|
||||||
@@ -874,7 +923,7 @@ meshtoken="$(
|
|||||||
MESH_TOKEN_KEY = "${MESHTOKENKEY}"
|
MESH_TOKEN_KEY = "${MESHTOKENKEY}"
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
echo "${meshtoken}" | tee --append /rmm/api/tacticalrmm/tacticalrmm/local_settings.py >/dev/null
|
echo "${meshtoken}" | tee --append $local_settings >/dev/null
|
||||||
|
|
||||||
print_green 'Creating meshcentral account and group'
|
print_green 'Creating meshcentral account and group'
|
||||||
|
|
||||||
@@ -911,7 +960,7 @@ sudo systemctl enable nats-api.service
|
|||||||
sudo systemctl start nats-api.service
|
sudo systemctl start nats-api.service
|
||||||
|
|
||||||
## disable django admin
|
## disable django admin
|
||||||
sed -i 's/ADMIN_ENABLED = True/ADMIN_ENABLED = False/g' /rmm/api/tacticalrmm/tacticalrmm/local_settings.py
|
sed -i 's/ADMIN_ENABLED = True/ADMIN_ENABLED = False/g' $local_settings
|
||||||
|
|
||||||
print_green 'Restarting services'
|
print_green 'Restarting services'
|
||||||
for i in rmm.service daphne.service celery.service celerybeat.service; do
|
for i in rmm.service daphne.service celery.service celerybeat.service; do
|
||||||
@@ -923,7 +972,6 @@ printf >&2 "${YELLOW}%0.s*${NC}" {1..80}
|
|||||||
printf >&2 "\n\n"
|
printf >&2 "\n\n"
|
||||||
printf >&2 "${YELLOW}Installation complete!${NC}\n\n"
|
printf >&2 "${YELLOW}Installation complete!${NC}\n\n"
|
||||||
printf >&2 "${YELLOW}Access your rmm at: ${GREEN}https://${frontenddomain}${NC}\n\n"
|
printf >&2 "${YELLOW}Access your rmm at: ${GREEN}https://${frontenddomain}${NC}\n\n"
|
||||||
printf >&2 "${YELLOW}Django admin url (disabled by default): ${GREEN}https://${rmmdomain}/${ADMINURL}/${NC}\n\n"
|
|
||||||
printf >&2 "${YELLOW}MeshCentral username: ${GREEN}${meshusername}${NC}\n"
|
printf >&2 "${YELLOW}MeshCentral username: ${GREEN}${meshusername}${NC}\n"
|
||||||
printf >&2 "${YELLOW}MeshCentral password: ${GREEN}${MESHPASSWD}${NC}\n\n"
|
printf >&2 "${YELLOW}MeshCentral password: ${GREEN}${MESHPASSWD}${NC}\n\n"
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "3.4.7"
|
version = "3.4.8"
|
||||||
log = logrus.New()
|
log = logrus.New()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
53
restore.sh
53
restore.sh
@@ -1,10 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
SCRIPT_VERSION="49"
|
SCRIPT_VERSION="52"
|
||||||
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/restore.sh'
|
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/restore.sh'
|
||||||
|
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y curl wget dirmngr gnupg lsb-release
|
sudo apt install -y curl wget dirmngr gnupg lsb-release ca-certificates
|
||||||
|
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
@@ -122,7 +122,10 @@ sudo apt update
|
|||||||
|
|
||||||
print_green 'Installing NodeJS'
|
print_green 'Installing NodeJS'
|
||||||
|
|
||||||
curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
sudo mkdir -p /etc/apt/keyrings
|
||||||
|
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||||
|
NODE_MAJOR=18
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y gcc g++ make
|
sudo apt install -y gcc g++ make
|
||||||
sudo apt install -y nodejs
|
sudo apt install -y nodejs
|
||||||
@@ -193,10 +196,30 @@ sudo apt install -y certbot openssl
|
|||||||
|
|
||||||
print_green 'Restoring certs'
|
print_green 'Restoring certs'
|
||||||
|
|
||||||
|
if [ -f "$tmp_dir/certs/etc-letsencrypt.tar.gz" ]; then
|
||||||
sudo rm -rf /etc/letsencrypt
|
sudo rm -rf /etc/letsencrypt
|
||||||
sudo mkdir /etc/letsencrypt
|
sudo mkdir /etc/letsencrypt
|
||||||
sudo tar -xzf $tmp_dir/certs/etc-letsencrypt.tar.gz -C /etc/letsencrypt
|
sudo tar -xzf $tmp_dir/certs/etc-letsencrypt.tar.gz -C /etc/letsencrypt
|
||||||
sudo chown ${USER}:${USER} -R /etc/letsencrypt
|
sudo chown ${USER}:${USER} -R /etc/letsencrypt
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "${tmp_dir}/certs/custom" ]; then
|
||||||
|
CERT_FILE=$(grep "^CERT_FILE" "$tmp_dir/rmm/local_settings.py" | awk -F'[= "]' '{print $5}')
|
||||||
|
KEY_FILE=$(grep "^KEY_FILE" "$tmp_dir/rmm/local_settings.py" | awk -F'[= "]' '{print $5}')
|
||||||
|
|
||||||
|
sudo mkdir -p $(dirname $CERT_FILE) $(dirname $KEY_FILE)
|
||||||
|
sudo chown ${USER}:${USER} $(dirname $CERT_FILE) $(dirname $KEY_FILE)
|
||||||
|
|
||||||
|
cp -p ${tmp_dir}/certs/custom/cert $CERT_FILE
|
||||||
|
cp -p ${tmp_dir}/certs/custom/key $KEY_FILE
|
||||||
|
elif [ -d "${tmp_dir}/certs/selfsigned" ]; then
|
||||||
|
certdir='/etc/ssl/tactical'
|
||||||
|
sudo mkdir -p $certdir
|
||||||
|
sudo chown ${USER}:${USER} $certdir
|
||||||
|
sudo chmod 770 $certdir
|
||||||
|
cp -p ${tmp_dir}/certs/selfsigned/key.pem $certdir
|
||||||
|
cp -p ${tmp_dir}/certs/selfsigned/cert.pem $certdir
|
||||||
|
fi
|
||||||
|
|
||||||
print_green 'Restoring celery configs'
|
print_green 'Restoring celery configs'
|
||||||
|
|
||||||
@@ -224,7 +247,7 @@ cd ~
|
|||||||
sudo rm -rf Python-${PYTHON_VER} Python-${PYTHON_VER}.tgz
|
sudo rm -rf Python-${PYTHON_VER} Python-${PYTHON_VER}.tgz
|
||||||
|
|
||||||
print_green 'Installing redis and git'
|
print_green 'Installing redis and git'
|
||||||
sudo apt install -y ca-certificates redis git
|
sudo apt install -y redis git
|
||||||
|
|
||||||
print_green 'Installing postgresql'
|
print_green 'Installing postgresql'
|
||||||
|
|
||||||
@@ -335,7 +358,21 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
cd /meshcentral
|
cd /meshcentral
|
||||||
npm install meshcentral@${MESH_VER}
|
mesh_pkg="$(
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"archiver": "5.3.1",
|
||||||
|
"meshcentral": "${MESH_VER}",
|
||||||
|
"otplib": "10.2.3",
|
||||||
|
"pg": "8.7.1",
|
||||||
|
"pgtools": "0.3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
echo "${mesh_pkg}" >/meshcentral/package.json
|
||||||
|
npm install
|
||||||
|
|
||||||
if [ "$FROM_MONGO" = true ]; then
|
if [ "$FROM_MONGO" = true ]; then
|
||||||
node node_modules/meshcentral --dbimport >/dev/null
|
node node_modules/meshcentral --dbimport >/dev/null
|
||||||
@@ -396,6 +433,12 @@ deactivate
|
|||||||
|
|
||||||
print_green 'Restoring hosts file'
|
print_green 'Restoring hosts file'
|
||||||
|
|
||||||
|
if grep -q manage_etc_hosts /etc/hosts; then
|
||||||
|
sudo sed -i '/manage_etc_hosts: true/d' /etc/cloud/cloud.cfg >/dev/null
|
||||||
|
echo -e "\nmanage_etc_hosts: false" | sudo tee --append /etc/cloud/cloud.cfg >/dev/null
|
||||||
|
sudo systemctl restart cloud-init >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
HAS_11=$(grep 127.0.1.1 /etc/hosts)
|
HAS_11=$(grep 127.0.1.1 /etc/hosts)
|
||||||
if [[ $HAS_11 ]]; then
|
if [[ $HAS_11 ]]; then
|
||||||
sudo sed -i "/127.0.1.1/s/$/ ${API} ${webdomain} ${meshdomain}/" /etc/hosts
|
sudo sed -i "/127.0.1.1/s/$/ ${API} ${webdomain} ${meshdomain}/" /etc/hosts
|
||||||
|
|||||||
@@ -17,11 +17,18 @@ NC='\033[0m'
|
|||||||
now=$(date)
|
now=$(date)
|
||||||
echo -e -------------- $now -------------- | tee -a checklog.log
|
echo -e -------------- $now -------------- | tee -a checklog.log
|
||||||
|
|
||||||
|
osname=$(lsb_release -si)
|
||||||
|
osname=${osname^}
|
||||||
|
osname=$(echo "$osname" | tr '[A-Z]' '[a-z]')
|
||||||
|
relno=$(lsb_release -sr | cut -d. -f1)
|
||||||
|
|
||||||
# Resolve Locally used DNS server
|
# Resolve Locally used DNS server
|
||||||
resolvestatus=$(systemctl is-active systemd-resolved.service)
|
resolvestatus=$(systemctl is-active systemd-resolved.service)
|
||||||
if [ $resolvestatus = active ]; then
|
if [[ "$osname" == "debian" && "$relno" == 12 ]]; then
|
||||||
locdns=$(resolvectl | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
locdns=$(resolvconf -l | tail -n +1 | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
||||||
echo -e $locdns
|
|
||||||
|
elif [ $resolvestatus = active ]; then
|
||||||
|
locdns=$(resolvectl | tail -n +1 | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
||||||
else
|
else
|
||||||
while ! [[ $resolveconf ]]; do
|
while ! [[ $resolveconf ]]; do
|
||||||
resolveconf=$(sudo systemctl status systemd-resolved.service | grep "Active: active (running)")
|
resolveconf=$(sudo systemctl status systemd-resolved.service | grep "Active: active (running)")
|
||||||
@@ -29,19 +36,16 @@ else
|
|||||||
echo -ne "DNS Resolver not ready yet...${NC}\n"
|
echo -ne "DNS Resolver not ready yet...${NC}\n"
|
||||||
sleep 3
|
sleep 3
|
||||||
done
|
done
|
||||||
locdns=$(resolvectl | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
locdns=$(resolvectl | tail -n +1 | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
||||||
echo -e $locdns
|
|
||||||
sudo systemctl stop systemd-resolved.service
|
sudo systemctl stop systemd-resolved.service
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while [[ $rmmdomain != *[.]*[.]* ]]
|
while [[ $rmmdomain != *[.]*[.]* ]]; do
|
||||||
do
|
|
||||||
echo -e "${YELLOW}Enter the subdomain for the backend (e.g. api.example.com)${NC}: "
|
echo -e "${YELLOW}Enter the subdomain for the backend (e.g. api.example.com)${NC}: "
|
||||||
read rmmdomain
|
read rmmdomain
|
||||||
done
|
done
|
||||||
|
|
||||||
if ping -c 1 $rmmdomain &> /dev/null
|
if ping -c 1 $rmmdomain &>/dev/null; then
|
||||||
then
|
|
||||||
echo -e ${GREEN} Verified $rmmdomain | tee -a checklog.log
|
echo -e ${GREEN} Verified $rmmdomain | tee -a checklog.log
|
||||||
printf >&2 "\n\n"
|
printf >&2 "\n\n"
|
||||||
else
|
else
|
||||||
@@ -52,14 +56,12 @@ else
|
|||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while [[ $frontenddomain != *[.]*[.]* ]]
|
while [[ $frontenddomain != *[.]*[.]* ]]; do
|
||||||
do
|
|
||||||
echo -e "${YELLOW}Enter the subdomain for the frontend (e.g. rmm.example.com)${NC}: "
|
echo -e "${YELLOW}Enter the subdomain for the frontend (e.g. rmm.example.com)${NC}: "
|
||||||
read frontenddomain
|
read frontenddomain
|
||||||
done
|
done
|
||||||
|
|
||||||
if ping -c 1 $frontenddomain &> /dev/null
|
if ping -c 1 $frontenddomain &>/dev/null; then
|
||||||
then
|
|
||||||
echo -e ${GREEN} Verified $frontenddomain | tee -a checklog.log
|
echo -e ${GREEN} Verified $frontenddomain | tee -a checklog.log
|
||||||
printf >&2 "\n\n"
|
printf >&2 "\n\n"
|
||||||
else
|
else
|
||||||
@@ -70,14 +72,12 @@ else
|
|||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while [[ $meshdomain != *[.]*[.]* ]]
|
while [[ $meshdomain != *[.]*[.]* ]]; do
|
||||||
do
|
|
||||||
echo -e "${YELLOW}Enter the subdomain for meshcentral (e.g. mesh.example.com)${NC}: "
|
echo -e "${YELLOW}Enter the subdomain for meshcentral (e.g. mesh.example.com)${NC}: "
|
||||||
read meshdomain
|
read meshdomain
|
||||||
done
|
done
|
||||||
|
|
||||||
if ping -c 1 $meshdomain &> /dev/null
|
if ping -c 1 $meshdomain &>/dev/null; then
|
||||||
then
|
|
||||||
echo -e ${GREEN} Verified $meshdomain | tee -a checklog.log
|
echo -e ${GREEN} Verified $meshdomain | tee -a checklog.log
|
||||||
printf >&2 "\n\n"
|
printf >&2 "\n\n"
|
||||||
else
|
else
|
||||||
@@ -88,8 +88,7 @@ else
|
|||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while [[ $domain != *[.]* ]]
|
while [[ $domain != *[.]* ]]; do
|
||||||
do
|
|
||||||
echo -e "${YELLOW}Enter yourdomain used for letsencrypt (e.g. example.com)${NC}: "
|
echo -e "${YELLOW}Enter yourdomain used for letsencrypt (e.g. example.com)${NC}: "
|
||||||
read domain
|
read domain
|
||||||
done
|
done
|
||||||
@@ -112,7 +111,6 @@ else
|
|||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
# Check Frontenddomain IPs
|
# Check Frontenddomain IPs
|
||||||
locrmmip=$(dig @"$locdns" +short $frontenddomain | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
locrmmip=$(dig @"$locdns" +short $frontenddomain | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
||||||
remrmmip=$(dig @8.8.8.8 +short $frontenddomain | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
remrmmip=$(dig @8.8.8.8 +short $frontenddomain | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
||||||
@@ -248,6 +246,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# mongod Service
|
# mongod Service
|
||||||
|
if grep -q mongo "/meshcentral/meshcentral-data/config.json"; then
|
||||||
if [ $mongodstatus = active ]; then
|
if [ $mongodstatus = active ]; then
|
||||||
echo -e ${GREEN} Success mongod Service is running | tee -a checklog.log
|
echo -e ${GREEN} Success mongod Service is running | tee -a checklog.log
|
||||||
printf >&2 "\n\n"
|
printf >&2 "\n\n"
|
||||||
@@ -257,7 +256,7 @@ else
|
|||||||
printf >&2 "\n\n"
|
printf >&2 "\n\n"
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
# postgresql Service
|
# postgresql Service
|
||||||
if [ $postgresqlstatus = active ]; then
|
if [ $postgresqlstatus = active ]; then
|
||||||
echo -e ${GREEN} Success postgresql Service is running | tee -a checklog.log
|
echo -e ${GREEN} Success postgresql Service is running | tee -a checklog.log
|
||||||
@@ -289,8 +288,7 @@ wanip=$(dig @resolver4.opendns.com myip.opendns.com +short)
|
|||||||
echo -e ${GREEN} WAN IP is $wanip | tee -a checklog.log
|
echo -e ${GREEN} WAN IP is $wanip | tee -a checklog.log
|
||||||
printf >&2 "\n\n"
|
printf >&2 "\n\n"
|
||||||
|
|
||||||
if ! which nc >/dev/null
|
if ! which nc >/dev/null; then
|
||||||
then
|
|
||||||
echo "netcat is not installed, installing now"
|
echo "netcat is not installed, installing now"
|
||||||
sudo apt-get install netcat -y
|
sudo apt-get install netcat -y
|
||||||
fi
|
fi
|
||||||
|
|||||||
41
update.sh
41
update.sh
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
SCRIPT_VERSION="145"
|
SCRIPT_VERSION="147"
|
||||||
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/update.sh'
|
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/update.sh'
|
||||||
LATEST_SETTINGS_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/api/tacticalrmm/tacticalrmm/settings.py'
|
LATEST_SETTINGS_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/api/tacticalrmm/tacticalrmm/settings.py'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
@@ -357,8 +357,29 @@ python manage.py clear_redis_celery_locks
|
|||||||
python manage.py post_update_tasks
|
python manage.py post_update_tasks
|
||||||
API=$(python manage.py get_config api)
|
API=$(python manage.py get_config api)
|
||||||
WEB_VERSION=$(python manage.py get_config webversion)
|
WEB_VERSION=$(python manage.py get_config webversion)
|
||||||
|
FRONTEND=$(python manage.py get_config webdomain)
|
||||||
|
MESHDOMAIN=$(python manage.py get_config meshdomain)
|
||||||
deactivate
|
deactivate
|
||||||
|
|
||||||
|
if grep -q manage_etc_hosts /etc/hosts; then
|
||||||
|
sudo sed -i '/manage_etc_hosts: true/d' /etc/cloud/cloud.cfg >/dev/null
|
||||||
|
if ! grep -q "manage_etc_hosts: false" /etc/cloud/cloud.cfg; then
|
||||||
|
echo -e "\nmanage_etc_hosts: false" | sudo tee --append /etc/cloud/cloud.cfg >/dev/null
|
||||||
|
sudo systemctl restart cloud-init >/dev/null
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
CHECK_HOSTS=$(grep 127.0.1.1 /etc/hosts | grep "$API" | grep "$FRONTEND" | grep "$MESHDOMAIN")
|
||||||
|
HAS_11=$(grep 127.0.1.1 /etc/hosts)
|
||||||
|
|
||||||
|
if ! [[ $CHECK_HOSTS ]]; then
|
||||||
|
if [[ $HAS_11 ]]; then
|
||||||
|
sudo sed -i "/127.0.1.1/s/$/ ${API} ${FRONTEND} ${MESHDOMAIN}/" /etc/hosts
|
||||||
|
else
|
||||||
|
echo "127.0.1.1 ${API} ${FRONTEND} ${MESHDOMAIN}" | sudo tee --append /etc/hosts >/dev/null
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -d /rmm/web ]; then
|
if [ -d /rmm/web ]; then
|
||||||
rm -rf /rmm/web
|
rm -rf /rmm/web
|
||||||
fi
|
fi
|
||||||
@@ -386,8 +407,22 @@ if [[ "${CURRENT_MESH_VER}" != "${LATEST_MESH_VER}" ]] || [[ "$force" = true ]];
|
|||||||
sudo systemctl stop meshcentral
|
sudo systemctl stop meshcentral
|
||||||
sudo chown ${USER}:${USER} -R /meshcentral
|
sudo chown ${USER}:${USER} -R /meshcentral
|
||||||
cd /meshcentral
|
cd /meshcentral
|
||||||
rm -rf node_modules/
|
rm -rf node_modules/ package.json package-lock.json
|
||||||
npm install meshcentral@${LATEST_MESH_VER}
|
mesh_pkg="$(
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"archiver": "5.3.1",
|
||||||
|
"meshcentral": "${LATEST_MESH_VER}",
|
||||||
|
"otplib": "10.2.3",
|
||||||
|
"pg": "8.7.1",
|
||||||
|
"pgtools": "0.3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
echo "${mesh_pkg}" >/meshcentral/package.json
|
||||||
|
npm install
|
||||||
sudo systemctl start meshcentral
|
sudo systemctl start meshcentral
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user