Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
2baf119299 | ||
|
|
6fe4c5a2ed | ||
|
|
4abc8e41d8 | ||
|
|
af694f1ce9 | ||
|
|
7c3a5fcb83 | ||
|
|
57f64b18c6 | ||
|
|
4cccc7c2f8 | ||
|
|
903a2d6a6e | ||
|
|
34c674487a | ||
|
|
d15a8c5af3 | ||
|
|
3e0dec9383 | ||
|
|
8b810aad81 | ||
|
|
e676bcb4f4 | ||
|
|
a7aed77764 | ||
|
|
88875c0257 | ||
|
|
f711a0c91a | ||
|
|
d8a076cc6e | ||
|
|
c900831ee9 | ||
|
|
76a30c7ef4 | ||
|
|
ae5d0b1d81 | ||
|
|
cd5e87be34 | ||
|
|
3e967f58d2 | ||
|
|
1ea005ba7e | ||
|
|
092772ba90 | ||
|
|
b959854a76 | ||
|
|
8ccb1ebe4f | ||
|
|
91b3be6467 | ||
|
|
d79d5feacc | ||
|
|
5cc78ef9d5 | ||
|
|
8639cd5a72 | ||
|
|
021ddc17e7 | ||
|
|
ee47b8d004 | ||
|
|
55d267c935 | ||
|
|
0fd0b9128d | ||
|
|
d9cf505b50 | ||
|
|
6079332dda | ||
|
|
929ec20365 | ||
|
|
d0cad3055f | ||
|
|
4974a13bc0 | ||
|
|
bd048df225 | ||
|
|
ed83cbd574 | ||
|
|
7230207853 | ||
|
|
1ead8a72ab | ||
|
|
36a2e9d931 | ||
|
|
0f147a5518 | ||
|
|
fce511a18b | ||
|
|
64bb61b009 | ||
|
|
c6eefec5ce | ||
|
|
4c6f829c92 | ||
|
|
8c5cdd2acb | ||
|
|
e5357599c4 | ||
|
|
3800f19966 | ||
|
|
7336f84a4b | ||
|
|
7bf4a5b2b5 | ||
|
|
43a7b97218 | ||
|
|
9f95c57a09 | ||
|
|
8f6056ae66 | ||
|
|
9bcac6b10e | ||
|
|
86318e1b7d | ||
|
|
a8a1458833 | ||
|
|
942c1e2dfe | ||
|
|
a6b6814eae | ||
|
|
0af95aa9b1 | ||
|
|
b4b9256867 | ||
|
|
a6f1281a98 |
@@ -1,11 +1,11 @@
|
||||
# pulls community scripts from git repo
|
||||
FROM python:3.11.3-slim AS GET_SCRIPTS_STAGE
|
||||
FROM python:3.11.4-slim AS GET_SCRIPTS_STAGE
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends git && \
|
||||
RUN apt-get update &&
|
||||
apt-get install -y --no-install-recommends git &&
|
||||
git clone https://github.com/amidaware/community-scripts.git /community-scripts
|
||||
|
||||
FROM python:3.11.3-slim
|
||||
FROM python:3.11.4-slim
|
||||
|
||||
ENV TACTICAL_DIR /opt/tactical
|
||||
ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready
|
||||
@@ -17,17 +17,17 @@ ENV PYTHONUNBUFFERED=1
|
||||
|
||||
EXPOSE 8000 8383 8005
|
||||
|
||||
RUN apt-get update && \
|
||||
RUN apt-get update &&
|
||||
apt-get install -y build-essential
|
||||
|
||||
RUN groupadd -g 1000 tactical && \
|
||||
RUN groupadd -g 1000 tactical &&
|
||||
useradd -u 1000 -g 1000 tactical
|
||||
|
||||
# copy community scripts
|
||||
COPY --from=GET_SCRIPTS_STAGE /community-scripts /community-scripts
|
||||
|
||||
# Copy dev python reqs
|
||||
COPY .devcontainer/requirements.txt /
|
||||
COPY .devcontainer/requirements.txt /
|
||||
|
||||
# Copy docker entrypoint.sh
|
||||
COPY .devcontainer/entrypoint.sh /
|
||||
|
||||
4
.github/workflows/ci-tests.yml
vendored
4
.github/workflows/ci-tests.yml
vendored
@@ -14,14 +14,14 @@ jobs:
|
||||
name: Tests
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.11.3"]
|
||||
python-version: ["3.11.4"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: harmon758/postgresql-action@v1
|
||||
with:
|
||||
postgresql version: "14"
|
||||
postgresql version: "15"
|
||||
postgresql db: "pipeline"
|
||||
postgresql user: "pipeline"
|
||||
postgresql password: "pipeline123456"
|
||||
|
||||
20
.vscode/settings.json
vendored
20
.vscode/settings.json
vendored
@@ -1,10 +1,7 @@
|
||||
{
|
||||
"python.defaultInterpreterPath": "api/env/bin/python",
|
||||
"python.languageServer": "Pylance",
|
||||
"python.analysis.extraPaths": [
|
||||
"api/tacticalrmm",
|
||||
"api/env"
|
||||
],
|
||||
"python.analysis.extraPaths": ["api/tacticalrmm", "api/env"],
|
||||
"python.analysis.diagnosticSeverityOverrides": {
|
||||
"reportUnusedImport": "error",
|
||||
"reportDuplicateImport": "error",
|
||||
@@ -24,11 +21,11 @@
|
||||
".vscode/*.py",
|
||||
"**env/**"
|
||||
],
|
||||
"python.formatting.provider": "black",
|
||||
"mypy.targets": [
|
||||
"api/tacticalrmm"
|
||||
],
|
||||
"mypy.runUsingActiveInterpreter": true,
|
||||
"python.formatting.provider": "none",
|
||||
//"mypy.targets": [
|
||||
//"api/tacticalrmm"
|
||||
//],
|
||||
//"mypy.runUsingActiveInterpreter": true,
|
||||
"editor.bracketPairColorization.enabled": true,
|
||||
"editor.guides.bracketPairs": true,
|
||||
"editor.formatOnSave": true,
|
||||
@@ -74,5 +71,8 @@
|
||||
"usePlaceholders": true,
|
||||
"completeUnimported": true,
|
||||
"staticcheck": true
|
||||
},
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "ms-python.black-formatter"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
user: "tactical"
|
||||
python_ver: "3.11.3"
|
||||
go_ver: "1.20.3"
|
||||
python_ver: "3.11.4"
|
||||
go_ver: "1.20.4"
|
||||
backend_repo: "https://github.com/amidaware/tacticalrmm.git"
|
||||
frontend_repo: "https://github.com/amidaware/tacticalrmm-web.git"
|
||||
scripts_repo: "https://github.com/amidaware/community-scripts.git"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
deb https://nginx.org/packages/debian/ bullseye nginx
|
||||
deb-src https://nginx.org/packages/debian/ bullseye nginx
|
||||
@@ -1,4 +1,13 @@
|
||||
---
|
||||
- name: Append subdomains to hosts
|
||||
tags: hosts
|
||||
become: yes
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/hosts
|
||||
backrefs: yes
|
||||
regexp: '^(127\.0\.1\.1 .*)$'
|
||||
line: "\\1 {{ api }} {{ mesh }} {{ rmm }}"
|
||||
|
||||
- name: set mouse mode for vim
|
||||
tags: vim
|
||||
become: yes
|
||||
@@ -32,11 +41,15 @@
|
||||
with_items:
|
||||
- "{{ base_pkgs }}"
|
||||
|
||||
- name: set arch fact
|
||||
ansible.builtin.set_fact:
|
||||
goarch: "{{ 'amd64' if ansible_architecture == 'x86_64' else 'arm64' }}"
|
||||
|
||||
- name: download and install golang
|
||||
tags: golang
|
||||
become: yes
|
||||
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
|
||||
remote_src: yes
|
||||
|
||||
@@ -102,7 +115,7 @@
|
||||
tags: postgres
|
||||
become: yes
|
||||
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
|
||||
owner: root
|
||||
group: root
|
||||
@@ -119,7 +132,7 @@
|
||||
tags: postgres
|
||||
become: yes
|
||||
ansible.builtin.apt:
|
||||
pkg: postgresql-14
|
||||
pkg: postgresql-15
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
@@ -131,7 +144,7 @@
|
||||
enabled: yes
|
||||
state: started
|
||||
|
||||
- name: setup database
|
||||
- name: setup trmm database
|
||||
tags: postgres
|
||||
become: yes
|
||||
become_user: postgres
|
||||
@@ -144,6 +157,23 @@
|
||||
psql -c "ALTER ROLE {{ db_user }} SET timezone TO 'UTC'"
|
||||
psql -c "ALTER ROLE {{ db_user }} CREATEDB"
|
||||
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
|
||||
become: yes
|
||||
@@ -193,7 +223,7 @@
|
||||
- name: download and extract nats
|
||||
tags: nats
|
||||
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 }}"
|
||||
remote_src: yes
|
||||
|
||||
@@ -202,7 +232,7 @@
|
||||
become: yes
|
||||
ansible.builtin.copy:
|
||||
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
|
||||
owner: "{{ user }}"
|
||||
group: "{{ user }}"
|
||||
@@ -218,7 +248,7 @@
|
||||
- name: download nodejs setup
|
||||
tags: nodejs
|
||||
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"
|
||||
mode: "0755"
|
||||
|
||||
@@ -305,8 +335,8 @@
|
||||
- name: add nginx repo
|
||||
tags: nginx
|
||||
become: yes
|
||||
ansible.builtin.copy:
|
||||
src: nginx.repo
|
||||
ansible.builtin.template:
|
||||
src: nginx.repo.j2
|
||||
dest: /etc/apt/sources.list.d/nginx.list
|
||||
owner: "root"
|
||||
group: "root"
|
||||
@@ -382,12 +412,16 @@
|
||||
enabled: yes
|
||||
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
|
||||
tags: nats-api
|
||||
become: yes
|
||||
ansible.builtin.copy:
|
||||
remote_src: yes
|
||||
src: "{{ backend_dir }}/natsapi/bin/nats-api"
|
||||
src: "{{ backend_dir }}/natsapi/bin/{{ natsapi }}"
|
||||
dest: /usr/local/bin/nats-api
|
||||
owner: "{{ user }}"
|
||||
group: "{{ user }}"
|
||||
@@ -473,39 +507,6 @@
|
||||
- { src: nats-server.systemd.j2, dest: /etc/systemd/system/nats.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
|
||||
tags: mesh
|
||||
ansible.builtin.shell: grep "^MESH_VER" {{ settings_file }} | awk -F'[= "]' '{print $5}'
|
||||
|
||||
@@ -2,10 +2,6 @@ SECRET_KEY = "{{ django_secret }}"
|
||||
DEBUG = True
|
||||
ALLOWED_HOSTS = ['{{ api }}']
|
||||
ADMIN_URL = "admin/"
|
||||
CORS_ORIGIN_WHITELIST = [
|
||||
"http://{{ rmm }}:8080",
|
||||
"https://{{ rmm }}:8080",
|
||||
]
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
DATABASES = {
|
||||
'default': {
|
||||
@@ -19,7 +15,7 @@ DATABASES = {
|
||||
}
|
||||
REDIS_HOST = "localhost"
|
||||
ADMIN_ENABLED = True
|
||||
CERT_FILE = "{{ fullchain_src }}"
|
||||
KEY_FILE = "{{ privkey_src }}"
|
||||
CERT_FILE = "{{ fullchain_dest }}"
|
||||
KEY_FILE = "{{ privkey_dest }}"
|
||||
MESH_USERNAME = "{{ mesh_user }}"
|
||||
MESH_SITE = "https://{{ mesh }}"
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
{
|
||||
"settings": {
|
||||
"Cert": "{{ mesh }}",
|
||||
"MongoDb": "mongodb://127.0.0.1:27017",
|
||||
"MongoDbName": "meshcentral",
|
||||
"WANonly": true,
|
||||
"Minify": 1,
|
||||
"Port": 4430,
|
||||
@@ -10,19 +8,25 @@
|
||||
"RedirPort": 800,
|
||||
"AllowLoginToken": true,
|
||||
"AllowFraming": true,
|
||||
"AgentPong": 300,
|
||||
"AgentPing": 35,
|
||||
"AllowHighQualityDesktop": true,
|
||||
"TlsOffload": "127.0.0.1",
|
||||
"agentCoreDump": false,
|
||||
"Compression": true,
|
||||
"WsCompression": 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": {
|
||||
"": {
|
||||
"Title": "Tactical RMM",
|
||||
"Title2": "Tactical RMM",
|
||||
"Title": "Tactical RMM Dev",
|
||||
"Title2": "Tactical RMM Dev",
|
||||
"NewAccounts": false,
|
||||
"CertUrl": "https://{{ mesh }}:443/",
|
||||
"GeoLocation": true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[Unit]
|
||||
Description=MeshCentral Server
|
||||
After=network.target mongod.service nginx.service
|
||||
After=network.target postgresql.service nginx.service
|
||||
|
||||
[Service]
|
||||
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
|
||||
@@ -1,4 +1,4 @@
|
||||
DEV_URL = "http://{{ api }}:8000"
|
||||
DEV_HOST = "{{ rmm }}"
|
||||
DEV_HOST = "0.0.0.0"
|
||||
DEV_PORT = "8080"
|
||||
USE_HTTPS = false
|
||||
@@ -13,6 +13,8 @@
|
||||
mesh_password: "changeme"
|
||||
db_user: "changeme"
|
||||
db_passwd: "changeme"
|
||||
mesh_db_user: "changeme"
|
||||
mesh_db_passwd: "changeme"
|
||||
django_secret: "changeme"
|
||||
django_user: "changeme"
|
||||
django_password: "changeme"
|
||||
|
||||
@@ -3,6 +3,7 @@ import uuid
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from accounts.models import User
|
||||
from tacticalrmm.helpers import make_random_password
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@@ -17,7 +18,7 @@ class Command(BaseCommand):
|
||||
User.objects.create_user(
|
||||
username=uuid.uuid4().hex,
|
||||
is_installer_user=True,
|
||||
password=User.objects.make_random_password(60),
|
||||
password=make_random_password(len=60),
|
||||
block_dashboard_login=True,
|
||||
)
|
||||
self.stdout.write("Installer user has been created")
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 4.2.1 on 2023-05-17 07:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("accounts", "0031_user_date_format"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="default_agent_tbl_tab",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("server", "Servers"),
|
||||
("workstation", "Workstations"),
|
||||
("mixed", "Mixed"),
|
||||
],
|
||||
default="mixed",
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,32 @@
|
||||
# Generated by Django 4.2.1 on 2023-05-23 04:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("accounts", "0032_alter_user_default_agent_tbl_tab"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="dash_info_color",
|
||||
field=models.CharField(default="info", max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="dash_negative_color",
|
||||
field=models.CharField(default="negative", max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="dash_positive_color",
|
||||
field=models.CharField(default="positive", max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="dash_warning_color",
|
||||
field=models.CharField(default="warning", max_length=255),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 4.1.9 on 2023-05-26 23:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("accounts", "0033_user_dash_info_color_user_dash_negative_color_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="role",
|
||||
name="can_send_wol",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -31,7 +31,7 @@ class User(AbstractUser, BaseAuditModel):
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
default_agent_tbl_tab = models.CharField(
|
||||
max_length=50, choices=AgentTableTabs.choices, default=AgentTableTabs.SERVER
|
||||
max_length=50, choices=AgentTableTabs.choices, default=AgentTableTabs.MIXED
|
||||
)
|
||||
agents_per_page = models.PositiveIntegerField(default=50) # not currently used
|
||||
client_tree_sort = models.CharField(
|
||||
@@ -39,6 +39,10 @@ class User(AbstractUser, BaseAuditModel):
|
||||
)
|
||||
client_tree_splitter = models.PositiveIntegerField(default=11)
|
||||
loading_bar_color = models.CharField(max_length=255, default="red")
|
||||
dash_info_color = models.CharField(max_length=255, default="info")
|
||||
dash_positive_color = models.CharField(max_length=255, default="positive")
|
||||
dash_negative_color = models.CharField(max_length=255, default="negative")
|
||||
dash_warning_color = models.CharField(max_length=255, default="warning")
|
||||
clear_search_when_switching = models.BooleanField(default=True)
|
||||
date_format = models.CharField(max_length=30, blank=True, null=True)
|
||||
is_installer_user = models.BooleanField(default=False)
|
||||
@@ -105,6 +109,7 @@ class Role(BaseAuditModel):
|
||||
can_run_bulk = models.BooleanField(default=False)
|
||||
can_recover_agents = models.BooleanField(default=False)
|
||||
can_list_agent_history = models.BooleanField(default=False)
|
||||
can_send_wol = models.BooleanField(default=False)
|
||||
|
||||
# core
|
||||
can_list_notes = models.BooleanField(default=False)
|
||||
|
||||
@@ -5,6 +5,8 @@ from rest_framework.serializers import (
|
||||
SerializerMethodField,
|
||||
)
|
||||
|
||||
from tacticalrmm.helpers import get_webdomain
|
||||
|
||||
from .models import APIKey, Role, User
|
||||
|
||||
|
||||
@@ -20,6 +22,10 @@ class UserUISerializer(ModelSerializer):
|
||||
"client_tree_sort",
|
||||
"client_tree_splitter",
|
||||
"loading_bar_color",
|
||||
"dash_info_color",
|
||||
"dash_positive_color",
|
||||
"dash_negative_color",
|
||||
"dash_warning_color",
|
||||
"clear_search_when_switching",
|
||||
"block_dashboard_login",
|
||||
"date_format",
|
||||
@@ -57,7 +63,7 @@ class TOTPSetupSerializer(ModelSerializer):
|
||||
|
||||
def get_qr_url(self, obj):
|
||||
return pyotp.totp.TOTP(obj.totp_key).provisioning_uri(
|
||||
obj.username, issuer_name="Tactical RMM"
|
||||
obj.username, issuer_name=get_webdomain()
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class SendCMD(AsyncJsonWebsocketConsumer):
|
||||
await self.send_json({"ret": ret})
|
||||
|
||||
async def disconnect(self, _):
|
||||
await self.close()
|
||||
pass
|
||||
|
||||
def _has_perm(self, perm: str) -> bool:
|
||||
if self.user.is_superuser or (
|
||||
|
||||
@@ -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")},
|
||||
),
|
||||
]
|
||||
@@ -2,7 +2,6 @@ import asyncio
|
||||
import re
|
||||
from collections import Counter
|
||||
from contextlib import suppress
|
||||
from distutils.version import LooseVersion
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Union, cast
|
||||
|
||||
import msgpack
|
||||
@@ -16,6 +15,7 @@ from django.db import models
|
||||
from django.utils import timezone as djangotime
|
||||
from nats.errors import TimeoutError
|
||||
from packaging import version as pyver
|
||||
from packaging.version import Version as LooseVersion
|
||||
|
||||
from agents.utils import get_agent_url
|
||||
from checks.models import CheckResult
|
||||
@@ -408,6 +408,16 @@ class Agent(BaseAuditModel):
|
||||
except:
|
||||
return ["unknown disk"]
|
||||
|
||||
@property
|
||||
def serial_number(self) -> str:
|
||||
if self.is_posix:
|
||||
return ""
|
||||
|
||||
try:
|
||||
return self.wmi_detail["bios"][0][0]["SerialNumber"]
|
||||
except:
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
def online_agents(cls, min_version: str = "") -> "List[Agent]":
|
||||
if min_version:
|
||||
@@ -876,8 +886,10 @@ class Agent(BaseAuditModel):
|
||||
# extract the version from the title and sort from oldest to newest
|
||||
# skip if no version info is available therefore nothing to parse
|
||||
try:
|
||||
matches = r"(Version|Versão)"
|
||||
pattern = r"\(" + matches + r"(.*?)\)"
|
||||
vers = [
|
||||
re.search(r"\(Version(.*?)\)", i).group(1).strip()
|
||||
re.search(pattern, i, flags=re.IGNORECASE).group(2).strip()
|
||||
for i in titles
|
||||
]
|
||||
sorted_vers = sorted(vers, key=LooseVersion)
|
||||
@@ -999,6 +1011,9 @@ class AgentCustomField(models.Model):
|
||||
default=list,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = (("agent", "field"),)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.field.name
|
||||
|
||||
|
||||
@@ -122,3 +122,13 @@ class AgentHistoryPerms(permissions.BasePermission):
|
||||
)
|
||||
|
||||
return _has_perm(r, "can_list_agent_history")
|
||||
|
||||
|
||||
class AgentWOLPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view) -> bool:
|
||||
if "agent_id" in view.kwargs.keys():
|
||||
return _has_perm(r, "can_send_wol") and _has_perm_on_agent(
|
||||
r.user, view.kwargs["agent_id"]
|
||||
)
|
||||
|
||||
return _has_perm(r, "can_send_wol")
|
||||
|
||||
@@ -95,6 +95,7 @@ class AgentTableSerializer(serializers.ModelSerializer):
|
||||
local_ips = serializers.ReadOnlyField()
|
||||
make_model = serializers.ReadOnlyField()
|
||||
physical_disks = serializers.ReadOnlyField()
|
||||
serial_number = serializers.ReadOnlyField()
|
||||
custom_fields = AgentCustomFieldSerializer(many=True, read_only=True)
|
||||
|
||||
def get_alert_template(self, obj):
|
||||
@@ -155,6 +156,7 @@ class AgentTableSerializer(serializers.ModelSerializer):
|
||||
"make_model",
|
||||
"physical_disks",
|
||||
"custom_fields",
|
||||
"serial_number",
|
||||
]
|
||||
depth = 2
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import os
|
||||
from itertools import cycle
|
||||
from typing import TYPE_CHECKING
|
||||
from unittest.mock import patch
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.utils import timezone as djangotime
|
||||
from model_bakery import baker
|
||||
@@ -866,7 +866,7 @@ class TestAgentViews(TacticalTestCase):
|
||||
|
||||
# test pulling data
|
||||
r = self.client.get(url, format="json")
|
||||
ctx = {"default_tz": pytz.timezone("America/Los_Angeles")}
|
||||
ctx = {"default_tz": ZoneInfo("America/Los_Angeles")}
|
||||
data = AgentHistorySerializer(history, many=True, context=ctx).data
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.data, data) # type:ignore
|
||||
|
||||
@@ -43,4 +43,5 @@ urlpatterns = [
|
||||
path("installer/", views.install_agent),
|
||||
path("bulkrecovery/", views.bulk_agent_recovery),
|
||||
path("scripthistory/", views.ScriptRunHistory.as_view()),
|
||||
path("<agent:agent_id>/wol/", views.wol),
|
||||
]
|
||||
|
||||
@@ -6,19 +6,12 @@ import time
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
|
||||
from core.utils import (
|
||||
get_core_settings,
|
||||
get_mesh_ws_url,
|
||||
remove_mesh_agent,
|
||||
token_is_valid,
|
||||
)
|
||||
from django.conf import settings
|
||||
from django.db.models import Exists, OuterRef, Prefetch, Q
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone as djangotime
|
||||
from django.utils.dateparse import parse_datetime
|
||||
from logs.models import AuditLog, DebugLog, PendingAction
|
||||
from meshctrl.utils import get_login_token
|
||||
from packaging import version as pyver
|
||||
from rest_framework import serializers
|
||||
@@ -27,8 +20,17 @@ from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from core.utils import (
|
||||
get_core_settings,
|
||||
get_mesh_ws_url,
|
||||
remove_mesh_agent,
|
||||
token_is_valid,
|
||||
wake_on_lan,
|
||||
)
|
||||
from logs.models import AuditLog, DebugLog, PendingAction
|
||||
from scripts.models import Script
|
||||
from scripts.tasks import handle_bulk_command_task, handle_bulk_script_task
|
||||
from scripts.tasks import bulk_command_task, bulk_script_task
|
||||
from tacticalrmm.constants import (
|
||||
AGENT_DEFER,
|
||||
AGENT_STATUS_OFFLINE,
|
||||
@@ -58,6 +60,7 @@ from .permissions import (
|
||||
AgentHistoryPerms,
|
||||
AgentNotesPerms,
|
||||
AgentPerms,
|
||||
AgentWOLPerms,
|
||||
EvtLogPerms,
|
||||
InstallAgentPerms,
|
||||
ManageProcPerms,
|
||||
@@ -561,10 +564,11 @@ class Reboot(APIView):
|
||||
@api_view(["POST"])
|
||||
@permission_classes([IsAuthenticated, InstallAgentPerms])
|
||||
def install_agent(request):
|
||||
from knox.models import AuthToken
|
||||
|
||||
from accounts.models import User
|
||||
from agents.utils import get_agent_url
|
||||
from core.utils import token_is_valid
|
||||
from knox.models import AuthToken
|
||||
|
||||
# TODO rework this ghetto validation hack
|
||||
# https://github.com/amidaware/tacticalrmm/issues/1461
|
||||
@@ -947,7 +951,7 @@ def bulk(request):
|
||||
agents: list[int] = [agent.pk for agent in q]
|
||||
|
||||
if not agents:
|
||||
return notify_error("No agents where found meeting the selected criteria")
|
||||
return notify_error("No agents were found meeting the selected criteria")
|
||||
|
||||
AuditLog.audit_bulk_action(
|
||||
request.user,
|
||||
@@ -962,27 +966,29 @@ def bulk(request):
|
||||
else:
|
||||
shell = request.data["shell"]
|
||||
|
||||
handle_bulk_command_task.delay(
|
||||
agents,
|
||||
request.data["cmd"],
|
||||
shell,
|
||||
request.data["timeout"],
|
||||
request.user.username[:50],
|
||||
request.data["run_as_user"],
|
||||
bulk_command_task.delay(
|
||||
agent_pks=agents,
|
||||
cmd=request.data["cmd"],
|
||||
shell=shell,
|
||||
timeout=request.data["timeout"],
|
||||
username=request.user.username[:50],
|
||||
run_as_user=request.data["run_as_user"],
|
||||
)
|
||||
return Response(f"Command will now be run on {len(agents)} agents")
|
||||
|
||||
elif request.data["mode"] == "script":
|
||||
script = get_object_or_404(Script, pk=request.data["script"])
|
||||
handle_bulk_script_task.delay(
|
||||
script.pk,
|
||||
agents,
|
||||
request.data["args"],
|
||||
request.data["timeout"],
|
||||
request.user.username[:50],
|
||||
request.data["run_as_user"],
|
||||
request.data["env_vars"],
|
||||
|
||||
bulk_script_task.delay(
|
||||
script_pk=script.pk,
|
||||
agent_pks=agents,
|
||||
args=request.data["args"],
|
||||
timeout=request.data["timeout"],
|
||||
username=request.user.username[:50],
|
||||
run_as_user=request.data["run_as_user"],
|
||||
env_vars=request.data["env_vars"],
|
||||
)
|
||||
|
||||
return Response(f"{script.name} will now be run on {len(agents)} agents")
|
||||
|
||||
elif request.data["mode"] == "patch":
|
||||
@@ -1157,3 +1163,18 @@ class ScriptRunHistory(APIView):
|
||||
|
||||
ret = self.OutputSerializer(hists, many=True).data
|
||||
return Response(ret)
|
||||
|
||||
|
||||
@api_view(["POST"])
|
||||
@permission_classes([IsAuthenticated, AgentWOLPerms])
|
||||
def wol(request, agent_id):
|
||||
agent = get_object_or_404(
|
||||
Agent.objects.defer(*AGENT_DEFER),
|
||||
agent_id=agent_id,
|
||||
)
|
||||
try:
|
||||
uri = get_mesh_ws_url()
|
||||
asyncio.run(wake_on_lan(uri=uri, mesh_node_id=agent.mesh_node_id))
|
||||
except Exception as e:
|
||||
return notify_error(str(e))
|
||||
return Response(f"Wake-on-LAN sent to {agent.hostname}")
|
||||
|
||||
@@ -639,6 +639,8 @@ class Alert(models.Model):
|
||||
|
||||
try:
|
||||
temp_args.append(re.sub("\\{\\{.*\\}\\}", value, arg))
|
||||
except re.error:
|
||||
temp_args.append(re.sub("\\{\\{.*\\}\\}", re.escape(value), arg))
|
||||
except Exception as e:
|
||||
DebugLog.error(log_type=DebugLogType.SCRIPTING, message=str(e))
|
||||
continue
|
||||
|
||||
@@ -25,12 +25,16 @@ class GetAddAlerts(APIView):
|
||||
def patch(self, request):
|
||||
# top 10 alerts for dashboard icon
|
||||
if "top" in request.data.keys():
|
||||
alerts = Alert.objects.filter(
|
||||
resolved=False, snoozed=False, hidden=False
|
||||
).order_by("alert_time")[: int(request.data["top"])]
|
||||
count = Alert.objects.filter(
|
||||
resolved=False, snoozed=False, hidden=False
|
||||
).count()
|
||||
alerts = (
|
||||
Alert.objects.filter_by_role(request.user)
|
||||
.filter(resolved=False, snoozed=False, hidden=False)
|
||||
.order_by("alert_time")[: int(request.data["top"])]
|
||||
)
|
||||
count = (
|
||||
Alert.objects.filter_by_role(request.user)
|
||||
.filter(resolved=False, snoozed=False, hidden=False)
|
||||
.count()
|
||||
)
|
||||
return Response(
|
||||
{
|
||||
"alerts_count": count,
|
||||
|
||||
@@ -41,7 +41,7 @@ from tacticalrmm.constants import (
|
||||
MeshAgentIdent,
|
||||
PAStatus,
|
||||
)
|
||||
from tacticalrmm.helpers import notify_error
|
||||
from tacticalrmm.helpers import make_random_password, notify_error
|
||||
from tacticalrmm.utils import reload_nats
|
||||
from winupdate.models import WinUpdate, WinUpdatePolicy
|
||||
|
||||
@@ -457,7 +457,7 @@ class NewAgent(APIView):
|
||||
user = User.objects.create_user( # type: ignore
|
||||
username=request.data["agent_id"],
|
||||
agent=agent,
|
||||
password=User.objects.make_random_password(60), # type: ignore
|
||||
password=make_random_password(len=60),
|
||||
)
|
||||
|
||||
token = Token.objects.create(user=user)
|
||||
|
||||
@@ -3,8 +3,8 @@ import random
|
||||
import string
|
||||
from contextlib import suppress
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from django.core.cache import cache
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
@@ -270,12 +270,12 @@ class AutomatedTask(BaseAuditModel):
|
||||
and self.task_type == TaskType.RUN_ONCE
|
||||
and self.run_asap_after_missed
|
||||
and agent
|
||||
and self.run_time_date
|
||||
< djangotime.now().astimezone(pytz.timezone(agent.timezone))
|
||||
and self.run_time_date.replace(tzinfo=ZoneInfo(agent.timezone))
|
||||
< djangotime.now().astimezone(ZoneInfo(agent.timezone))
|
||||
):
|
||||
self.run_time_date = (
|
||||
djangotime.now() + djangotime.timedelta(minutes=5)
|
||||
).astimezone(pytz.timezone(agent.timezone))
|
||||
).astimezone(ZoneInfo(agent.timezone))
|
||||
|
||||
task["start_year"] = int(self.run_time_date.strftime("%Y"))
|
||||
task["start_month"] = int(self.run_time_date.strftime("%-m"))
|
||||
|
||||
@@ -149,6 +149,7 @@ def remove_orphaned_win_tasks(self) -> str:
|
||||
for item in items
|
||||
]
|
||||
await asyncio.gather(*tasks)
|
||||
await nc.flush()
|
||||
await nc.close()
|
||||
|
||||
asyncio.run(_run())
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import asyncio
|
||||
from datetime import datetime as dt
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import msgpack
|
||||
import nats
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone as djangotime
|
||||
@@ -17,16 +14,15 @@ from agents.models import Agent
|
||||
from alerts.models import Alert
|
||||
from automation.models import Policy
|
||||
from tacticalrmm.constants import CheckStatus, CheckType
|
||||
from tacticalrmm.helpers import notify_error, setup_nats_options
|
||||
from tacticalrmm.exceptions import NatsDown
|
||||
from tacticalrmm.helpers import notify_error
|
||||
from tacticalrmm.nats_utils import abulk_nats_command
|
||||
from tacticalrmm.permissions import _has_perm_on_agent
|
||||
|
||||
from .models import Check, CheckHistory, CheckResult
|
||||
from .permissions import BulkRunChecksPerms, ChecksPerms, RunChecksPerms
|
||||
from .serializers import CheckHistorySerializer, CheckSerializer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nats.aio.client import Client as NATSClient
|
||||
|
||||
|
||||
class GetAddChecks(APIView):
|
||||
permission_classes = [IsAuthenticated, ChecksPerms]
|
||||
@@ -189,29 +185,22 @@ def bulk_run_checks(request, target, pk):
|
||||
case "site":
|
||||
q = Q(site__id=pk)
|
||||
|
||||
agents = list(
|
||||
agent_ids = list(
|
||||
Agent.objects.only("agent_id", "site")
|
||||
.filter(q)
|
||||
.values_list("agent_id", flat=True)
|
||||
)
|
||||
|
||||
if not agents:
|
||||
if not agent_ids:
|
||||
return notify_error("No agents matched query")
|
||||
|
||||
async def _run_check(nc: "NATSClient", sub) -> None:
|
||||
await nc.publish(subject=sub, payload=msgpack.dumps({"func": "runchecks"}))
|
||||
payload = {"func": "runchecks"}
|
||||
items = [(agent_id, payload) for agent_id in agent_ids]
|
||||
|
||||
async def _run() -> None:
|
||||
opts = setup_nats_options()
|
||||
try:
|
||||
nc = await nats.connect(**opts)
|
||||
except Exception as e:
|
||||
return notify_error(str(e))
|
||||
try:
|
||||
asyncio.run(abulk_nats_command(items=items))
|
||||
except NatsDown as e:
|
||||
return notify_error(str(e))
|
||||
|
||||
tasks = [_run_check(nc=nc, sub=agent) for agent in agents]
|
||||
await asyncio.gather(*tasks)
|
||||
await nc.close()
|
||||
|
||||
asyncio.run(_run())
|
||||
ret = f"Checks will now be run on {len(agents)} agents"
|
||||
ret = f"Checks will now be run on {len(agent_ids)} agents"
|
||||
return Response(ret)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ $EUID -ne 0 ]; then
|
||||
echo "ERROR: Must be run as root"
|
||||
exit 1
|
||||
echo "ERROR: Must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HAS_SYSTEMD=$(ps --no-headers -o comm 1)
|
||||
@@ -37,15 +37,15 @@ deb=(ubuntu debian raspbian kali linuxmint)
|
||||
rhe=(fedora rocky centos rhel amzn arch opensuse)
|
||||
|
||||
set_locale_deb() {
|
||||
locale-gen "en_US.UTF-8"
|
||||
localectl set-locale LANG=en_US.UTF-8
|
||||
. /etc/default/locale
|
||||
locale-gen "en_US.UTF-8"
|
||||
localectl set-locale LANG=en_US.UTF-8
|
||||
. /etc/default/locale
|
||||
}
|
||||
|
||||
set_locale_rhel() {
|
||||
localedef -c -i en_US -f UTF-8 en_US.UTF-8 > /dev/null 2>&1
|
||||
localectl set-locale LANG=en_US.UTF-8
|
||||
. /etc/locale.conf
|
||||
localedef -c -i en_US -f UTF-8 en_US.UTF-8 >/dev/null 2>&1
|
||||
localectl set-locale LANG=en_US.UTF-8
|
||||
. /etc/locale.conf
|
||||
}
|
||||
|
||||
RemoveOldAgent() {
|
||||
@@ -67,8 +67,14 @@ RemoveOldAgent() {
|
||||
|
||||
InstallMesh() {
|
||||
if [ -f /etc/os-release ]; then
|
||||
distroID=$(. /etc/os-release; echo $ID)
|
||||
distroIDLIKE=$(. /etc/os-release; echo $ID_LIKE)
|
||||
distroID=$(
|
||||
. /etc/os-release
|
||||
echo $ID
|
||||
)
|
||||
distroIDLIKE=$(
|
||||
. /etc/os-release
|
||||
echo $ID_LIKE
|
||||
)
|
||||
if [[ " ${deb[*]} " =~ " ${distroID} " ]]; then
|
||||
set_locale_deb
|
||||
elif [[ " ${deb[*]} " =~ " ${distroIDLIKE} " ]]; then
|
||||
@@ -80,11 +86,9 @@ InstallMesh() {
|
||||
fi
|
||||
fi
|
||||
|
||||
meshTmpDir=$(mktemp -d -t "mesh-XXXXXXXXX")
|
||||
if [ $? -ne 0 ]; then
|
||||
meshTmpDir='/root/meshtemp'
|
||||
mkdir -p ${meshTmpDir}
|
||||
fi
|
||||
meshTmpDir='/root/meshtemp'
|
||||
mkdir -p $meshTmpDir
|
||||
|
||||
meshTmpBin="${meshTmpDir}/meshagent"
|
||||
wget --no-check-certificate -q -O ${meshTmpBin} ${meshDL}
|
||||
chmod +x ${meshTmpBin}
|
||||
@@ -101,8 +105,8 @@ RemoveMesh() {
|
||||
fi
|
||||
|
||||
if [ -f "${meshSysD}" ]; then
|
||||
systemctl stop ${meshSvcName} > /dev/null 2>&1
|
||||
systemctl disable ${meshSvcName} > /dev/null 2>&1
|
||||
systemctl stop ${meshSvcName} >/dev/null 2>&1
|
||||
systemctl disable ${meshSvcName} >/dev/null 2>&1
|
||||
rm -f ${meshSysD}
|
||||
fi
|
||||
|
||||
@@ -166,7 +170,8 @@ fi
|
||||
|
||||
eval ${INSTALL_CMD}
|
||||
|
||||
tacticalsvc="$(cat << EOF
|
||||
tacticalsvc="$(
|
||||
cat <<EOF
|
||||
[Unit]
|
||||
Description=Tactical RMM Linux Agent
|
||||
|
||||
@@ -184,7 +189,7 @@ KillMode=process
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
)"
|
||||
echo "${tacticalsvc}" | tee ${agentSysD} > /dev/null
|
||||
echo "${tacticalsvc}" | tee ${agentSysD} >/dev/null
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable ${agentSvcName}
|
||||
|
||||
@@ -9,6 +9,7 @@ from django.utils import timezone as djangotime
|
||||
|
||||
from agents.models import Agent
|
||||
from tacticalrmm.constants import AgentMonType
|
||||
from tacticalrmm.helpers import days_until_cert_expires
|
||||
|
||||
|
||||
class DashInfo(AsyncJsonWebsocketConsumer):
|
||||
@@ -27,7 +28,6 @@ class DashInfo(AsyncJsonWebsocketConsumer):
|
||||
self.dash_info.cancel()
|
||||
|
||||
self.connected = False
|
||||
await self.close()
|
||||
|
||||
async def receive_json(self, payload, **kwargs):
|
||||
pass
|
||||
@@ -68,6 +68,7 @@ class DashInfo(AsyncJsonWebsocketConsumer):
|
||||
"total_workstation_offline_count": offline_workstation_agents_count,
|
||||
"total_server_count": total_server_agents_count,
|
||||
"total_workstation_count": total_workstation_agents_count,
|
||||
"days_until_cert_expires": days_until_cert_expires(),
|
||||
}
|
||||
|
||||
async def send_dash_info(self):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from core.utils import clear_entire_cache
|
||||
@@ -10,3 +11,4 @@ class Command(BaseCommand):
|
||||
self.stdout.write(self.style.WARNING("Cleaning the cache"))
|
||||
clear_entire_cache()
|
||||
self.stdout.write(self.style.SUCCESS("Cache was cleared!"))
|
||||
call_command("fix_dupe_agent_customfields")
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.2 on 2023-04-09 15:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0036_alter_coresettings_default_time_zone'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='coresettings',
|
||||
name='open_ai_model',
|
||||
field=models.CharField(blank=True, default='gpt-3.5-turbo', max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='coresettings',
|
||||
name='open_ai_token',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
]
|
||||
@@ -98,6 +98,10 @@ class CoreSettings(BaseAuditModel):
|
||||
date_format = models.CharField(
|
||||
max_length=30, blank=True, default="MMM-DD-YYYY - HH:mm"
|
||||
)
|
||||
open_ai_token = models.CharField(max_length=255, null=True, blank=True)
|
||||
open_ai_model = models.CharField(
|
||||
max_length=255, blank=True, default="gpt-3.5-turbo"
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs) -> None:
|
||||
from alerts.tasks import cache_agents_alert_template
|
||||
|
||||
@@ -3,28 +3,30 @@ from unittest.mock import patch
|
||||
import requests
|
||||
from channels.db import database_sync_to_async
|
||||
from channels.testing import WebsocketCommunicator
|
||||
from django.conf import settings
|
||||
|
||||
# from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.test import override_settings
|
||||
from model_bakery import baker
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
from agents.models import Agent
|
||||
# from agents.models import Agent
|
||||
from core.utils import get_core_settings, get_meshagent_url
|
||||
from logs.models import PendingAction
|
||||
|
||||
# from logs.models import PendingAction
|
||||
from tacticalrmm.constants import (
|
||||
CONFIG_MGMT_CMDS,
|
||||
CustomFieldModel,
|
||||
MeshAgentIdent,
|
||||
PAAction,
|
||||
PAStatus,
|
||||
# PAAction,
|
||||
# PAStatus,
|
||||
)
|
||||
from tacticalrmm.test import TacticalTestCase
|
||||
|
||||
from .consumers import DashInfo
|
||||
from .models import CustomField, GlobalKVStore, URLAction
|
||||
from .serializers import CustomFieldSerializer, KeyStoreSerializer, URLActionSerializer
|
||||
from .tasks import core_maintenance_tasks, resolve_pending_actions
|
||||
from .tasks import core_maintenance_tasks # , resolve_pending_actions
|
||||
|
||||
|
||||
class TestCodeSign(TacticalTestCase):
|
||||
@@ -410,28 +412,28 @@ class TestCoreTasks(TacticalTestCase):
|
||||
|
||||
self.check_not_authenticated("get", url)
|
||||
|
||||
def test_resolved_pending_agentupdate_task(self):
|
||||
online = baker.make_recipe("agents.online_agent", version="2.0.0", _quantity=20)
|
||||
offline = baker.make_recipe(
|
||||
"agents.offline_agent", version="2.0.0", _quantity=20
|
||||
)
|
||||
agents = online + offline
|
||||
for agent in agents:
|
||||
baker.make_recipe("logs.pending_agentupdate_action", agent=agent)
|
||||
# def test_resolved_pending_agentupdate_task(self):
|
||||
# online = baker.make_recipe("agents.online_agent", version="2.0.0", _quantity=20)
|
||||
# offline = baker.make_recipe(
|
||||
# "agents.offline_agent", version="2.0.0", _quantity=20
|
||||
# )
|
||||
# agents = online + offline
|
||||
# for agent in agents:
|
||||
# baker.make_recipe("logs.pending_agentupdate_action", agent=agent)
|
||||
|
||||
Agent.objects.update(version=settings.LATEST_AGENT_VER)
|
||||
# Agent.objects.update(version=settings.LATEST_AGENT_VER)
|
||||
|
||||
resolve_pending_actions()
|
||||
# resolve_pending_actions()
|
||||
|
||||
complete = PendingAction.objects.filter(
|
||||
action_type=PAAction.AGENT_UPDATE, status=PAStatus.COMPLETED
|
||||
).count()
|
||||
old = PendingAction.objects.filter(
|
||||
action_type=PAAction.AGENT_UPDATE, status=PAStatus.PENDING
|
||||
).count()
|
||||
# complete = PendingAction.objects.filter(
|
||||
# action_type=PAAction.AGENT_UPDATE, status=PAStatus.COMPLETED
|
||||
# ).count()
|
||||
# old = PendingAction.objects.filter(
|
||||
# action_type=PAAction.AGENT_UPDATE, status=PAStatus.PENDING
|
||||
# ).count()
|
||||
|
||||
self.assertEqual(complete, 20)
|
||||
self.assertEqual(old, 20)
|
||||
# self.assertEqual(complete, 20)
|
||||
# self.assertEqual(old, 20)
|
||||
|
||||
|
||||
class TestCoreMgmtCommands(TacticalTestCase):
|
||||
|
||||
@@ -19,4 +19,5 @@ urlpatterns = [
|
||||
path("smstest/", views.TwilioSMSTest.as_view()),
|
||||
path("clearcache/", views.clear_cache),
|
||||
path("status/", views.status),
|
||||
path("openai/generate/", views.OpenAICodeCompletion.as_view()),
|
||||
]
|
||||
|
||||
@@ -142,6 +142,20 @@ async def send_command_with_mesh(
|
||||
)
|
||||
|
||||
|
||||
async def wake_on_lan(*, uri: str, mesh_node_id: str) -> None:
|
||||
node_id = _b64_to_hex(mesh_node_id)
|
||||
async with websockets.connect(uri) as ws:
|
||||
await ws.send(
|
||||
json.dumps(
|
||||
{
|
||||
"action": "wakedevices",
|
||||
"nodeids": [f"node//{node_id}"],
|
||||
"responseid": "trmm",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def remove_mesh_agent(uri: str, mesh_node_id: str) -> None:
|
||||
node_id = _b64_to_hex(mesh_node_id)
|
||||
async with websockets.connect(uri) as ws:
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import psutil
|
||||
import pytz
|
||||
import requests
|
||||
from cryptography import x509
|
||||
from django.conf import settings
|
||||
from django.http import JsonResponse
|
||||
@@ -12,6 +14,7 @@ from django.views.decorators.csrf import csrf_exempt
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
@@ -75,8 +78,9 @@ def clear_cache(request):
|
||||
@api_view()
|
||||
def dashboard_info(request):
|
||||
from core.utils import token_is_expired
|
||||
from tacticalrmm.utils import get_latest_trmm_ver
|
||||
from tacticalrmm.utils import get_latest_trmm_ver, runcmd_placeholder_text
|
||||
|
||||
core_settings = get_core_settings()
|
||||
return Response(
|
||||
{
|
||||
"trmm_version": settings.TRMM_VERSION,
|
||||
@@ -94,8 +98,14 @@ def dashboard_info(request):
|
||||
"clear_search_when_switching": request.user.clear_search_when_switching,
|
||||
"hosted": getattr(settings, "HOSTED", False),
|
||||
"date_format": request.user.date_format,
|
||||
"default_date_format": get_core_settings().date_format,
|
||||
"default_date_format": core_settings.date_format,
|
||||
"token_is_expired": token_is_expired(),
|
||||
"open_ai_integration_enabled": bool(core_settings.open_ai_token),
|
||||
"dash_info_color": request.user.dash_info_color,
|
||||
"dash_positive_color": request.user.dash_positive_color,
|
||||
"dash_negative_color": request.user.dash_negative_color,
|
||||
"dash_warning_color": request.user.dash_warning_color,
|
||||
"run_cmd_placeholder_text": runcmd_placeholder_text(),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -416,7 +426,7 @@ def status(request):
|
||||
cert_bytes = Path(cert_file).read_bytes()
|
||||
|
||||
cert = x509.load_pem_x509_certificate(cert_bytes)
|
||||
expires = pytz.utc.localize(cert.not_valid_after)
|
||||
expires = cert.not_valid_after.replace(tzinfo=ZoneInfo("UTC"))
|
||||
now = djangotime.now()
|
||||
delta = expires - now
|
||||
|
||||
@@ -449,3 +459,55 @@ def status(request):
|
||||
"nginx": sysd_svc_is_running("nginx.service"),
|
||||
}
|
||||
return JsonResponse(ret, json_dumps_params={"indent": 2})
|
||||
|
||||
|
||||
class OpenAICodeCompletion(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request: Request) -> Response:
|
||||
settings = get_core_settings()
|
||||
|
||||
if not settings.open_ai_token:
|
||||
return notify_error(
|
||||
"Open AI API Key not found. Open Global Settings > Open AI."
|
||||
)
|
||||
|
||||
if not request.data["prompt"]:
|
||||
return notify_error("Not prompt field found")
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {settings.open_ai_token}",
|
||||
}
|
||||
|
||||
data = {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": request.data["prompt"],
|
||||
},
|
||||
],
|
||||
"model": settings.open_ai_model,
|
||||
"temperature": 0.5,
|
||||
"max_tokens": 1000,
|
||||
"n": 1,
|
||||
"stop": None,
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
"https://api.openai.com/v1/chat/completions",
|
||||
headers=headers,
|
||||
data=json.dumps(data),
|
||||
)
|
||||
except Exception as e:
|
||||
return notify_error(str(e))
|
||||
|
||||
response_data = json.loads(response.text)
|
||||
|
||||
if "error" in response_data:
|
||||
return notify_error(
|
||||
f"The Open AI API returned an error: {response_data['error']['message']}"
|
||||
)
|
||||
|
||||
return Response(response_data["choices"][0]["message"]["content"])
|
||||
|
||||
@@ -5,6 +5,5 @@ pytest
|
||||
pytest-django
|
||||
pytest-xdist
|
||||
pytest-cov
|
||||
codecov
|
||||
refurb
|
||||
flake8
|
||||
@@ -1,39 +1,39 @@
|
||||
adrf==0.1.1
|
||||
celery==5.2.7
|
||||
certifi==2022.12.7
|
||||
asgiref==3.7.2
|
||||
celery==5.3.1
|
||||
certifi==2023.7.22
|
||||
cffi==1.15.1
|
||||
channels==4.0.0
|
||||
channels_redis==4.1.0
|
||||
chardet==4.0.0
|
||||
cryptography==40.0.1
|
||||
cryptography==41.0.3
|
||||
daphne==4.0.0
|
||||
Django==4.1.8
|
||||
django-cors-headers==3.14.0
|
||||
Django==4.2.4
|
||||
django-cors-headers==4.2.0
|
||||
django-ipware==5.0.0
|
||||
django-rest-knox==4.2.0
|
||||
djangorestframework==3.14.0
|
||||
drf-spectacular==0.26.1
|
||||
hiredis==2.2.2
|
||||
drf-spectacular==0.26.4
|
||||
hiredis==2.2.3
|
||||
meshctrl==0.1.15
|
||||
msgpack==1.0.5
|
||||
nats-py==2.2.0
|
||||
packaging==23.0
|
||||
psutil==5.9.4
|
||||
psycopg2-binary==2.9.6
|
||||
nats-py==2.3.1
|
||||
packaging==23.1
|
||||
psutil==5.9.5
|
||||
psycopg[binary]==3.1.10
|
||||
pycparser==2.21
|
||||
pycryptodome==3.17
|
||||
pyotp==2.8.0
|
||||
pyparsing==3.0.9
|
||||
pycryptodome==3.18.0
|
||||
pyotp==2.9.0
|
||||
pyparsing==3.1.1
|
||||
pytz==2023.3
|
||||
qrcode==7.4.2
|
||||
redis==4.5.4
|
||||
requests==2.28.2
|
||||
redis==4.5.5
|
||||
requests==2.31.0
|
||||
six==1.16.0
|
||||
sqlparse==0.4.3
|
||||
twilio==7.17.0
|
||||
urllib3==1.26.15
|
||||
uWSGI==2.0.21
|
||||
sqlparse==0.4.4
|
||||
twilio==8.5.0
|
||||
urllib3==2.0.4
|
||||
uWSGI==2.0.22
|
||||
validators==0.20.0
|
||||
vine==5.0.0
|
||||
websockets==11.0.1
|
||||
zipp==3.15.0
|
||||
websockets==11.0.3
|
||||
zipp==3.16.2
|
||||
|
||||
@@ -216,7 +216,12 @@ class Script(BaseAuditModel):
|
||||
)
|
||||
|
||||
if value:
|
||||
temp_args.append(re.sub("\\{\\{.*\\}\\}", value, arg))
|
||||
try:
|
||||
temp_args.append(re.sub("\\{\\{.*\\}\\}", value, arg))
|
||||
except re.error:
|
||||
temp_args.append(
|
||||
re.sub("\\{\\{.*\\}\\}", re.escape(value), arg)
|
||||
)
|
||||
else:
|
||||
# pass parameter unaltered
|
||||
temp_args.append(arg)
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
import asyncio
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
import msgpack
|
||||
import nats
|
||||
|
||||
from agents.models import Agent, AgentHistory
|
||||
from scripts.models import Script
|
||||
from tacticalrmm.celery import app
|
||||
from tacticalrmm.constants import AgentHistoryType
|
||||
from tacticalrmm.helpers import setup_nats_options
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nats.aio.client import Client as NATSClient
|
||||
from tacticalrmm.nats_utils import abulk_nats_command
|
||||
|
||||
|
||||
@app.task
|
||||
def handle_bulk_command_task(
|
||||
agentpks: list[int],
|
||||
def bulk_command_task(
|
||||
*,
|
||||
agent_pks: list[int],
|
||||
cmd: str,
|
||||
shell: str,
|
||||
timeout,
|
||||
username,
|
||||
timeout: int,
|
||||
username: str,
|
||||
run_as_user: bool = False,
|
||||
) -> None:
|
||||
items = []
|
||||
@@ -34,7 +28,7 @@ def handle_bulk_command_task(
|
||||
"run_as_user": run_as_user,
|
||||
}
|
||||
agent: "Agent"
|
||||
for agent in Agent.objects.filter(pk__in=agentpks):
|
||||
for agent in Agent.objects.filter(pk__in=agent_pks):
|
||||
hist = AgentHistory.objects.create(
|
||||
agent=agent,
|
||||
type=AgentHistoryType.CMD_RUN,
|
||||
@@ -45,48 +39,47 @@ def handle_bulk_command_task(
|
||||
tmp["id"] = hist.pk
|
||||
items.append((agent.agent_id, tmp))
|
||||
|
||||
async def _run_cmd(nc: "NATSClient", sub, data) -> None:
|
||||
await nc.publish(subject=sub, payload=msgpack.dumps(data))
|
||||
|
||||
async def _run() -> None:
|
||||
opts = setup_nats_options()
|
||||
try:
|
||||
nc = await nats.connect(**opts)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return
|
||||
|
||||
tasks = [_run_cmd(nc=nc, sub=item[0], data=item[1]) for item in items]
|
||||
await asyncio.gather(*tasks)
|
||||
await nc.close()
|
||||
|
||||
asyncio.run(_run())
|
||||
asyncio.run(abulk_nats_command(items=items))
|
||||
|
||||
|
||||
@app.task
|
||||
def handle_bulk_script_task(
|
||||
scriptpk: int,
|
||||
agentpks: List[int],
|
||||
args: List[str],
|
||||
def bulk_script_task(
|
||||
*,
|
||||
script_pk: int,
|
||||
agent_pks: list[int],
|
||||
args: list[str] = [],
|
||||
timeout: int,
|
||||
username: str,
|
||||
run_as_user: bool = False,
|
||||
env_vars: list[str] = [],
|
||||
) -> None:
|
||||
script = Script.objects.get(pk=scriptpk)
|
||||
script = Script.objects.get(pk=script_pk)
|
||||
# always override if set on script model
|
||||
if script.run_as_user:
|
||||
run_as_user = True
|
||||
|
||||
items = []
|
||||
agent: "Agent"
|
||||
for agent in Agent.objects.filter(pk__in=agentpks):
|
||||
for agent in Agent.objects.filter(pk__in=agent_pks):
|
||||
hist = AgentHistory.objects.create(
|
||||
agent=agent,
|
||||
type=AgentHistoryType.SCRIPT_RUN,
|
||||
script=script,
|
||||
username=username,
|
||||
)
|
||||
agent.run_script(
|
||||
scriptpk=script.pk,
|
||||
args=args,
|
||||
timeout=timeout,
|
||||
history_pk=hist.pk,
|
||||
run_as_user=run_as_user,
|
||||
env_vars=env_vars,
|
||||
)
|
||||
data = {
|
||||
"func": "runscriptfull",
|
||||
"id": hist.pk,
|
||||
"timeout": timeout,
|
||||
"script_args": script.parse_script_args(agent, script.shell, args),
|
||||
"payload": {
|
||||
"code": script.code,
|
||||
"shell": script.shell,
|
||||
},
|
||||
"run_as_user": run_as_user,
|
||||
"env_vars": env_vars,
|
||||
}
|
||||
tup = (agent.agent_id, data)
|
||||
items.append(tup)
|
||||
|
||||
asyncio.run(abulk_nats_command(items=items))
|
||||
|
||||
@@ -65,6 +65,8 @@ class GetEditActionService(APIView):
|
||||
# win service action
|
||||
def post(self, request, agent_id, svcname):
|
||||
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"]
|
||||
data = {
|
||||
"func": "winsvcaction",
|
||||
|
||||
@@ -425,6 +425,9 @@ DEMO_NOT_ALLOWED = [
|
||||
{"name": "clear_cache", "methods": ["GET"]},
|
||||
{"name": "ResetPass", "methods": ["PUT"]},
|
||||
{"name": "Reset2FA", "methods": ["PUT"]},
|
||||
{"name": "bulk_run_checks", "methods": ["GET"]},
|
||||
{"name": "OpenAICodeCompletion", "methods": ["POST"]},
|
||||
{"name": "wol", "methods": ["POST"]},
|
||||
]
|
||||
|
||||
CONFIG_MGMT_CMDS = (
|
||||
|
||||
7
api/tacticalrmm/tacticalrmm/exceptions.py
Normal file
7
api/tacticalrmm/tacticalrmm/exceptions.py
Normal file
@@ -0,0 +1,7 @@
|
||||
class NatsDown(Exception):
|
||||
"""
|
||||
Raised when a connection to NATS cannot be established.
|
||||
"""
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "Unable to connect to NATS"
|
||||
@@ -1,8 +1,12 @@
|
||||
import random
|
||||
import secrets
|
||||
import string
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from urllib.parse import urlparse
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
import random
|
||||
from cryptography import x509
|
||||
from django.conf import settings
|
||||
from django.utils import timezone as djangotime
|
||||
from rest_framework import status
|
||||
@@ -42,12 +46,10 @@ def date_is_in_past(*, datetime_obj: "datetime", agent_tz: str) -> bool:
|
||||
"""
|
||||
datetime_obj must be a naive datetime
|
||||
"""
|
||||
now = djangotime.now()
|
||||
# convert agent tz to UTC to compare
|
||||
agent_pytz = pytz.timezone(agent_tz)
|
||||
localized = agent_pytz.localize(datetime_obj)
|
||||
utc_time = localized.astimezone(pytz.utc)
|
||||
return now > utc_time
|
||||
localized = datetime_obj.replace(tzinfo=ZoneInfo(agent_tz))
|
||||
utc_time = localized.astimezone(ZoneInfo("UTC"))
|
||||
return djangotime.now() > utc_time
|
||||
|
||||
|
||||
def get_webdomain() -> str:
|
||||
@@ -73,3 +75,19 @@ def setup_nats_options() -> dict[str, Any]:
|
||||
"max_reconnect_attempts": 2,
|
||||
}
|
||||
return opts
|
||||
|
||||
|
||||
def make_random_password(*, len: int) -> str:
|
||||
alphabet = string.ascii_letters + string.digits
|
||||
return "".join(secrets.choice(alphabet) for i in range(len))
|
||||
|
||||
|
||||
def days_until_cert_expires() -> int:
|
||||
cert_file, _ = get_certs()
|
||||
cert_bytes = Path(cert_file).read_bytes()
|
||||
|
||||
cert = x509.load_pem_x509_certificate(cert_bytes)
|
||||
expires = cert.not_valid_after.replace(tzinfo=ZoneInfo("UTC"))
|
||||
delta = expires - djangotime.now()
|
||||
|
||||
return delta.days
|
||||
|
||||
38
api/tacticalrmm/tacticalrmm/nats_utils.py
Normal file
38
api/tacticalrmm/tacticalrmm/nats_utils.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import asyncio
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import msgpack
|
||||
import nats
|
||||
|
||||
from tacticalrmm.exceptions import NatsDown
|
||||
from tacticalrmm.helpers import setup_nats_options
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nats.aio.client import Client as NClient
|
||||
|
||||
NATS_DATA = dict[str, Any]
|
||||
|
||||
BULK_NATS_TASKS = list[tuple[str, Any]]
|
||||
|
||||
|
||||
async def _anats_message(*, nc: "NClient", subject: str, data: "NATS_DATA") -> None:
|
||||
try:
|
||||
payload = msgpack.dumps(data)
|
||||
except:
|
||||
return
|
||||
|
||||
await nc.publish(subject=subject, payload=payload)
|
||||
|
||||
|
||||
async def abulk_nats_command(*, items: "BULK_NATS_TASKS") -> None:
|
||||
"""Fire and forget"""
|
||||
opts = setup_nats_options()
|
||||
try:
|
||||
nc = await nats.connect(**opts)
|
||||
except Exception:
|
||||
raise NatsDown
|
||||
|
||||
tasks = [_anats_message(nc=nc, subject=item[0], data=item[1]) for item in items]
|
||||
await asyncio.gather(*tasks)
|
||||
await nc.flush()
|
||||
await nc.close()
|
||||
@@ -20,32 +20,29 @@ MAC_UNINSTALL = BASE_DIR / "core" / "mac_uninstall.sh"
|
||||
AUTH_USER_MODEL = "accounts.User"
|
||||
|
||||
# latest release
|
||||
TRMM_VERSION = "0.15.9"
|
||||
TRMM_VERSION = "0.16.2"
|
||||
|
||||
# https://github.com/amidaware/tacticalrmm-web
|
||||
WEB_VERSION = "0.101.18"
|
||||
WEB_VERSION = "0.101.28"
|
||||
|
||||
# bump this version everytime vue code is changed
|
||||
# to alert user they need to manually refresh their browser
|
||||
APP_VER = "0.0.179"
|
||||
APP_VER = "0.0.183"
|
||||
|
||||
# https://github.com/amidaware/rmmagent
|
||||
LATEST_AGENT_VER = "2.4.6"
|
||||
LATEST_AGENT_VER = "2.4.11"
|
||||
|
||||
MESH_VER = "1.1.4"
|
||||
MESH_VER = "1.1.9"
|
||||
|
||||
NATS_SERVER_VER = "2.9.15"
|
||||
NATS_SERVER_VER = "2.9.21"
|
||||
|
||||
# for the update script, bump when need to recreate venv
|
||||
PIP_VER = "36"
|
||||
PIP_VER = "38"
|
||||
|
||||
SETUPTOOLS_VER = "67.6.1"
|
||||
WHEEL_VER = "0.40.0"
|
||||
SETUPTOOLS_VER = "68.0.0"
|
||||
WHEEL_VER = "0.41.1"
|
||||
|
||||
AGENT_BASE_URL = "https://agents.tacticalrmm.com"
|
||||
CHECK_TOKEN_URL = f"{AGENT_BASE_URL}/api/v2/checktoken"
|
||||
AGENTS_URL = f"{AGENT_BASE_URL}/api/v2/agents/?"
|
||||
EXE_GEN_URL = f"{AGENT_BASE_URL}/api/v2/exe"
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||
|
||||
@@ -77,6 +74,10 @@ REDIS_HOST = "127.0.0.1"
|
||||
with suppress(ImportError):
|
||||
from .local_settings import * # noqa
|
||||
|
||||
CHECK_TOKEN_URL = f"{AGENT_BASE_URL}/api/v2/checktoken"
|
||||
AGENTS_URL = f"{AGENT_BASE_URL}/api/v2/agents/?"
|
||||
EXE_GEN_URL = f"{AGENT_BASE_URL}/api/v2/exe"
|
||||
|
||||
if "GHACTIONS" in os.environ:
|
||||
DEBUG = False
|
||||
ADMIN_ENABLED = False
|
||||
|
||||
@@ -11,6 +11,7 @@ from agents.models import Agent
|
||||
from automation.models import Policy
|
||||
from core.models import CoreSettings
|
||||
from tacticalrmm.constants import CustomFieldModel, CustomFieldType
|
||||
from tacticalrmm.helpers import make_random_password
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from checks.models import Check
|
||||
@@ -64,7 +65,7 @@ class TacticalTestCase(TestCase):
|
||||
User.objects.create_user( # type: ignore
|
||||
username=uuid.uuid4().hex,
|
||||
is_installer_user=True,
|
||||
password=User.objects.make_random_password(60), # type: ignore
|
||||
password=make_random_password(len=60), # type: ignore
|
||||
)
|
||||
|
||||
def setup_client(self) -> None:
|
||||
@@ -73,7 +74,7 @@ class TacticalTestCase(TestCase):
|
||||
def setup_agent_auth(self, agent: "Agent") -> None:
|
||||
agent_user = User.objects.create_user( # type: ignore
|
||||
username=agent.agent_id,
|
||||
password=User.objects.make_random_password(60), # type: ignore
|
||||
password=make_random_password(len=60), # type: ignore
|
||||
)
|
||||
Token.objects.create(user=agent_user)
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
from contextlib import contextmanager
|
||||
from functools import wraps
|
||||
from typing import List, Optional, Union
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
import requests
|
||||
from channels.auth import AuthMiddlewareStack
|
||||
from channels.db import database_sync_to_async
|
||||
@@ -100,7 +100,7 @@ def generate_winagent_exe(
|
||||
|
||||
|
||||
def get_default_timezone():
|
||||
return pytz.timezone(get_core_settings().default_time_zone)
|
||||
return ZoneInfo(get_core_settings().default_time_zone)
|
||||
|
||||
|
||||
def get_bit_days(days: list[str]) -> int:
|
||||
@@ -470,3 +470,22 @@ class DjangoConnectionThreadPoolExecutor(ThreadPoolExecutor):
|
||||
self, *args = args
|
||||
|
||||
return super(self.__class__, self).submit(fn, *args, **kwargs)
|
||||
|
||||
|
||||
def runcmd_placeholder_text() -> dict[str, str]:
|
||||
ret = {
|
||||
"cmd": getattr(
|
||||
settings,
|
||||
"CMD_PLACEHOLDER_TEXT",
|
||||
"rmdir /S /Q C:\\Windows\\System32",
|
||||
),
|
||||
"powershell": getattr(
|
||||
settings,
|
||||
"POWERSHELL_PLACEHOLDER_TEXT",
|
||||
"Remove-Item -Recurse -Force C:\\Windows\\System32",
|
||||
),
|
||||
"shell": getattr(
|
||||
settings, "SHELL_PLACEHOLDER_TEXT", "rm -rf --no-preserve-root /"
|
||||
),
|
||||
}
|
||||
return ret
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from datetime import datetime as dt
|
||||
from itertools import cycle
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from model_bakery.recipe import Recipe, seq
|
||||
|
||||
from .models import WinUpdate, WinUpdatePolicy
|
||||
|
||||
timezone = pytz.timezone("America/Los_Angeles")
|
||||
timezone = ZoneInfo("America/Los_Angeles")
|
||||
|
||||
severity = ["Critical", "Important", "Moderate", "Low", ""]
|
||||
winupdate = Recipe(
|
||||
|
||||
@@ -2,8 +2,8 @@ import asyncio
|
||||
import datetime as dt
|
||||
import time
|
||||
from contextlib import suppress
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from django.utils import timezone as djangotime
|
||||
from packaging import version as pyver
|
||||
|
||||
@@ -58,7 +58,7 @@ def check_agent_update_schedule_task() -> None:
|
||||
or patch_policy.other == "approve"
|
||||
):
|
||||
# get current time in agent local time
|
||||
timezone = pytz.timezone(agent.timezone)
|
||||
timezone = ZoneInfo(agent.timezone)
|
||||
agent_localtime_now = dt.datetime.now(timezone)
|
||||
weekday = agent_localtime_now.weekday()
|
||||
hour = agent_localtime_now.hour
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from itertools import cycle
|
||||
# from itertools import cycle
|
||||
from unittest.mock import patch
|
||||
|
||||
from model_bakery import baker
|
||||
@@ -161,34 +161,34 @@ class WinupdateTasks(TacticalTestCase):
|
||||
)
|
||||
self.offline_agent = baker.make_recipe("agents.agent", site=site)
|
||||
|
||||
@patch("agents.models.Agent.nats_cmd")
|
||||
@patch("time.sleep")
|
||||
def test_auto_approve_task(self, mock_sleep, nats_cmd):
|
||||
from .tasks import auto_approve_updates_task
|
||||
# @patch("agents.models.Agent.nats_cmd")
|
||||
# @patch("time.sleep")
|
||||
# def test_auto_approve_task(self, mock_sleep, nats_cmd):
|
||||
# from .tasks import auto_approve_updates_task
|
||||
|
||||
# Setup data
|
||||
baker.make_recipe(
|
||||
"winupdate.winupdate",
|
||||
agent=cycle(
|
||||
[self.online_agents[0], self.online_agents[1], self.offline_agent]
|
||||
),
|
||||
_quantity=20,
|
||||
)
|
||||
baker.make_recipe(
|
||||
"winupdate.winupdate_approve",
|
||||
agent=cycle(
|
||||
[self.online_agents[0], self.online_agents[1], self.offline_agent]
|
||||
),
|
||||
_quantity=3,
|
||||
)
|
||||
# # Setup data
|
||||
# baker.make_recipe(
|
||||
# "winupdate.winupdate",
|
||||
# agent=cycle(
|
||||
# [self.online_agents[0], self.online_agents[1], self.offline_agent]
|
||||
# ),
|
||||
# _quantity=20,
|
||||
# )
|
||||
# baker.make_recipe(
|
||||
# "winupdate.winupdate_approve",
|
||||
# agent=cycle(
|
||||
# [self.online_agents[0], self.online_agents[1], self.offline_agent]
|
||||
# ),
|
||||
# _quantity=3,
|
||||
# )
|
||||
|
||||
# run task synchronously
|
||||
auto_approve_updates_task()
|
||||
# # run task synchronously
|
||||
# auto_approve_updates_task()
|
||||
|
||||
# make sure the check_for_updates_task was run once for each online agent
|
||||
self.assertEqual(nats_cmd.call_count, 2)
|
||||
# # make sure the check_for_updates_task was run once for each online agent
|
||||
# self.assertEqual(nats_cmd.call_count, 2)
|
||||
|
||||
# check if all of the created updates were approved
|
||||
winupdates = WinUpdate.objects.all()
|
||||
for update in winupdates:
|
||||
self.assertEqual(update.action, "approve")
|
||||
# # check if all of the created updates were approved
|
||||
# winupdates = WinUpdate.objects.all()
|
||||
# for update in winupdates:
|
||||
# self.assertEqual(update.action, "approve")
|
||||
|
||||
86
backup.sh
86
backup.sh
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_VERSION="22"
|
||||
SCRIPT_VERSION="28"
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
@@ -8,20 +8,38 @@ BLUE='\033[0;34m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
if [[ $* == *--schedule* ]]; then
|
||||
(
|
||||
crontab -l 2>/dev/null
|
||||
echo "0 0 * * * /rmm/backup.sh --auto"
|
||||
) | crontab -
|
||||
printf >&2 "${GREEN}Backups setup to run at midnight and rotate.${NC}\n"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ $EUID -eq 0 ]; then
|
||||
echo -ne "\033[0;31mDo NOT run this script as root. Exiting.\e[0m\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $* == *--schedule* ]]; then
|
||||
(
|
||||
crontab -l 2>/dev/null
|
||||
echo "0 0 * * * /rmm/backup.sh --auto > /dev/null 2>&1"
|
||||
) | crontab -
|
||||
|
||||
if [ ! -d /rmmbackups ]; then
|
||||
sudo mkdir /rmmbackups
|
||||
fi
|
||||
|
||||
if [ ! -d /rmmbackups/daily ]; then
|
||||
sudo mkdir /rmmbackups/daily
|
||||
fi
|
||||
|
||||
if [ ! -d /rmmbackups/weekly ]; then
|
||||
sudo mkdir /rmmbackups/weekly
|
||||
fi
|
||||
|
||||
if [ ! -d /rmmbackups/monthly ]; then
|
||||
sudo mkdir /rmmbackups/monthly
|
||||
fi
|
||||
sudo chown ${USER}:${USER} -R /rmmbackups
|
||||
|
||||
printf >&2 "${GREEN}Backups setup to run at midnight and rotate.${NC}\n"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ! -d /rmmbackups ]; then
|
||||
sudo mkdir /rmmbackups
|
||||
sudo chown ${USER}:${USER} /rmmbackups
|
||||
@@ -31,6 +49,10 @@ if [ -d /meshcentral/meshcentral-backup ]; then
|
||||
rm -rf /meshcentral/meshcentral-backup/*
|
||||
fi
|
||||
|
||||
if [ -d /meshcentral/meshcentral-backups ]; then
|
||||
rm -rf /meshcentral/meshcentral-backups/*
|
||||
fi
|
||||
|
||||
if [ -d /meshcentral/meshcentral-coredumps ]; then
|
||||
rm -f /meshcentral/meshcentral-coredumps/*
|
||||
fi
|
||||
@@ -52,11 +74,11 @@ POSTGRES_PW=$(/rmm/api/env/bin/python /rmm/api/tacticalrmm/manage.py get_config
|
||||
|
||||
pg_dump --dbname=postgresql://"${POSTGRES_USER}":"${POSTGRES_PW}"@127.0.0.1:5432/tacticalrmm | gzip -9 >${tmp_dir}/postgres/db-${dt_now}.psql.gz
|
||||
|
||||
tar -czvf ${tmp_dir}/meshcentral/mesh.tar.gz --exclude=/meshcentral/node_modules /meshcentral
|
||||
node /meshcentral/node_modules/meshcentral --dbexport # for import to postgres
|
||||
|
||||
if grep -q postgres "/meshcentral/meshcentral-data/config.json"; then
|
||||
if ! which jq >/dev/null; then
|
||||
sudo apt-get install -y jq >null
|
||||
sudo apt-get install -y jq >/dev/null
|
||||
fi
|
||||
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)
|
||||
@@ -65,7 +87,21 @@ else
|
||||
mongodump --gzip --out=${tmp_dir}/meshcentral/mongo
|
||||
fi
|
||||
|
||||
sudo tar -czvf ${tmp_dir}/certs/etc-letsencrypt.tar.gz -C /etc/letsencrypt .
|
||||
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 .
|
||||
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
|
||||
fi
|
||||
|
||||
for i in rmm frontend meshcentral; do
|
||||
sudo cp /etc/nginx/sites-available/${i}.conf ${tmp_dir}/nginx/
|
||||
@@ -75,26 +111,10 @@ 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/
|
||||
|
||||
cat /rmm/api/tacticalrmm/tacticalrmm/private/log/django_debug.log | gzip -9 >${tmp_dir}/rmm/debug.log.gz
|
||||
cp /rmm/api/tacticalrmm/tacticalrmm/local_settings.py ${tmp_dir}/rmm/
|
||||
cp $local_settings ${tmp_dir}/rmm/
|
||||
|
||||
if [[ $* == *--auto* ]]; then
|
||||
|
||||
if [ ! -d /rmmbackups/daily ]; then
|
||||
sudo mkdir /rmmbackups/daily
|
||||
sudo chown ${USER}:${USER} /rmmbackups/daily
|
||||
fi
|
||||
|
||||
if [ ! -d /rmmbackups/weekly ]; then
|
||||
sudo mkdir /rmmbackups/weekly
|
||||
sudo chown ${USER}:${USER} /rmmbackups/weekly
|
||||
fi
|
||||
|
||||
if [ ! -d /rmmbackups/monthly ]; then
|
||||
sudo mkdir /rmmbackups/monthly
|
||||
sudo chown ${USER}:${USER} /rmmbackups/monthly
|
||||
fi
|
||||
|
||||
month_day=$(date +"%d")
|
||||
week_day=$(date +"%u")
|
||||
|
||||
@@ -110,9 +130,9 @@ if [[ $* == *--auto* ]]; then
|
||||
|
||||
rm -rf ${tmp_dir}
|
||||
|
||||
find /rmmbackups/daily/ -maxdepth 1 -mtime +14 -type d -exec rm -rv {} \;
|
||||
find /rmmbackups/weekly/ -maxdepth 1 -mtime +60 -type d -exec rm -rv {} \;
|
||||
find /rmmbackups/monthly/ -maxdepth 1 -mtime +380 -type d -exec rm -rv {} \;
|
||||
find /rmmbackups/daily/ -type f -mtime +14 -name '*.tar' -execdir rm -- '{}' \;
|
||||
find /rmmbackups/weekly/ -type f -mtime +60 -name '*.tar' -execdir rm -- '{}' \;
|
||||
find /rmmbackups/monthly/ -type f -mtime +380 -name '*.tar' -execdir rm -- '{}' \;
|
||||
echo -ne "${GREEN}Backup Completed${NC}\n"
|
||||
exit
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM nats:2.9.15-alpine
|
||||
FROM nats:2.9.20-alpine
|
||||
|
||||
ENV TACTICAL_DIR /opt/tactical
|
||||
ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready
|
||||
@@ -13,7 +13,6 @@ RUN chmod +x /usr/local/bin/nats-api
|
||||
RUN touch /usr/local/bin/config_watcher.sh
|
||||
RUN chown 1000:1000 /usr/local/bin/config_watcher.sh
|
||||
|
||||
|
||||
RUN mkdir -p /var/log/supervisor
|
||||
RUN mkdir -p /etc/supervisor/conf.d
|
||||
RUN touch /etc/supervisor/conf.d/supervisor.conf
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# creates python virtual env
|
||||
FROM python:3.11.3-slim AS CREATE_VENV_STAGE
|
||||
FROM python:3.11.4-slim AS CREATE_VENV_STAGE
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
@@ -21,14 +21,14 @@ RUN apt-get update && \
|
||||
pip install --no-cache-dir -r ${TACTICAL_TMP_DIR}/api/requirements.txt
|
||||
|
||||
# pulls community scripts from git repo
|
||||
FROM python:3.11.3-slim AS GET_SCRIPTS_STAGE
|
||||
FROM python:3.11.4-slim AS GET_SCRIPTS_STAGE
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends git && \
|
||||
git clone https://github.com/amidaware/community-scripts.git /community-scripts
|
||||
|
||||
# runtime image
|
||||
FROM python:3.11.3-slim
|
||||
FROM python:3.11.4-slim
|
||||
|
||||
# set env variables
|
||||
ENV VIRTUAL_ENV /opt/venv
|
||||
|
||||
15
go.mod
15
go.mod
@@ -5,21 +5,22 @@ go 1.20
|
||||
require (
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/lib/pq v1.10.7
|
||||
github.com/nats-io/nats-server/v2 v2.9.15 // indirect
|
||||
github.com/nats-io/nats.go v1.25.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/nats-io/nats-server/v2 v2.9.21 // indirect
|
||||
github.com/nats-io/nats.go v1.28.0
|
||||
github.com/ugorji/go/codec v1.2.11
|
||||
github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139
|
||||
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 (
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/nats-io/nkeys v0.4.4 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/stretchr/testify v1.7.1 // indirect
|
||||
golang.org/x/crypto v0.6.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
31
go.sum
31
go.sum
@@ -9,18 +9,19 @@ 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/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
|
||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||
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.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
||||
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
|
||||
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/nats-io/jwt/v2 v2.3.0 h1:z2mA1a7tIf5ShggOFlR1oBPgd6hGqcDYsISxZByUzdI=
|
||||
github.com/nats-io/nats-server/v2 v2.9.15 h1:MuwEJheIwpvFgqvbs20W8Ish2azcygjf4Z0liVu2I4c=
|
||||
github.com/nats-io/nats-server/v2 v2.9.15/go.mod h1:QlCTy115fqpx4KSOPFIxSV7DdI6OxtZsGOL1JLdeRlE=
|
||||
github.com/nats-io/nats.go v1.25.0 h1:t5/wCPGciR7X3Mu8QOi4jiJaXaWM8qtkLu4lzGZvYHE=
|
||||
github.com/nats-io/nats.go v1.25.0/go.mod h1:D2WALIhz7V8M0pH8Scx8JZXlg6Oqz5VG+nQkK8nJdvg=
|
||||
github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4=
|
||||
github.com/nats-io/nats-server/v2 v2.9.21 h1:2TBTh0UDE74eNXQmV4HofsmRSCiVN0TH2Wgrp6BD6fk=
|
||||
github.com/nats-io/nats-server/v2 v2.9.21/go.mod h1:ozqMZc2vTHcNcblOiXMWIXkf8+0lDGAi5wQcG+O1mHU=
|
||||
github.com/nats-io/nats.go v1.28.0 h1:Th4G6zdsz2d0OqXdfzKLClo6bOfoI/b1kInhRtFIy5c=
|
||||
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/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
@@ -29,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/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.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/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
@@ -37,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/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=
|
||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
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.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
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=
|
||||
@@ -50,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=
|
||||
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-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
134
install.sh
134
install.sh
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_VERSION="73"
|
||||
SCRIPT_VERSION="75"
|
||||
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/install.sh'
|
||||
|
||||
sudo apt install -y curl wget dirmngr gnupg lsb-release
|
||||
@@ -12,7 +12,7 @@ RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
SCRIPTS_DIR='/opt/trmm-community-scripts'
|
||||
PYTHON_VER='3.11.3'
|
||||
PYTHON_VER='3.11.4'
|
||||
SETTINGS_FILE='/rmm/api/tacticalrmm/tacticalrmm/settings.py'
|
||||
|
||||
TMP_FILE=$(mktemp -p "" "rmminstall_XXXXXXXXXX")
|
||||
@@ -30,8 +30,8 @@ fi
|
||||
rm -f $TMP_FILE
|
||||
|
||||
arch=$(uname -m)
|
||||
if [ "$arch" != "x86_64" ]; then
|
||||
echo -ne "${RED}ERROR: Only x86_64 arch is supported, not ${arch}${NC}\n"
|
||||
if [[ "$arch" != "x86_64" ]] && [[ "$arch" != "aarch64" ]]; then
|
||||
echo -ne "${RED}ERROR: Only x86_64 and aarch64 is supported, not ${arch}${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -49,19 +49,22 @@ codename=$(lsb_release -sc)
|
||||
relno=$(lsb_release -sr | cut -d. -f1)
|
||||
fullrelno=$(lsb_release -sr)
|
||||
|
||||
# Fallback if lsb_release -si returns anything else than Ubuntu, Debian or Raspbian
|
||||
if [ ! "$osname" = "ubuntu" ] && [ ! "$osname" = "debian" ]; then
|
||||
osname=$(grep -oP '(?<=^ID=).+' /etc/os-release | tr -d '"')
|
||||
osname=${osname^}
|
||||
fi
|
||||
not_supported() {
|
||||
echo -ne "${RED}ERROR: Only Debian 11, Debian 12 and Ubuntu 22.04 are supported.${NC}\n"
|
||||
}
|
||||
|
||||
# determine system
|
||||
if ([ "$osname" = "ubuntu" ] && [ "$fullrelno" = "20.04" ]) || ([ "$osname" = "debian" ] && [ $relno -ge 10 ]); then
|
||||
echo $fullrel
|
||||
if [[ "$osname" == "debian" ]]; then
|
||||
if [[ "$relno" -ne 11 && "$relno" -ne 12 ]]; then
|
||||
not_supported
|
||||
exit 1
|
||||
fi
|
||||
elif [[ "$osname" == "ubuntu" ]]; then
|
||||
if [[ "$fullrelno" != "22.04" ]]; then
|
||||
not_supported
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo $fullrel
|
||||
echo -ne "${RED}Supported versions: Ubuntu 20.04, Debian 10 and 11\n"
|
||||
echo -ne "Your system does not appear to be supported${NC}\n"
|
||||
not_supported
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -78,16 +81,12 @@ if [[ "$LANG" != *".UTF-8" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ([ "$osname" = "ubuntu" ]); then
|
||||
mongodb_repo="deb [arch=amd64] https://repo.mongodb.org/apt/$osname $codename/mongodb-org/4.4 multiverse"
|
||||
# there is no bullseye repo yet for mongo so just use buster on debian 11
|
||||
elif ([ "$osname" = "debian" ] && [ $relno -eq 11 ]); then
|
||||
mongodb_repo="deb [arch=amd64] https://repo.mongodb.org/apt/$osname buster/mongodb-org/4.4 main"
|
||||
if [ "$arch" = "x86_64" ]; then
|
||||
pgarch='amd64'
|
||||
else
|
||||
mongodb_repo="deb [arch=amd64] https://repo.mongodb.org/apt/$osname $codename/mongodb-org/4.4 main"
|
||||
pgarch='arm64'
|
||||
fi
|
||||
|
||||
postgresql_repo="deb [arch=amd64] https://apt.postgresql.org/pub/repos/apt/ $codename-pgdg main"
|
||||
postgresql_repo="deb [arch=${pgarch}] https://apt.postgresql.org/pub/repos/apt/ $codename-pgdg main"
|
||||
|
||||
# prevents logging issues with some VPS providers like Vultr if this is a freshly provisioned instance that hasn't been rebooted yet
|
||||
sudo systemctl restart systemd-journald.service
|
||||
@@ -98,6 +97,8 @@ MESHPASSWD=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 25 | head -n 1)
|
||||
pgusername=$(cat /dev/urandom | tr -dc 'a-z' | fold -w 8 | head -n 1)
|
||||
pgpw=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 20 | head -n 1)
|
||||
meshusername=$(cat /dev/urandom | tr -dc 'a-z' | fold -w 8 | head -n 1)
|
||||
MESHPGUSER=$(cat /dev/urandom | tr -dc 'a-z' | fold -w 8 | head -n 1)
|
||||
MESHPGPWD=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 20 | head -n 1)
|
||||
|
||||
cls() {
|
||||
printf "\033c"
|
||||
@@ -136,11 +137,12 @@ while [[ $letsemail != *[@]*[.]* ]]; do
|
||||
read letsemail
|
||||
done
|
||||
|
||||
# if server is behind NAT we need to add the 3 subdomains to the host file
|
||||
# so that nginx can properly route between the frontend, backend and meshcentral
|
||||
# EDIT 8-29-2020
|
||||
# running this even if server is __not__ behind NAT just to make DNS resolving faster
|
||||
# this also allows the install script to properly finish even if DNS has not fully propagated
|
||||
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")
|
||||
HAS_11=$(grep 127.0.1.1 /etc/hosts)
|
||||
|
||||
@@ -230,21 +232,12 @@ done
|
||||
|
||||
print_green 'Installing NodeJS'
|
||||
|
||||
curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
|
||||
curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
||||
sudo apt update
|
||||
sudo apt install -y gcc g++ make
|
||||
sudo apt install -y nodejs
|
||||
sudo npm install -g npm
|
||||
|
||||
print_green 'Installing MongoDB'
|
||||
|
||||
wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -
|
||||
echo "$mongodb_repo" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list
|
||||
sudo apt update
|
||||
sudo apt install -y mongodb-org
|
||||
sudo systemctl enable mongod
|
||||
sudo systemctl restart mongod
|
||||
|
||||
print_green "Installing Python ${PYTHON_VER}"
|
||||
|
||||
sudo apt install -y build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev libbz2-dev
|
||||
@@ -268,7 +261,7 @@ echo "$postgresql_repo" | sudo tee /etc/apt/sources.list.d/pgdg.list
|
||||
|
||||
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
|
||||
sudo apt update
|
||||
sudo apt install -y postgresql-14
|
||||
sudo apt install -y postgresql-15
|
||||
sleep 2
|
||||
sudo systemctl enable --now postgresql
|
||||
|
||||
@@ -277,7 +270,7 @@ until pg_isready >/dev/null; do
|
||||
sleep 3
|
||||
done
|
||||
|
||||
print_green 'Creating database for the rmm'
|
||||
print_green 'Creating database for trmm'
|
||||
|
||||
sudo -u postgres psql -c "CREATE DATABASE tacticalrmm"
|
||||
sudo -u postgres psql -c "CREATE USER ${pgusername} WITH PASSWORD '${pgpw}'"
|
||||
@@ -285,6 +278,19 @@ sudo -u postgres psql -c "ALTER ROLE ${pgusername} SET client_encoding TO 'utf8'
|
||||
sudo -u postgres psql -c "ALTER ROLE ${pgusername} SET default_transaction_isolation TO 'read committed'"
|
||||
sudo -u postgres psql -c "ALTER ROLE ${pgusername} SET timezone TO 'UTC'"
|
||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE tacticalrmm TO ${pgusername}"
|
||||
sudo -u postgres psql -c "ALTER DATABASE tacticalrmm OWNER TO ${pgusername}"
|
||||
sudo -u postgres psql -c "GRANT USAGE, CREATE ON SCHEMA PUBLIC TO ${pgusername}"
|
||||
|
||||
print_green 'Creating database for meshcentral'
|
||||
|
||||
sudo -u postgres psql -c "CREATE DATABASE meshcentral"
|
||||
sudo -u postgres psql -c "CREATE USER ${MESHPGUSER} WITH PASSWORD '${MESHPGPWD}'"
|
||||
sudo -u postgres psql -c "ALTER ROLE ${MESHPGUSER} SET client_encoding TO 'utf8'"
|
||||
sudo -u postgres psql -c "ALTER ROLE ${MESHPGUSER} SET default_transaction_isolation TO 'read committed'"
|
||||
sudo -u postgres psql -c "ALTER ROLE ${MESHPGUSER} SET timezone TO 'UTC'"
|
||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE meshcentral TO ${MESHPGUSER}"
|
||||
sudo -u postgres psql -c "ALTER DATABASE meshcentral OWNER TO ${MESHPGUSER}"
|
||||
sudo -u postgres psql -c "GRANT USAGE, CREATE ON SCHEMA PUBLIC TO ${MESHPGUSER}"
|
||||
|
||||
print_green 'Cloning repos'
|
||||
|
||||
@@ -308,11 +314,17 @@ git checkout main
|
||||
|
||||
print_green 'Downloading NATS'
|
||||
|
||||
if [ "$arch" = "x86_64" ]; then
|
||||
natsarch='amd64'
|
||||
else
|
||||
natsarch='arm64'
|
||||
fi
|
||||
|
||||
NATS_SERVER_VER=$(grep "^NATS_SERVER_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
|
||||
nats_tmp=$(mktemp -d -t nats-XXXXXXXXXX)
|
||||
wget https://github.com/nats-io/nats-server/releases/download/v${NATS_SERVER_VER}/nats-server-v${NATS_SERVER_VER}-linux-amd64.tar.gz -P ${nats_tmp}
|
||||
tar -xzf ${nats_tmp}/nats-server-v${NATS_SERVER_VER}-linux-amd64.tar.gz -C ${nats_tmp}
|
||||
sudo mv ${nats_tmp}/nats-server-v${NATS_SERVER_VER}-linux-amd64/nats-server /usr/local/bin/
|
||||
wget https://github.com/nats-io/nats-server/releases/download/v${NATS_SERVER_VER}/nats-server-v${NATS_SERVER_VER}-linux-${natsarch}.tar.gz -P ${nats_tmp}
|
||||
tar -xzf ${nats_tmp}/nats-server-v${NATS_SERVER_VER}-linux-${natsarch}.tar.gz -C ${nats_tmp}
|
||||
sudo mv ${nats_tmp}/nats-server-v${NATS_SERVER_VER}-linux-${natsarch}/nats-server /usr/local/bin/
|
||||
sudo chmod +x /usr/local/bin/nats-server
|
||||
sudo chown ${USER}:${USER} /usr/local/bin/nats-server
|
||||
rm -rf ${nats_tmp}
|
||||
@@ -332,8 +344,6 @@ meshcfg="$(
|
||||
{
|
||||
"settings": {
|
||||
"cert": "${meshdomain}",
|
||||
"mongoDb": "mongodb://127.0.0.1:27017",
|
||||
"mongoDbName": "meshcentral",
|
||||
"WANonly": true,
|
||||
"minify": 1,
|
||||
"port": 4430,
|
||||
@@ -341,15 +351,20 @@ meshcfg="$(
|
||||
"redirPort": 800,
|
||||
"allowLoginToken": true,
|
||||
"allowFraming": true,
|
||||
"_agentPing": 60,
|
||||
"agentPong": 300,
|
||||
"agentPing": 35,
|
||||
"allowHighQualityDesktop": true,
|
||||
"tlsOffload": "127.0.0.1",
|
||||
"agentCoreDump": false,
|
||||
"compression": true,
|
||||
"wsCompression": true,
|
||||
"agentWsCompression": true,
|
||||
"maxInvalidLogin": { "time": 5, "count": 5, "coolofftime": 30 }
|
||||
"maxInvalidLogin": { "time": 5, "count": 5, "coolofftime": 30 },
|
||||
"postgres": {
|
||||
"user": "${MESHPGUSER}",
|
||||
"password": "${MESHPGPWD}",
|
||||
"port": "5432",
|
||||
"host": "localhost"
|
||||
}
|
||||
},
|
||||
"domains": {
|
||||
"": {
|
||||
@@ -400,7 +415,13 @@ EOF
|
||||
)"
|
||||
echo "${localvars}" >/rmm/api/tacticalrmm/tacticalrmm/local_settings.py
|
||||
|
||||
sudo cp /rmm/natsapi/bin/nats-api /usr/local/bin
|
||||
if [ "$arch" = "x86_64" ]; then
|
||||
natsapi='nats-api'
|
||||
else
|
||||
natsapi='nats-api-arm64'
|
||||
fi
|
||||
|
||||
sudo cp /rmm/natsapi/bin/${natsapi} /usr/local/bin/nats-api
|
||||
sudo chown ${USER}:${USER} /usr/local/bin/nats-api
|
||||
sudo chmod +x /usr/local/bin/nats-api
|
||||
|
||||
@@ -495,7 +516,7 @@ 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
|
||||
Group=${USER}
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
LimitNOFILE=1000000
|
||||
@@ -567,6 +588,7 @@ server {
|
||||
|
||||
location /static/ {
|
||||
root /rmm/api/tacticalrmm;
|
||||
add_header "Access-Control-Allow-Origin" "https://${frontenddomain}";
|
||||
}
|
||||
|
||||
location /private/ {
|
||||
@@ -575,6 +597,12 @@ server {
|
||||
alias /rmm/api/tacticalrmm/tacticalrmm/private/;
|
||||
}
|
||||
|
||||
location /assets/ {
|
||||
internal;
|
||||
add_header "Access-Control-Allow-Origin" "https://${frontenddomain}";
|
||||
alias /opt/tactical/reporting/assets/;
|
||||
}
|
||||
|
||||
location ~ ^/ws/ {
|
||||
proxy_pass http://unix:/rmm/daphne.sock;
|
||||
|
||||
@@ -736,7 +764,7 @@ meshservice="$(
|
||||
cat <<EOF
|
||||
[Unit]
|
||||
Description=MeshCentral Server
|
||||
After=network.target mongod.service nginx.service
|
||||
After=network.target postgresql.service nginx.service
|
||||
[Service]
|
||||
Type=simple
|
||||
LimitNOFILE=1000000
|
||||
@@ -840,7 +868,7 @@ sleep 3
|
||||
while ! [[ $CHECK_MESH_READY ]]; do
|
||||
CHECK_MESH_READY=$(sudo journalctl -u meshcentral.service -b --no-pager | grep "MeshCentral HTTP server running on port")
|
||||
echo -ne "${GREEN}Mesh Central not ready yet...${NC}\n"
|
||||
sleep 3
|
||||
sleep 5
|
||||
done
|
||||
|
||||
print_green 'Generating meshcentral login token key'
|
||||
@@ -870,7 +898,7 @@ sleep 5
|
||||
while ! [[ $CHECK_MESH_READY2 ]]; do
|
||||
CHECK_MESH_READY2=$(sudo journalctl -u meshcentral.service -b --no-pager | grep "MeshCentral HTTP server running on port")
|
||||
echo -ne "${GREEN}Mesh Central not ready yet...${NC}\n"
|
||||
sleep 3
|
||||
sleep 5
|
||||
done
|
||||
|
||||
node node_modules/meshcentral/meshctrl.js --url wss://${meshdomain}:443 --loginuser ${meshusername} --loginpass ${MESHPASSWD} AddDeviceGroup --name TacticalRMM
|
||||
|
||||
2
main.go
2
main.go
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
version = "3.4.4"
|
||||
version = "3.4.8"
|
||||
log = logrus.New()
|
||||
)
|
||||
|
||||
|
||||
Binary file not shown.
BIN
natsapi/bin/nats-api-arm64
Executable file
BIN
natsapi/bin/nats-api-arm64
Executable file
Binary file not shown.
200
restore.sh
200
restore.sh
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_VERSION="48"
|
||||
SCRIPT_VERSION="50"
|
||||
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/restore.sh'
|
||||
|
||||
sudo apt update
|
||||
@@ -13,7 +13,7 @@ RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
SCRIPTS_DIR='/opt/trmm-community-scripts'
|
||||
PYTHON_VER='3.11.3'
|
||||
PYTHON_VER='3.11.4'
|
||||
SETTINGS_FILE='/rmm/api/tacticalrmm/tacticalrmm/settings.py'
|
||||
|
||||
TMP_FILE=$(mktemp -p "" "rmmrestore_XXXXXXXXXX")
|
||||
@@ -30,8 +30,8 @@ fi
|
||||
rm -f $TMP_FILE
|
||||
|
||||
arch=$(uname -m)
|
||||
if [ "$arch" != "x86_64" ]; then
|
||||
echo -ne "${RED}ERROR: Only x86_64 arch is supported, not ${arch}${NC}\n"
|
||||
if [[ "$arch" != "x86_64" ]] && [[ "$arch" != "aarch64" ]]; then
|
||||
echo -ne "${RED}ERROR: Only x86_64 and aarch64 is supported, not ${arch}${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -49,33 +49,25 @@ codename=$(lsb_release -sc)
|
||||
relno=$(lsb_release -sr | cut -d. -f1)
|
||||
fullrelno=$(lsb_release -sr)
|
||||
|
||||
# Fallback if lsb_release -si returns anything else than Ubuntu, Debian or Raspbian
|
||||
if [ ! "$osname" = "ubuntu" ] && [ ! "$osname" = "debian" ]; then
|
||||
osname=$(grep -oP '(?<=^ID=).+' /etc/os-release | tr -d '"')
|
||||
osname=${osname^}
|
||||
fi
|
||||
not_supported() {
|
||||
echo -ne "${RED}ERROR: Only Debian 11, Debian 12 and Ubuntu 22.04 are supported.${NC}\n"
|
||||
}
|
||||
|
||||
# determine system
|
||||
if ([ "$osname" = "ubuntu" ] && [ "$fullrelno" = "20.04" ]) || ([ "$osname" = "debian" ] && [ $relno -ge 10 ]); then
|
||||
echo $fullrel
|
||||
if [[ "$osname" == "debian" ]]; then
|
||||
if [[ "$relno" -ne 11 && "$relno" -ne 12 ]]; then
|
||||
not_supported
|
||||
exit 1
|
||||
fi
|
||||
elif [[ "$osname" == "ubuntu" ]]; then
|
||||
if [[ "$fullrelno" != "22.04" ]]; then
|
||||
not_supported
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo $fullrel
|
||||
echo -ne "${RED}Supported versions: Ubuntu 20.04, Debian 10 and 11\n"
|
||||
echo -ne "Your system does not appear to be supported${NC}\n"
|
||||
not_supported
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ([ "$osname" = "ubuntu" ]); then
|
||||
mongodb_repo="deb [arch=amd64] https://repo.mongodb.org/apt/$osname $codename/mongodb-org/4.4 multiverse"
|
||||
# there is no bullseye repo yet for mongo so just use buster on debian 11
|
||||
elif ([ "$osname" = "debian" ] && [ $relno -eq 11 ]); then
|
||||
mongodb_repo="deb [arch=amd64] https://repo.mongodb.org/apt/$osname buster/mongodb-org/4.4 main"
|
||||
else
|
||||
mongodb_repo="deb [arch=amd64] https://repo.mongodb.org/apt/$osname $codename/mongodb-org/4.4 main"
|
||||
fi
|
||||
|
||||
postgresql_repo="deb [arch=amd64] https://apt.postgresql.org/pub/repos/apt/ $codename-pgdg main"
|
||||
|
||||
if [ $EUID -eq 0 ]; then
|
||||
echo -ne "\033[0;31mDo NOT run this script as root. Exiting.\e[0m\n"
|
||||
exit 1
|
||||
@@ -89,6 +81,13 @@ if [[ "$LANG" != *".UTF-8" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$arch" = "x86_64" ]; then
|
||||
pgarch='amd64'
|
||||
else
|
||||
pgarch='arm64'
|
||||
fi
|
||||
postgresql_repo="deb [arch=${pgarch}] https://apt.postgresql.org/pub/repos/apt/ $codename-pgdg main"
|
||||
|
||||
if [ ! -f "${1}" ]; then
|
||||
echo -ne "\n${RED}usage: ./restore.sh rmm-backup-xxxx.tar${NC}\n"
|
||||
exit 1
|
||||
@@ -123,7 +122,7 @@ sudo apt update
|
||||
|
||||
print_green 'Installing NodeJS'
|
||||
|
||||
curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
|
||||
curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
||||
sudo apt update
|
||||
sudo apt install -y gcc g++ make
|
||||
sudo apt install -y nodejs
|
||||
@@ -194,10 +193,24 @@ sudo apt install -y certbot openssl
|
||||
|
||||
print_green 'Restoring certs'
|
||||
|
||||
sudo rm -rf /etc/letsencrypt
|
||||
sudo mkdir /etc/letsencrypt
|
||||
sudo tar -xzf $tmp_dir/certs/etc-letsencrypt.tar.gz -C /etc/letsencrypt
|
||||
sudo chown ${USER}:${USER} -R /etc/letsencrypt
|
||||
if [ -f "$tmp_dir/certs/etc-letsencrypt.tar.gz" ]; then
|
||||
sudo rm -rf /etc/letsencrypt
|
||||
sudo mkdir /etc/letsencrypt
|
||||
sudo tar -xzf $tmp_dir/certs/etc-letsencrypt.tar.gz -C /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
|
||||
|
||||
fi
|
||||
|
||||
print_green 'Restoring celery configs'
|
||||
|
||||
@@ -232,7 +245,7 @@ print_green 'Installing postgresql'
|
||||
echo "$postgresql_repo" | sudo tee /etc/apt/sources.list.d/pgdg.list
|
||||
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
|
||||
sudo apt update
|
||||
sudo apt install -y postgresql-14
|
||||
sudo apt install -y postgresql-15
|
||||
sleep 2
|
||||
sudo systemctl enable --now postgresql
|
||||
|
||||
@@ -261,73 +274,114 @@ git checkout main
|
||||
|
||||
print_green 'Restoring NATS'
|
||||
|
||||
if [ "$arch" = "x86_64" ]; then
|
||||
natsarch='amd64'
|
||||
else
|
||||
natsarch='arm64'
|
||||
fi
|
||||
|
||||
NATS_SERVER_VER=$(grep "^NATS_SERVER_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
|
||||
nats_tmp=$(mktemp -d -t nats-XXXXXXXXXX)
|
||||
wget https://github.com/nats-io/nats-server/releases/download/v${NATS_SERVER_VER}/nats-server-v${NATS_SERVER_VER}-linux-amd64.tar.gz -P ${nats_tmp}
|
||||
tar -xzf ${nats_tmp}/nats-server-v${NATS_SERVER_VER}-linux-amd64.tar.gz -C ${nats_tmp}
|
||||
sudo mv ${nats_tmp}/nats-server-v${NATS_SERVER_VER}-linux-amd64/nats-server /usr/local/bin/
|
||||
wget https://github.com/nats-io/nats-server/releases/download/v${NATS_SERVER_VER}/nats-server-v${NATS_SERVER_VER}-linux-${natsarch}.tar.gz -P ${nats_tmp}
|
||||
tar -xzf ${nats_tmp}/nats-server-v${NATS_SERVER_VER}-linux-${natsarch}.tar.gz -C ${nats_tmp}
|
||||
sudo mv ${nats_tmp}/nats-server-v${NATS_SERVER_VER}-linux-${natsarch}/nats-server /usr/local/bin/
|
||||
sudo chmod +x /usr/local/bin/nats-server
|
||||
sudo chown ${USER}:${USER} /usr/local/bin/nats-server
|
||||
rm -rf ${nats_tmp}
|
||||
|
||||
print_green 'Restoring MeshCentral'
|
||||
|
||||
sudo apt install -y jq
|
||||
|
||||
MESH_VER=$(grep "^MESH_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
|
||||
sudo tar -xzf $tmp_dir/meshcentral/mesh.tar.gz -C /
|
||||
sudo chown ${USER}:${USER} -R /meshcentral
|
||||
rm -f /meshcentral/package.json /meshcentral/package-lock.json
|
||||
|
||||
FROM_MONGO=false
|
||||
if grep -q postgres "/meshcentral/meshcentral-data/config.json"; then
|
||||
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)
|
||||
else
|
||||
FROM_MONGO=true
|
||||
MESH_POSTGRES_USER=$(cat /dev/urandom | tr -dc 'a-z' | fold -w 8 | head -n 1)
|
||||
MESH_POSTGRES_PW=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 20 | head -n 1)
|
||||
fi
|
||||
|
||||
print_green 'Creating MeshCentral DB'
|
||||
|
||||
sudo -u postgres psql -c "CREATE DATABASE meshcentral"
|
||||
sudo -u postgres psql -c "CREATE USER ${MESH_POSTGRES_USER} WITH PASSWORD '${MESH_POSTGRES_PW}'"
|
||||
sudo -u postgres psql -c "ALTER ROLE ${MESH_POSTGRES_USER} SET client_encoding TO 'utf8'"
|
||||
sudo -u postgres psql -c "ALTER ROLE ${MESH_POSTGRES_USER} SET default_transaction_isolation TO 'read committed'"
|
||||
sudo -u postgres psql -c "ALTER ROLE ${MESH_POSTGRES_USER} SET timezone TO 'UTC'"
|
||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE meshcentral TO ${MESH_POSTGRES_USER}"
|
||||
sudo -u postgres psql -c "ALTER DATABASE meshcentral OWNER TO ${MESH_POSTGRES_USER}"
|
||||
sudo -u postgres psql -c "GRANT USAGE, CREATE ON SCHEMA PUBLIC TO ${MESH_POSTGRES_USER}"
|
||||
|
||||
if [ "$FROM_MONGO" = true ]; then
|
||||
print_green 'Converting mesh mongo to postgres'
|
||||
|
||||
# https://github.com/amidaware/trmm-awesome/blob/main/scripts/migrate-mesh-to-postgres.sh
|
||||
mesh_data='/meshcentral/meshcentral-data'
|
||||
if [[ ! -f "${mesh_data}/meshcentral.db.json" ]]; then
|
||||
echo -ne "${RED}ERROR: meshcentral.db.json was not found${NC}\n"
|
||||
echo -ne "${RED}Unable to convert mongo to postgres${NC}\n"
|
||||
echo -ne "${RED}You probably didn't download the lastest backup.sh file before doing a backup and were using an outdated version${NC}\n"
|
||||
echo -ne "${RED}You will need to download the latest backup script, run a fresh backup on your old server, wipe this server and attempt a fresh restore.${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
MESH_PG_PORT='5432'
|
||||
MESH_PG_HOST='localhost'
|
||||
cp ${mesh_data}/config.json ${mesh_data}/config-mongodb-$(date "+%Y%m%dT%H%M%S").bak
|
||||
|
||||
cat ${mesh_data}/config.json |
|
||||
jq '.settings |= with_entries(select((.key | ascii_downcase) as $key | $key != "mongodb" and $key != "mongodbname"))' |
|
||||
jq " .settings.postgres.user |= \"${MESH_POSTGRES_USER}\" " |
|
||||
jq " .settings.postgres.password |= \"${MESH_POSTGRES_PW}\" " |
|
||||
jq " .settings.postgres.port |= \"${MESH_PG_PORT}\" " |
|
||||
jq " .settings.postgres.host |= \"${MESH_PG_HOST}\" " >${mesh_data}/config-postgres.json
|
||||
|
||||
mv ${mesh_data}/config-postgres.json ${mesh_data}/config.json
|
||||
else
|
||||
gzip -d $tmp_dir/postgres/mesh-db*.psql.gz
|
||||
PGPASSWORD=${MESH_POSTGRES_PW} psql -h localhost -U ${MESH_POSTGRES_USER} -d meshcentral -f $tmp_dir/postgres/mesh-db*.psql
|
||||
fi
|
||||
|
||||
cd /meshcentral
|
||||
npm install meshcentral@${MESH_VER}
|
||||
|
||||
print_green 'Restoring MeshCentral DB'
|
||||
|
||||
if grep -q postgres "/meshcentral/meshcentral-data/config.json"; then
|
||||
if ! which jq >/dev/null; then
|
||||
sudo apt-get install -y jq >null
|
||||
fi
|
||||
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)
|
||||
sudo -u postgres psql -c "DROP DATABASE IF EXISTS meshcentral"
|
||||
sudo -u postgres psql -c "CREATE DATABASE meshcentral"
|
||||
sudo -u postgres psql -c "CREATE USER ${MESH_POSTGRES_USER} WITH PASSWORD '${MESH_POSTGRES_PW}'"
|
||||
sudo -u postgres psql -c "ALTER ROLE ${MESH_POSTGRES_USER} SET client_encoding TO 'utf8'"
|
||||
sudo -u postgres psql -c "ALTER ROLE ${MESH_POSTGRES_USER} SET default_transaction_isolation TO 'read committed'"
|
||||
sudo -u postgres psql -c "ALTER ROLE ${MESH_POSTGRES_USER} SET timezone TO 'UTC'"
|
||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE meshcentral TO ${MESH_POSTGRES_USER}"
|
||||
gzip -d $tmp_dir/postgres/mesh-db*.psql.gz
|
||||
PGPASSWORD=${MESH_POSTGRES_PW} psql -h localhost -U ${MESH_POSTGRES_USER} -d meshcentral -f $tmp_dir/postgres/mesh-db*.psql
|
||||
else
|
||||
print_green 'Installing MongoDB'
|
||||
wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -
|
||||
echo "$mongodb_repo" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list
|
||||
sudo apt update
|
||||
sudo apt install -y mongodb-org
|
||||
sudo systemctl enable --now mongod
|
||||
sleep 5
|
||||
mongorestore --gzip $tmp_dir/meshcentral/mongo
|
||||
if [ "$FROM_MONGO" = true ]; then
|
||||
node node_modules/meshcentral --dbimport >/dev/null
|
||||
fi
|
||||
|
||||
print_green 'Restoring the backend'
|
||||
|
||||
cp $tmp_dir/rmm/local_settings.py /rmm/api/tacticalrmm/tacticalrmm/
|
||||
gzip -d $tmp_dir/rmm/debug.log.gz
|
||||
cp $tmp_dir/rmm/django_debug.log /rmm/api/tacticalrmm/tacticalrmm/private/log/
|
||||
|
||||
sudo cp /rmm/natsapi/bin/nats-api /usr/local/bin
|
||||
if [ "$arch" = "x86_64" ]; then
|
||||
natsapi='nats-api'
|
||||
else
|
||||
natsapi='nats-api-arm64'
|
||||
fi
|
||||
|
||||
sudo cp /rmm/natsapi/bin/${natsapi} /usr/local/bin/nats-api
|
||||
sudo chown ${USER}:${USER} /usr/local/bin/nats-api
|
||||
sudo chmod +x /usr/local/bin/nats-api
|
||||
|
||||
print_green 'Restoring the database'
|
||||
print_green 'Restoring the trmm database'
|
||||
|
||||
pgusername=$(grep -w USER /rmm/api/tacticalrmm/tacticalrmm/local_settings.py | sed 's/^.*: //' | sed 's/.//' | sed -r 's/.{2}$//')
|
||||
pgpw=$(grep -w PASSWORD /rmm/api/tacticalrmm/tacticalrmm/local_settings.py | sed 's/^.*: //' | sed 's/.//' | sed -r 's/.{2}$//')
|
||||
|
||||
sudo -u postgres psql -c "DROP DATABASE IF EXISTS tacticalrmm"
|
||||
sudo -u postgres psql -c "CREATE DATABASE tacticalrmm"
|
||||
sudo -u postgres psql -c "CREATE USER ${pgusername} WITH PASSWORD '${pgpw}'"
|
||||
sudo -u postgres psql -c "ALTER ROLE ${pgusername} SET client_encoding TO 'utf8'"
|
||||
sudo -u postgres psql -c "ALTER ROLE ${pgusername} SET default_transaction_isolation TO 'read committed'"
|
||||
sudo -u postgres psql -c "ALTER ROLE ${pgusername} SET timezone TO 'UTC'"
|
||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE tacticalrmm TO ${pgusername}"
|
||||
sudo -u postgres psql -c "ALTER DATABASE tacticalrmm OWNER TO ${pgusername}"
|
||||
sudo -u postgres psql -c "GRANT USAGE, CREATE ON SCHEMA PUBLIC TO ${pgusername}"
|
||||
|
||||
gzip -d $tmp_dir/postgres/db*.psql.gz
|
||||
PGPASSWORD=${pgpw} psql -h localhost -U ${pgusername} -d tacticalrmm -f $tmp_dir/postgres/db*.psql
|
||||
@@ -356,6 +410,12 @@ deactivate
|
||||
|
||||
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)
|
||||
if [[ $HAS_11 ]]; then
|
||||
sudo sed -i "/127.0.1.1/s/$/ ${API} ${webdomain} ${meshdomain}/" /etc/hosts
|
||||
@@ -384,7 +444,13 @@ sudo chown -R $USER:$GROUP /home/${USER}/.npm
|
||||
sudo chown -R $USER:$GROUP /home/${USER}/.config
|
||||
sudo chown -R $USER:$GROUP /home/${USER}/.cache
|
||||
|
||||
print_green 'Enabling Services'
|
||||
print_green 'Enabling and starting services'
|
||||
|
||||
HAS_OLD_MONGO_DEP=$(grep mongod /etc/systemd/system/meshcentral.service)
|
||||
if [[ $HAS_OLD_MONGO_DEP ]]; then
|
||||
sudo sed -i 's/mongod.service/postgresql.service/g' /etc/systemd/system/meshcentral.service
|
||||
fi
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
for i in celery.service celerybeat.service rmm.service daphne.service nats-api.service nginx; do
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
# Tactical RMM install troubleshooting script
|
||||
# Contributed by https://github.com/dinger1986
|
||||
# v1.1 1/21/2022 update to include all services
|
||||
# v 1.2 6/24/2023 changed to add date, easier readability and ipv4 addresses only for checks
|
||||
|
||||
# This script asks for the 3 subdomains, checks they exist, checks they resolve locally and remotely (using google dns for remote),
|
||||
# This script asks for the 3 subdomains, checks they exist, checks they resolve locally and remotely (using google dns for remote),
|
||||
# checks services are running, checks ports are opened. The only part that will make the script stop is if the sub domains dont exist, theres literally no point in going further if thats the case
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
@@ -12,120 +13,136 @@ YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Set date at the top of the troubleshooting script
|
||||
now=$(date)
|
||||
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
|
||||
locdns=$(resolvectl | grep 'Current DNS Server:' | cut -d: -f2 | awk '{ print $1}')
|
||||
resolvestatus=$(systemctl is-active systemd-resolved.service)
|
||||
if [[ "$osname" == "debian" && "$relno" == 12 ]]; then
|
||||
locdns=$(resolvconf -l | tail -n +1 | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
||||
|
||||
while [[ $rmmdomain != *[.]*[.]* ]]
|
||||
do
|
||||
echo -ne "${YELLOW}Enter the subdomain for the backend (e.g. api.example.com)${NC}: "
|
||||
read rmmdomain
|
||||
done
|
||||
|
||||
if ping -c 1 $rmmdomain &> /dev/null
|
||||
then
|
||||
echo -ne ${GREEN} Verified $rmmdomain | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
elif [ $resolvestatus = active ]; then
|
||||
locdns=$(resolvectl | tail -n +1 | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
||||
else
|
||||
echo -ne ${RED} $rmmdomain doesnt exist please create it or check for a typo | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
printf >&2 "You will have a log file called checklog.log in the directory you ran this script from\n\n"
|
||||
printf >&2 "\n\n"
|
||||
exit
|
||||
while ! [[ $resolveconf ]]; do
|
||||
resolveconf=$(sudo systemctl status systemd-resolved.service | grep "Active: active (running)")
|
||||
sudo systemctl start systemd-resolved.service
|
||||
echo -ne "DNS Resolver not ready yet...${NC}\n"
|
||||
sleep 3
|
||||
done
|
||||
locdns=$(resolvectl | tail -n +1 | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
||||
sudo systemctl stop systemd-resolved.service
|
||||
fi
|
||||
|
||||
while [[ $frontenddomain != *[.]*[.]* ]]
|
||||
do
|
||||
echo -ne "${YELLOW}Enter the subdomain for the frontend (e.g. rmm.example.com)${NC}: "
|
||||
read frontenddomain
|
||||
while [[ $rmmdomain != *[.]*[.]* ]]; do
|
||||
echo -e "${YELLOW}Enter the subdomain for the backend (e.g. api.example.com)${NC}: "
|
||||
read rmmdomain
|
||||
done
|
||||
|
||||
if ping -c 1 $frontenddomain &> /dev/null
|
||||
then
|
||||
echo -ne ${GREEN} Verified $frontenddomain | tee -a checklog.log
|
||||
if ping -c 1 $rmmdomain &>/dev/null; then
|
||||
echo -e ${GREEN} Verified $rmmdomain | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
echo -ne ${RED} $frontenddomain doesnt exist please create it or check for a typo | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
printf >&2 "You will have a log file called checklog.log in the directory you ran this script from\n\n"
|
||||
printf >&2 "\n\n"
|
||||
exit
|
||||
echo -e ${RED} $rmmdomain doesnt exist please create it or check for a typo | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
printf >&2 "You will have a log file called checklog.log in the directory you ran this script from\n\n"
|
||||
printf >&2 "\n\n"
|
||||
exit
|
||||
fi
|
||||
|
||||
while [[ $meshdomain != *[.]*[.]* ]]
|
||||
do
|
||||
echo -ne "${YELLOW}Enter the subdomain for meshcentral (e.g. mesh.example.com)${NC}: "
|
||||
read meshdomain
|
||||
while [[ $frontenddomain != *[.]*[.]* ]]; do
|
||||
echo -e "${YELLOW}Enter the subdomain for the frontend (e.g. rmm.example.com)${NC}: "
|
||||
read frontenddomain
|
||||
done
|
||||
|
||||
if ping -c 1 $meshdomain &> /dev/null
|
||||
then
|
||||
echo -ne ${GREEN} Verified $meshdomain | tee -a checklog.log
|
||||
if ping -c 1 $frontenddomain &>/dev/null; then
|
||||
echo -e ${GREEN} Verified $frontenddomain | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
echo -ne ${RED} $meshdomain doesnt exist please create it or check for a typo | tee -a checklog.log
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
printf >&2 "You will have a log file called checklog.log in the directory you ran this script from\n\n"
|
||||
printf >&2 "\n\n"
|
||||
exit
|
||||
echo -e ${RED} $frontenddomain doesnt exist please create it or check for a typo | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
printf >&2 "You will have a log file called checklog.log in the directory you ran this script from\n\n"
|
||||
printf >&2 "\n\n"
|
||||
exit
|
||||
fi
|
||||
|
||||
while [[ $domain != *[.]* ]]
|
||||
do
|
||||
echo -ne "${YELLOW}Enter yourdomain used for letsencrypt (e.g. example.com)${NC}: "
|
||||
read domain
|
||||
while [[ $meshdomain != *[.]*[.]* ]]; do
|
||||
echo -e "${YELLOW}Enter the subdomain for meshcentral (e.g. mesh.example.com)${NC}: "
|
||||
read meshdomain
|
||||
done
|
||||
|
||||
echo -ne ${YELLOW} Checking IPs | tee -a checklog.log
|
||||
if ping -c 1 $meshdomain &>/dev/null; then
|
||||
echo -e ${GREEN} Verified $meshdomain | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
echo -e ${RED} $meshdomain doesnt exist please create it or check for a typo | tee -a checklog.log
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
printf >&2 "You will have a log file called checklog.log in the directory you ran this script from\n\n"
|
||||
printf >&2 "\n\n"
|
||||
exit
|
||||
fi
|
||||
|
||||
while [[ $domain != *[.]* ]]; do
|
||||
echo -e "${YELLOW}Enter yourdomain used for letsencrypt (e.g. example.com)${NC}: "
|
||||
read domain
|
||||
done
|
||||
|
||||
echo -e ${YELLOW} Checking IPs | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
# Check rmmdomain IPs
|
||||
locapiip=`dig @"$locdns" +short $rmmdomain`
|
||||
remapiip=`dig @8.8.8.8 +short $rmmdomain`
|
||||
locapiip=$(dig @"$locdns" +short $rmmdomain | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
||||
remapiip=$(dig @8.8.8.8 +short $rmmdomain | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
||||
|
||||
if [ "$locapiip" = "$remapiip" ]; then
|
||||
echo -ne ${GREEN} Success $rmmdomain is Locally Resolved: "$locapiip" Remotely Resolved: "$remapiip" | tee -a checklog.log
|
||||
echo -e ${GREEN} Success $rmmdomain is Locally Resolved: "$locapiip" Remotely Resolved: "$remapiip" | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
echo -ne ${RED} Locally Resolved: "$locapiip" Remotely Resolved: "$remapiip" | tee -a checklog.log
|
||||
echo -e ${RED} Locally Resolved: "$locapiip" Remotely Resolved: "$remapiip" | tee -a checklog.log
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -ne ${RED} Your Local and Remote IP for $rmmdomain all agents will require non-public DNS to find TRMM server | tee -a checklog.log
|
||||
echo -e ${RED} Your Local and Remote IP for $rmmdomain all agents will require non-public DNS to find TRMM server | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
fi
|
||||
|
||||
|
||||
# Check Frontenddomain IPs
|
||||
locrmmip=`dig @"$locdns" +short $frontenddomain`
|
||||
remrmmip=`dig @8.8.8.8 +short $frontenddomain`
|
||||
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]+')
|
||||
|
||||
if [ "$locrmmip" = "$remrmmip" ]; then
|
||||
echo -ne ${GREEN} Success $frontenddomain is Locally Resolved: "$locrmmip" Remotely Resolved: "$remrmmip"| tee -a checklog.log
|
||||
echo -e ${GREEN} Success $frontenddomain is Locally Resolved: "$locrmmip" Remotely Resolved: "$remrmmip" | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
echo -ne ${RED} Locally Resolved: "$locrmmip" Remotely Resolved: "$remrmmip" | tee -a checklog.log
|
||||
echo -e ${RED} Locally Resolved: "$locrmmip" Remotely Resolved: "$remrmmip" | tee -a checklog.log
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -ne ${RED} echo Your Local and Remote IP for $frontenddomain all agents will require non-public DNS to find TRMM server | tee -a checklog.log
|
||||
echo -e ${RED} echo Your Local and Remote IP for $frontenddomain all agents will require non-public DNS to find TRMM server | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
fi
|
||||
|
||||
# Check meshdomain IPs
|
||||
locmeship=`dig @"$locdns" +short $meshdomain`
|
||||
remmeship=`dig @8.8.8.8 +short $meshdomain`
|
||||
locmeship=$(dig @"$locdns" +short $meshdomain | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
||||
remmeship=$(dig @8.8.8.8 +short $meshdomain | grep -m 1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
|
||||
|
||||
if [ "$locmeship" = "$remmeship" ]; then
|
||||
echo -ne ${GREEN} Success $meshdomain is Locally Resolved: "$locmeship" Remotely Resolved: "$remmeship" | tee -a checklog.log
|
||||
echo -e ${GREEN} Success $meshdomain is Locally Resolved: "$locmeship" Remotely Resolved: "$remmeship" | tee -a checklog.log
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
else
|
||||
echo -ne ${RED} Locally Resolved: "$locmeship" Remotely Resolved: "$remmeship" | tee -a checklog.log
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -ne ${RED} Your Local and Remote IP for $meshdomain all agents will require non-public DNS to find TRMM server | tee -a checklog.log
|
||||
echo -e ${RED} Locally Resolved: "$locmeship" Remotely Resolved: "$remmeship" | tee -a checklog.log
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -e ${RED} Your Local and Remote IP for $meshdomain all agents will require non-public DNS to find TRMM server | tee -a checklog.log
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
|
||||
fi
|
||||
|
||||
echo -ne ${YELLOW} Checking Services | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
echo -e ${YELLOW} Checking Services | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
# Check if services are running
|
||||
rmmstatus=$(systemctl is-active rmm)
|
||||
@@ -142,195 +159,201 @@ redisserverstatus=$(systemctl is-active redis-server)
|
||||
|
||||
# RMM Service
|
||||
if [ $rmmstatus = active ]; then
|
||||
echo -ne ${GREEN} Success RMM Service is Running | tee -a checklog.log
|
||||
echo -e ${GREEN} Success RMM Service is Running | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -ne ${RED} 'RMM Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
echo -e ${RED} 'RMM Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
fi
|
||||
|
||||
# daphne Service
|
||||
if [ $daphnestatus = active ]; then
|
||||
echo -ne ${GREEN} Success daphne Service is Running | tee -a checklog.log
|
||||
echo -e ${GREEN} Success daphne Service is Running | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -ne ${RED} 'daphne Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
echo -e ${RED} 'daphne Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
fi
|
||||
|
||||
# celery Service
|
||||
if [ $celerystatus = active ]; then
|
||||
echo -ne ${GREEN} Success celery Service is Running | tee -a checklog.log
|
||||
echo -e ${GREEN} Success celery Service is Running | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -ne ${RED} 'celery Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
echo -e ${RED} 'celery Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
fi
|
||||
|
||||
# celerybeat Service
|
||||
if [ $celerybeatstatus = active ]; then
|
||||
echo -ne ${GREEN} Success celerybeat Service is Running | tee -a checklog.log
|
||||
echo -e ${GREEN} Success celerybeat Service is Running | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -ne ${RED} 'celerybeat Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
echo -e ${RED} 'celerybeat Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
fi
|
||||
|
||||
# nginx Service
|
||||
if [ $nginxstatus = active ]; then
|
||||
echo -ne ${GREEN} Success nginx Service is Running | tee -a checklog.log
|
||||
echo -e ${GREEN} Success nginx Service is Running | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -ne ${RED} 'nginx Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
echo -e ${RED} 'nginx Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
fi
|
||||
|
||||
# nats Service
|
||||
if [ $natsstatus = active ]; then
|
||||
echo -ne ${GREEN} Success nats Service is running | tee -a checklog.log
|
||||
echo -e ${GREEN} Success nats Service is running | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -ne ${RED} 'nats Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
echo -e ${RED} 'nats Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
fi
|
||||
|
||||
# nats-api Service
|
||||
if [ $natsapistatus = active ]; then
|
||||
echo -ne ${GREEN} Success nats-api Service is running | tee -a checklog.log
|
||||
echo -e ${GREEN} Success nats-api Service is running | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -ne ${RED} 'nats-api Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
echo -e ${RED} 'nats-api Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
fi
|
||||
|
||||
# meshcentral Service
|
||||
if [ $meshcentralstatus = active ]; then
|
||||
echo -ne ${GREEN} Success meshcentral Service is running | tee -a checklog.log
|
||||
echo -e ${GREEN} Success meshcentral Service is running | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -ne ${RED} 'meshcentral Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
echo -e ${RED} 'meshcentral Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
fi
|
||||
|
||||
# mongod Service
|
||||
if [ $mongodstatus = active ]; then
|
||||
echo -ne ${GREEN} Success mongod Service is running | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -ne ${RED} 'mongod Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
if grep -q mongo "/meshcentral/meshcentral-data/config.json"; then
|
||||
if [ $mongodstatus = active ]; then
|
||||
echo -e ${GREEN} Success mongod Service is running | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -e ${RED} 'mongod Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
fi
|
||||
fi
|
||||
|
||||
# postgresql Service
|
||||
if [ $postgresqlstatus = active ]; then
|
||||
echo -ne ${GREEN} Success postgresql Service is running | tee -a checklog.log
|
||||
echo -e ${GREEN} Success postgresql Service is running | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -ne ${RED} 'postgresql Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
echo -e ${RED} 'postgresql Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
fi
|
||||
|
||||
# redis-server Service
|
||||
if [ $redisserverstatus = active ]; then
|
||||
echo -ne ${GREEN} Success redis-server Service is running | tee -a checklog.log
|
||||
echo -e ${GREEN} Success redis-server Service is running | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
printf >&2 "\n\n" | tee -a checklog.log
|
||||
echo -ne ${RED} 'redis-server Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
echo -e ${RED} 'redis-server Service isnt running (Tactical wont work without this)' | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
fi
|
||||
|
||||
echo -ne ${YELLOW} Checking Open Ports | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
echo -e ${YELLOW} Checking Open Ports | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
#Get WAN IP
|
||||
wanip=$(dig @resolver4.opendns.com myip.opendns.com +short)
|
||||
|
||||
echo -ne ${GREEN} WAN IP is $wanip | tee -a checklog.log
|
||||
echo -e ${GREEN} WAN IP is $wanip | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
if ! which nc >/dev/null; then
|
||||
echo "netcat is not installed, installing now"
|
||||
sudo apt-get install netcat -y
|
||||
fi
|
||||
|
||||
#Check if HTTPs Port is open
|
||||
if ( nc -zv $wanip 443 2>&1 >/dev/null ); then
|
||||
echo -ne ${GREEN} 'HTTPs Port is open' | tee -a checklog.log
|
||||
if (nc -zv $wanip 443 2>&1 >/dev/null); then
|
||||
echo -e ${GREEN} 'HTTPs Port is open' | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
echo -ne ${RED} 'HTTPs port is closed (you may want this if running locally only)' | tee -a checklog.log
|
||||
echo -e ${RED} 'HTTPs port is closed (you may want this if running locally only)' | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
fi
|
||||
|
||||
echo -ne ${YELLOW} Checking For Proxy | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
echo -ne ${YELLOW} ......this might take a while!!
|
||||
printf >&2 "\n\n"
|
||||
echo -e ${YELLOW} Checking For Proxy | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
echo -e ${YELLOW} ......this might take a while!!
|
||||
printf >&2 "\n\n"
|
||||
|
||||
# Detect Proxy via cert
|
||||
proxyext=$(openssl s_client -showcerts -servername $remapiip -connect $remapiip:443 2>/dev/null | openssl x509 -inform pem -noout -text)
|
||||
proxyint=$(openssl s_client -showcerts -servername 127.0.0.1 -connect 127.0.0.1:443 2>/dev/null | openssl x509 -inform pem -noout -text)
|
||||
|
||||
if [[ $proxyext == $proxyint ]]; then
|
||||
echo -ne ${GREEN} No Proxy detected using Certificate | tee -a checklog.log
|
||||
echo -e ${GREEN} No Proxy detected using Certificate | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
echo -ne ${RED} Proxy detected using Certificate | tee -a checklog.log
|
||||
echo -e ${RED} Proxy detected using Certificate | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
fi
|
||||
|
||||
# Detect Proxy via IP
|
||||
if [ $wanip != $remrmmip ]; then
|
||||
echo -ne ${RED} Proxy detected using IP | tee -a checklog.log
|
||||
echo -e ${RED} Proxy detected using IP | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
else
|
||||
echo -ne ${GREEN} No Proxy detected using IP | tee -a checklog.log
|
||||
echo -e ${GREEN} No Proxy detected using IP | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
fi
|
||||
|
||||
echo -ne ${YELLOW} Checking SSL Certificate is up to date | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
echo -e ${YELLOW} Checking SSL Certificate is up to date | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
#SSL Certificate check
|
||||
cert=$(sudo certbot certificates)
|
||||
|
||||
if [[ "$cert" != *"INVALID"* ]]; then
|
||||
echo -ne ${GREEN} SSL Certificate for $domain is fine | tee -a checklog.log
|
||||
echo -e ${GREEN} SSL Certificate for $domain is fine | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
else
|
||||
echo -ne ${RED} SSL Certificate has expired or doesnt exist for $domain | tee -a checklog.log
|
||||
echo -e ${RED} SSL Certificate has expired or doesnt exist for $domain | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
fi
|
||||
|
||||
# Get List of Certbot Certificates
|
||||
sudo certbot certificates | tee -a checklog.log
|
||||
|
||||
echo -ne ${YELLOW} Getting summary output of logs | tee -a checklog.log
|
||||
echo -e ${YELLOW} Getting summary output of logs | tee -a checklog.log
|
||||
|
||||
tail /rmm/api/tacticalrmm/tacticalrmm/private/log/django_debug.log | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
tail /rmm/api/tacticalrmm/tacticalrmm/private/log/error.log | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
tail /rmm/api/tacticalrmm/tacticalrmm/private/log/django_debug.log | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
tail /rmm/api/tacticalrmm/tacticalrmm/private/log/error.log | tee -a checklog.log
|
||||
printf >&2 "\n\n"
|
||||
|
||||
printf >&2 "\n\n"
|
||||
echo -ne ${YELLOW}
|
||||
echo -e ${YELLOW}
|
||||
printf >&2 "You will have a log file called checklog.log in the directory you ran this script from\n\n"
|
||||
echo -ne ${NC}
|
||||
echo -e ${NC}
|
||||
|
||||
83
update.sh
83
update.sh
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_VERSION="143"
|
||||
SCRIPT_VERSION="146"
|
||||
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'
|
||||
YELLOW='\033[1;33m'
|
||||
@@ -10,7 +10,7 @@ NC='\033[0m'
|
||||
THIS_SCRIPT=$(readlink -f "$0")
|
||||
|
||||
SCRIPTS_DIR='/opt/trmm-community-scripts'
|
||||
PYTHON_VER='3.11.3'
|
||||
PYTHON_VER='3.11.4'
|
||||
SETTINGS_FILE='/rmm/api/tacticalrmm/tacticalrmm/settings.py'
|
||||
|
||||
TMP_FILE=$(mktemp -p "" "rmmupdate_XXXXXXXXXX")
|
||||
@@ -225,16 +225,24 @@ if ! [[ $HAS_PY311 ]]; then
|
||||
sudo rm -rf Python-${PYTHON_VER} Python-${PYTHON_VER}.tgz
|
||||
fi
|
||||
|
||||
arch=$(uname -m)
|
||||
nats_server='/usr/local/bin/nats-server'
|
||||
|
||||
HAS_LATEST_NATS=$(/usr/local/bin/nats-server -version | grep "${NATS_SERVER_VER}")
|
||||
if ! [[ $HAS_LATEST_NATS ]]; then
|
||||
printf >&2 "${GREEN}Updating nats to v${NATS_SERVER_VER}${NC}\n"
|
||||
nats_tmp=$(mktemp -d -t nats-XXXXXXXXXX)
|
||||
wget https://github.com/nats-io/nats-server/releases/download/v${NATS_SERVER_VER}/nats-server-v${NATS_SERVER_VER}-linux-amd64.tar.gz -P ${nats_tmp}
|
||||
tar -xzf ${nats_tmp}/nats-server-v${NATS_SERVER_VER}-linux-amd64.tar.gz -C ${nats_tmp}
|
||||
sudo rm -f /usr/local/bin/nats-server
|
||||
sudo mv ${nats_tmp}/nats-server-v${NATS_SERVER_VER}-linux-amd64/nats-server /usr/local/bin/
|
||||
sudo chmod +x /usr/local/bin/nats-server
|
||||
sudo chown ${USER}:${USER} /usr/local/bin/nats-server
|
||||
if [ "$arch" = "x86_64" ]; then
|
||||
natsarch='amd64'
|
||||
else
|
||||
natsarch='arm64'
|
||||
fi
|
||||
wget https://github.com/nats-io/nats-server/releases/download/v${NATS_SERVER_VER}/nats-server-v${NATS_SERVER_VER}-linux-${natsarch}.tar.gz -P ${nats_tmp}
|
||||
tar -xzf ${nats_tmp}/nats-server-v${NATS_SERVER_VER}-linux-${natsarch}.tar.gz -C ${nats_tmp}
|
||||
sudo rm -f $nats_server
|
||||
sudo mv ${nats_tmp}/nats-server-v${NATS_SERVER_VER}-linux-${natsarch}/nats-server /usr/local/bin/
|
||||
sudo chmod +x $nats_server
|
||||
sudo chown ${USER}:${USER} $nats_server
|
||||
rm -rf ${nats_tmp}
|
||||
fi
|
||||
|
||||
@@ -250,24 +258,6 @@ if [ -d ~/.config ]; then
|
||||
sudo chown -R $USER:$GROUP ~/.config
|
||||
fi
|
||||
|
||||
HAS_NODE16=$(node --version | grep v16)
|
||||
if ! [[ $HAS_NODE16 ]]; then
|
||||
printf >&2 "${GREEN}Updating NodeJS to v16${NC}\n"
|
||||
rm -rf /rmm/web/node_modules
|
||||
sudo systemctl stop meshcentral
|
||||
sudo apt remove -y nodejs
|
||||
sudo rm -rf /usr/lib/node_modules
|
||||
curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
|
||||
sudo apt update
|
||||
sudo apt install -y nodejs
|
||||
sudo npm install -g npm
|
||||
sudo chown ${USER}:${USER} -R /meshcentral
|
||||
cd /meshcentral
|
||||
rm -rf node_modules/
|
||||
npm install meshcentral@${LATEST_MESH_VER}
|
||||
sudo systemctl start meshcentral
|
||||
fi
|
||||
|
||||
sudo npm install -g npm
|
||||
|
||||
# update from main repo
|
||||
@@ -308,6 +298,10 @@ sudo chown ${USER}:${USER} /var/log/celery
|
||||
sudo chown ${USER}:${USER} -R /etc/conf.d/
|
||||
sudo chown ${USER}:${USER} -R /etc/letsencrypt
|
||||
|
||||
if [ -d /rmmbackups ]; then
|
||||
sudo chown ${USER}:${USER} -R /rmmbackups
|
||||
fi
|
||||
|
||||
CHECK_CELERY_CONFIG=$(grep "autoscale=20,2" /etc/conf.d/celery.conf)
|
||||
if ! [[ $CHECK_CELERY_CONFIG ]]; then
|
||||
sed -i 's/CELERYD_OPTS=.*/CELERYD_OPTS="--time-limit=86400 --autoscale=20,2"/g' /etc/conf.d/celery.conf
|
||||
@@ -323,9 +317,16 @@ EOF
|
||||
echo "${adminenabled}" | tee --append /rmm/api/tacticalrmm/tacticalrmm/local_settings.py >/dev/null
|
||||
fi
|
||||
|
||||
sudo cp /rmm/natsapi/bin/nats-api /usr/local/bin
|
||||
sudo chown ${USER}:${USER} /usr/local/bin/nats-api
|
||||
sudo chmod +x /usr/local/bin/nats-api
|
||||
if [ "$arch" = "x86_64" ]; then
|
||||
natsapi='nats-api'
|
||||
else
|
||||
natsapi='nats-api-arm64'
|
||||
fi
|
||||
|
||||
nats_api='/usr/local/bin/nats-api'
|
||||
sudo cp /rmm/natsapi/bin/${natsapi} $nats_api
|
||||
sudo chown ${USER}:${USER} $nats_api
|
||||
sudo chmod +x $nats_api
|
||||
|
||||
if [[ "${CURRENT_PIP_VER}" != "${LATEST_PIP_VER}" ]] || [[ "$force" = true ]]; then
|
||||
rm -rf /rmm/api/env
|
||||
@@ -356,8 +357,29 @@ python manage.py clear_redis_celery_locks
|
||||
python manage.py post_update_tasks
|
||||
API=$(python manage.py get_config api)
|
||||
WEB_VERSION=$(python manage.py get_config webversion)
|
||||
FRONTEND=$(python manage.py get_config webdomain)
|
||||
MESHDOMAIN=$(python manage.py get_config meshdomain)
|
||||
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
|
||||
rm -rf /rmm/web
|
||||
fi
|
||||
@@ -379,9 +401,6 @@ for i in nats nats-api rmm daphne celery celerybeat nginx; do
|
||||
sudo systemctl start ${i}
|
||||
done
|
||||
|
||||
sleep 1
|
||||
/rmm/api/env/bin/python /rmm/api/tacticalrmm/manage.py update_agents
|
||||
|
||||
CURRENT_MESH_VER=$(cd /meshcentral/node_modules/meshcentral && node -p -e "require('./package.json').version")
|
||||
if [[ "${CURRENT_MESH_VER}" != "${LATEST_MESH_VER}" ]] || [[ "$force" = true ]]; then
|
||||
printf >&2 "${GREEN}Updating meshcentral from ${CURRENT_MESH_VER} to ${LATEST_MESH_VER}${NC}\n"
|
||||
|
||||
Reference in New Issue
Block a user