Compare commits
	
		
			43 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 72126052ad | ||
|  | 75d9f6a7e7 | ||
|  | de677294c6 | ||
|  | da1e6b8259 | ||
|  | a9633b3990 | ||
|  | 7ac9af1cc1 | ||
|  | 00d8b8cd61 | ||
|  | af7ff7f5cf | ||
|  | 2a20719130 | ||
|  | f481940180 | ||
|  | ca8824d1e3 | ||
|  | f4be199b77 | ||
|  | 6bcef8334e | ||
|  | 3955eff683 | ||
|  | aa0f6ecd75 | ||
|  | ef4a94ed78 | ||
|  | b5c803ce65 | ||
|  | d4325ed82e | ||
|  | 3805fb8f26 | ||
|  | d4d938c655 | ||
|  | 1c6911e361 | ||
|  | a1b364f337 | ||
|  | ece5c3da86 | ||
|  | 5d1ae6047b | ||
|  | 5605c72253 | ||
|  | 66bbcf0733 | ||
|  | acc23ea7bb | ||
|  | 663bd0c9f0 | ||
|  | 39b1025dfa | ||
|  | d2875e90b2 | ||
|  | ff461d1d02 | ||
|  | 58164ea2d3 | ||
|  | 1bf4834004 | ||
|  | bf58d78281 | ||
|  | 0dc749bb3d | ||
|  | a8aedfde55 | ||
|  | b174a89032 | ||
|  | 9b92d1b673 | ||
|  | febc9aed11 | ||
|  | de2462677e | ||
|  | 8bd94d46eb | ||
|  | d43cefe28f | ||
|  | b82874e261 | 
| @@ -1,11 +1,11 @@ | ||||
| # pulls community scripts from git repo | ||||
| FROM python:3.10-slim AS GET_SCRIPTS_STAGE | ||||
| FROM python:3.10.6-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 | ||||
|  | ||||
| FROM python:3.10-slim | ||||
| FROM python:3.10.6-slim | ||||
|  | ||||
| ENV TACTICAL_DIR /opt/tactical | ||||
| ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready | ||||
|   | ||||
| @@ -22,22 +22,6 @@ services: | ||||
|         aliases: | ||||
|           - tactical-backend | ||||
|  | ||||
|   app-dev: | ||||
|     container_name: trmm-app-dev | ||||
|     image: node:16-alpine | ||||
|     restart: always | ||||
|     command: /bin/sh -c "npm install --cache ~/.npm && npm run serve" | ||||
|     user: 1000:1000 | ||||
|     working_dir: /workspace/web | ||||
|     volumes: | ||||
|       - ..:/workspace:cached | ||||
|     ports: | ||||
|       - "8080:${APP_PORT}" | ||||
|     networks: | ||||
|       dev: | ||||
|         aliases: | ||||
|           - tactical-frontend | ||||
|  | ||||
|   # nats | ||||
|   nats-dev: | ||||
|     container_name: trmm-nats-dev | ||||
|   | ||||
| @@ -15,10 +15,7 @@ set -e | ||||
| : "${MESH_PASS:=meshcentralpass}" | ||||
| : "${MESH_HOST:=tactical-meshcentral}" | ||||
| : "${API_HOST:=tactical-backend}" | ||||
| : "${APP_HOST:=tactical-frontend}" | ||||
| : "${REDIS_HOST:=tactical-redis}" | ||||
| : "${HTTP_PROTOCOL:=http}" | ||||
| : "${APP_PORT:=8080}" | ||||
| : "${API_PORT:=8000}" | ||||
|  | ||||
| : "${CERT_PRIV_PATH:=${TACTICAL_DIR}/certs/privkey.pem}" | ||||
| @@ -142,16 +139,6 @@ if [ "$1" = 'tactical-init-dev' ]; then | ||||
|  | ||||
|   django_setup | ||||
|  | ||||
|   # create .env file for frontend | ||||
|   webenv="$(cat << EOF | ||||
| PROD_URL = "${HTTP_PROTOCOL}://${API_HOST}" | ||||
| DEV_URL = "${HTTP_PROTOCOL}://${API_HOST}" | ||||
| DEV_PORT = ${APP_PORT} | ||||
| DOCKER_BUILD = 1 | ||||
| EOF | ||||
| )" | ||||
|   echo "${webenv}" | tee "${WORKSPACE_DIR}"/web/.env > /dev/null | ||||
|  | ||||
|   # chown everything to tactical user | ||||
|   chown -R "${TACTICAL_USER}":"${TACTICAL_USER}" "${WORKSPACE_DIR}" | ||||
|   chown -R "${TACTICAL_USER}":"${TACTICAL_USER}" "${TACTICAL_DIR}" | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| -r ../api/tacticalrmm/requirements.txt | ||||
| -r ../api/tacticalrmm/requirements-dev.txt | ||||
| -r ../api/tacticalrmm/requirements-test.txt | ||||
| -r /workspace/api/tacticalrmm/requirements.txt | ||||
| -r /workspace/api/tacticalrmm/requirements-dev.txt | ||||
| -r /workspace/api/tacticalrmm/requirements-test.txt | ||||
|   | ||||
							
								
								
									
										16
									
								
								.github/workflows/ci-tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.github/workflows/ci-tests.yml
									
									
									
									
										vendored
									
									
								
							| @@ -14,18 +14,18 @@ jobs: | ||||
|     name: Tests | ||||
|     strategy: | ||||
|       matrix: | ||||
|         python-version: ['3.10.4'] | ||||
|         python-version: ["3.10.6"] | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|  | ||||
|       - uses: harmon758/postgresql-action@v1 | ||||
|         with: | ||||
|           postgresql version: '14' | ||||
|           postgresql db: 'pipeline' | ||||
|           postgresql user: 'pipeline' | ||||
|           postgresql password: 'pipeline123456' | ||||
|        | ||||
|           postgresql version: "14" | ||||
|           postgresql db: "pipeline" | ||||
|           postgresql user: "pipeline" | ||||
|           postgresql password: "pipeline123456" | ||||
|  | ||||
|       - name: Setup Python ${{ matrix.python-version }} | ||||
|         uses: actions/setup-python@v3 | ||||
|         with: | ||||
| @@ -49,13 +49,13 @@ jobs: | ||||
|           pip install -r requirements.txt -r requirements-test.txt | ||||
|  | ||||
|       - name: Codestyle black | ||||
|         working-directory: api/tacticalrmm | ||||
|         working-directory: api | ||||
|         run: | | ||||
|           black --exclude migrations/ --check tacticalrmm | ||||
|           if [ $? -ne 0 ]; then | ||||
|               exit 1 | ||||
|           fi | ||||
|        | ||||
|  | ||||
|       - name: Run django tests | ||||
|         env: | ||||
|           GHACTIONS: "yes" | ||||
|   | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -55,3 +55,4 @@ coverage.lcov | ||||
| daphne.sock.lock | ||||
| .pytest_cache | ||||
| coverage.xml | ||||
| setup_dev.yml | ||||
|   | ||||
							
								
								
									
										4
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @@ -27,6 +27,10 @@ | ||||
|   "editor.bracketPairColorization.enabled": true, | ||||
|   "editor.guides.bracketPairs": true, | ||||
|   "editor.formatOnSave": true, | ||||
|   "files.associations": { | ||||
|     "**/ansible/**/*.yml": "ansible", | ||||
|     "**/docker/**/docker-compose*.yml": "dockercompose" | ||||
|   }, | ||||
|   "files.watcherExclude": { | ||||
|     "files.watcherExclude": { | ||||
|       "**/.git/objects/**": true, | ||||
|   | ||||
| @@ -1,15 +1,19 @@ | ||||
| --- | ||||
| user: "tactical" | ||||
| python_ver: "3.10.4" | ||||
| python_ver: "3.10.6" | ||||
| go_ver: "1.18.5" | ||||
| 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" | ||||
| backend_dir: "/opt/trmm" | ||||
| frontend_dir: "/opt/trmm-web" | ||||
| scripts_dir: "/opt/community-scripts" | ||||
| trmm_dir: "/opt/trmm/api/tacticalrmm/tacticalrmm" | ||||
| scripts_dir: "/opt/trmm-community-scripts" | ||||
| trmm_dir: "{{ backend_dir }}/api/tacticalrmm/tacticalrmm" | ||||
| mesh_dir: "/opt/meshcentral" | ||||
| settings_file: "{{ trmm_dir }}/settings.py" | ||||
| local_settings_file: "{{ trmm_dir }}/local_settings.py" | ||||
| fullchain_dest: /etc/ssl/certs/fullchain.pem | ||||
| privkey_dest: /etc/ssl/certs/privkey.pem | ||||
|  | ||||
| base_pkgs: | ||||
|   - build-essential | ||||
| @@ -22,7 +26,6 @@ base_pkgs: | ||||
|   - g++ | ||||
|   - make | ||||
|   - ca-certificates | ||||
|   - redis | ||||
|   - git | ||||
|  | ||||
| python_pkgs: | ||||
|   | ||||
| @@ -5,18 +5,24 @@ pid /run/nginx.pid; | ||||
| include /etc/nginx/modules-enabled/*.conf; | ||||
|  | ||||
| events { | ||||
|         worker_connections 2048; | ||||
|         worker_connections 4096; | ||||
| } | ||||
|  | ||||
| http { | ||||
|         sendfile on; | ||||
|         server_tokens off; | ||||
|         tcp_nopush on; | ||||
|         types_hash_max_size 2048; | ||||
|         server_names_hash_bucket_size 64; | ||||
|         include /etc/nginx/mime.types; | ||||
|         default_type application/octet-stream; | ||||
|         ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; | ||||
|         ssl_protocols TLSv1.2 TLSv1.3; | ||||
|         ssl_prefer_server_ciphers on; | ||||
|         ssl_ciphers EECDH+AESGCM:EDH+AESGCM; | ||||
|         ssl_ecdh_curve secp384r1; | ||||
|         ssl_stapling on; | ||||
|         ssl_stapling_verify on; | ||||
|         add_header X-Content-Type-Options nosniff; | ||||
|         access_log /var/log/nginx/access.log; | ||||
|         error_log /var/log/nginx/error.log; | ||||
|         gzip on; | ||||
|   | ||||
							
								
								
									
										2
									
								
								ansible/roles/trmm_dev/files/nginx.repo
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								ansible/roles/trmm_dev/files/nginx.repo
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| deb https://nginx.org/packages/debian/ bullseye nginx | ||||
| deb-src https://nginx.org/packages/debian/ bullseye nginx | ||||
| @@ -9,6 +9,19 @@ | ||||
|     group: "root" | ||||
|     mode: "0644" | ||||
|  | ||||
| - name: set max_user_watches | ||||
|   tags: sysctl | ||||
|   become: yes | ||||
|   ansible.builtin.lineinfile: | ||||
|     path: /etc/sysctl.conf | ||||
|     line: fs.inotify.max_user_watches=524288 | ||||
|  | ||||
| - name: reload sysctl | ||||
|   tags: sysctl | ||||
|   become: yes | ||||
|   ansible.builtin.command: | ||||
|     cmd: sysctl -p | ||||
|  | ||||
| - name: install base packages | ||||
|   tags: base | ||||
|   become: yes | ||||
| @@ -19,6 +32,21 @@ | ||||
|   with_items: | ||||
|     - "{{ base_pkgs }}" | ||||
|  | ||||
| - name: download and install golang | ||||
|   tags: golang | ||||
|   become: yes | ||||
|   ansible.builtin.unarchive: | ||||
|     src: "https://go.dev/dl/go{{ go_ver }}.linux-amd64.tar.gz" | ||||
|     dest: /usr/local | ||||
|     remote_src: yes | ||||
|  | ||||
| - name: add golang to path | ||||
|   become: yes | ||||
|   tags: golang | ||||
|   ansible.builtin.copy: | ||||
|     dest: /etc/profile.d/golang.sh | ||||
|     content: "PATH=$PATH:/usr/local/go/bin" | ||||
|  | ||||
| - name: install python prereqs | ||||
|   tags: python | ||||
|   become: yes | ||||
| @@ -63,31 +91,13 @@ | ||||
|     cmd: | | ||||
|       make altinstall | ||||
|  | ||||
| - name: install nginx | ||||
|   tags: nginx | ||||
| - name: install redis | ||||
|   tags: redis | ||||
|   become: yes | ||||
|   ansible.builtin.apt: | ||||
|     pkg: nginx | ||||
|     pkg: redis | ||||
|     state: present | ||||
|  | ||||
| - name: set nginx default conf | ||||
|   tags: nginx | ||||
|   become: yes | ||||
|   ansible.builtin.copy: | ||||
|     src: nginx-default.conf | ||||
|     dest: /etc/nginx/nginx.conf | ||||
|     owner: "root" | ||||
|     group: "root" | ||||
|     mode: "0644" | ||||
|  | ||||
| - name: ensure nginx enabled and restarted | ||||
|   tags: nginx | ||||
|   become: yes | ||||
|   ansible.builtin.service: | ||||
|     name: nginx | ||||
|     enabled: yes | ||||
|     state: restarted | ||||
|  | ||||
| - name: create postgres repo | ||||
|   tags: postgres | ||||
|   become: yes | ||||
| @@ -96,7 +106,7 @@ | ||||
|     dest: /etc/apt/sources.list.d/pgdg.list | ||||
|     owner: root | ||||
|     group: root | ||||
|     mode: "0440" | ||||
|     mode: "0644" | ||||
|  | ||||
| - name: import postgres repo signing key | ||||
|   tags: postgres | ||||
| @@ -232,6 +242,200 @@ | ||||
|   ansible.builtin.shell: | ||||
|     cmd: npm install -g npm | ||||
|  | ||||
| - name: install quasar cli | ||||
|   tags: quasar | ||||
|   become: yes | ||||
|   ansible.builtin.shell: | ||||
|     cmd: npm install -g @quasar/cli | ||||
|  | ||||
| - name: install frontend | ||||
|   tags: quasar | ||||
|   ansible.builtin.shell: | ||||
|     chdir: "{{ frontend_dir }}" | ||||
|     cmd: npm install | ||||
|  | ||||
| - name: add quasar env | ||||
|   tags: quasar | ||||
|   ansible.builtin.template: | ||||
|     src: quasar.env.j2 | ||||
|     dest: "{{ frontend_dir }}/.env" | ||||
|     owner: "{{ user }}" | ||||
|     group: "{{ user }}" | ||||
|     mode: "0644" | ||||
|  | ||||
| - name: remove tempdirs | ||||
|   tags: cleanup | ||||
|   become: yes | ||||
|   ignore_errors: yes | ||||
|   ansible.builtin.file: | ||||
|     path: "{{ item }}" | ||||
|     state: absent | ||||
|   with_items: | ||||
|     - "{{ nats_tmp.path }}" | ||||
|     - "{{ python_tmp.path }}" | ||||
|     - "{{ nodejs_tmp.path }}" | ||||
|  | ||||
| - name: deploy fullchain | ||||
|   tags: certs | ||||
|   become: yes | ||||
|   ansible.builtin.copy: | ||||
|     src: "{{ fullchain_src }}" | ||||
|     dest: "{{ fullchain_dest }}" | ||||
|     owner: "{{ user }}" | ||||
|     group: "{{ user }}" | ||||
|     mode: "0440" | ||||
|  | ||||
| - name: deploy privkey | ||||
|   tags: certs | ||||
|   become: yes | ||||
|   ansible.builtin.copy: | ||||
|     src: "{{ privkey_src }}" | ||||
|     dest: "{{ privkey_dest }}" | ||||
|     owner: "{{ user }}" | ||||
|     group: "{{ user }}" | ||||
|     mode: "0440" | ||||
|  | ||||
| - name: import nginx signing key | ||||
|   tags: nginx | ||||
|   become: yes | ||||
|   ansible.builtin.apt_key: | ||||
|     url: https://nginx.org/packages/keys/nginx_signing.key | ||||
|     state: present | ||||
|  | ||||
| - name: add nginx repo | ||||
|   tags: nginx | ||||
|   become: yes | ||||
|   ansible.builtin.copy: | ||||
|     src: nginx.repo | ||||
|     dest: /etc/apt/sources.list.d/nginx.list | ||||
|     owner: "root" | ||||
|     group: "root" | ||||
|     mode: "0644" | ||||
|  | ||||
| - name: install nginx | ||||
|   tags: nginx | ||||
|   become: yes | ||||
|   ansible.builtin.apt: | ||||
|     pkg: nginx | ||||
|     state: present | ||||
|     update_cache: yes | ||||
|  | ||||
| - name: set nginx default conf | ||||
|   tags: nginx | ||||
|   become: yes | ||||
|   ansible.builtin.copy: | ||||
|     src: nginx-default.conf | ||||
|     dest: /etc/nginx/nginx.conf | ||||
|     owner: "root" | ||||
|     group: "root" | ||||
|     mode: "0644" | ||||
|  | ||||
| - name: create nginx dirs | ||||
|   become: yes | ||||
|   tags: nginx | ||||
|   ansible.builtin.file: | ||||
|     state: directory | ||||
|     path: "{{ item }}" | ||||
|     mode: "0755" | ||||
|   with_items: | ||||
|     - /etc/nginx/sites-available | ||||
|     - /etc/nginx/sites-enabled | ||||
|  | ||||
| - name: deploy nginx sites | ||||
|   become: yes | ||||
|   tags: nginx | ||||
|   ansible.builtin.template: | ||||
|     src: "{{ item.src }}" | ||||
|     dest: "{{ item.dest }}" | ||||
|     mode: "0644" | ||||
|     owner: root | ||||
|     group: root | ||||
|   with_items: | ||||
|     - { src: backend.nginx.j2, dest: /etc/nginx/sites-available/backend.conf } | ||||
|     - { src: mesh.nginx.j2, dest: /etc/nginx/sites-available/mesh.conf } | ||||
|  | ||||
| - name: enable nginx sites | ||||
|   become: yes | ||||
|   tags: nginx | ||||
|   ansible.builtin.file: | ||||
|     src: "{{ item.src }}" | ||||
|     dest: "{{ item.dest }}" | ||||
|     mode: "0644" | ||||
|     owner: root | ||||
|     group: root | ||||
|     state: link | ||||
|   with_items: | ||||
|     - { | ||||
|         src: /etc/nginx/sites-available/backend.conf, | ||||
|         dest: /etc/nginx/sites-enabled/backend.conf, | ||||
|       } | ||||
|     - { | ||||
|         src: /etc/nginx/sites-available/mesh.conf, | ||||
|         dest: /etc/nginx/sites-enabled/mesh.conf, | ||||
|       } | ||||
|  | ||||
| - name: ensure nginx enabled and restarted | ||||
|   tags: nginx | ||||
|   become: yes | ||||
|   ansible.builtin.service: | ||||
|     name: nginx | ||||
|     enabled: yes | ||||
|     state: restarted | ||||
|  | ||||
| - name: copy nats-api bin | ||||
|   tags: nats-api | ||||
|   become: yes | ||||
|   ansible.builtin.copy: | ||||
|     remote_src: yes | ||||
|     src: "{{ backend_dir }}/natsapi/bin/nats-api" | ||||
|     dest: /usr/local/bin/nats-api | ||||
|     owner: "{{ user }}" | ||||
|     group: "{{ user }}" | ||||
|     mode: "0755" | ||||
|  | ||||
| - name: get setuptools_ver | ||||
|   tags: pip | ||||
|   ansible.builtin.shell: grep "^SETUPTOOLS_VER" {{ settings_file }} | awk -F'[= "]' '{print $5}' | ||||
|   register: setuptools_ver | ||||
|  | ||||
| - name: get wheel_ver | ||||
|   tags: pip | ||||
|   ansible.builtin.shell: grep "^WHEEL_VER" {{ settings_file }} | awk -F'[= "]' '{print $5}' | ||||
|   register: wheel_ver | ||||
|  | ||||
| - name: setup virtual env | ||||
|   tags: pip | ||||
|   ansible.builtin.shell: | ||||
|     chdir: "{{ backend_dir }}/api" | ||||
|     cmd: python3.10 -m venv env | ||||
|  | ||||
| - name: update pip to latest | ||||
|   tags: pip | ||||
|   ansible.builtin.pip: | ||||
|     virtualenv: "{{ backend_dir }}/api/env" | ||||
|     name: pip | ||||
|     state: latest | ||||
|  | ||||
| - name: install setuptools and wheel | ||||
|   tags: pip | ||||
|   ansible.builtin.pip: | ||||
|     virtualenv: "{{ backend_dir }}/api/env" | ||||
|     name: "{{ item }}" | ||||
|   with_items: | ||||
|     - "setuptools=={{ setuptools_ver.stdout }}" | ||||
|     - "wheel=={{ wheel_ver.stdout }}" | ||||
|  | ||||
| - name: install python packages | ||||
|   tags: pip | ||||
|   ansible.builtin.pip: | ||||
|     virtualenv: "{{ backend_dir }}/api/env" | ||||
|     chdir: "{{ backend_dir }}/api/tacticalrmm" | ||||
|     requirements: "{{ item }}" | ||||
|   with_items: | ||||
|     - requirements.txt | ||||
|     - requirements-dev.txt | ||||
|     - requirements-test.txt | ||||
|  | ||||
| - name: deploy django local settings | ||||
|   tags: django | ||||
|   ansible.builtin.template: | ||||
| @@ -241,13 +445,189 @@ | ||||
|     owner: "{{ user }}" | ||||
|     group: "{{ user }}" | ||||
|  | ||||
| - name: remove tempdirs | ||||
|   tags: cleanup | ||||
| - name: setup django | ||||
|   tags: django | ||||
|   ansible.builtin.shell: | ||||
|     chdir: "{{ backend_dir }}/api/tacticalrmm" | ||||
|     cmd: | | ||||
|       . ../env/bin/activate | ||||
|       python manage.py migrate --no-input | ||||
|       python manage.py collectstatic --no-input | ||||
|       python manage.py create_natsapi_conf | ||||
|       python manage.py load_chocos | ||||
|       python manage.py load_community_scripts | ||||
|       echo "from accounts.models import User; User.objects.create_superuser('{{ django_user }}', '{{ github_email }}', '{{ django_password }}') if not User.objects.filter(username='{{ django_user }}').exists() else 0;" | python manage.py shell | ||||
|       python manage.py create_installer_user | ||||
|  | ||||
| - name: deploy services | ||||
|   tags: services | ||||
|   become: yes | ||||
|   ansible.builtin.template: | ||||
|     src: "{{ item.src }}" | ||||
|     dest: "{{ item.dest }}" | ||||
|     mode: "0644" | ||||
|     owner: "root" | ||||
|     group: "root" | ||||
|   with_items: | ||||
|     - { src: nats-api.systemd.j2, dest: /etc/systemd/system/nats-api.service } | ||||
|     - { 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}' | ||||
|   register: mesh_ver | ||||
|  | ||||
| - name: create meshcentral data directory | ||||
|   tags: mesh | ||||
|   become: yes | ||||
|   ansible.builtin.file: | ||||
|     path: "{{ item }}" | ||||
|     state: absent | ||||
|     path: "{{ mesh_dir }}/meshcentral-data" | ||||
|     state: directory | ||||
|     owner: "{{ user }}" | ||||
|     group: "{{ user }}" | ||||
|     mode: "0755" | ||||
|  | ||||
| - name: install meshcentral | ||||
|   tags: mesh | ||||
|   ansible.builtin.command: | ||||
|     chdir: "{{ mesh_dir }}" | ||||
|     cmd: "npm install meshcentral@{{ mesh_ver.stdout }}" | ||||
|  | ||||
| - name: deploy mesh config | ||||
|   tags: mesh | ||||
|   ansible.builtin.template: | ||||
|     src: mesh.cfg.j2 | ||||
|     dest: "{{ mesh_dir }}/meshcentral-data/config.json" | ||||
|     mode: "0644" | ||||
|     owner: "{{ user }}" | ||||
|     group: "{{ user }}" | ||||
|  | ||||
| - name: start meshcentral | ||||
|   tags: mesh | ||||
|   become: yes | ||||
|   ansible.builtin.systemd: | ||||
|     name: meshcentral.service | ||||
|     state: started | ||||
|     enabled: yes | ||||
|     daemon_reload: yes | ||||
|  | ||||
| - name: wait for meshcentral to be ready | ||||
|   tags: mesh | ||||
|   uri: | ||||
|     url: "https://{{ mesh }}" | ||||
|     return_content: yes | ||||
|     validate_certs: yes | ||||
|     status_code: 200 | ||||
|   register: mesh_status | ||||
|   until: mesh_status.status == 200 | ||||
|   retries: 20 | ||||
|   delay: 3 | ||||
|  | ||||
| - name: get meshcentral login token key | ||||
|   tags: mesh_key | ||||
|   ansible.builtin.command: | ||||
|     chdir: "{{ mesh_dir }}" | ||||
|     cmd: node node_modules/meshcentral --logintokenkey | ||||
|   register: mesh_token_key | ||||
|  | ||||
| - name: add mesh key to django settings file | ||||
|   tags: mesh_key | ||||
|   ansible.builtin.lineinfile: | ||||
|     path: "{{ local_settings_file }}" | ||||
|     line: 'MESH_TOKEN_KEY = "{{ mesh_token_key.stdout }}"' | ||||
|  | ||||
| - name: stop meshcentral service | ||||
|   tags: mesh_user | ||||
|   become: yes | ||||
|   ansible.builtin.service: | ||||
|     name: meshcentral.service | ||||
|     state: stopped | ||||
|  | ||||
| - name: create mesh user | ||||
|   tags: mesh_user | ||||
|   ansible.builtin.shell: | ||||
|     chdir: "{{ mesh_dir }}" | ||||
|     cmd: | | ||||
|       node node_modules/meshcentral --createaccount {{ mesh_user }} --pass {{ mesh_password }} --email {{ github_email }} | ||||
|       node node_modules/meshcentral --adminaccount {{ mesh_user }} | ||||
|  | ||||
| - name: start meshcentral service | ||||
|   tags: mesh_user | ||||
|   become: yes | ||||
|   ansible.builtin.service: | ||||
|     name: meshcentral.service | ||||
|     state: started | ||||
|  | ||||
| - name: wait for meshcentral to be ready | ||||
|   tags: mesh_user | ||||
|   uri: | ||||
|     url: "https://{{ mesh }}" | ||||
|     return_content: yes | ||||
|     validate_certs: yes | ||||
|     status_code: 200 | ||||
|   register: mesh_status | ||||
|   until: mesh_status.status == 200 | ||||
|   retries: 20 | ||||
|   delay: 3 | ||||
|  | ||||
| - name: create mesh device group | ||||
|   tags: mesh_user | ||||
|   ansible.builtin.shell: | ||||
|     chdir: "{{ mesh_dir }}" | ||||
|     cmd: | | ||||
|       node node_modules/meshcentral/meshctrl.js --url wss://{{ mesh }}:443 --loginuser {{ mesh_user }} --loginpass {{ mesh_password }} AddDeviceGroup --name TacticalRMM | ||||
|  | ||||
| - name: finish up django | ||||
|   tags: mesh_user | ||||
|   ansible.builtin.shell: | ||||
|     chdir: "{{ backend_dir }}/api/tacticalrmm" | ||||
|     cmd: | | ||||
|       . ../env/bin/activate | ||||
|       python manage.py initial_db_setup | ||||
|       python manage.py reload_nats | ||||
|  | ||||
| - name: restart services | ||||
|   tags: services | ||||
|   become: yes | ||||
|   ansible.builtin.systemd: | ||||
|     daemon_reload: yes | ||||
|     enabled: yes | ||||
|     state: restarted | ||||
|     name: "{{ item }}.service" | ||||
|   with_items: | ||||
|     - "{{ nats_tmp.path }}" | ||||
|     - "{{ python_tmp.path }}" | ||||
|     - "{{ nodejs_tmp.path }}" | ||||
|     - nats | ||||
|     - nats-api | ||||
|   | ||||
							
								
								
									
										20
									
								
								ansible/roles/trmm_dev/templates/backend.nginx.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								ansible/roles/trmm_dev/templates/backend.nginx.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| server { | ||||
|     listen 443 ssl reuseport; | ||||
|     listen [::]:443 ssl; | ||||
|     server_name {{ api }}; | ||||
|     client_max_body_size 300M; | ||||
|     ssl_certificate {{ fullchain_dest }}; | ||||
|     ssl_certificate_key {{ privkey_dest }}; | ||||
|  | ||||
|  | ||||
|     location ~ ^/natsws { | ||||
|         proxy_pass http://127.0.0.1:9235; | ||||
|         proxy_http_version 1.1; | ||||
|         proxy_set_header Host $host; | ||||
|         proxy_set_header Upgrade $http_upgrade; | ||||
|         proxy_set_header Connection "upgrade"; | ||||
|         proxy_set_header X-Forwarded-Host $host:$server_port; | ||||
|         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||||
|         proxy_set_header X-Forwarded-Proto $scheme; | ||||
|     } | ||||
| } | ||||
| @@ -2,9 +2,7 @@ SECRET_KEY = "{{ django_secret }}" | ||||
| DEBUG = True | ||||
| ALLOWED_HOSTS = ['{{ api }}'] | ||||
| ADMIN_URL = "admin/" | ||||
| CORS_ORIGIN_WHITELIST = [ | ||||
|     "https://{{ rmm }}" | ||||
| ] | ||||
| CORS_ORIGIN_ALLOW_ALL = True | ||||
| DATABASES = { | ||||
|     'default': { | ||||
|         'ENGINE': 'django.db.backends.postgresql', | ||||
| @@ -17,3 +15,7 @@ DATABASES = { | ||||
| } | ||||
| REDIS_HOST    = "localhost" | ||||
| ADMIN_ENABLED = True | ||||
| CERT_FILE = "{{ fullchain_src }}" | ||||
| KEY_FILE = "{{ privkey_src }}" | ||||
| MESH_USERNAME = "{{ mesh_user }}" | ||||
| MESH_SITE = "{{ mesh }}" | ||||
|   | ||||
							
								
								
									
										33
									
								
								ansible/roles/trmm_dev/templates/mesh.cfg.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								ansible/roles/trmm_dev/templates/mesh.cfg.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| { | ||||
|   "settings": { | ||||
|     "Cert": "{{ mesh }}", | ||||
|     "MongoDb": "mongodb://127.0.0.1:27017", | ||||
|     "MongoDbName": "meshcentral", | ||||
|     "WANonly": true, | ||||
|     "Minify": 1, | ||||
|     "Port": 4430, | ||||
|     "AliasPort": 443, | ||||
|     "RedirPort": 800, | ||||
|     "AllowLoginToken": true, | ||||
|     "AllowFraming": true, | ||||
|     "AgentPong": 300, | ||||
|     "AllowHighQualityDesktop": true, | ||||
|     "TlsOffload": "127.0.0.1", | ||||
|     "agentCoreDump": false, | ||||
|     "Compression": true, | ||||
|     "WsCompression": true, | ||||
|     "AgentWsCompression": true, | ||||
|     "MaxInvalidLogin": { "time": 5, "count": 5, "coolofftime": 30 } | ||||
|   }, | ||||
|   "domains": { | ||||
|     "": { | ||||
|       "Title": "Tactical RMM", | ||||
|       "Title2": "Tactical RMM", | ||||
|       "NewAccounts": false, | ||||
|       "CertUrl": "https://{{ mesh }}:443/", | ||||
|       "GeoLocation": true, | ||||
|       "CookieIpCheck": false, | ||||
|       "mstsc": true | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										22
									
								
								ansible/roles/trmm_dev/templates/mesh.nginx.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								ansible/roles/trmm_dev/templates/mesh.nginx.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| server { | ||||
|     listen 443 ssl; | ||||
|     listen [::]:443 ssl; | ||||
|     proxy_send_timeout 330s; | ||||
|     proxy_read_timeout 330s; | ||||
|     server_name {{ mesh }}; | ||||
|     ssl_certificate {{ fullchain_dest }}; | ||||
|     ssl_certificate_key {{ privkey_dest }}; | ||||
|  | ||||
|     ssl_session_cache shared:WEBSSL:10m; | ||||
|  | ||||
|     location / { | ||||
|         proxy_pass http://127.0.0.1:4430/; | ||||
|         proxy_http_version 1.1; | ||||
|         proxy_set_header Host $host; | ||||
|         proxy_set_header Upgrade $http_upgrade; | ||||
|         proxy_set_header Connection "upgrade"; | ||||
|         proxy_set_header X-Forwarded-Host $host:$server_port; | ||||
|         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||||
|         proxy_set_header X-Forwarded-Proto $scheme; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										17
									
								
								ansible/roles/trmm_dev/templates/mesh.systemd.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								ansible/roles/trmm_dev/templates/mesh.systemd.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| [Unit] | ||||
| Description=MeshCentral Server | ||||
| After=network.target mongod.service nginx.service | ||||
|  | ||||
| [Service] | ||||
| Type=simple | ||||
| LimitNOFILE=1000000 | ||||
| ExecStart=/usr/bin/node node_modules/meshcentral | ||||
| Environment=NODE_ENV=production | ||||
| WorkingDirectory={{ mesh_dir }} | ||||
| User={{ user }} | ||||
| Group={{ user }} | ||||
| Restart=always | ||||
| RestartSec=10s | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
							
								
								
									
										14
									
								
								ansible/roles/trmm_dev/templates/nats-api.systemd.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								ansible/roles/trmm_dev/templates/nats-api.systemd.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| [Unit] | ||||
| Description=TacticalRMM Nats Api | ||||
| After=nats.service | ||||
|  | ||||
| [Service] | ||||
| Type=simple | ||||
| ExecStart=/usr/local/bin/nats-api | ||||
| User={{ user }} | ||||
| Group={{ user }} | ||||
| Restart=always | ||||
| RestartSec=5s | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
							
								
								
									
										18
									
								
								ansible/roles/trmm_dev/templates/nats-server.systemd.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								ansible/roles/trmm_dev/templates/nats-server.systemd.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| [Unit] | ||||
| Description=NATS Server | ||||
| After=network.target | ||||
|  | ||||
| [Service] | ||||
| PrivateTmp=true | ||||
| Type=simple | ||||
| ExecStart=/usr/local/bin/nats-server -c {{ backend_dir }}/api/tacticalrmm/nats-rmm.conf | ||||
| ExecReload=/usr/bin/kill -s HUP $MAINPID | ||||
| ExecStop=/usr/bin/kill -s SIGINT $MAINPID | ||||
| User={{ user }} | ||||
| Group={{ user }} | ||||
| Restart=always | ||||
| RestartSec=5s | ||||
| LimitNOFILE=1000000 | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
							
								
								
									
										4
									
								
								ansible/roles/trmm_dev/templates/quasar.env.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								ansible/roles/trmm_dev/templates/quasar.env.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| DEV_URL = "http://{{ api }}:8000" | ||||
| DEV_HOST = "{{ rmm }}" | ||||
| DEV_PORT = "8080" | ||||
| USE_HTTPS = false | ||||
| @@ -1,14 +0,0 @@ | ||||
| --- | ||||
| api: 'api.example.com' | ||||
| rmm: 'rmm.example.com' | ||||
| mesh: 'mesh.example.com' | ||||
| github_username: 'changeme' | ||||
| github_email: 'changeme@example.com' | ||||
| mesh_site: 'changeme' | ||||
| mesh_user: 'changeme' | ||||
| mesh_token: 'changeme' | ||||
| db_user: 'changeme' | ||||
| db_passwd: 'changeme' | ||||
| django_secret: 'changeme' | ||||
|  | ||||
|  | ||||
| @@ -1,6 +0,0 @@ | ||||
| --- | ||||
| - hosts: "{{ target }}" | ||||
|   vars: | ||||
|     ansible_user: tactical | ||||
|   roles: | ||||
|     - trmm_dev | ||||
							
								
								
									
										20
									
								
								ansible/setup_dev.yml.example
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								ansible/setup_dev.yml.example
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| --- | ||||
| - hosts: "{{ target }}" | ||||
|   vars: | ||||
|     ansible_user: tactical | ||||
|     fullchain_src: /path/to/fullchain.pem | ||||
|     privkey_src: /path/to/privkey.pem | ||||
|     api: "api.example.com" | ||||
|     rmm: "rmm.example.com" | ||||
|     mesh: "mesh.example.com" | ||||
|     github_username: "changeme" | ||||
|     github_email: "changeme@example.com" | ||||
|     mesh_user: "changeme" | ||||
|     mesh_password: "changeme" | ||||
|     db_user: "changeme" | ||||
|     db_passwd: "changeme" | ||||
|     django_secret: "changeme" | ||||
|     django_user: "changeme" | ||||
|     django_password: "changeme" | ||||
|   roles: | ||||
|     - trmm_dev | ||||
							
								
								
									
										18
									
								
								api/tacticalrmm/accounts/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								api/tacticalrmm/accounts/utils.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| from typing import TYPE_CHECKING | ||||
| from django.conf import settings | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from django.http import HttpRequest | ||||
|     from accounts.models import User | ||||
|  | ||||
|  | ||||
| def is_root_user(*, request: "HttpRequest", user: "User") -> bool: | ||||
|     root = ( | ||||
|         hasattr(settings, "ROOT_USER") | ||||
|         and request.user != user | ||||
|         and user.username == settings.ROOT_USER | ||||
|     ) | ||||
|     demo = ( | ||||
|         getattr(settings, "DEMO", False) and request.user.username == settings.ROOT_USER | ||||
|     ) | ||||
|     return root or demo | ||||
| @@ -22,18 +22,7 @@ from .serializers import ( | ||||
|     UserSerializer, | ||||
|     UserUISerializer, | ||||
| ) | ||||
|  | ||||
|  | ||||
| def _is_root_user(request, user) -> bool: | ||||
|     root = ( | ||||
|         hasattr(settings, "ROOT_USER") | ||||
|         and request.user != user | ||||
|         and user.username == settings.ROOT_USER | ||||
|     ) | ||||
|     demo = ( | ||||
|         getattr(settings, "DEMO", False) and request.user.username == settings.ROOT_USER | ||||
|     ) | ||||
|     return root or demo | ||||
| from accounts.utils import is_root_user | ||||
|  | ||||
|  | ||||
| class CheckCreds(KnoxLoginView): | ||||
| @@ -159,7 +148,7 @@ class GetUpdateDeleteUser(APIView): | ||||
|     def put(self, request, pk): | ||||
|         user = get_object_or_404(User, pk=pk) | ||||
|  | ||||
|         if _is_root_user(request, user): | ||||
|         if is_root_user(request=request, user=user): | ||||
|             return notify_error("The root user cannot be modified from the UI") | ||||
|  | ||||
|         serializer = UserSerializer(instance=user, data=request.data, partial=True) | ||||
| @@ -170,7 +159,7 @@ class GetUpdateDeleteUser(APIView): | ||||
|  | ||||
|     def delete(self, request, pk): | ||||
|         user = get_object_or_404(User, pk=pk) | ||||
|         if _is_root_user(request, user): | ||||
|         if is_root_user(request=request, user=user): | ||||
|             return notify_error("The root user cannot be deleted from the UI") | ||||
|  | ||||
|         user.delete() | ||||
| @@ -183,7 +172,7 @@ class UserActions(APIView): | ||||
|     # reset password | ||||
|     def post(self, request): | ||||
|         user = get_object_or_404(User, pk=request.data["id"]) | ||||
|         if _is_root_user(request, user): | ||||
|         if is_root_user(request=request, user=user): | ||||
|             return notify_error("The root user cannot be modified from the UI") | ||||
|  | ||||
|         user.set_password(request.data["password"]) | ||||
| @@ -194,7 +183,7 @@ class UserActions(APIView): | ||||
|     # reset two factor token | ||||
|     def put(self, request): | ||||
|         user = get_object_or_404(User, pk=request.data["id"]) | ||||
|         if _is_root_user(request, user): | ||||
|         if is_root_user(request=request, user=user): | ||||
|             return notify_error("The root user cannot be modified from the UI") | ||||
|  | ||||
|         user.totp_key = "" | ||||
|   | ||||
| @@ -568,6 +568,12 @@ class Command(BaseCommand): | ||||
|                     check5_history.y = 1 | ||||
|                 else: | ||||
|                     check5_history.y = 0 | ||||
|                 check5_history.results = { | ||||
|                     "retcode": 0, | ||||
|                     "stdout": None, | ||||
|                     "stderr": None, | ||||
|                     "execution_time": "4.0000", | ||||
|                 } | ||||
|                 check5_history.save() | ||||
|  | ||||
|             check6 = Check() | ||||
| @@ -595,6 +601,12 @@ class Command(BaseCommand): | ||||
|                 check6_history.agent_id = agent.agent_id | ||||
|                 check6_history.x = django_now - djangotime.timedelta(minutes=i * 2) | ||||
|                 check6_history.y = 0 | ||||
|                 check6_history.results = { | ||||
|                     "retcode": 0, | ||||
|                     "stdout": None, | ||||
|                     "stderr": None, | ||||
|                     "execution_time": "4.0000", | ||||
|                 } | ||||
|                 check6_history.save() | ||||
|  | ||||
|             nla_task = AutomatedTask() | ||||
| @@ -712,6 +724,12 @@ class Command(BaseCommand): | ||||
|                 check7_history.agent_id = agent.agent_id | ||||
|                 check7_history.x = django_now - djangotime.timedelta(minutes=i * 2) | ||||
|                 check7_history.y = 0 | ||||
|                 check7_history.results = { | ||||
|                     "retcode": 0, | ||||
|                     "stdout": spooler_stdout, | ||||
|                     "stderr": None, | ||||
|                     "execution_time": "3.1337", | ||||
|                 } | ||||
|                 check7_history.save() | ||||
|  | ||||
|             if agent.plat == AgentPlat.WINDOWS: | ||||
|   | ||||
| @@ -532,12 +532,17 @@ class Agent(BaseAuditModel): | ||||
|         wait: bool = False, | ||||
|         run_on_any: bool = False, | ||||
|         history_pk: int = 0, | ||||
|         run_as_user: bool = False, | ||||
|     ) -> Any: | ||||
|  | ||||
|         from scripts.models import Script | ||||
|  | ||||
|         script = Script.objects.get(pk=scriptpk) | ||||
|  | ||||
|         # always override if set on script model | ||||
|         if script.run_as_user: | ||||
|             run_as_user = True | ||||
|  | ||||
|         parsed_args = script.parse_script_args(self, script.shell, args) | ||||
|  | ||||
|         data = { | ||||
| @@ -548,6 +553,7 @@ class Agent(BaseAuditModel): | ||||
|                 "code": script.code, | ||||
|                 "shell": script.shell, | ||||
|             }, | ||||
|             "run_as_user": run_as_user, | ||||
|         } | ||||
|  | ||||
|         if history_pk != 0: | ||||
| @@ -742,10 +748,10 @@ class Agent(BaseAuditModel): | ||||
|             cache_key = f"agent_{self.agent_id}_checks" | ||||
|  | ||||
|         elif self.policy: | ||||
|             cache_key = f"site_{self.monitoring_type}_{self.site_id}_policy_{self.policy_id}_checks" | ||||
|             cache_key = f"site_{self.monitoring_type}_{self.plat}_{self.site_id}_policy_{self.policy_id}_checks" | ||||
|  | ||||
|         else: | ||||
|             cache_key = f"site_{self.monitoring_type}_{self.site_id}_checks" | ||||
|             cache_key = f"site_{self.monitoring_type}_{self.plat}_{self.site_id}_checks" | ||||
|  | ||||
|         cached_checks = cache.get(cache_key) | ||||
|         if isinstance(cached_checks, list): | ||||
| @@ -767,10 +773,10 @@ class Agent(BaseAuditModel): | ||||
|             cache_key = f"agent_{self.agent_id}_tasks" | ||||
|  | ||||
|         elif self.policy: | ||||
|             cache_key = f"site_{self.monitoring_type}_{self.site_id}_policy_{self.policy_id}_tasks" | ||||
|             cache_key = f"site_{self.monitoring_type}_{self.plat}_{self.site_id}_policy_{self.policy_id}_tasks" | ||||
|  | ||||
|         else: | ||||
|             cache_key = f"site_{self.monitoring_type}_{self.site_id}_tasks" | ||||
|             cache_key = f"site_{self.monitoring_type}_{self.plat}_{self.site_id}_tasks" | ||||
|  | ||||
|         cached_tasks = cache.get(cache_key) | ||||
|         if isinstance(cached_tasks, list): | ||||
| @@ -778,7 +784,7 @@ class Agent(BaseAuditModel): | ||||
|         else: | ||||
|             # get agent tasks based on policies | ||||
|             tasks = Policy.get_policy_tasks(self) | ||||
|             cache.set(f"site_{self.site_id}_tasks", tasks, 600) | ||||
|             cache.set(cache_key, tasks, 600) | ||||
|             return tasks | ||||
|  | ||||
|     def _do_nats_debug(self, agent: "Agent", message: str) -> None: | ||||
| @@ -839,22 +845,22 @@ class Agent(BaseAuditModel): | ||||
|             asyncio.run( | ||||
|                 send_command_with_mesh(cmd, mesh_uri, self.mesh_node_id, shell, 0) | ||||
|             ) | ||||
|             return ("ok", False) | ||||
|             return "ok", False | ||||
|  | ||||
|         elif mode == "mesh": | ||||
|             data = {"func": "recover", "payload": {"mode": mode}} | ||||
|             if wait: | ||||
|                 r = asyncio.run(self.nats_cmd(data, timeout=20)) | ||||
|                 if r == "ok": | ||||
|                     return ("ok", False) | ||||
|                     return "ok", False | ||||
|                 else: | ||||
|                     return (str(r), True) | ||||
|                     return str(r), True | ||||
|             else: | ||||
|                 asyncio.run(self.nats_cmd(data, timeout=20, wait=False)) | ||||
|  | ||||
|             return ("ok", False) | ||||
|             return "ok", False | ||||
|  | ||||
|         return ("invalid", True) | ||||
|         return "invalid", True | ||||
|  | ||||
|     @staticmethod | ||||
|     def serialize(agent: "Agent") -> Dict[str, Any]: | ||||
|   | ||||
| @@ -90,6 +90,11 @@ class AgentTableSerializer(serializers.ModelSerializer): | ||||
|     last_seen = serializers.ReadOnlyField() | ||||
|     pending_actions_count = serializers.ReadOnlyField() | ||||
|     has_patches_pending = serializers.ReadOnlyField() | ||||
|     cpu_model = serializers.ReadOnlyField() | ||||
|     graphics = serializers.ReadOnlyField() | ||||
|     local_ips = serializers.ReadOnlyField() | ||||
|     make_model = serializers.ReadOnlyField() | ||||
|     physical_disks = serializers.ReadOnlyField() | ||||
|  | ||||
|     def get_alert_template(self, obj): | ||||
|  | ||||
| @@ -141,16 +146,18 @@ class AgentTableSerializer(serializers.ModelSerializer): | ||||
|             "plat", | ||||
|             "goarch", | ||||
|             "has_patches_pending", | ||||
|             "version", | ||||
|             "operating_system", | ||||
|             "public_ip", | ||||
|             "cpu_model", | ||||
|             "graphics", | ||||
|             "local_ips", | ||||
|             "make_model", | ||||
|             "physical_disks", | ||||
|         ] | ||||
|         depth = 2 | ||||
|  | ||||
|  | ||||
| class WinAgentSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = Agent | ||||
|         fields = "__all__" | ||||
|  | ||||
|  | ||||
| class AgentHostnameSerializer(serializers.ModelSerializer): | ||||
|     client = serializers.ReadOnlyField(source="client.name") | ||||
|     site = serializers.ReadOnlyField(source="site.name") | ||||
|   | ||||
| @@ -153,6 +153,7 @@ def run_script_email_results_task( | ||||
|     emails: list[str], | ||||
|     args: list[str] = [], | ||||
|     history_pk: int = 0, | ||||
|     run_as_user: bool = False, | ||||
| ): | ||||
|     agent = Agent.objects.get(pk=agentpk) | ||||
|     script = Script.objects.get(pk=scriptpk) | ||||
| @@ -163,6 +164,7 @@ def run_script_email_results_task( | ||||
|         timeout=nats_timeout, | ||||
|         wait=True, | ||||
|         history_pk=history_pk, | ||||
|         run_as_user=run_as_user, | ||||
|     ) | ||||
|     if r == "timeout": | ||||
|         DebugLog.error( | ||||
|   | ||||
| @@ -84,7 +84,7 @@ class TestAgentUpdate(TacticalTestCase): | ||||
|             site=self.site1, | ||||
|             monitoring_type=AgentMonType.SERVER, | ||||
|             plat=AgentPlat.WINDOWS, | ||||
|             version="2.3.0", | ||||
|             version="2.1.1", | ||||
|         ) | ||||
|         r = agent_noarch.do_update(token="", force=True) | ||||
|         self.assertEqual(r, "noarch") | ||||
| @@ -106,7 +106,7 @@ class TestAgentUpdate(TacticalTestCase): | ||||
|             site=self.site1, | ||||
|             monitoring_type=AgentMonType.SERVER, | ||||
|             plat=AgentPlat.WINDOWS, | ||||
|             version="2.3.0", | ||||
|             version="2.1.1", | ||||
|             goarch=GoArch.AMD64, | ||||
|         ) | ||||
|  | ||||
| @@ -115,7 +115,7 @@ class TestAgentUpdate(TacticalTestCase): | ||||
|             site=self.site3, | ||||
|             monitoring_type=AgentMonType.WORKSTATION, | ||||
|             plat=AgentPlat.LINUX, | ||||
|             version="2.3.0", | ||||
|             version="2.1.1", | ||||
|             goarch=GoArch.ARM32, | ||||
|         ) | ||||
|  | ||||
| @@ -193,7 +193,7 @@ class TestAgentUpdate(TacticalTestCase): | ||||
|             site=self.site2, | ||||
|             monitoring_type=AgentMonType.SERVER, | ||||
|             plat=AgentPlat.WINDOWS, | ||||
|             version="2.3.0", | ||||
|             version="2.1.1", | ||||
|             goarch=GoArch.AMD64, | ||||
|             _quantity=6, | ||||
|         ) | ||||
| @@ -215,7 +215,7 @@ class TestAgentUpdate(TacticalTestCase): | ||||
|             site=self.site2, | ||||
|             monitoring_type=AgentMonType.SERVER, | ||||
|             plat=AgentPlat.WINDOWS, | ||||
|             version="2.3.0", | ||||
|             version="2.1.1", | ||||
|             goarch=GoArch.AMD64, | ||||
|             _quantity=7, | ||||
|         ) | ||||
|   | ||||
| @@ -403,6 +403,7 @@ class TestAgentViews(TacticalTestCase): | ||||
|             "cmd": "ipconfig", | ||||
|             "shell": "cmd", | ||||
|             "timeout": 30, | ||||
|             "run_as_user": False, | ||||
|         } | ||||
|         mock_ret.return_value = "nt authority\\system" | ||||
|         r = self.client.post(url, data, format="json") | ||||
| @@ -421,13 +422,13 @@ class TestAgentViews(TacticalTestCase): | ||||
|         url = f"{base_url}/{self.agent.agent_id}/reboot/" | ||||
|  | ||||
|         # ensure we don't allow dates in past | ||||
|         data = {"datetime": "2022-07-11T01:51:11"} | ||||
|         data = {"datetime": "2022-07-11T01:51"} | ||||
|         r = self.client.patch(url, data, format="json") | ||||
|         self.assertEqual(r.status_code, 400) | ||||
|         self.assertEqual(r.data, "Date cannot be set in the past") | ||||
|  | ||||
|         # test with date in future | ||||
|         data["datetime"] = "2027-08-29T18:41:02" | ||||
|         data["datetime"] = "2027-08-29T18:41" | ||||
|         r = self.client.patch(url, data, format="json") | ||||
|         self.assertEqual(r.status_code, 200) | ||||
|         self.assertEqual(r.data["time"], "August 29, 2027 at 06:41 PM") | ||||
| @@ -538,6 +539,7 @@ class TestAgentViews(TacticalTestCase): | ||||
|             "output": "wait", | ||||
|             "args": [], | ||||
|             "timeout": 15, | ||||
|             "run_as_user": False, | ||||
|         } | ||||
|  | ||||
|         r = self.client.post(url, data, format="json") | ||||
| @@ -547,7 +549,12 @@ class TestAgentViews(TacticalTestCase): | ||||
|             raise AgentHistory.DoesNotExist | ||||
|  | ||||
|         run_script.assert_called_with( | ||||
|             scriptpk=script.pk, args=[], timeout=18, wait=True, history_pk=hist.pk | ||||
|             scriptpk=script.pk, | ||||
|             args=[], | ||||
|             timeout=18, | ||||
|             wait=True, | ||||
|             history_pk=hist.pk, | ||||
|             run_as_user=False, | ||||
|         ) | ||||
|         run_script.reset_mock() | ||||
|  | ||||
| @@ -559,6 +566,7 @@ class TestAgentViews(TacticalTestCase): | ||||
|             "timeout": 15, | ||||
|             "emailMode": "default", | ||||
|             "emails": ["admin@example.com", "bob@example.com"], | ||||
|             "run_as_user": False, | ||||
|         } | ||||
|         r = self.client.post(url, data, format="json") | ||||
|         self.assertEqual(r.status_code, 200) | ||||
| @@ -568,6 +576,7 @@ class TestAgentViews(TacticalTestCase): | ||||
|             nats_timeout=18, | ||||
|             emails=[], | ||||
|             args=["abc", "123"], | ||||
|             run_as_user=False, | ||||
|         ) | ||||
|         email_task.reset_mock() | ||||
|  | ||||
| @@ -581,6 +590,7 @@ class TestAgentViews(TacticalTestCase): | ||||
|             nats_timeout=18, | ||||
|             emails=["admin@example.com", "bob@example.com"], | ||||
|             args=["abc", "123"], | ||||
|             run_as_user=False, | ||||
|         ) | ||||
|  | ||||
|         # test fire and forget | ||||
| @@ -589,6 +599,7 @@ class TestAgentViews(TacticalTestCase): | ||||
|             "output": "forget", | ||||
|             "args": ["hello", "world"], | ||||
|             "timeout": 22, | ||||
|             "run_as_user": True, | ||||
|         } | ||||
|  | ||||
|         r = self.client.post(url, data, format="json") | ||||
| @@ -598,7 +609,11 @@ class TestAgentViews(TacticalTestCase): | ||||
|             raise AgentHistory.DoesNotExist | ||||
|  | ||||
|         run_script.assert_called_with( | ||||
|             scriptpk=script.pk, args=["hello", "world"], timeout=25, history_pk=hist.pk | ||||
|             scriptpk=script.pk, | ||||
|             args=["hello", "world"], | ||||
|             timeout=25, | ||||
|             history_pk=hist.pk, | ||||
|             run_as_user=True, | ||||
|         ) | ||||
|         run_script.reset_mock() | ||||
|  | ||||
| @@ -613,6 +628,7 @@ class TestAgentViews(TacticalTestCase): | ||||
|             "timeout": 22, | ||||
|             "custom_field": custom_field.pk, | ||||
|             "save_all_output": True, | ||||
|             "run_as_user": False, | ||||
|         } | ||||
|  | ||||
|         r = self.client.post(url, data, format="json") | ||||
| @@ -627,6 +643,7 @@ class TestAgentViews(TacticalTestCase): | ||||
|             timeout=25, | ||||
|             wait=True, | ||||
|             history_pk=hist.pk, | ||||
|             run_as_user=False, | ||||
|         ) | ||||
|         run_script.reset_mock() | ||||
|  | ||||
| @@ -644,6 +661,7 @@ class TestAgentViews(TacticalTestCase): | ||||
|             "timeout": 22, | ||||
|             "custom_field": custom_field.pk, | ||||
|             "save_all_output": False, | ||||
|             "run_as_user": False, | ||||
|         } | ||||
|  | ||||
|         r = self.client.post(url, data, format="json") | ||||
| @@ -658,6 +676,7 @@ class TestAgentViews(TacticalTestCase): | ||||
|             timeout=25, | ||||
|             wait=True, | ||||
|             history_pk=hist.pk, | ||||
|             run_as_user=False, | ||||
|         ) | ||||
|         run_script.reset_mock() | ||||
|  | ||||
| @@ -677,6 +696,7 @@ class TestAgentViews(TacticalTestCase): | ||||
|             "timeout": 22, | ||||
|             "custom_field": custom_field.pk, | ||||
|             "save_all_output": False, | ||||
|             "run_as_user": False, | ||||
|         } | ||||
|  | ||||
|         r = self.client.post(url, data, format="json") | ||||
| @@ -691,6 +711,7 @@ class TestAgentViews(TacticalTestCase): | ||||
|             timeout=25, | ||||
|             wait=True, | ||||
|             history_pk=hist.pk, | ||||
|             run_as_user=False, | ||||
|         ) | ||||
|         run_script.reset_mock() | ||||
|  | ||||
| @@ -707,6 +728,7 @@ class TestAgentViews(TacticalTestCase): | ||||
|             "output": "note", | ||||
|             "args": ["hello", "world"], | ||||
|             "timeout": 22, | ||||
|             "run_as_user": False, | ||||
|         } | ||||
|  | ||||
|         r = self.client.post(url, data, format="json") | ||||
| @@ -721,6 +743,7 @@ class TestAgentViews(TacticalTestCase): | ||||
|             timeout=25, | ||||
|             wait=True, | ||||
|             history_pk=hist.pk, | ||||
|             run_as_user=False, | ||||
|         ) | ||||
|         run_script.reset_mock() | ||||
|  | ||||
|   | ||||
| @@ -12,6 +12,7 @@ from django.shortcuts import get_object_or_404 | ||||
| from django.utils import timezone as djangotime | ||||
| from meshctrl.utils import get_login_token | ||||
| from packaging import version as pyver | ||||
| from rest_framework import serializers | ||||
| from rest_framework.decorators import api_view, permission_classes | ||||
| from rest_framework.exceptions import PermissionDenied | ||||
| from rest_framework.permissions import IsAuthenticated | ||||
| @@ -29,6 +30,7 @@ from scripts.models import Script | ||||
| from scripts.tasks import handle_bulk_command_task, handle_bulk_script_task | ||||
| from tacticalrmm.constants import ( | ||||
|     AGENT_DEFER, | ||||
|     AGENT_TABLE_DEFER, | ||||
|     AGENT_STATUS_OFFLINE, | ||||
|     AGENT_STATUS_ONLINE, | ||||
|     AgentHistoryType, | ||||
| @@ -114,7 +116,7 @@ class GetAgents(APIView): | ||||
|                 Agent.objects.filter_by_role(request.user)  # type: ignore | ||||
|                 .filter(monitoring_type_filter) | ||||
|                 .filter(client_site_filter) | ||||
|                 .defer(*AGENT_DEFER) | ||||
|                 .defer(*AGENT_TABLE_DEFER) | ||||
|                 .select_related( | ||||
|                     "site__server_policy", | ||||
|                     "site__workstation_policy", | ||||
| @@ -166,6 +168,22 @@ class GetAgents(APIView): | ||||
| class GetUpdateDeleteAgent(APIView): | ||||
|     permission_classes = [IsAuthenticated, AgentPerms] | ||||
|  | ||||
|     class InputSerializer(serializers.ModelSerializer): | ||||
|         class Meta: | ||||
|             model = Agent | ||||
|             fields = [ | ||||
|                 "monitoring_type", | ||||
|                 "description", | ||||
|                 "overdue_email_alert", | ||||
|                 "overdue_text_alert", | ||||
|                 "overdue_dashboard_alert", | ||||
|                 "offline_time", | ||||
|                 "overdue_time", | ||||
|                 "check_interval", | ||||
|                 "time_zone", | ||||
|                 "site", | ||||
|             ] | ||||
|  | ||||
|     # get agent details | ||||
|     def get(self, request, agent_id): | ||||
|         agent = get_object_or_404(Agent, agent_id=agent_id) | ||||
| @@ -175,9 +193,9 @@ class GetUpdateDeleteAgent(APIView): | ||||
|     def put(self, request, agent_id): | ||||
|         agent = get_object_or_404(Agent, agent_id=agent_id) | ||||
|  | ||||
|         a_serializer = AgentSerializer(instance=agent, data=request.data, partial=True) | ||||
|         a_serializer.is_valid(raise_exception=True) | ||||
|         a_serializer.save() | ||||
|         s = self.InputSerializer(instance=agent, data=request.data, partial=True) | ||||
|         s.is_valid(raise_exception=True) | ||||
|         s.save() | ||||
|  | ||||
|         if "winupdatepolicy" in request.data.keys(): | ||||
|             policy = agent.winupdatepolicy.get()  # type: ignore | ||||
| @@ -415,6 +433,7 @@ def send_raw_cmd(request, agent_id): | ||||
|             "command": request.data["cmd"], | ||||
|             "shell": shell, | ||||
|         }, | ||||
|         "run_as_user": request.data["run_as_user"], | ||||
|     } | ||||
|  | ||||
|     hist = AgentHistory.objects.create( | ||||
| @@ -459,7 +478,7 @@ class Reboot(APIView): | ||||
|             return notify_error(f"Not currently implemented for {agent.plat}") | ||||
|  | ||||
|         try: | ||||
|             obj = dt.datetime.strptime(request.data["datetime"], "%Y-%m-%dT%H:%M:%S") | ||||
|             obj = dt.datetime.strptime(request.data["datetime"], "%Y-%m-%dT%H:%M") | ||||
|         except Exception: | ||||
|             return notify_error("Invalid date") | ||||
|  | ||||
| @@ -691,6 +710,7 @@ def run_script(request, agent_id): | ||||
|     script = get_object_or_404(Script, pk=request.data["script"]) | ||||
|     output = request.data["output"] | ||||
|     args = request.data["args"] | ||||
|     run_as_user: bool = request.data["run_as_user"] | ||||
|     req_timeout = int(request.data["timeout"]) + 3 | ||||
|  | ||||
|     AuditLog.audit_script_run( | ||||
| @@ -715,6 +735,7 @@ def run_script(request, agent_id): | ||||
|             timeout=req_timeout, | ||||
|             wait=True, | ||||
|             history_pk=history_pk, | ||||
|             run_as_user=run_as_user, | ||||
|         ) | ||||
|         return Response(r) | ||||
|  | ||||
| @@ -728,6 +749,7 @@ def run_script(request, agent_id): | ||||
|             nats_timeout=req_timeout, | ||||
|             emails=emails, | ||||
|             args=args, | ||||
|             run_as_user=run_as_user, | ||||
|         ) | ||||
|     elif output == "collector": | ||||
|         from core.models import CustomField | ||||
| @@ -738,6 +760,7 @@ def run_script(request, agent_id): | ||||
|             timeout=req_timeout, | ||||
|             wait=True, | ||||
|             history_pk=history_pk, | ||||
|             run_as_user=run_as_user, | ||||
|         ) | ||||
|  | ||||
|         custom_field = CustomField.objects.get(pk=request.data["custom_field"]) | ||||
| @@ -766,13 +789,18 @@ def run_script(request, agent_id): | ||||
|             timeout=req_timeout, | ||||
|             wait=True, | ||||
|             history_pk=history_pk, | ||||
|             run_as_user=run_as_user, | ||||
|         ) | ||||
|  | ||||
|         Note.objects.create(agent=agent, user=request.user, note=r) | ||||
|         return Response(r) | ||||
|     else: | ||||
|         agent.run_script( | ||||
|             scriptpk=script.pk, args=args, timeout=req_timeout, history_pk=history_pk | ||||
|             scriptpk=script.pk, | ||||
|             args=args, | ||||
|             timeout=req_timeout, | ||||
|             history_pk=history_pk, | ||||
|             run_as_user=run_as_user, | ||||
|         ) | ||||
|  | ||||
|     return Response(f"{script.name} will now be run on {agent.hostname}") | ||||
| @@ -907,7 +935,7 @@ def bulk(request): | ||||
|             shell, | ||||
|             request.data["timeout"], | ||||
|             request.user.username[:50], | ||||
|             run_on_offline=request.data["offlineAgents"], | ||||
|             request.data["run_as_user"], | ||||
|         ) | ||||
|         return Response(f"Command will now be run on {len(agents)} agents") | ||||
|  | ||||
| @@ -919,6 +947,7 @@ def bulk(request): | ||||
|             request.data["args"], | ||||
|             request.data["timeout"], | ||||
|             request.user.username[:50], | ||||
|             request.data["run_as_user"], | ||||
|         ) | ||||
|         return Response(f"{script.name} will now be run on {len(agents)} agents") | ||||
|  | ||||
|   | ||||
| @@ -469,6 +469,7 @@ class Alert(models.Model): | ||||
|                 wait=True, | ||||
|                 full=True, | ||||
|                 run_on_any=True, | ||||
|                 run_as_user=False, | ||||
|             ) | ||||
|  | ||||
|             # command was successful | ||||
| @@ -591,6 +592,7 @@ class Alert(models.Model): | ||||
|                 wait=True, | ||||
|                 full=True, | ||||
|                 run_on_any=True, | ||||
|                 run_as_user=False, | ||||
|             ) | ||||
|  | ||||
|             # command was successful | ||||
|   | ||||
| @@ -1424,6 +1424,7 @@ class TestAlertTasks(TacticalTestCase): | ||||
|             "timeout": 30, | ||||
|             "script_args": [], | ||||
|             "payload": {"code": failure_action.code, "shell": failure_action.shell}, | ||||
|             "run_as_user": False, | ||||
|         } | ||||
|  | ||||
|         nats_cmd.assert_called_with(data, timeout=30, wait=True) | ||||
| @@ -1452,6 +1453,7 @@ class TestAlertTasks(TacticalTestCase): | ||||
|             "timeout": 35, | ||||
|             "script_args": ["nice_arg"], | ||||
|             "payload": {"code": resolved_action.code, "shell": resolved_action.shell}, | ||||
|             "run_as_user": False, | ||||
|         } | ||||
|  | ||||
|         nats_cmd.assert_called_with(data, timeout=35, wait=True) | ||||
|   | ||||
| @@ -7,8 +7,11 @@ from rest_framework.serializers import ( | ||||
| from agents.serializers import AgentHostnameSerializer | ||||
| from autotasks.models import TaskResult | ||||
| from checks.models import CheckResult | ||||
| from clients.models import Client | ||||
| from clients.serializers import ClientMinimumSerializer, SiteMinimumSerializer | ||||
| from clients.models import Client, Site | ||||
| from clients.serializers import ( | ||||
|     ClientMinimumSerializer, | ||||
|     SiteMinimumSerializer, | ||||
| ) | ||||
| from winupdate.serializers import WinUpdatePolicySerializer | ||||
|  | ||||
| from .models import Policy | ||||
| @@ -85,11 +88,29 @@ class PolicyRelatedSerializer(ModelSerializer): | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class PolicyOverviewSiteSerializer(ModelSerializer): | ||||
|     workstation_policy = PolicySerializer(read_only=True) | ||||
|     server_policy = PolicySerializer(read_only=True) | ||||
|  | ||||
|     class Meta: | ||||
|         model = Site | ||||
|         fields = ("pk", "name", "workstation_policy", "server_policy") | ||||
|  | ||||
|  | ||||
| class PolicyOverviewSerializer(ModelSerializer): | ||||
|     sites = SerializerMethodField() | ||||
|     workstation_policy = PolicySerializer(read_only=True) | ||||
|     server_policy = PolicySerializer(read_only=True) | ||||
|  | ||||
|     def get_sites(self, obj): | ||||
|         return PolicyOverviewSiteSerializer( | ||||
|             obj.filtered_sites, | ||||
|             many=True, | ||||
|         ).data | ||||
|  | ||||
|     class Meta: | ||||
|         model = Client | ||||
|         fields = ("pk", "name", "sites", "workstation_policy", "server_policy") | ||||
|         depth = 2 | ||||
|  | ||||
|  | ||||
| class PolicyCheckStatusSerializer(ModelSerializer): | ||||
|   | ||||
| @@ -2,8 +2,9 @@ from itertools import cycle | ||||
| from unittest.mock import patch | ||||
|  | ||||
| from model_bakery import baker, seq | ||||
|  | ||||
| from django.db.models import Prefetch | ||||
| from agents.models import Agent | ||||
| from clients.models import Site | ||||
| from core.utils import get_core_settings | ||||
| from tacticalrmm.constants import AgentMonType, TaskSyncStatus | ||||
| from tacticalrmm.test import TacticalTestCase | ||||
| @@ -186,7 +187,17 @@ class TestPolicyViews(TacticalTestCase): | ||||
|  | ||||
|         baker.make("clients.Site", client=cycle(clients), _quantity=3) | ||||
|         resp = self.client.get(url, format="json") | ||||
|         clients = Client.objects.all() | ||||
|         clients = Client.objects.select_related( | ||||
|             "workstation_policy", "server_policy" | ||||
|         ).prefetch_related( | ||||
|             Prefetch( | ||||
|                 "sites", | ||||
|                 queryset=Site.objects.select_related( | ||||
|                     "workstation_policy", "server_policy" | ||||
|                 ), | ||||
|                 to_attr="filtered_sites", | ||||
|             ) | ||||
|         ) | ||||
|         serializer = PolicyOverviewSerializer(clients, many=True) | ||||
|  | ||||
|         self.assertEqual(resp.status_code, 200) | ||||
|   | ||||
| @@ -7,10 +7,11 @@ from rest_framework.views import APIView | ||||
| from agents.models import Agent | ||||
| from autotasks.models import TaskResult | ||||
| from checks.models import CheckResult | ||||
| from clients.models import Client | ||||
| from clients.models import Client, Site | ||||
| from tacticalrmm.permissions import _has_perm_on_client, _has_perm_on_site | ||||
| from winupdate.models import WinUpdatePolicy | ||||
| from winupdate.serializers import WinUpdatePolicySerializer | ||||
| from django.db.models import Prefetch | ||||
|  | ||||
| from .models import Policy | ||||
| from .permissions import AutomationPolicyPerms | ||||
| @@ -108,8 +109,18 @@ class PolicyCheck(APIView): | ||||
| class OverviewPolicy(APIView): | ||||
|     def get(self, request): | ||||
|  | ||||
|         clients = Client.objects.filter_by_role(request.user).select_related( | ||||
|             "workstation_policy", "server_policy" | ||||
|         clients = ( | ||||
|             Client.objects.filter_by_role(request.user) | ||||
|             .select_related("workstation_policy", "server_policy") | ||||
|             .prefetch_related( | ||||
|                 Prefetch( | ||||
|                     "sites", | ||||
|                     queryset=Site.objects.select_related( | ||||
|                         "workstation_policy", "server_policy" | ||||
|                     ), | ||||
|                     to_attr="filtered_sites", | ||||
|                 ) | ||||
|             ) | ||||
|         ) | ||||
|         return Response(PolicyOverviewSerializer(clients, many=True).data) | ||||
|  | ||||
|   | ||||
| @@ -241,6 +241,7 @@ class TaskGOGetSerializer(serializers.ModelSerializer): | ||||
|                         ), | ||||
|                         "shell": script.shell, | ||||
|                         "timeout": action["timeout"], | ||||
|                         "run_as_user": script.run_as_user, | ||||
|                     } | ||||
|                 ) | ||||
|         if actions_to_remove: | ||||
|   | ||||
| @@ -70,11 +70,11 @@ class Client(BaseAuditModel): | ||||
|             sites = self.sites.all() | ||||
|             if old_client.workstation_policy != self.workstation_policy: | ||||
|                 for site in sites: | ||||
|                     cache.delete_many_pattern(f"site_workstation_{site.pk}_*") | ||||
|                     cache.delete_many_pattern(f"site_workstation_*{site.pk}_*") | ||||
|  | ||||
|             if old_client.server_policy != self.server_policy: | ||||
|                 for site in sites: | ||||
|                     cache.delete_many_pattern(f"site_server_{site.pk}_*") | ||||
|                     cache.delete_many_pattern(f"site_server_*{site.pk}_*") | ||||
|  | ||||
|     class Meta: | ||||
|         ordering = ("name",) | ||||
| @@ -145,10 +145,10 @@ class Site(BaseAuditModel): | ||||
|                 cache_agents_alert_template.delay() | ||||
|  | ||||
|             if old_site.workstation_policy != self.workstation_policy: | ||||
|                 cache.delete_many_pattern(f"site_workstation_{self.pk}_*") | ||||
|                 cache.delete_many_pattern(f"site_workstation_*{self.pk}_*") | ||||
|  | ||||
|             if old_site.server_policy != self.server_policy: | ||||
|                 cache.delete_many_pattern(f"site_server_{self.pk}_*") | ||||
|                 cache.delete_many_pattern(f"site_server_*{self.pk}_*") | ||||
|  | ||||
|     class Meta: | ||||
|         ordering = ("name",) | ||||
|   | ||||
| @@ -39,9 +39,8 @@ If (Get-Service $serviceName -ErrorAction SilentlyContinue) { | ||||
|         $DefenderStatus = Get-MpComputerStatus | select  AntivirusEnabled | ||||
|         if ($DefenderStatus -match "True") { | ||||
|             Add-MpPreference -ExclusionPath 'C:\Program Files\TacticalAgent\*' | ||||
|             Add-MpPreference -ExclusionPath 'C:\Windows\Temp\winagent-v*.exe' | ||||
|             Add-MpPreference -ExclusionPath 'C:\Program Files\Mesh Agent\*' | ||||
|             Add-MpPreference -ExclusionPath 'C:\Windows\Temp\trmm*\*' | ||||
|             Add-MpPreference -ExclusionPath 'C:\ProgramData\TacticalRMM\*' | ||||
|         } | ||||
|     } | ||||
|     Catch { | ||||
|   | ||||
| @@ -182,10 +182,10 @@ class CoreSettings(BaseAuditModel): | ||||
|         test: bool = False, | ||||
|     ) -> tuple[str, bool]: | ||||
|         if test and not self.email_is_configured: | ||||
|             return ("There needs to be at least one email recipient configured", False) | ||||
|             return "There needs to be at least one email recipient configured", False | ||||
|         # return since email must be configured to continue | ||||
|         elif not self.email_is_configured: | ||||
|             return ("SMTP messaging not configured.", False) | ||||
|             return "SMTP messaging not configured.", False | ||||
|  | ||||
|         # override email from if alert_template is passed and is set | ||||
|         if alert_template and alert_template.email_from: | ||||
| @@ -199,7 +199,7 @@ class CoreSettings(BaseAuditModel): | ||||
|         elif self.email_alert_recipients: | ||||
|             email_recipients = ", ".join(cast(List[str], self.email_alert_recipients)) | ||||
|         else: | ||||
|             return ("There needs to be at least one email recipient configured", False) | ||||
|             return "There needs to be at least one email recipient configured", False | ||||
|  | ||||
|         try: | ||||
|             msg = EmailMessage() | ||||
| @@ -226,12 +226,12 @@ class CoreSettings(BaseAuditModel): | ||||
|         except Exception as e: | ||||
|             DebugLog.error(message=f"Sending email failed with error: {e}") | ||||
|             if test: | ||||
|                 return (str(e), False) | ||||
|                 return str(e), False | ||||
|  | ||||
|         if test: | ||||
|             return ("Email test ok!", True) | ||||
|             return "Email test ok!", True | ||||
|  | ||||
|         return ("ok", True) | ||||
|         return "ok", True | ||||
|  | ||||
|     def send_sms( | ||||
|         self, | ||||
| @@ -240,7 +240,7 @@ class CoreSettings(BaseAuditModel): | ||||
|         test: bool = False, | ||||
|     ) -> tuple[str, bool]: | ||||
|         if not self.sms_is_configured: | ||||
|             return ("Sms alerting is not setup correctly.", False) | ||||
|             return "Sms alerting is not setup correctly.", False | ||||
|  | ||||
|         # override email recipients if alert_template is passed and is set | ||||
|         if alert_template and alert_template.text_recipients: | ||||
| @@ -248,7 +248,7 @@ class CoreSettings(BaseAuditModel): | ||||
|         elif self.sms_alert_recipients: | ||||
|             text_recipients = cast(List[str], self.sms_alert_recipients) | ||||
|         else: | ||||
|             return ("No sms recipients found", False) | ||||
|             return "No sms recipients found", False | ||||
|  | ||||
|         tw_client = TwClient(self.twilio_account_sid, self.twilio_auth_token) | ||||
|         for num in text_recipients: | ||||
| @@ -257,12 +257,12 @@ class CoreSettings(BaseAuditModel): | ||||
|             except TwilioRestException as e: | ||||
|                 DebugLog.error(message=f"SMS failed to send: {e}") | ||||
|                 if test: | ||||
|                     return (str(e), False) | ||||
|                     return str(e), False | ||||
|  | ||||
|         if test: | ||||
|             return ("SMS Test sent successfully!", True) | ||||
|             return "SMS Test sent successfully!", True | ||||
|  | ||||
|         return ("ok", True) | ||||
|         return "ok", True | ||||
|  | ||||
|     @staticmethod | ||||
|     def serialize(core): | ||||
|   | ||||
| @@ -174,7 +174,7 @@ def _get_failing_data(agents: "QuerySet[Any]") -> Dict[str, bool]: | ||||
|                     and task.task_result.status == TaskStatus.FAILING | ||||
|                     and task.alert_severity == AlertSeverity.WARNING | ||||
|                 ): | ||||
|                     data["warning"] | ||||
|                     data["warning"] = True | ||||
|  | ||||
|     return data | ||||
|  | ||||
|   | ||||
| @@ -7,33 +7,33 @@ channels_redis==3.4.1 | ||||
| chardet==4.0.0 | ||||
| cryptography==37.0.4 | ||||
| daphne==3.0.2 | ||||
| Django==4.0.6 | ||||
| Django==4.1 | ||||
| django-cors-headers==3.13.0 | ||||
| django-ipware==4.0.2 | ||||
| django-rest-knox==4.2.0 | ||||
| djangorestframework==3.13.1 | ||||
| future==0.18.2 | ||||
| msgpack==1.0.4 | ||||
| nats-py==2.1.4 | ||||
| nats-py==2.1.7 | ||||
| psutil==5.9.1 | ||||
| psycopg2-binary==2.9.3 | ||||
| pycparser==2.21 | ||||
| pycryptodome==3.15.0 | ||||
| pyotp==2.6.0 | ||||
| pyparsing==3.0.9 | ||||
| pytz==2022.1 | ||||
| pytz==2022.2.1 | ||||
| qrcode==7.3.1 | ||||
| redis==4.3.4 | ||||
| hiredis==2.0.0 | ||||
| requests==2.28.1 | ||||
| six==1.16.0 | ||||
| sqlparse==0.4.2 | ||||
| twilio==7.12.0 | ||||
| twilio==7.12.1 | ||||
| urllib3==1.26.11 | ||||
| uWSGI==2.0.20 | ||||
| validators==0.20.0 | ||||
| vine==5.0.0 | ||||
| websockets==10.3 | ||||
| zipp==3.8.1 | ||||
| drf_spectacular==0.22.1 | ||||
| drf-spectacular==0.23.1 | ||||
| meshctrl==0.1.15 | ||||
|   | ||||
| @@ -0,0 +1,18 @@ | ||||
| # Generated by Django 4.0.6 on 2022-07-30 21:05 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('scripts', '0017_auto_20220311_0100'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='script', | ||||
|             name='run_as_user', | ||||
|             field=models.BooleanField(default=False), | ||||
|         ), | ||||
|     ] | ||||
| @@ -40,6 +40,7 @@ class Script(BaseAuditModel): | ||||
|     supported_platforms = ArrayField( | ||||
|         models.CharField(max_length=20), null=True, blank=True, default=list | ||||
|     ) | ||||
|     run_as_user = models.BooleanField(default=False) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|   | ||||
| @@ -20,6 +20,7 @@ class ScriptTableSerializer(ModelSerializer): | ||||
|             "filename", | ||||
|             "hidden", | ||||
|             "supported_platforms", | ||||
|             "run_as_user", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| @@ -43,16 +44,17 @@ class ScriptSerializer(ModelSerializer): | ||||
|             "filename", | ||||
|             "hidden", | ||||
|             "supported_platforms", | ||||
|             "run_as_user", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class ScriptCheckSerializer(ModelSerializer): | ||||
|     code = ReadOnlyField() | ||||
|     script_hash = ReadOnlyField | ||||
|     script_hash = ReadOnlyField() | ||||
|  | ||||
|     class Meta: | ||||
|         model = Script | ||||
|         fields = ["code", "shell", "script_hash"] | ||||
|         fields = ["code", "shell", "run_as_user", "script_hash"] | ||||
|  | ||||
|  | ||||
| class ScriptSnippetSerializer(ModelSerializer): | ||||
|   | ||||
| @@ -9,7 +9,12 @@ from tacticalrmm.constants import AgentHistoryType | ||||
|  | ||||
| @app.task | ||||
| def handle_bulk_command_task( | ||||
|     agentpks, cmd, shell, timeout, username, run_on_offline=False | ||||
|     agentpks: list[int], | ||||
|     cmd: str, | ||||
|     shell: str, | ||||
|     timeout, | ||||
|     username, | ||||
|     run_as_user: bool = False, | ||||
| ) -> None: | ||||
|     nats_data = { | ||||
|         "func": "rawcmd", | ||||
| @@ -18,7 +23,9 @@ def handle_bulk_command_task( | ||||
|             "command": cmd, | ||||
|             "shell": shell, | ||||
|         }, | ||||
|         "run_as_user": run_as_user, | ||||
|     } | ||||
|     agent: "Agent" | ||||
|     for agent in Agent.objects.filter(pk__in=agentpks): | ||||
|         hist = AgentHistory.objects.create( | ||||
|             agent=agent, | ||||
| @@ -33,9 +40,15 @@ def handle_bulk_command_task( | ||||
|  | ||||
| @app.task | ||||
| def handle_bulk_script_task( | ||||
|     scriptpk: int, agentpks: List[int], args: List[str], timeout: int, username: str | ||||
|     scriptpk: int, | ||||
|     agentpks: List[int], | ||||
|     args: List[str], | ||||
|     timeout: int, | ||||
|     username: str, | ||||
|     run_as_user: bool = False, | ||||
| ) -> None: | ||||
|     script = Script.objects.get(pk=scriptpk) | ||||
|     agent: "Agent" | ||||
|     for agent in Agent.objects.filter(pk__in=agentpks): | ||||
|         hist = AgentHistory.objects.create( | ||||
|             agent=agent, | ||||
| @@ -44,5 +57,9 @@ def handle_bulk_script_task( | ||||
|             username=username, | ||||
|         ) | ||||
|         agent.run_script( | ||||
|             scriptpk=script.pk, args=args, timeout=timeout, history_pk=hist.pk | ||||
|             scriptpk=script.pk, | ||||
|             args=args, | ||||
|             timeout=timeout, | ||||
|             history_pk=hist.pk, | ||||
|             run_as_user=run_as_user, | ||||
|         ) | ||||
|   | ||||
| @@ -145,6 +145,7 @@ class TestScriptViews(TacticalTestCase): | ||||
|             "timeout": 90, | ||||
|             "args": [], | ||||
|             "shell": ScriptShell.POWERSHELL, | ||||
|             "run_as_user": False, | ||||
|         } | ||||
|  | ||||
|         resp = self.client.post(url, data, format="json") | ||||
|   | ||||
| @@ -160,6 +160,7 @@ class TestScript(APIView): | ||||
|                 "code": Script.replace_with_snippets(request.data["code"]), | ||||
|                 "shell": request.data["shell"], | ||||
|             }, | ||||
|             "run_as_user": request.data["run_as_user"], | ||||
|         } | ||||
|  | ||||
|         r = asyncio.run( | ||||
|   | ||||
| @@ -26,7 +26,7 @@ def process_nats_response(data: Union[str, Dict]) -> Tuple[bool, bool, str]: | ||||
|         else "timeout" | ||||
|     ) | ||||
|  | ||||
|     return (success, natserror, errormsg) | ||||
|     return success, natserror, errormsg | ||||
|  | ||||
|  | ||||
| class GetServices(APIView): | ||||
|   | ||||
| @@ -61,4 +61,4 @@ class APIAuthentication(BaseAuthentication): | ||||
|         if apikey.expiration and apikey.expiration < djangotime.now(): | ||||
|             raise exceptions.AuthenticationFailed(_("The token as expired.")) | ||||
|  | ||||
|         return (apikey.user, apikey.key) | ||||
|         return apikey.user, apikey.key | ||||
|   | ||||
| @@ -240,6 +240,14 @@ AGENT_DEFER = ( | ||||
|     "modified_time", | ||||
| ) | ||||
|  | ||||
| AGENT_TABLE_DEFER = ( | ||||
|     "services", | ||||
|     "created_by", | ||||
|     "created_time", | ||||
|     "modified_by", | ||||
|     "modified_time", | ||||
| ) | ||||
|  | ||||
| ONLINE_AGENTS = ( | ||||
|     "pk", | ||||
|     "agent_id", | ||||
|   | ||||
| @@ -19,7 +19,7 @@ def get_certs() -> tuple[str, str]: | ||||
|         cert_file = settings.CERT_FILE | ||||
|         key_file = settings.KEY_FILE | ||||
|  | ||||
|     return (cert_file, key_file) | ||||
|     return cert_file, key_file | ||||
|  | ||||
|  | ||||
| def notify_error(msg: str) -> Response: | ||||
| @@ -33,7 +33,7 @@ def get_nats_ports() -> tuple[int, int]: | ||||
|     nats_standard_port = getattr(settings, "NATS_STANDARD_PORT", 4222) | ||||
|     nats_websocket_port = getattr(settings, "NATS_WEBSOCKET_PORT", 9235) | ||||
|  | ||||
|     return (nats_standard_port, nats_websocket_port) | ||||
|     return nats_standard_port, nats_websocket_port | ||||
|  | ||||
|  | ||||
| def date_is_in_past(*, datetime_obj: "datetime", agent_tz: str) -> bool: | ||||
|   | ||||
| @@ -17,26 +17,26 @@ LINUX_AGENT_SCRIPT = BASE_DIR / "core" / "agent_linux.sh" | ||||
| AUTH_USER_MODEL = "accounts.User" | ||||
|  | ||||
| # latest release | ||||
| TRMM_VERSION = "0.14.3" | ||||
| TRMM_VERSION = "0.14.7" | ||||
|  | ||||
| # https://github.com/amidaware/tacticalrmm-web | ||||
| WEB_VERSION = "0.100.6" | ||||
| WEB_VERSION = "0.100.9" | ||||
|  | ||||
| # bump this version everytime vue code is changed | ||||
| # to alert user they need to manually refresh their browser | ||||
| APP_VER = "0.0.167" | ||||
| APP_VER = "0.0.170" | ||||
|  | ||||
| # https://github.com/amidaware/rmmagent | ||||
| LATEST_AGENT_VER = "2.1.2" | ||||
| LATEST_AGENT_VER = "2.3.0" | ||||
|  | ||||
| MESH_VER = "1.0.60" | ||||
| MESH_VER = "1.0.72" | ||||
|  | ||||
| NATS_SERVER_VER = "2.8.4" | ||||
|  | ||||
| # for the update script, bump when need to recreate venv | ||||
| PIP_VER = "31" | ||||
| PIP_VER = "32" | ||||
|  | ||||
| SETUPTOOLS_VER = "62.6.0" | ||||
| SETUPTOOLS_VER = "65.2.0" | ||||
| WHEEL_VER = "0.37.1" | ||||
|  | ||||
| AGENT_BASE_URL = "https://agents.tacticalrmm.com" | ||||
|   | ||||
| @@ -35,8 +35,7 @@ nginxdefaultconf='/etc/nginx/nginx.conf' | ||||
| # increase default nginx worker connections | ||||
| /bin/bash -c "sed -i 's/worker_connections.*/worker_connections ${WORKER_CONNECTIONS};/g' $nginxdefaultconf" | ||||
|  | ||||
| sed -i '1s/^/worker_rlimit_nofile 1000000;\ | ||||
| /' $nginxdefaultconf | ||||
| grep -q -e 'worker_rlimit_nofile' "${nginxdefaultconf}" || sed -i -e '/worker_processes.*/a\' -e 'worker_rlimit_nofile 1000000;' "${nginxdefaultconf}" | ||||
|  | ||||
| if [[ $DEV -eq 1 ]]; then | ||||
|     API_NGINX=" | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| # creates python virtual env | ||||
| FROM python:3.10.4-slim AS CREATE_VENV_STAGE | ||||
| FROM python:3.10.6-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.10.4-slim AS GET_SCRIPTS_STAGE | ||||
| FROM python:3.10.6-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.10.4-slim | ||||
| FROM python:3.10.6-slim | ||||
|  | ||||
| # set env variables | ||||
| ENV VIRTUAL_ENV /opt/venv | ||||
|   | ||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							| @@ -13,11 +13,11 @@ require ( | ||||
| 	google.golang.org/protobuf v1.28.0 // indirect | ||||
| ) | ||||
|  | ||||
| require github.com/sirupsen/logrus v1.8.1 | ||||
| require github.com/sirupsen/logrus v1.9.0 | ||||
|  | ||||
| require ( | ||||
| 	github.com/nats-io/nkeys v0.3.0 // indirect | ||||
| 	github.com/nats-io/nuid v1.0.1 // indirect | ||||
| 	golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect | ||||
| 	golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect | ||||
| 	golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect | ||||
| ) | ||||
|   | ||||
							
								
								
									
										18
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								go.sum
									
									
									
									
									
								
							| @@ -1,3 +1,4 @@ | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= | ||||
| @@ -26,10 +27,11 @@ github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= | ||||
| github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= | ||||
| github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= | ||||
| github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= | ||||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||
| github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= | ||||
| github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | ||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= | ||||
| github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= | ||||
| github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= | ||||
| @@ -39,10 +41,9 @@ golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm | ||||
| golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= | ||||
| golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||
| golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8= | ||||
| golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= | ||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= | ||||
| @@ -52,3 +53,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 | ||||
| google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||
| google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= | ||||
| 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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| SCRIPT_VERSION="66" | ||||
| SCRIPT_VERSION="67" | ||||
| 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.10.4' | ||||
| PYTHON_VER='3.10.6' | ||||
| SETTINGS_FILE='/rmm/api/tacticalrmm/tacticalrmm/settings.py' | ||||
|  | ||||
| TMP_FILE=$(mktemp -p "" "rmminstall_XXXXXXXXXX") | ||||
|   | ||||
							
								
								
									
										2
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								main.go
									
									
									
									
									
								
							| @@ -12,7 +12,7 @@ import ( | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	version = "3.1.0" | ||||
| 	version = "3.2.0" | ||||
| 	log     = logrus.New() | ||||
| ) | ||||
|  | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -74,24 +74,15 @@ func Svc(logger *logrus.Logger, cfg string) { | ||||
| 					stmt := ` | ||||
| 						UPDATE agents_agent | ||||
| 						SET hostname=$1, operating_system=$2, | ||||
| 						plat=$3, total_ram=$4, boot_time=$5, needs_reboot=$6, logged_in_username=$7 | ||||
| 						WHERE agents_agent.agent_id=$8;` | ||||
| 						plat=$3, total_ram=$4, boot_time=$5, needs_reboot=$6, logged_in_username=$7, goarch=$8 | ||||
| 						WHERE agents_agent.agent_id=$9;` | ||||
|  | ||||
| 					logger.Debugln("Info", r) | ||||
| 					_, err = db.Exec(stmt, r.Hostname, r.OS, r.Platform, r.TotalRAM, r.BootTime, r.RebootNeeded, r.Username, r.Agentid) | ||||
| 					_, err = db.Exec(stmt, r.Hostname, r.OS, r.Platform, r.TotalRAM, r.BootTime, r.RebootNeeded, r.Username, r.GoArch, r.Agentid) | ||||
| 					if err != nil { | ||||
| 						logger.Errorln(err) | ||||
| 					} | ||||
|  | ||||
| 					// TODO add this to main stmt once agent 2.0.0 has been out for a while | ||||
| 					if r.GoArch != "" { | ||||
| 						stmt = `UPDATE agents_agent SET goarch=$1 WHERE agents_agent.agent_id=$2;` | ||||
| 						_, err = db.Exec(stmt, r.GoArch, r.Agentid) | ||||
| 						if err != nil { | ||||
| 							logger.Errorln(err) | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| 					if r.Username != "None" { | ||||
| 						stmt = `UPDATE agents_agent SET last_logged_in_user=$1 WHERE agents_agent.agent_id=$2;` | ||||
| 						logger.Debugln("Updating last logged in user:", r.Username) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| SCRIPT_VERSION="40" | ||||
| SCRIPT_VERSION="41" | ||||
| 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.10.4' | ||||
| PYTHON_VER='3.10.6' | ||||
| SETTINGS_FILE='/rmm/api/tacticalrmm/tacticalrmm/settings.py' | ||||
|  | ||||
| TMP_FILE=$(mktemp -p "" "rmmrestore_XXXXXXXXXX") | ||||
| @@ -175,7 +175,7 @@ print_green 'Restoring systemd services' | ||||
| sudo cp $tmp_dir/systemd/* /etc/systemd/system/ | ||||
| sudo systemctl daemon-reload | ||||
|  | ||||
| print_green 'Installing Python 3.10.4' | ||||
| 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 | ||||
| numprocs=$(nproc) | ||||
|   | ||||
| @@ -309,9 +309,9 @@ fi | ||||
| 	printf >&2 "\n\n" | ||||
|  | ||||
| #SSL Certificate check | ||||
| cert=$(openssl verify -CAfile /etc/letsencrypt/live/$domain/chain.pem /etc/letsencrypt/live/$domain/cert.pem) | ||||
| cert=$(sudo certbot certificates) | ||||
|  | ||||
| if [[ "$cert" == *"OK"* ]]; then | ||||
| if [[ "$cert" != *"INVALID"* ]]; then | ||||
|     echo -ne ${GREEN} SSL Certificate for $domain is fine  | tee -a checklog.log | ||||
| 	printf >&2 "\n\n" | ||||
|  | ||||
| @@ -319,6 +319,10 @@ else | ||||
|     echo -ne ${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   | ||||
|  | ||||
| tail /rmm/api/tacticalrmm/tacticalrmm/private/log/django_debug.log  | tee -a checklog.log | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| SCRIPT_VERSION="138" | ||||
| SCRIPT_VERSION="139" | ||||
| 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.10.4' | ||||
| PYTHON_VER='3.10.6' | ||||
| SETTINGS_FILE='/rmm/api/tacticalrmm/tacticalrmm/settings.py' | ||||
|  | ||||
| TMP_FILE=$(mktemp -p "" "rmmupdate_XXXXXXXXXX") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user