diff --git a/.vscode/settings.json b/.vscode/settings.json index 977027a5b6..d70a5e3a35 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -69,5 +69,8 @@ "usePlaceholders": true, "completeUnimported": true, "staticcheck": true + }, + "files.associations": { + "*.cfg": "shellscript" } } diff --git a/api/tacticalrmm/core/management/commands/pre_update_tasks.py b/api/tacticalrmm/core/management/commands/pre_update_tasks.py index 38de0e13a1..3c29deb617 100644 --- a/api/tacticalrmm/core/management/commands/pre_update_tasks.py +++ b/api/tacticalrmm/core/management/commands/pre_update_tasks.py @@ -7,6 +7,6 @@ class Command(BaseCommand): help = "Collection of tasks to run after updating the rmm, before migrations" def handle(self, *args, **kwargs): - self.stdout.write(self.style.WARNING("Clearning the cache")) + self.stdout.write(self.style.WARNING("Clearing the cache")) clear_entire_cache() self.stdout.write(self.style.SUCCESS("Cache was cleared!")) diff --git a/api/tacticalrmm/tacticalrmm/utils.py b/api/tacticalrmm/tacticalrmm/utils.py index 12dbbd5d7d..c258486528 100644 --- a/api/tacticalrmm/tacticalrmm/utils.py +++ b/api/tacticalrmm/tacticalrmm/utils.py @@ -211,7 +211,7 @@ def reload_nats() -> None: conf = os.path.join(settings.BASE_DIR, "nats-rmm.conf") with open(conf, "w") as f: - json.dump(config, f) + json.dump(config, f, indent=4) if not settings.DOCKER_BUILD: time.sleep(0.5) diff --git a/default-configs/celery/celery.conf b/default-configs/celery/celery.conf new file mode 100644 index 0000000000..92cb5e1486 --- /dev/null +++ b/default-configs/celery/celery.conf @@ -0,0 +1,16 @@ +CELERYD_NODES="w1" + +CELERY_BIN="/rmm/api/env/bin/celery" + +CELERY_APP="tacticalrmm" + +CELERYD_MULTI="multi" + +CELERYD_OPTS="--time-limit=86400 --autoscale=20,2" + +CELERYD_PID_FILE="/rmm/api/tacticalrmm/%n.pid" +CELERYD_LOG_FILE="/var/log/celery/%n%I.log" +CELERYD_LOG_LEVEL="ERROR" + +CELERYBEAT_PID_FILE="/rmm/api/tacticalrmm/beat.pid" +CELERYBEAT_LOG_FILE="/var/log/celery/beat.log" diff --git a/default-configs/mesh/config.json b/default-configs/mesh/config.json new file mode 100644 index 0000000000..889d05d074 --- /dev/null +++ b/default-configs/mesh/config.json @@ -0,0 +1,34 @@ +{ + "settings": { + "cert": "mesh.example.com", + "mongoDb": "mongodb://127.0.0.1:27017", + "mongoDbName": "meshcentral", + "WANonly": true, + "minify": 1, + "port": 4443, + "agentAliasPort": 443, + "aliasPort": 443, + "allowLoginToken": true, + "allowFraming": true, + "_agentPing": 60, + "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.example.com:443/", + "geoLocation": true, + "cookieIpCheck": false, + "mstsc": true + } + } +} diff --git a/default-configs/nginx/frontend.conf b/default-configs/nginx/frontend.conf new file mode 100644 index 0000000000..5250828cbf --- /dev/null +++ b/default-configs/nginx/frontend.conf @@ -0,0 +1,41 @@ +server { + listen 80; + listen [::]:80; + server_name rmm.example.com; + + #location /.well-known/acme-challenge/ { + #root /var/www/letsencrypt/; + #} + + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + server_name rmm.example.com; + charset utf-8; + location / { + root /var/www/rmm/dist; + try_files $uri $uri/ /index.html; + add_header Cache-Control "no-store, no-cache, must-revalidate"; + add_header Pragma "no-cache"; + } + error_log /var/log/nginx/frontend-error.log; + access_log /var/log/nginx/frontend-access.log; + + ssl_certificate /etc/letsencrypt/live/rootdomain/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/rootdomain/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_dhparam /etc/ssl/certs/dhparam.pem; + ssl_ciphers EECDH+AESGCM:EDH+AESGCM; + ssl_ecdh_curve secp384r1; + ssl_stapling on; + ssl_stapling_verify on; + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; + add_header X-Content-Type-Options nosniff; +} diff --git a/default-configs/nginx/meshcentral.conf b/default-configs/nginx/meshcentral.conf new file mode 100644 index 0000000000..b815e5a9f0 --- /dev/null +++ b/default-configs/nginx/meshcentral.conf @@ -0,0 +1,47 @@ +server { + listen 80; + listen [::]:80; + server_name mesh.example.com; + + #location /.well-known/acme-challenge/ { + #root /var/www/letsencrypt/; + #} + + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + proxy_send_timeout 330s; + proxy_read_timeout 330s; + server_name mesh.example.com; + ssl_certificate /etc/letsencrypt/live/rootdomain/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/rootdomain/privkey.pem; + + ssl_session_cache shared:WEBSSL:10m; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_dhparam /etc/ssl/certs/dhparam.pem; + ssl_ciphers EECDH+AESGCM:EDH+AESGCM; + ssl_ecdh_curve secp384r1; + ssl_stapling on; + ssl_stapling_verify on; + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; + add_header X-Content-Type-Options nosniff; + + location / { + proxy_pass http://127.0.0.1:4443/; + 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; + } +} diff --git a/default-configs/nginx/nginx.conf b/default-configs/nginx/nginx.conf new file mode 100644 index 0000000000..7412f87cfe --- /dev/null +++ b/default-configs/nginx/nginx.conf @@ -0,0 +1,25 @@ +worker_rlimit_nofile 1000000; +user www-data; +worker_processes auto; +pid /run/nginx.pid; +include /etc/nginx/modules-enabled/*.conf; + +events { + worker_connections 4096; +} + +http { + sendfile on; + 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.2 TLSv1.3; + ssl_prefer_server_ciphers on; + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + gzip on; + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} \ No newline at end of file diff --git a/default-configs/nginx/rmm.conf b/default-configs/nginx/rmm.conf new file mode 100644 index 0000000000..44383f7694 --- /dev/null +++ b/default-configs/nginx/rmm.conf @@ -0,0 +1,88 @@ +server_tokens off; + +upstream tacticalrmm { + server unix:////rmm/api/tacticalrmm/tacticalrmm.sock; +} + +map $http_user_agent $ignore_ua { + "~python-requests.*" 0; + "~go-resty.*" 0; + default 1; +} + +server { + listen 80; + listen [::]:80; + server_name api.example.com; + + #location /.well-known/acme-challenge/ { + #root /var/www/letsencrypt/; + #} + + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl reuseport; + listen [::]:443 ssl; + server_name api.example.com; + client_max_body_size 300M; + access_log /rmm/api/tacticalrmm/tacticalrmm/private/log/access.log combined if=$ignore_ua; + error_log /rmm/api/tacticalrmm/tacticalrmm/private/log/error.log; + ssl_certificate /etc/letsencrypt/live/rootdomain/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/rootdomain/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_dhparam /etc/ssl/certs/dhparam.pem; + ssl_ciphers EECDH+AESGCM:EDH+AESGCM; + ssl_ecdh_curve secp384r1; + ssl_stapling on; + ssl_stapling_verify on; + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; + add_header X-Content-Type-Options nosniff; + + location /static/ { + root /rmm/api/tacticalrmm; + } + + location /private/ { + internal; + add_header "Access-Control-Allow-Origin" "https://rmm.example.com"; + alias /rmm/api/tacticalrmm/tacticalrmm/private/; + } + + location ~ ^/ws/ { + proxy_pass http://unix:/rmm/daphne.sock; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $server_name; + } + + 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; + } + + location / { + uwsgi_pass tacticalrmm; + include /etc/nginx/uwsgi_params; + uwsgi_read_timeout 300s; + uwsgi_ignore_client_abort on; + } +} diff --git a/default-configs/python/local_settings.py b/default-configs/python/local_settings.py new file mode 100644 index 0000000000..3962e0dfba --- /dev/null +++ b/default-configs/python/local_settings.py @@ -0,0 +1,25 @@ +SECRET_KEY = "DJANGO_SEKRET" + +DEBUG = False + +ALLOWED_HOSTS = ["api.example.com"] + +ADMIN_URL = "ADMINURL/" + +CORS_ORIGIN_WHITELIST = ["https://rmm.example.com"] + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": "tacticalrmm", + "USER": "pgusername", + "PASSWORD": "pgpw", + "HOST": "localhost", + "PORT": "5432", + } +} + +MESH_USERNAME = "meshusername" +MESH_SITE = "https://mesh.example.com" +REDIS_HOST = "localhost" +ADMIN_ENABLED = True diff --git a/default-configs/uwsgi/app.ini b/default-configs/uwsgi/app.ini new file mode 100644 index 0000000000..eb691cbc52 --- /dev/null +++ b/default-configs/uwsgi/app.ini @@ -0,0 +1,24 @@ +[uwsgi] +chdir = /rmm/api/tacticalrmm +module = tacticalrmm.wsgi +home = /rmm/api/env +master = true +enable-threads = true +socket = /rmm/api/tacticalrmm/tacticalrmm.sock +harakiri = 300 +chmod-socket = 660 +buffer-size = 65535 +vacuum = true +die-on-term = true +max-requests = 500 +disable-logging = true +cheaper-algo = busyness +cheaper = 4 +cheaper-initial = 4 +workers = 20 +cheaper-step = 2 +cheaper-overload = 3 +cheaper-busyness-min = 5 +cheaper-busyness-max = 10 +# stats = /tmp/stats.socket # uncomment when debugging +# cheaper-busyness-verbose = true # uncomment when debugging \ No newline at end of file diff --git a/installer-util.sh b/installer-util.sh new file mode 100644 index 0000000000..82362dcf4b --- /dev/null +++ b/installer-util.sh @@ -0,0 +1,448 @@ +#!/usr/bin/env bash + +# Fix for ncurses derpy lines in putty and similar apps +export NCURSES_NO_UTF8_ACS=1 + +# Menu option variables +INPUT=/tmp/menu.sh.$$ +menuselection="" + +# Arrays +declare -a mainmenuoptions=('Installation' 'Update' 'Utilities' 'Exit') +declare -a installmenuoptions=('Standard Install' 'Dev Test Prereqs' 'Dev Test Install' 'Return' 'Exit') +declare -a updatemenuoptions=('Standard Update' 'Backup and Update' 'Force Update' 'Return' 'Exit') +declare -a utilitymenuoptions=('Backup' 'Restore' 'Renew Certs' 'Import Certs' 'Add Fail2ban - Use at your own risk' 'Run Server Troubleshooter' 'Return' 'Exit') +declare -a cfgfiles=('01-InputAndError.cfg' '02-MiscFunctions.cfg' '03-SystemInfoFunctions.cfg' '04-UserInput.cfg' '05-NetworkFunctions.cfg' '06-InstallFunctions.cfg' '07-DatabaseFunctions.cfg' '08-CertificateFunctions.cfg' '09-ConfigAndServiceFunctions.cfg' '10-UpdateRestoreFunctions.cfg' '11-TroubleshootingFunctions.cfg' '12-ParentFunctions.cfg') + +# Log file variables +rundate="$(date '+%Y_%m_%d__%H_%M_%S')" +installlog="$PWD/trmm-install_$rundate.log" +restorelog="$PWD/trmm-restore_$rundate.log" +updatelog="$PWD/trmm-update_$rundate.log" +backuplog="$PWD/trmm-backup_$rundate.log" +checklog="$PWD/trmm-checklog_$rundate.log" +preinstalllog="$PWD/trmm-preinstall-log_$rundate.log" +currentlog="$preinstalllog" + +# Script Info variables +REPO_OWNER="ninjamonkey198206" +BRANCH="develop-installer-update" +SCRIPT_VERSION="69" +CFG_URL="https://raw.githubusercontent.com/${REPO_OWNER}/tacticalrmm/${BRANCH}" +SCRIPT_URL="https://raw.githubusercontent.com/${REPO_OWNER}/tacticalrmm/${BRANCH}/installer-util.sh" +REPO_URL="https://github.com/${REPO_OWNER}/tacticalrmm.git" +SCRIPTS_REPO_URL="https://github.com/amidaware/community-scripts.git" +THIS_SCRIPT=$(readlink -f "$0") + +# Misc info variables +INSTALL_TYPE="install" +UPDATE_TYPE="standard" +SCRIPTS_DIR='/opt/trmm-community-scripts' +PYTHON_VER='3.10.4' +NODE_MAJOR_VER='16' +SETTINGS_FILE='/rmm/api/tacticalrmm/tacticalrmm/settings.py' +LATEST_SETTINGS_URL="https://raw.githubusercontent.com/${REPO_OWNER}/tacticalrmm/${BRANCH}/api/tacticalrmm/tacticalrmm/settings.py" + +# Variables required for automated install +autoinstall="" +rmmhost="" +frontendhost="" +meshhost="" +letsemail="" +rootdomain="" +sslcacert="" +sslkey="" +sslcert="" +trmmuser="" +trmmpass="" +backupfile="" +troubleshoot="" +certtype="" +sudopass="" + +# Remove script run command from history to prevent sudo password leak +#history -d $(($(history 1 | awk '{print $1}')-1)) + +# Check if directory exists, if not, create +if [ ! -d "$PWD"/script-cfg ]; then + mkdir "$PWD"/script-cfg 2>&1 | tee -a "${currentlog}" +fi + +# Get cfg files function +getCfgFiles() +{ + if [ ! -f "$PWD/script-cfg/$2" ]; then + wget "$1/script-cfg/$2" -O "$PWD/script-cfg/$2" 2>&1 | tee -a "${currentlog}" + fi +} + +# Get cfg files +for i in "${cfgfiles[@]}" +do + getCfgFiles "$CFG_URL" "$i"; +done + +# Import functions +. "$PWD"/script-cfg/01-InputAndError.cfg +. "$PWD"/script-cfg/02-MiscFunctions.cfg +. "$PWD"/script-cfg/03-SystemInfoFunctions.cfg +. "$PWD"/script-cfg/04-UserInput.cfg +. "$PWD"/script-cfg/05-NetworkFunctions.cfg +. "$PWD"/script-cfg/06-InstallFunctions.cfg +. "$PWD"/script-cfg/07-DatabaseFunctions.cfg +. "$PWD"/script-cfg/08-CertificateFunctions.cfg +. "$PWD"/script-cfg/09-ConfigAndServiceFunctions.cfg +. "$PWD"/script-cfg/10-UpdateRestoreFunctions.cfg +. "$PWD"/script-cfg/11-TroubleshootingFunctions.cfg +. "$PWD"/script-cfg/12-ParentFunctions.cfg + +# Set colors +# MiscFunctions +setColors; + +# Get commandline input +while getopts i:a:b:c:e:d:m:f:g:h:k:s:p:r:o:t:u:n:w: option +do + case $option in + i) autoinstall="1" + INSTALL_TYPE="$(translateToLowerCase ${OPTARG})";; + a) rmmhost="$(translateToLowerCase ${OPTARG})";; + b) BRANCH="$(translateToLowerCase ${OPTARG})";; + c) sslcacert="${OPTARG}";; + e) sslcert="${OPTARG}";; + d) rootdomain="$(translateToLowerCase ${OPTARG})";; + m) letsemail="$(translateToLowerCase ${OPTARG})";; + f) backupfile="${OPTARG}";; + g) sudopass="${OPTARG}";; + h) helpText + exit 1;; + k) sslkey="${OPTARG}";; + s) meshhost="$(translateToLowerCase ${OPTARG})";; + p) trmmpass="${OPTARG}";; + r) REPO_OWNER="$(translateToLowerCase ${OPTARG})";; + o) frontendhost="$(translateToLowerCase ${OPTARG})";; + t) troubleshoot="1";; + u) UPDATE_TYPE="$(translateToLowerCase ${OPTARG})";; + n) trmmuser="${OPTARG}";; + w) certtype="$(translateToLowerCase ${OPTARG})";; + \?) echo -e "Error: Invalid option" + helpText + exit 1;; + esac +done + +if [ "$autoinstall" == "1" ]; then + + # Check that update type is valid + if [ "$INSTALL_TYPE" == "update" ] && ([ "$UPDATE_TYPE" != "standard" ] && [ "$UPDATE_TYPE" != "forced" ]); then + echo -e "${RED} Error: You've selected update, but not selected an appropriate type. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Run $THIS_SCRIPT -h help for details on how to select them. ${NC}" + exit 1 + fi + + # Check that backup file exists + if [ "$INSTALL_TYPE" == "restore" ] && ([ -z "$backupfile" ] || [ ! -f "$backupfile" ]); then + echo -e "${RED} Error: You've selected restore, but not provided a valid backup file. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Run $THIS_SCRIPT -h help for details on how to enter this. ${NC}" + exit 1 + fi + + # Check that install type is valid + if [ "$INSTALL_TYPE" != "devprep" ] && [ "$INSTALL_TYPE" != "devinstall" ] && [ "$INSTALL_TYPE" != "install" ] && [ "$INSTALL_TYPE" != "update" ] && [ "$INSTALL_TYPE" != "restore" ] && [ "$INSTALL_TYPE" != "backup" ]; then + echo -e "${RED} Error: You've selected an invalid function type. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Run $THIS_SCRIPT -h help for details on the available options. ${NC}" + exit 1 + fi + + # Check all required input is available for install + if [ "$INSTALL_TYPE" == "devprep" ] || [ "$INSTALL_TYPE" == "devinstall" ] || [ "$INSTALL_TYPE" == "install" ]; then + if [ -z "$rmmhost" ] || [ -z "$certtype" ] || [ -z "$rootdomain" ] || [ -z "$meshhost" ] || [ -z "$frontendhost" ] || [ -z "$trmmuser" ] || [ -z "$trmmpass" ] || [ -z "$letsemail" ]; then + echo -e "${RED} Error: To perform an automated installation, you must provide all required information. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} install type, api host, mesh host, rmm host, root domain, email address, certificate install type, and T-RMM username and password are all required. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Run $THIS_SCRIPT -h help for further details. ${NC}" + exit 1 + fi + + # Check that certificate install type is valid + if [ "$certtype" != "import" ] && [ "$certtype" != "webroot" ]; then + echo -e "${RED} Error: You've selected an invalid certificate installation type. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Run $THIS_SCRIPT -h help for details on the available options. ${NC}" + exit 1 + fi + + # Check for required input if import certificate + if [ "$certtype" == "import" ] && ([ -z "$sslcacert" ] || [ -z "$sslcert" ] || [ -z "$sslkey" ]); then + echo -e "${RED} Error: To perform an automated installation using imported certificates, you must provide all required information. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} install type, api host, mesh host, rmm host, root domain, email address, certificate install type, CA cert path, Cert path, Private key path, and T-RMM username and password are all required. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Run $THIS_SCRIPT -h help for further details. ${NC}" + exit 1 + fi + fi + + # Check that email address format is valid + if [ "$INSTALL_TYPE" == "devprep" ] || [ "$INSTALL_TYPE" == "devinstall" ] || [ "$INSTALL_TYPE" == "install" ]; then + if [[ $letsemail != *[@]*[.]* ]]; then + echo -e "${RED} Error: You've entered an invalid email address. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Run $THIS_SCRIPT -h help for details on the correct format. ${NC}" + exit 1 + fi + fi + + # Check that repo and branch match install type + if ([ "$INSTALL_TYPE" == "devprep" ] || [ "$INSTALL_TYPE" == "devinstall" ]) && [ "$BRANCH" == "master" ]; then + echo -e "${RED} Error: You've selected a developer installation type, but not changed the repo, branch, or both. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Run $THIS_SCRIPT -h help for details on how to select them. ${NC}" + exit 1 + fi + + if [ -n "$rmmhost" ] && [ -n "$meshhost" ] && [ -n "$frontendhost" ] && [ -n "$rootdomain" ]; then + # Check subdomains are valid format + # User Input + subdomainFormatCheck "$rmmhost" "api"; + subdomainFormatCheck "$meshhost" "mesh"; + subdomainFormatCheck "$frontendhost" "rmm"; + + # Check root domain format is valid + # User Input + rootDomainFormatCheck "$rootdomain"; + + # Check that entries resolve via dns + # User Input + rmmdomain="$rmmhost.$rootdomain" + frontenddomain="$frontendhost.$rootdomain" + meshdomain="$meshhost.$rootdomain" + checkDNSEntriesExist "$rmmdomain"; + checkDNSEntriesExist "$frontenddomain"; + checkDNSEntriesExist "$meshdomain"; + fi + + if [ -n "$sslcacert" ] && [ -n "$sslcert" ] && [ -n "$sslkey" ]; then + # Check that cert file exists + # User Input + checkCertExists "$sslcacert" "CA Chain"; + checkCertExists "$sslcert" "Fullchain Cert"; + checkCertExists "$sslkey" "Private Key"; + fi + + # Verify repo exists + # 02-MiscFunctions + verifyRepoExists "$SCRIPT_URL"; +fi + +# Gather OS info +# 03-SystemInfoFunctions +getOSInfo; + +# Install script pre-reqs +# 06-InstallFunctions +installPreReqs; + +# Check for new functions versions, include url, filename, and script name as variables +for i in "${cfgfiles[@]}" +do + # 02-MiscFunctions + checkCfgVer "$CFG_URL" "$i" "$THIS_SCRIPT"; +done + +# Check for new script version, pass script version, url, and script name variables in that order +# 02-MiscFunctions +checkScriptVer "$SCRIPT_VERSION" "$SCRIPT_URL" "$THIS_SCRIPT"; + +# Install additional prereqs +# 06-InstallFunctions +installAdditionalPreReqs; + +# Fallback if lsb_release -si returns anything else than Ubuntu, Debian, or Raspbian +# 03-SystemInfoFunctions +wutOSThis; + +# Verify compatible OS and version +# 03-SystemInfoFunctions +verifySupportedOS; + +# Verify system meets minimum recommended specs +# 03-SystemInfoFunctions +checkTotalSystemMemory; +checkCPUAndThreadCount; + +# Check if root +# 02-MiscFunctions +checkRoot; + +# Check if Tactical user exists, if not prompt to create it +# 02-MiscFunctions +checkTacticalUser; + +# Check language/locale +# 03-SystemInfoFunctions +checkLocale; + +# Prevents logging issues with some VPS providers like Vultr if this is a freshly provisioned instance that hasn't been rebooted yet +sudo systemctl restart systemd-journald.service 2>&1 | tee -a "${currentlog}" + +#################### +# Menu Functions # +#################### + +# Installation menu +installMenu() +{ + until [ "$menuselection" = "0" ]; do + dialog --cr-wrap --clear --no-ok --no-cancel --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Installation Menu" --menu "Use the 'Up' and 'Down' keys to navigate, and the 'Enter' key to make your selections.\n\nSelect Return to return to the previous menu." 0 0 0 \ + 1 "${installmenuoptions[0]}" \ + 2 "${installmenuoptions[1]}" \ + 3 "${installmenuoptions[2]}" \ + 4 "${installmenuoptions[3]}" \ + 5 "${installmenuoptions[4]}" 2>"${INPUT}" + + menuselection=$(<"${INPUT}") + + case $menuselection in + 1 ) INSTALL_TYPE="install" + mainInstall;; + 2 ) INSTALL_TYPE="devprep" + mainInstall;; + 3 ) INSTALL_TYPE="devinstall" + decideMainRepos + mainInstall;; + 4 ) return;; + 5 ) [ -f $INPUT ] && rm $INPUT + clear -x + exit;; + \?) derpDerp;; + esac + done + + return +} + +# Utilities menu +utilityMenu() +{ + until [ "$menuselection" = "0" ]; do + dialog --cr-wrap --clear --no-ok --no-cancel --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Utility Menu" --menu "Use the 'Up' and 'Down' keys to navigate, and the 'Enter' key to make your selections.\n\nSelect Return to return to the previous menu." 0 0 0 \ + 1 "${utilitymenuoptions[0]}" \ + 2 "${utilitymenuoptions[1]}" \ + 3 "${utilitymenuoptions[2]}" \ + 4 "${utilitymenuoptions[3]}" \ + 5 "${utilitymenuoptions[4]}" \ + 6 "${utilitymenuoptions[5]}" \ + 7 "${utilitymenuoptions[6]}" \ + 8 "${utilitymenuoptions[7]}" 2>"${INPUT}" + + menuselection=$(<"${INPUT}") + + case $menuselection in + 1 ) backupTRMM;; + 2 ) INSTALL_TYPE="restore" + restoreTRMM;; + 3 ) getHostAndDomainInfo + renewCerts;; + 4 ) if [ ! -f /etc/nginx/sites-available/rmm.conf ]; then + troubleshoot="1" + getHostAndDomainInfo + else + getExistingDomainInfo + fi + importCerts;; + 5 ) installFail2ban;; + 6 ) troubleShoot;; + 7 ) return;; + 8 ) [ -f $INPUT ] && rm $INPUT + clear -x + exit;; + \?) derpDerp;; + esac + done + + return +} + +# Update menu +updateMenu() +{ + until [ "$menuselection" = "0" ]; do + dialog --cr-wrap --clear --no-ok --no-cancel --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Update Menu" --menu "Use the 'Up' and 'Down' keys to navigate, and the 'Enter' key to make your selections.\n\nSelect Return to return to the previous menu." 0 0 0 \ + 1 "${updatemenuoptions[0]}" \ + 2 "${updatemenuoptions[1]}" \ + 3 "${updatemenuoptions[2]}" \ + 4 "${updatemenuoptions[3]}" \ + 5 "${updatemenuoptions[4]}" 2>"${INPUT}" + + menuselection=$(<"${INPUT}") + + case $menuselection in + 1 ) INSTALL_TYPE="update" + UPDATE_TYPE="standard" + updateTRMM;; + 2 ) INSTALL_TYPE="update" + UPDATE_TYPE="standard" + backupTRMM + updateTRMM;; + 3 ) INSTALL_TYPE="update" + UPDATE_TYPE="forced" + updateTRMM;; + 4 ) return;; + 5 ) [ -f $INPUT ] && rm $INPUT + clear -x + exit;; + \?) derpDerp;; + esac + done + + return +} + +# Main menu +mainMenu() +{ + # Automated install types install, devprep, or devinstall + if [ "$autoinstall" == "1" ] && ([ "$INSTALL_TYPE" == "install" ] || [ "$INSTALL_TYPE" == "devinstall" ] || [ "$INSTALL_TYPE" == "devprep" ]); then + echo 'Defaults timestamp_timeout=15' | sudo tee /etc/sudoers.d/timeout + mainInstall; + sudo rm /etc/sudoers.d/timeout + # Automated update + elif [ "$autoinstall" == "1" ] && [ "$INSTALL_TYPE" == "update" ]; then + echo 'Defaults timestamp_timeout=15' | sudo tee /etc/sudoers.d/timeout + updateTRMM; + sudo rm /etc/sudoers.d/timeout + # Automated restore + elif [ "$autoinstall" == "1" ] && [ "$INSTALL_TYPE" == "restore" ]; then + echo 'Defaults timestamp_timeout=15' | sudo tee /etc/sudoers.d/timeout + restoreTRMM; + sudo rm /etc/sudoers.d/timeout + # Automated backup + elif [ "$autoinstall" == "1" ] && [ "$INSTALL_TYPE" == "backup" ]; then + echo 'Defaults timestamp_timeout=15' | sudo tee /etc/sudoers.d/timeout + backupTRMM; + sudo rm /etc/sudoers.d/timeout + # Automated troubleshoot + elif [ "$autoinstall" != "1" ] && [ "$troubleshoot" == "1" ]; then + echo 'Defaults timestamp_timeout=15' | sudo tee /etc/sudoers.d/timeout + troubleShoot; + sudo rm /etc/sudoers.d/timeout + else + until [ "$menuselection" = "0" ]; do + dialog --cr-wrap --clear --no-ok --no-cancel --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Main Menu" --menu "Use the 'Up' and 'Down' keys to navigate, and the 'Enter' key to make your selections." 0 0 0 \ + 1 "${mainmenuoptions[0]}" \ + 2 "${mainmenuoptions[1]}" \ + 3 "${mainmenuoptions[2]}" \ + 4 "${mainmenuoptions[3]}" 2>"${INPUT}" + + menuselection=$(<"${INPUT}") + + case $menuselection in + 1 ) installMenu;; + 2 ) updateMenu;; + 3 ) utilityMenu;; + 4 ) [ -f $INPUT ] && rm $INPUT + clear -x + exit;; + \?) derpDerp;; + esac + done + fi + return +} + +mainMenu; diff --git a/script-cfg/01-InputAndError.cfg b/script-cfg/01-InputAndError.cfg new file mode 100644 index 0000000000..45f85e83ae --- /dev/null +++ b/script-cfg/01-InputAndError.cfg @@ -0,0 +1,47 @@ +################### +# CFG file info # +################### + +CFG_VERSION="8" + + +############################# +# Input cleanup and error # +############################# + +errortrack=0 + +# Translate user input to all lower case to prevent ID10T errors +translateToLowerCase() +{ + local lowercase="" + lowercase="$(echo $1 | tr '[:upper:]' '[:lower:]')" + echo "$lowercase" +} + +# Function to track and mock ID10T errors +derpDerp() +{ + if [ "$errortrack" -lt 12 ]; then + errortrack=$((errortrack+1)) + fi + + derptext="" + + case $errortrack in + 1 ) dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "SHAME... SHAME... SHAME..." --msgbox "Need some coffee?" 10 30;; + 2 ) dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "SHAME... SHAME... SHAME..." --msgbox "You should get some coffee." 10 35;; + 3 ) dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "SHAME... SHAME... SHAME..." --msgbox "You are paying attention, right?" 10 40;; + 4 ) dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "SHAME... SHAME... SHAME..." --msgbox "All your typos are belong to us." 10 40;; + 5 ) dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "SHAME... SHAME... SHAME..." --msgbox "We're sorry, your fingers are too fat.\n\nIf you would like to obtain a typing wand,\nplease mash your hand on the keyboard now." 15 75;; + 6 ) dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "SHAME... SHAME... SHAME..." --msgbox "You have got to be kidding me...\n\nThis really isn't that difficult." 15 50;; + 7 ) dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "SHAME... SHAME... SHAME..." --msgbox "You're doing this intentionally, aren't you?\n\nThis really isn't that difficult." 15 75;; + 8 ) dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "SHAME... SHAME... SHAME..." --msgbox "Please step away from the keyboard, and back away slowly.\n\nThe instructions are literally right there." 15 75;; + 9 ) dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "SHAME... SHAME... SHAME..." --msgbox "You're making me angry.\nYou wouldn't like me when I'm angry.\n\nThe instructions are literally right there." 15 75;; + 10 ) dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "SHAME... SHAME... SHAME..." --msgbox "How did you even get to this point?!\n\nThe instructions are literally right there." 15 75;; + 11 ) dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "SHAME... SHAME... SHAME..." --msgbox "Seriously, just give it up.\n\nI just can't with you right now..." 15 60;; + * ) dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "SHAME... SHAME... SHAME..." --msgbox "\*sigh\* Why are you still here?\n\nI just can't with you right now..." 15 60;; + esac + + return +} \ No newline at end of file diff --git a/script-cfg/02-MiscFunctions.cfg b/script-cfg/02-MiscFunctions.cfg new file mode 100644 index 0000000000..fcc76aeb21 --- /dev/null +++ b/script-cfg/02-MiscFunctions.cfg @@ -0,0 +1,391 @@ +################### +# CFG file info # +################### + +CFG_VERSION="8" + + +###################################################################################### +# Misc Functions, must be before any functions except input verification functions # +###################################################################################### + +# Set bash text colors +setColors() +{ + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + RED='\033[0;31m' + NC='\033[0m' +} + +# Purdy install text in green +print_green() +{ + printf >&2 "${GREEN}%0.s-${NC}" {1..80} + printf >&2 "\n" + printf >&2 "${GREEN}${1}${NC}\n" + printf >&2 "${GREEN}%0.s-${NC}" {1..80} + printf >&2 "\n" +} + +# Purdy install text in yellow +print_yellow() +{ + printf >&2 "${YELLOW}%0.s-${NC}" {1..80} + printf >&2 "\n" + printf >&2 "${YELLOW}${1}${NC}\n" + printf >&2 "${YELLOW}%0.s-${NC}" {1..80} + printf >&2 "\n" +} + +# Command-line help function +helpText() +{ + echo -e "" + echo -e 'Syntax: ./installer-util.sh [-i|a|b|c|e|d|m|f|g|h|k|s|p|r|o|t|u|n|w]' + echo -e "" + echo -e "Options:\n" + echo -e "i Select type of automated function. Options are ${YELLOW}install${NC},${YELLOW} devprep${NC},${YELLOW} devinstall${NC},${YELLOW} backup${NC}, or ${YELLOW}restore${NC}.\n" + echo -e "a Provide the api hostname/subdomain, eg ${YELLOW}api${NC}.example.com\n" + echo -e "b If performing a dev install, this will select the branch. eg, https://github.com/amidaware/tacticalrmm/${YELLOW}branch${NC}/.git\n" + echo -e "c Full path to SSL CA Chain certificate for import. Must be in .pem format. Include the filename.\n" + echo -e "e Full path to SSL certificate for import. Must be in .pem format. Include the filename.\n" + echo -e "d Provide the root domain, eg rmm.${YELLOW}example.com${NC}\n" + echo -e "m Provide a valid email address for LetsEncrypt cert generation and primary T-RMM user.\n" + echo -e "f Provide the full path to the backup file, including file name.\n" + echo -e "g Provide the sudo password to allow non-interactive use." + echo -e "h Type ${YELLOW}help${NC} to show this help text.\n" + echo -e "k Full path to SSL private key for import. Must be in .pem format. Include the filename.\n" + echo -e "s Provide the mesh hostname/subdomain, eg ${YELLOW}mesh${NC}.example.com\n" + echo -e "p Provide the password for the initial T-RMM user.\n" + echo -e "r If performing a dev install, this will select the repo owner. eg, https://github.com/${YELLOW}repo-owner${NC}/tacticalrmm/master/.git\n" + echo -e "o Provide the rmm hostname/subdomain, eg ${YELLOW}rmm${NC}.example.com\n" + echo -e "t Enter ${YELLOW}trouble${NC} to perform troubleshooting functions.\n" + echo -e "u Select update type. Options are ${YELLOW}standard${NC} or ${YELLOW}forced${NC}.\n" + echo -e "n Provide the username for the initial T-RMM user.\n" + echo -e "w Select certificate install type. Options are ${YELLOW}import${NC} or ${YELLOW}webroot${NC}.\n" +} + +# Create env dirs and files +createEnvDir() +{ + if [ ! -d /etc/trmm ]; then + # Create directory + mkdir /etc/trmm 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" /etc/trmm 2>&1 | tee -a "${currentlog}" + fi +} + +# Create env dirs and files +createSysInfoEnv() +{ + echo "RMMHOST=$rmmhost" | tee -a /etc/trmm/sysinfo.env + echo "FRONTENDHOST=$frontendhost" | tee -a /etc/trmm/sysinfo.env + echo "MESHHOST=$meshhost" | tee -a /etc/trmm/sysinfo.env + echo "ROOTDOMAIN=$rootdomain" | tee -a /etc/trmm/sysinfo.env + echo "RMMDOMAIN=$rmmdomain" | tee -a /etc/trmm/sysinfo.env + echo "FRONTENDDOMAIN=$frontenddomain" | tee -a /etc/trmm/sysinfo.env + echo "MESHDOMAIN=$meshdomain" | tee -a /etc/trmm/sysinfo.env + echo "LETSEMAIL=$letsemail" | tee -a /etc/trmm/sysinfo.env +} + +# Create env dirs and files +createUserInfoEnv() +{ + echo "TRMMUSER=$trmmuser" | tee -a /etc/trmm/userinfo.env +} + +# Check for new script version +checkScriptVer() +{ + # create temp file and download current script to use for comparison + TMP_FILE=$(mktemp -p "" "rmminstall_XXXXXXXXXX") + curl -s -L "$2" > "$TMP_FILE" 2>&1 | tee -a "${currentlog}" + NEW_VER=$(grep "^SCRIPT_VERSION" "$TMP_FILE" | awk -F'[="]' '{print $3}') + + # download new file if available + if [ "$1" -ne "${NEW_VER}" ]; then + wget -q "$2" -O "$3" 2>&1 | tee -a "${currentlog}" + if [ "$autoinstall" == "1" ]; then + echo -e "${RED} Error: ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Old ${3} detected. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} The latest version has been downloaded. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Please re-run ${3} ${NC}" + echo -e "${RED} Exiting. ${NC}" + else + dialog --keep-tite --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "Old $3 detected.\nThe latest version has been downloaded.\n\nPlease re-run $3" 10 40 + clear -x + fi + rm -f "$TMP_FILE" 2>&1 | tee -a "${currentlog}" + exit 1 + fi + + rm -f "$TMP_FILE" 2>&1 | tee -a "${currentlog}" +} + +# Check for functions updates +checkCfgVer() +{ + local currentcfgver="" + currentcfgver=$(grep "^CFG_VERSION" "$PWD/script-cfg/$2" | awk -F'[="]' '{print $3}') + + # create temp file and download current file to use for comparison + TMP_FILE=$(mktemp -p "" "rmminstall_XXXXXXXXXX") + curl -s -L "$1/script-cfg/$2" > "$TMP_FILE" 2>&1 | tee -a "${currentlog}" + NEW_VER=$(grep "^CFG_VERSION" "$TMP_FILE" | awk -F'[="]' '{print $3}') + + # download new file if available + if [ "$currentcfgver" -ne "${NEW_VER}" ]; then + wget -q "$1/script-cfg/$2" -O "$PWD/script-cfg/$2" 2>&1 | tee -a "${currentlog}" + if [ "$autoinstall" == "1" ]; then + echo -e "${RED} Error: ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Old ${2} detected. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} The latest version has been downloaded. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Please re-run ${3} ${NC}" + echo -e "${RED} Exiting. ${NC}" + else + dialog --keep-tite --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "Old $2 file detected.\nThe latest version has been downloaded.\n\nPlease re-run $3" 10 40 + clear -x + fi + rm -f "$TMP_FILE" 2>&1 | tee -a "${currentlog}" + exit 1 + fi + + rm -f "$TMP_FILE" 2>&1 | tee -a "${currentlog}" +} + +# Check for root as user +checkRoot() +{ + # if user is root, exit + if [ $EUID -eq 0 ]; then + if [ "$autoinstall" == "1" ]; then + echo -e "${RED} Error: ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Do NOT run this script as root. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Exiting. ${NC}" + else + dialog --keep-tite --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "Do NOT run this script as root. Exiting." 0 0 + clear -x + fi + exit 1 + fi +} + +# Check if Tactical user exists, if not prompt to create it +checkTacticalUser() +{ + local tacticaluser="" + local hassudo="" + local tacpass="" + + # Check if dir exists, if so pull tactical user + if [ -d /rmm ]; then + tacticaluser=$(stat -c '%U' /rmm) + if [ "$tacticaluser" != "${USER}" ]; then + if [ "$autoinstall" == "1" ]; then + echo -e "${RED} Error: ${NC}" | tee -a "${currentlog}" + echo -e "${RED} This script must be run as the Tactical user. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Please switch users, and run it again. ${NC}" + echo -e "${RED} Exiting. ${NC}" + else + dialog --keep-tite --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "This script must be run as the Tactical user.\nPlease switch users, and try again.\n\nExiting." 0 0 + clear -x + fi + exit 1 + fi + + # If user not tactical, check if it exists. If so, exit. If not, create it + elif [ "${USER}" != "tactical" ]; then + tacticaluser="$(id -u tactical > /dev/null 2>&1; echo $?)" + case $tacticaluser in + 0 ) if [ "$autoinstall" == "1" ]; then + echo -e "${RED} Error: ${NC}" | tee -a "${currentlog}" + echo -e "${RED} This script must be run as the Tactical user. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Please switch users, and run it again. ${NC}" + echo -e "${RED} Exiting. ${NC}" + else + dialog --keep-tite --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "This script must be run as the Tactical user.\nPlease switch users, and try again.\n\nExiting." 0 0 + clear -x + fi + exit 1;; + + 1 ) echo -e "\n${YELLOW} Tactical user does not exist. Creating now. ${NC}\n" | tee -a "${currentlog}" + sudo useradd -m -G sudo -s /bin/bash tactical 2>&1 | tee -a "${currentlog}" + tacpass="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 20 | head -n 1)" + echo "tactical:$tacpass"| sudo chpasswd + if [ "$autoinstall" == "1" ]; then + echo -e "${RED} The Tactical user has been created. ${NC}\n" | tee -a "${currentlog}" + echo -e "${RED} Username: tactical ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Password: $tacpass ${NC}\n" | tee -a "${currentlog}" + echo -e "${RED} Please switch users, re-download the script, and run it again. ${NC}" + echo -e "${RED} Exiting. ${NC}" + else + dialog --keep-tite --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Tactical User Created" --msgbox "The Tactical user has been created.\n\nUsername: tactical\n\nPassword: $tacpass\n\nPlease switch users, re-download the script, and try again.\n\nExiting." 0 0 + clear -x + fi + exit 1;; + esac + + # If user is tactical, verify sudo privileges. If none, add to sudo group + elif [ "${USER}" == "tactical" ]; then + hassudo="$(id -nG tactical | grep -w 'sudo' > /dev/null 2>&1; echo $?)" + case $hassudo in + 0 ) return;; + + 1 ) echo -e "\n${YELLOW} Adding Tactical user to sudo group. ${NC}\n" | tee -a "${currentlog}" + sudo usermod -a -G sudo tactical 2>&1 | tee -a "${currentlog}" + if [ "$autoinstall" == "1" ]; then + echo -e "${RED} The Tactical user has been added to the sudo group. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Please log out of the tactical user, log back in, and try again. ${NC}" + echo -e "${RED} Exiting.${NC}" + else + dialog --keep-tite --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Tactical User Sudo Privileges Added" --msgbox "The Tactical user has been added to the sudo group.\nPlease log out of the tactical user, log back in, and try again.\n\nExiting." 0 0 + clear -x + fi + exit 1;; + esac + fi +} + +# Clone primary repo +clonePrimaryRepo() +{ + # use only if called from standard or dev install, or restore + if [ "$1" == "install" ] || [ "$1" == "devinstall" ] || [ "$1" == "restore" ]; then + sudo mkdir /rmm 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" /rmm 2>&1 | tee -a "${currentlog}" + sudo mkdir -p /var/log/celery 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" /var/log/celery 2>&1 | tee -a "${currentlog}" + git clone "$2" /rmm/ 2>&1 | tee -a "${currentlog}" + fi + + cd /rmm + git config user.email "admin@example.com" 2>&1 | tee -a "${currentlog}" + git config user.name "Bob" 2>&1 | tee -a "${currentlog}" + + # use only if called from standard or dev install, or restore + if [ "$1" == "install" ] || [ "$1" == "devinstall" ] || [ "$1" == "restore" ]; then + git checkout "$3" 2>&1 | tee -a "${currentlog}" + + # use only if called from update + elif [ "$1" == "update" ]; then + git fetch 2>&1 | tee -a "${currentlog}" + git checkout "$3" 2>&1 | tee -a "${currentlog}" + git reset --hard FETCH_HEAD 2>&1 | tee -a "${currentlog}" + git clean -df 2>&1 | tee -a "${currentlog}" + git pull 2>&1 | tee -a "${currentlog}" + fi +} + +# Clone scripts repo +cloneScriptsRepo() +{ + # if directory exists, skip, else create it + if [ ! -d "${SCRIPTS_DIR}" ]; then + sudo mkdir -p "${SCRIPTS_DIR}" 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" "${SCRIPTS_DIR}" 2>&1 | tee -a "${currentlog}" + git clone "$2" "${SCRIPTS_DIR}"/ 2>&1 | tee -a "${currentlog}" + fi + cd "${SCRIPTS_DIR}" + git config user.email "admin@example.com" 2>&1 | tee -a "${currentlog}" + git config user.name "Bob" 2>&1 | tee -a "${currentlog}" + + # use only if called from standard or dev install, or restore + if [ "$1" == "install" ] || [ "$1" == "devinstall" ] || [ "$1" == "restore" ]; then + git checkout main 2>&1 | tee -a "${currentlog}" + + # use only if called from update + elif [ "$1" == "update" ]; then + git fetch 2>&1 | tee -a "${currentlog}" + git checkout main 2>&1 | tee -a "${currentlog}" + git reset --hard FETCH_HEAD 2>&1 | tee -a "${currentlog}" + git clean -df 2>&1 | tee -a "${currentlog}" + git pull 2>&1 | tee -a "${currentlog}" + fi +} + +# Verify repo exists +verifyRepoExists() +{ + local repostatus="" + repostatus=$(curl --output /dev/null --silent --write-out "%{http_code}" "$1") + if [ "$repostatus" == "200" ]; then + echo -e "\n${GREEN} Repo exists. ${NC}\n" | tee -a "${currentlog}" + else + echo -e "${RED} The Tactical RMM repository you entered does not exist. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Please try again. ${NC}" + echo -e "${RED} Exiting... ${NC}" + exit 1 + fi +} + +# Set primary repos to use +decideMainRepos() +{ + userconfirm="n" + local own="" + local bran="" + local repostatus="" + local goodrepo="n" + + until [ "$goodrepo" == "y" ]; do + # prompt for repo owner info and verify before proceeding + until [ "$userconfirm" == "y" ]; do + own=$(dialog --cr-wrap --no-cancel --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Select Repository" --inputbox "Enter the dev repo owner name.\nThis is right after github.com in the URL:" 10 90 3>&1 1>&2 2>&3) + own="$(translateToLowerCase $own)" + dialog --cr-wrap --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Select Repository" --yesno "Is this correct?\n$own" 0 0 + case $? in + 0 ) userconfirm="y" + clear -x;; + 1 ) userconfirm="n" + derpDerp;; + esac + done + userconfirm="n" + REPO_OWNER="$own" + + # prompt for branch info and verify before proceeding + until [ "$userconfirm" == "y" ]; do + bran=$(dialog --cr-wrap --no-cancel --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Select Repository Branch" --inputbox "Enter the dev repo branch name.\nThis is right after tacticalrmm in the URL:" 10 90 3>&1 1>&2 2>&3) + bran="$(translateToLowerCase $bran)" + dialog --cr-wrap --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Select Repository Branch" --yesno "Is this correct?\n$bran" 0 0 + case $? in + 0 ) userconfirm="y" + clear -x;; + 1 ) userconfirm="n" + derpDerp;; + esac + done + userconfirm="n" + BRANCH="$bran" + + repostatus=$(curl --output /dev/null --silent --write-out "%{http_code}" "$SCRIPT_URL") + if [ "$repostatus" == "200" ]; then + goodrepo="y" + else + goodrepo="n" + derpDerp; + dialog --keep-tite --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "The Tactical RMM repository you entered does not exist.\n\nPlease try again." 0 0 + fi + done + + # Check for new functions versions, include url, filename, and script name as variables + for i in "${cfgfiles[@]}" + do + checkCfgVer "$CFG_URL" "$i" "$THIS_SCRIPT"; + done + # Check for new script version, pass script version, url, and script name variables in that order + checkScriptVer "$SCRIPT_VERSION" "$SCRIPT_URL" "$THIS_SCRIPT"; +} + +# Pull domain info from existing installation +getExistingDomainInfo() +{ + rmmdomain=$(grep server_name /etc/nginx/sites-available/rmm.conf | grep -v 301 | head -1 | tr -d " \t" | sed 's/.*server_name//' | tr -d ';') + frontenddomain=$(grep server_name /etc/nginx/sites-available/frontend.conf | grep -v 301 | head -1 | tr -d " \t" | sed 's/.*server_name//' | tr -d ';') + meshdomain=$(grep server_name /etc/nginx/sites-available/meshcentral.conf | grep -v 301 | head -1 | tr -d " \t" | sed 's/.*server_name//' | tr -d ';') + rmmhost=$(echo "$rmmdomain" | cut -d '.' -f1) + frontendhost=$(echo "$frontenddomain" | cut -d '.' -f1) + meshhost=$(echo "$meshdomain" | cut -d '.' -f1) + rootdomain=$(echo "$rmmdomain" | cut -d '.' -f2-) +} diff --git a/script-cfg/03-SystemInfoFunctions.cfg b/script-cfg/03-SystemInfoFunctions.cfg new file mode 100644 index 0000000000..12aad9be9f --- /dev/null +++ b/script-cfg/03-SystemInfoFunctions.cfg @@ -0,0 +1,114 @@ +################### +# CFG file info # +################### + +CFG_VERSION="8" + + +########################### +# System info functions # +########################### + +# Gather OS info +getOSInfo() +{ + osname=$(lsb_release -si); osname=${osname^} + osname=$(echo "$osname" | tr '[:upper:]' '[:lower:]') + fullrel=$(lsb_release -sd) + codename=$(lsb_release -sc) + relno=$(lsb_release -sr | cut -d. -f1) + fullrelno=$(lsb_release -sr) +} + +# Check OS if not recognised +wutOSThis() +{ + if [ ! "$osname" == "ubuntu" ] && [ ! "$osname" == "debian" ]; then + osname=$(grep -oP '(?<=^ID=).+' /etc/os-release | tr -d '"') + osname=${osname^} + fi +} + +# Verify Debian or Ubuntu and version +verifySupportedOS() +{ + if ([ "$osname" == "ubuntu" ] && ([ "$fullrelno" == "20.04" ] || [ "$fullrelno" == "22.04" ])) || ([ "$osname" == "debian" ] && [ "$relno" -ge 10 ]); then + echo -e "\n${GREEN} $fullrel ${NC}\n" | tee -a "${currentlog}" + else + if [ "$autoinstall" == "1" ]; then + echo -e "${RED} Error: ${NC}" | tee -a "${currentlog}" + echo -e "${RED} T-RMM server supported OS versions: Ubuntu 20.04 and 22.04, and Debian 10 and 11 ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Your OS, ${fullrel}, does not appear to be supported. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Exiting. ${NC}" + else + dialog --keep-tite --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "T-RMM server supported OS versions: Ubuntu 20.04 and 22.04, and Debian 10 and 11.\n\nYour OS, ${fullrel}, does not appear to be supported.\n\nExiting." 0 0 + clear -x + fi + exit 1 + fi +} + +# Check language/locale +checkLocale() +{ + if [[ "$LANG" != *".UTF-8" ]]; then + if [ "$autoinstall" == "1" ]; then + echo -e "${RED} Error: ${NC}" | tee -a "${currentlog}" + echo -e "${RED} System locale must be .UTF-8, not ${LANG}. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Run the following command and change the default locale to your language of choice: ${NC}" + echo -e "${RED} sudo dpkg-reconfigure locales ${NC}" + echo -e "${RED} You will need to log out and back in for changes to take effect, then re-run this script. ${NC}" + echo -e "${RED} Exiting. ${NC}" + else + dialog --keep-tite --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "System locale must be .UTF-8, not ${LANG}.\nRun the following command and change the default locale to your language of choice:\n\nsudo dpkg-reconfigure locales\n\nYou will need to log out and back in for changes to take effect, then re-run this script.\n\nExiting." 0 0 + clear -x + fi + exit 1 + fi +} + +# Check total system memory +checkTotalSystemMemory() +{ + local totsysmemory="" + totsysmemory=$(grep MemTotal /proc/meminfo | awk '{print $2 / 1024 / 1000}' | cut -d '.' -f1) + + if [ "$totsysmemory" -ge 3 ]; then + return + else + if [ "$autoinstall" == "1" ]; then + echo -e "${RED} Error: System does not meet the minimum recommended RAM amount of 3GB. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Please add RAM to the system to prevent potential issues during install. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Re-run $THIS_SCRIPT after resolving the issue. ${NC}" + echo -e "${RED} Exiting... ${NC}" + exit 1 + elif [ "$autoinstall" != "1" ]; then + dialog --keep-tite --cr-wrap --yes-label "Continue" --no-label "Exit" --backtitle "Tactical RMM Installation and Maintenance Utility" --title "WARNING" --yesno "Amidaware recommends a minimum of 3GB of RAM for a T-RMM server.\n\nYour system does not meet the minimum recommended RAM amount of 3GB.\n\nYou may experience issues during install.\n\nIt's recommended to exit and add RAM to the system before continuing with installation,\nthough you may continue with the install now if you wish." 0 0 + case $? in + 0 ) return;; + + 1 ) clear -x + exit 1;; + esac + fi + fi +} + +# Check cpu core/thread count +checkCPUAndThreadCount() +{ + local threadcount="" + threadcount=$(nproc) + + if [ "$threadcount" -ge 2 ] || [ "$autoinstall" == "1" ]; then + return + else + dialog --keep-tite --cr-wrap --yes-label "Continue" --no-label "Exit" --backtitle "Tactical RMM Installation and Maintenance Utility" --title "WARNING" --yesno "T-RMM is CPU intensive, and Amidaware recommends 2+ CPUs.\n\nYour system does not meet the minimum recommended CPU core or thread count of 2+.\n\nYou may experience issues during install and use.\n\nIt's recommended to exit and upgrade the system before continuing with installation,\nthough you may continue with the install now if you wish." 0 0 + case $? in + 0 ) return;; + + 1 ) clear -x + exit 1;; + esac + fi +} \ No newline at end of file diff --git a/script-cfg/04-UserInput.cfg b/script-cfg/04-UserInput.cfg new file mode 100644 index 0000000000..110c552d40 --- /dev/null +++ b/script-cfg/04-UserInput.cfg @@ -0,0 +1,266 @@ +################### +# CFG file info # +################### + +CFG_VERSION="8" + + +################ +# User Input # +################ + +# Create usernames and passwords +generateUsersAndPass() +{ + if [ "$1" != "devprep" ]; then + # generate django key and admin url + DJANGO_SEKRET=$(cat /dev/urandom | tr -dc '[:alnum:]' | fold -w 80 | head -n 1) + ADMINURL=$(cat /dev/urandom | tr -dc '[:alnum:]' | fold -w 70 | head -n 1) + + # prompt to see if user wants to manually enter info or have it generated + if [ "$autoinstall" == "1" ]; then + meshpasswd=$(cat /dev/urandom | tr -dc '[:alnum:]' | fold -w 25 | head -n 1) + pgusername=$(cat /dev/urandom | tr -dc '[:lower:]' | fold -w 8 | head -n 1) + pgpw=$(cat /dev/urandom | tr -dc '[:alnum:]' | fold -w 20 | head -n 1) + meshusername=$(cat /dev/urandom | tr -dc '[:lower:]' | fold -w 8 | head -n 1) + else + dialog --cr-wrap --clear --yes-label "Automatic" --no-label "Manual" --backtitle "Tactical RMM Installation and Maintenance Utility" --title "User and Password Generation" --yesno "Would you like to have Postgresql and MeshCentral usernames and passwords automatically randomly generated, or enter your own manually?" 0 0 + case $? in + # auto gen info for user + 0 ) meshpasswd=$(cat /dev/urandom | tr -dc '[:alnum:]' | fold -w 25 | head -n 1) + pgusername=$(cat /dev/urandom | tr -dc '[:lower:]' | fold -w 8 | head -n 1) + pgpw=$(cat /dev/urandom | tr -dc '[:alnum:]' | fold -w 20 | head -n 1) + meshusername=$(cat /dev/urandom | tr -dc '[:lower:]' | fold -w 8 | head -n 1) + clear -x;; + + 1 ) userconfirm="n" + + # Get MeshCentral admin username + until [ "$userconfirm" == "y" ]; do + meshusername=$(dialog --cr-wrap --no-cancel --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "MeshCentral Admin" --inputbox "Enter the MeshCentral Admin username you wish to use:" 10 90 3>&1 1>&2 2>&3) + dialog --cr-wrap --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "MeshCentral Admin" --yesno "Is this correct?\n$meshusername" 0 0 + case $? in + 0 ) userconfirm="y";; + + 1 ) userconfirm="n" + derpDerp;; + esac + done + userconfirm="n" + + # Get MeshCentral admin password + meshpasswd="dont" + passinput="match" + until [ "$passinput" == "$meshpasswd" ]; do + meshpasswd=$(dialog --cr-wrap --no-cancel --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "MeshCentral Admin Password" --passwordbox "Enter the MeshCentral Admin password you wish to use:" 10 90 3>&1 1>&2 2>&3) + passinput=$(dialog --cr-wrap --no-cancel --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "MeshCentral Admin Password" --passwordbox "Re-enter the MeshCentral Admin password you wish to use:" 10 90 3>&1 1>&2 2>&3) + if [ "$passinput" != "$meshpasswd" ]; then + dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "Passwords do not match." 0 0 + derpDerp; + else + dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "SUCCESS" --msgbox "Passwords match." 0 0 + fi + done + + # Get Postgresql admin username + until [ "$userconfirm" == "y" ]; do + pgusername=$(dialog --cr-wrap --no-cancel --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Postgresql Admin" --inputbox "Enter the Postgresql Admin username you wish to use:" 10 90 3>&1 1>&2 2>&3) + dialog --cr-wrap --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Postgresql Admin" --yesno "Is this correct?\n$pgusername" 0 0 + case $? in + 0 ) userconfirm="y";; + + 1 ) userconfirm="n" + derpDerp;; + esac + done + userconfirm="n" + + # Get Postgresql admin password + pgpw="dont" + passinput="match" + until [ "$passinput" == "$pgpw" ]; do + pgpw=$(dialog --cr-wrap --no-cancel --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Postgresql Admin Password" --passwordbox "Enter the Postgresql Admin password you wish to use:" 10 90 3>&1 1>&2 2>&3) + passinput=$(dialog --cr-wrap --no-cancel --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Postgresql Admin Password" --passwordbox "Re-enter the Postgresql Admin password you wish to use:" 10 90 3>&1 1>&2 2>&3) + if [ "$passinput" != "$pgpw" ]; then + dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "Passwords do not match." 0 0 + derpDerp; + else + dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "SUCCESS" --msgbox "Passwords match." 0 0 + fi + done + clear -x;; + esac + fi + fi +} + +# Check subdomain provided is valid format +subdomainFormatCheck() +{ + if [[ $(grep "\." <<< "$1") ]] 2>/dev/null; then + echo -e "${RED} Error: The $2 hostname/subdomain you provided is in the incorrect format. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Do not include the root domain. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Run $THIS_SCRIPT -h help for further details. ${NC}" + echo -e "${RED} Exiting... ${NC}" + exit 1 + else + echo -e "${GREEN} $2 hostname format ok. ${NC}" | tee -a "${currentlog}" + fi +} + +# Check root domain format +rootDomainFormatCheck() +{ + if [[ $(grep "\." <<< "$1") ]] 2>/dev/null; then + echo -e "${GREEN} Root domain format ok. ${NC}" | tee -a "${currentlog}" + else + echo -e "${RED} Error: The root domain you provided is in the incorrect format. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Run $THIS_SCRIPT -h help for further details. ${NC}" + echo -e "${RED} Exiting... ${NC}" + exit 1 + fi +} + +# Check host/domain entries exist in DNS +checkDNSEntriesExist() +{ + if [[ $(dig +noall +answer "$1") ]] 2>/dev/null; then + echo -e "${GREEN} DNS record for $1 exists. ${NC}" | tee -a "${currentlog}" + else + echo -e "${RED} Error: $1 does not resolve via DNS. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Please correct the issue and run $THIS_SCRIPT again. ${NC}" + echo -e "${RED} Exiting... ${NC}" + exit 1 + fi +} + +# Check cert file exists +checkCertExists() +{ + if [ ! -f "$1" ]; then + echo -e "${RED} Error: The $2 path and/or filename you provided is invalid. ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Run $THIS_SCRIPT -h help for further details. ${NC}" + echo -e "${RED} Exiting... ${NC}" + exit 1 + else + echo -e "${GREEN} The $2 path you provided is valid. ${NC}" | tee -a "${currentlog}" + fi +} + +# Get email address +getEmailAddress() +{ + # Get Admin email + while [[ "$letsemail" != *[@]*[.]* ]]; do + letsemail=$(dialog --cr-wrap --no-cancel --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Enter Admin E-Mail Address" --inputbox "Enter a valid e-mail address for Django, MeshCentral, and LetsEncrypt:" 10 90 3>&1 1>&2 2>&3) + letsemail="$(translateToLowerCase $letsemail)" + if [[ "$letsemail" != *[@]*[.]* ]]; then + derpDerp; + dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "The e-mail address you entered is not correctly formatted.\n\nPlease try again." 0 0 + fi + done +} + +# Get host and domain info +getHostAndDomainInfo() +{ + if [ "$autoinstall" != "1" ]; then + hostsconfirm="n" + + until [ "$hostsconfirm" == "y" ]; do + rootdomain="none" + letsemail="none" + local dnsgood="n" + + # Get root domain + while [[ "$rootdomain" == "none" ]]; do + rootdomain=$(dialog --cr-wrap --no-cancel --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Enter Root Domain" --inputbox "Enter the root domain (eg example.com or example.co.uk):" 10 90 3>&1 1>&2 2>&3) + rootdomain="$(translateToLowerCase $rootdomain)" + if [[ $(grep "\." <<< "$rootdomain") ]] 2>/dev/null; then + echo -e "" + else + rootdomain="none" + derpDerp; + dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "The root domain you provided is in the incorrect format\n\nPlease try again." 0 0 + fi + done + + # Get backend hostname + until [ "$dnsgood" == "y" ]; do + rmmhost=$(dialog --cr-wrap --no-cancel --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Enter Backend Hostname" --inputbox "Enter the hostname for the backend (e.g. api):" 10 90 3>&1 1>&2 2>&3) + rmmhost="$(translateToLowerCase $rmmhost)" + if [[ $(grep "\." <<< "$rmmhost") ]] 2>/dev/null; then + derpDerp; + dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "The backend hostname/subdomain you provided is in the incorrect format.\n\nDo not include the root domain.\n\nPlease try again." 0 0 + dnsgood="n" + else + rmmdomain="$rmmhost.$rootdomain" + if [[ $(dig +noall +answer "$rmmdomain") ]] 2>/dev/null; then + dnsgood="y" + else + derpDerp; + dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "$rmmdomain does not resolve via DNS.\n\nPlease correct the issue before trying again." 0 0 + dnsgood="n" + fi + fi + done + dnsgood="n" + + # Get frontend hostname + until [ $dnsgood == "y" ]; do + frontendhost=$(dialog --cr-wrap --no-cancel --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Enter Frontend Hostname" --inputbox "Enter the hostname for the frontend (e.g. rmm):" 10 90 3>&1 1>&2 2>&3) + frontendhost="$(translateToLowerCase $frontendhost)" + if [[ $(grep "\." <<< "$frontendhost") ]] 2>/dev/null; then + derpDerp; + dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "The frontend hostname/subdomain you provided is in the incorrect format.\n\nDo not include the root domain.\n\nPlease try again." 0 0 + dnsgood="n" + else + frontenddomain="$frontendhost.$rootdomain" + if [[ $(dig +noall +answer "$frontenddomain") ]] 2>/dev/null; then + dnsgood="y" + else + derpDerp; + dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "$frontenddomain does not resolve via DNS.\n\nPlease correct the issue before trying again." 0 0 + dnsgood="n" + fi + fi + done + dnsgood="n" + + # Get MeshCentral hostname + until [ $dnsgood == "y" ]; do + meshhost=$(dialog --cr-wrap --no-cancel --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Enter MeshCentral Hostname" --inputbox "Enter the hostname for MeshCentral (e.g. mesh):" 10 90 3>&1 1>&2 2>&3) + meshhost="$(translateToLowerCase $meshhost)" + if [[ $(grep "\." <<< "$meshhost") ]] 2>/dev/null; then + derpDerp; + dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "The meshcentral hostname/subdomain you provided is in the incorrect format.\n\nDo not include the root domain.\n\nPlease try again." 0 0 + dnsgood="n" + else + meshdomain="$meshhost.$rootdomain" + if [[ $(dig +noall +answer "$meshdomain") ]] 2>/dev/null; then + dnsgood="y" + else + derpDerp; + dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "$meshdomain does not resolve via DNS.\n\nPlease correct the issue before trying again." 0 0 + dnsgood="n" + fi + fi + done + + if [ "$troubleshoot" != "1" ]; then + # Get Admin email + getEmailAddress; + + # Verify input + dialog --cr-wrap --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Confirm Input" --yesno "Is this correct?\n\nroot domain: $rootdomain\nbackend: $rmmhost.$rootdomain\nfrontend: $frontendhost.$rootdomain\nmeshcentral: $meshhost.$rootdomain\ne-mail address: $letsemail" 0 0 + case $? in + 0 ) hostsconfirm="y";; + + 1 ) hostsconfirm="n" + derpDerp;; + esac + fi + done + clear -x + fi +} \ No newline at end of file diff --git a/script-cfg/05-NetworkFunctions.cfg b/script-cfg/05-NetworkFunctions.cfg new file mode 100644 index 0000000000..4a358e190f --- /dev/null +++ b/script-cfg/05-NetworkFunctions.cfg @@ -0,0 +1,70 @@ +################### +# CFG file info # +################### + +CFG_VERSION="10" + + +####################### +# Network Functions # +####################### + +# Function to set site hostnames +setSiteHostname() +{ + local textcomp="0" + local runtimes="0" + local line1="" + local line2="" + until [ "$textcomp" == "" ]; do + runtimes=$(($runtimes+1)) + + # determine line number of localhost entry (this should always be the first entry) + line1=$(sed -n "/127.0.0.1/=" /etc/hosts) + + # determine next cumulative line number + line2=$(($line1+$runtimes)) + + # read total lines after localhost + textcomp=$(sed -n "$line2"p /etc/hosts) + + if [ "$textcomp" == "" ]; then + sudo sed -i "${runtimes} a\127.0.1.1\t$1.$2 $1" /etc/hosts 2>&1 | tee -a "${currentlog}" + fi + done + + return +} + +# Check hosts file, add hosts +configHosts() +{ + # Check if localhost exists in hosts file, if not add it + CHECK_LOCALHOST=$(grep "127.0.0.1" /etc/hosts | grep "localhost") + if ! [[ $CHECK_LOCALHOST ]]; then + echo -e "\n${GREEN} Adding localhost to hosts file... ${NC}\n" | tee -a "${currentlog}" + sudo sed -i "1i 127.0.0.1\tlocalhost" /etc/hosts 2>&1 | tee -a "${currentlog}" + fi + + # Check for t-rmm hosts definitions. If they exist in old form, replace, if not, insert them + CHECK_HOSTS=$(grep "127.0.1.1 $rmmdomain $frontenddomain $meshdomain" /etc/hosts) + if [[ $CHECK_HOSTS ]]; then + echo -e "\n${GREEN} Correcting subdomain entries... ${NC}\n" | tee -a "${currentlog}" + sudo sed -i "/127.0.1.1 $rmmdomain $frontenddomain $meshdomain/d" /etc/hosts 2>&1 | tee -a "${currentlog}" + setSiteHostname "$rmmhost" "$rootdomain"; + setSiteHostname "$frontendhost" "$rootdomain"; + setSiteHostname "$meshhost" "$rootdomain"; + elif [ "$1" != "devinstall" ]; then + echo -e "\n${GREEN} Adding subdomain entries... ${NC}\n" | tee -a "${currentlog}" + setSiteHostname "$rmmhost" "$rootdomain"; + setSiteHostname "$frontendhost" "$rootdomain"; + setSiteHostname "$meshhost" "$rootdomain"; + fi + + # Check if likely behind nat + BEHIND_NAT=false + IPV4=$(ip -4 addr | sed -ne 's|^.* inet \([^/]*\)/.* scope global.*$|\1|p' | head -1) + if echo "$IPV4" | grep -qE '^(10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.|192\.168)'; then + BEHIND_NAT=true + fi +} \ No newline at end of file diff --git a/script-cfg/06-InstallFunctions.cfg b/script-cfg/06-InstallFunctions.cfg new file mode 100644 index 0000000000..1d6f7cc2b8 --- /dev/null +++ b/script-cfg/06-InstallFunctions.cfg @@ -0,0 +1,363 @@ +################### +# CFG file info # +################### + +CFG_VERSION="8" + + +####################### +# Install Functions # +####################### + +# Install script prereqs +installPreReqs() +{ + if [ "$autoinstall" == "1" ]; then + if [ -z "$sudopass" ]; then + sudo apt-get update && sudo apt-get install -y curl vim wget dirmngr gnupg lsb-release ncurses-base ncurses-bin ncurses-doc ncurses-examples ncurses-term dialog libncurses5 libncursesw5 libncurses5-dev libncursesw5-dev 2>&1 | tee -a "${currentlog}" + elif [ -n "$sudopass" ]; then + echo "$sudopass" | sudo -S apt-get update && sudo apt-get install -y curl vim wget dirmngr gnupg lsb-release ncurses-base ncurses-bin ncurses-doc ncurses-examples ncurses-term dialog libncurses5 libncursesw5 libncurses5-dev libncursesw5-dev 2>&1 | tee -a "${currentlog}" + fi + elif [ "$autoinstall" != "1" ]; then + sudo apt-get update && sudo apt-get install -y curl vim wget dirmngr gnupg lsb-release ncurses-base ncurses-bin ncurses-doc ncurses-examples ncurses-term dialog libncurses5 libncursesw5 libncurses5-dev libncursesw5-dev 2>&1 | tee -a "${currentlog}" + fi +} + +# Install remaining prereqs +installAdditionalPreReqs() +{ + sudo apt-get install -y software-properties-common dnsutils openssl ca-certificates apt-transport-https gcc g++ make build-essential zlib1g-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev libbz2-dev git 2>&1 | tee -a "${currentlog}" +} + +# Configure repos for stuff +setInstallRepos() +{ + local tempcodename="$codename" + + # There is no Jammy repo yet so use Focal for Ubuntu 22.04 + if [ "$osname" == "ubuntu" ] && [ "$fullrelno" == "20.04" ]; then + mongodb_repo="deb [arch=amd64] https://repo.mongodb.org/apt/$osname $codename/mongodb-org/4.4 multiverse" + elif [ "$osname" == "ubuntu" ] && [ "$fullrelno" == "22.04" ]; then + codename="focal" + mongodb_repo="deb [arch=amd64] https://repo.mongodb.org/apt/$osname $codename/mongodb-org/4.4 multiverse" + codename="$tempcodename" + # There is no bullseye repo yet for mongo so just use Buster on Debian 11 + elif [ "$osname" == "debian" ] && [ "$relno" -eq 10 ]; then + mongodb_repo="deb [arch=amd64] https://repo.mongodb.org/apt/$osname $codename/mongodb-org/4.4 main" + elif [ "$osname" == "debian" ] && [ "$relno" -eq 11 ]; then + codename="buster" + mongodb_repo="deb [arch=amd64] https://repo.mongodb.org/apt/$osname $codename/mongodb-org/4.4 main" + codename="$tempcodename" + fi + + nginx_repo="deb https://nginx.org/packages/$osname/ $codename nginx" + nginx_src_repo="deb-src https://nginx.org/packages/$osname/ $codename nginx" + + postgresql_repo="deb [arch=amd64] https://apt.postgresql.org/pub/repos/apt/ $codename-pgdg main" +} + +# Install MongoDB +installMongo() +{ + if [ "$1" != "devinstall" ]; then + wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/mongo.gpg > /dev/null 2>&1 | tee -a "${currentlog}" + echo "$mongodb_repo" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list 2>&1 | tee -a "${currentlog}" + + # Temp fix for Ubuntu 22.04 openssl1.1.1 missing + if [ "$osname" == "ubuntu" ] && [ "$fullrelno" == "22.04" ]; then + wget -q http://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1l-1ubuntu1.5_amd64.deb 2>&1 | tee -a "${currentlog}" + wget -q http://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl-dev_1.1.1l-1ubuntu1.5_amd64.deb 2>&1 | tee -a "${currentlog}" + sudo dpkg -i libssl1.1_1.1.1l-1ubuntu1.5_amd64.deb + sudo dpkg -i libssl-dev_1.1.1l-1ubuntu1.5_amd64.deb + rm libssl1.1_1.1.1l-1ubuntu1.5_amd64 + rm libssl-dev_1.1.1l-1ubuntu1.5_amd64.deb + fi + sudo apt-get update && sudo apt-get install -y mongodb-org 2>&1 | tee -a "${currentlog}" + sudo systemctl enable mongod 2>&1 | tee -a "${currentlog}" + sudo systemctl restart mongod 2>&1 | tee -a "${currentlog}" + sleep 5 + fi +} + +# Install NodeJS +installNodeJS() +{ + if [ "$1" != "devinstall" ]; then + if [ "$1" == "update" ]; then + CURRENT_NODE_VER=$(node --version | cut -d "v" -f2 | cut -d "." -f1) + if [ "$CURRENT_NODE_VER" != "$NODE_MAJOR_VER" ]; then + echo -e "\n${GREEN} Updating NodeJS to v${NODE_MAJOR_VER}... ${NC}\n" | tee -a "${currentlog}" + rm -rf /rmm/web/node_modules 2>&1 | tee -a "${currentlog}" + sudo systemctl stop meshcentral 2>&1 | tee -a "${currentlog}" + sudo apt remove -y nodejs 2>&1 | tee -a "${currentlog}" + sudo rm -rf /usr/lib/node_modules 2>&1 | tee -a "${currentlog}" + else + echo -e "\n${GREEN} NodeJS up to date. ${NC}\n" | tee -a "${currentlog}" + return + fi + fi + curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash - 2>&1 | tee -a "${currentlog}" + sudo apt-get update && sudo apt-get install -y nodejs 2>&1 | tee -a "${currentlog}" + sudo npm install -g npm 2>&1 | tee -a "${currentlog}" + if [ "$1" == "update" ]; then + sudo chown "${USER}:${USER}" -R /meshcentral 2>&1 | tee -a "${currentlog}" + cd /meshcentral + rm -rf node_modules/ 2>&1 | tee -a "${currentlog}" + npm install meshcentral@"${LATEST_MESH_VER}" 2>&1 | tee -a "${currentlog}" + sudo systemctl start meshcentral 2>&1 | tee -a "${currentlog}" + fi + fi +} + +# Install Redis +installRedis() +{ + sudo apt-get install -y redis 2>&1 | tee -a "${currentlog}" +} + +# Install Python +installPython() +{ + if [ "$1" == "update" ]; then + CURRENT_PY=$(python3.10 --version | cut -d " " -f2) + if [ "$CURRENT_PY" != "$PYTHON_VER" ]; then + echo -e "\n${GREEN} Updating to ${PYTHON_VER}... ${NC}\n" | tee -a "${currentlog}" + elif [ "$CURRENT_PY" == "$PYTHON_VER" ]; then + echo -e "\n${GREEN} Python is up to date. ${NC}\n" | tee -a "${currentlog}" + return + fi + fi + + if [ "$1" == "devinstall" ]; then + echo -e "\n${GREEN} Python already installed. ${NC}\n" | tee -a "${currentlog}" + return + else + numprocs=$(nproc) + cd ~ + wget https://www.python.org/ftp/python/"${PYTHON_VER}"/Python-"${PYTHON_VER}".tgz 2>&1 | tee -a "${currentlog}" + tar -xf Python-"${PYTHON_VER}".tgz 2>&1 | tee -a "${currentlog}" + cd Python-"${PYTHON_VER}" + ./configure --enable-optimizations 2>&1 | tee -a "${currentlog}" + make -j "$numprocs" 2>&1 | tee -a "${currentlog}" + sudo make altinstall 2>&1 | tee -a "${currentlog}" + cd ~ + sudo rm -rf Python-"${PYTHON_VER}" Python-"${PYTHON_VER}".tgz 2>&1 | tee -a "${currentlog}" + if [ "$1" == "devprep" ]; then + # 02-MiscFunctions + echo -e "\n${GREEN} All Prereqs installed. ${NC}\n" | tee -a "${currentlog}" + exit + fi + fi +} + +# Install Postgresql +installPostgresql() +{ + echo "$postgresql_repo" | sudo tee /etc/apt/sources.list.d/pgdg.list 2>&1 | tee -a "${currentlog}" + wget -qO - https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/postgresql.gpg > /dev/null 2>&1 | tee -a "${currentlog}" + sudo apt-get update && sudo apt-get install -y postgresql-14 2>&1 | tee -a "${currentlog}" + sleep 2 + sudo systemctl enable postgresql 2>&1 | tee -a "${currentlog}" + sudo systemctl restart postgresql 2>&1 | tee -a "${currentlog}" + sleep 5 +} + +# Install NATS +installNats() +{ + if [ "$1" == "update" ]; then + HAS_LATEST_NATS=$(/usr/local/bin/nats-server -version | cut -d " " -f2 | cut -d "v" -f2) + if [ "$HAS_LATEST_NATS" != "${NATS_SERVER_VER}" ]; then + echo -e "\n${GREEN} Updating nats to v${NATS_SERVER_VER}... ${NC}\n" | tee -a "${currentlog}" + else + echo -e "\n${GREEN} Nats is up to date. ${NC}\n" | tee -a "${currentlog}" + return + fi + fi + if [ "$1" == "install" ] || [ "$1" == "devinstall" ] || [ "$1" == "restore" ]; then + NATS_SERVER_VER=$(grep "^NATS_SERVER_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}') + fi + nats_tmp=$(mktemp -d -t nats-XXXXXXXXXX) + wget -q https://github.com/nats-io/nats-server/releases/download/v"${NATS_SERVER_VER}"/nats-server-v"${NATS_SERVER_VER}"-linux-amd64.tar.gz -P "${nats_tmp}" 2>&1 | tee -a "${currentlog}" + tar -xzf "${nats_tmp}"/nats-server-v"${NATS_SERVER_VER}"-linux-amd64.tar.gz -C "${nats_tmp}" 2>&1 | tee -a "${currentlog}" + if [ "$1" == "update" ]; then + sudo rm -f /usr/local/bin/nats-server 2>&1 | tee -a "${currentlog}" + fi + sudo mv "${nats_tmp}"/nats-server-v"${NATS_SERVER_VER}"-linux-amd64/nats-server /usr/local/bin/ 2>&1 | tee -a "${currentlog}" + sudo chmod +x /usr/local/bin/nats-server 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" /usr/local/bin/nats-server 2>&1 | tee -a "${currentlog}" + rm -rf "${nats_tmp}" 2>&1 | tee -a "${currentlog}" +} + +# Install frontend +installFrontEnd() +{ + if [ "$1" == "update" ]; then + if [ -d /rmm/web ]; then + sudo rm -rf /rmm/web 2>&1 | tee -a "${currentlog}" + fi + if [ -d /var/www/rmm/dist ]; then + sudo rm -rf /var/www/rmm/dist 2>&1 | tee -a "${currentlog}" + fi + fi + + webtar="trmm-web-v${WEB_VERSION}.tar.gz" + wget -q https://github.com/amidaware/tacticalrmm-web/releases/download/v"${WEB_VERSION}"/"${webtar}" -O /tmp/"${webtar}" 2>&1 | tee -a "${currentlog}" + + if [ ! -d /var/www/rmm ]; then + sudo mkdir -p /var/www/rmm 2>&1 | tee -a "${currentlog}" + fi + + sudo tar -xzf /tmp/"${webtar}" -C /var/www/rmm 2>&1 | tee -a "${currentlog}" + echo "window._env_ = {PROD_URL: \"https://${rmmdomain}\"}" | sudo tee /var/www/rmm/dist/env-config.js > /dev/null 2>&1 | tee -a "${currentlog}" + sudo chown www-data:www-data -R /var/www/rmm/dist 2>&1 | tee -a "${currentlog}" + rm -f /tmp/"${webtar}" 2>&1 | tee -a "${currentlog}" +} + +# Install Nginx +installNginx() +{ + if [ "$1" != "devinstall" ]; then + if [ "$1" == "install" ] || [ "$1" == "devprep" ]; then + wget -qO - https://nginx.org/packages/keys/nginx_signing.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/nginx.gpg > /dev/null 2>&1 | tee -a "${currentlog}" + echo "$nginx_repo" | sudo tee /etc/apt/sources.list.d/nginx.list 2>&1 | tee -a "${currentlog}" + echo "$nginx_src_repo" | sudo tee -a /etc/apt/sources.list.d/nginx.list 2>&1 | tee -a "${currentlog}" + sudo apt-get update 2>&1 | tee -a "${currentlog}" + sudo apt-get install -y nginx 2>&1 | tee -a "${currentlog}" + sudo systemctl stop nginx 2>&1 | tee -a "${currentlog}" + sudo mkdir -p /etc/nginx/sites-available 2>&1 | tee -a "${currentlog}" + sudo mkdir -p /etc/nginx/sites-enabled 2>&1 | tee -a "${currentlog}" + elif [ "$1" == "updatepart1" ]; then + sudo cp /etc/nginx/sites-available/rmm.conf /etc/nginx/sites-available/rmm.conf.bak-"$rundate" 2>&1 | tee -a "${currentlog}" + sudo cp /etc/nginx/sites-available/meshcentral.conf /etc/nginx/sites-available/meshcentral.conf.bak-"$rundate" 2>&1 | tee -a "${currentlog}" + sudo cp /etc/nginx/sites-available/frontend.conf /etc/nginx/sites-available/frontend.conf.bak-"$rundate" 2>&1 | tee -a "${currentlog}" + if [ ! -f /etc/apt/trusted.gpg.d/nginx.gpg ]; then + wget -qO - https://nginx.org/packages/keys/nginx_signing.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/nginx.gpg > /dev/null 2>&1 | tee -a "${currentlog}" + fi + if [ ! -f /etc/apt/sources.list.d/nginx.list ]; then + echo "$nginx_repo" | sudo tee /etc/apt/sources.list.d/nginx.list 2>&1 | tee -a "${currentlog}" + echo "$nginx_src_repo" | sudo tee -a /etc/apt/sources.list.d/nginx.list 2>&1 | tee -a "${currentlog}" + sudo apt-get update 2>&1 | tee -a "${currentlog}" + sudo rm -f /etc/nginx/modules-enabled/*.conf 2>&1 | tee -a "${currentlog}" + sudo apt-get install -y nginx 2>&1 | tee -a "${currentlog}" + sudo apt-get -y --fix-broken install 2>&1 | tee -a "${currentlog}" + fi + sudo apt-get update 2>&1 | tee -a "${currentlog}" + sudo apt-get -y upgrade 2>&1 | tee -a "${currentlog}" + sudo systemctl stop nginx 2>&1 | tee -a "${currentlog}" + elif [ "$1" == "updatepart2" ]; then + # Check Nginx config + if ! sudo nginx -t > /dev/null 2>&1; then + sudo nginx -t 2>&1 | tee -a "${currentlog}" + echo -e "\n${RED} You have syntax errors in your nginx configs. See errors above. Please fix them and re-run this script. ${NC}\n" | tee -a "${currentlog}" + echo -e "\n${RED} Aborting... ${NC}\n" | tee -a "${currentlog}" + exit 1 + fi + elif [ "$1" == "restore" ]; then + wget -qO - https://nginx.org/packages/keys/nginx_signing.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/nginx.gpg > /dev/null 2>&1 | tee -a "${currentlog}" + echo "$nginx_repo" | sudo tee /etc/apt/sources.list.d/nginx.list 2>&1 | tee -a "${currentlog}" + echo "$nginx_src_repo" | sudo tee -a /etc/apt/sources.list.d/nginx.list 2>&1 | tee -a "${currentlog}" + sudo apt-get update 2>&1 | tee -a "${currentlog}" + sudo apt-get install -y nginx 2>&1 | tee -a "${currentlog}" + sudo systemctl stop nginx 2>&1 | tee -a "${currentlog}" + sudo rm -rf /etc/nginx 2>&1 | tee -a "${currentlog}" + sudo mkdir /etc/nginx 2>&1 | tee -a "${currentlog}" + sudo tar -xzf "$tmp_dir"/nginx/etc-nginx.tar.gz -C /etc/nginx 2>&1 | tee -a "${currentlog}" + sudo cp /etc/nginx/sites-available/rmm.conf /etc/nginx/sites-available/rmm.conf.bak-"$rundate" 2>&1 | tee -a "${currentlog}" + sudo cp /etc/nginx/sites-available/meshcentral.conf /etc/nginx/sites-available/meshcentral.conf.bak-"$rundate" 2>&1 | tee -a "${currentlog}" + sudo cp /etc/nginx/sites-available/frontend.conf /etc/nginx/sites-available/frontend.conf.bak-"$rundate" 2>&1 | tee -a "${currentlog}" + # Get previous domain info + # 02-MiscFunctions + getExistingDomainInfo; + fi + fi +} + +# Install NATS Api +installNatsApi() +{ + sudo cp /rmm/natsapi/bin/nats-api /usr/local/bin 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" /usr/local/bin/nats-api 2>&1 | tee -a "${currentlog}" + sudo chmod +x /usr/local/bin/nats-api 2>&1 | tee -a "${currentlog}" +} + +# Install MeshCentral +installMeshCentral() +{ + MESH_VER=$(grep "^MESH_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}') + + if [ "$1" == "install" ] || [ "$1" == "devinstall" ]; then + sudo mkdir -p /meshcentral/meshcentral-data 2>&1 | tee -a "${currentlog}" + elif [ "$1" == "restore" ]; then + sudo tar -xzf "$tmp_dir"/meshcentral/mesh.tar.gz -C / 2>&1 | tee -a "${currentlog}" + fi + sudo chown "${USER}:${USER}" -R /meshcentral 2>&1 | tee -a "${currentlog}" + cd /meshcentral + npm install meshcentral@"${MESH_VER}" 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" -R /meshcentral 2>&1 | tee -a "${currentlog}" +} + +# Install fail2ban +installFail2ban() +{ + sudo apt-get install -y fail2ban 2>&1 | tee -a "${currentlog}" + + # Copy default jail config to create override config + sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo truncate -s 0 /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + + # Write default jail config override + sudo chmod 777 /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf '[DEFAULT]' >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\nignoreip = 127.0.0.1/8" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\n\n" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf '[sshd]' >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\nenabled = true" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\nport = ssh" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\n\n" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf '[nginx-http-auth]' >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\nenabled = true" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\n\n" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf '[nginx-botsearch]' >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\nenabled = true" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\n\n" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf '[tacticalrmm]' >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\nenabled = true" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\nport = http,https" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\nfilter = tacticalrmm" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\n" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf 'action = iptables-allports[name=tactical]' >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\n" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf 'logpath = /rmm/api/tacticalrmm/tacticalrmm/private/log/access.log' >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\nmaxretry = 5" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\nbantime = 3600" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\nfindtime = 3600" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\n\n" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf '[recidive]' >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\nenabled = true" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo printf "\nbantime = 31536000 ; 1 year" >> /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + sudo chmod 644 /etc/fail2ban/jail.local 2>&1 | tee -a "${currentlog}" + + # Copy default app config to create override config + sudo cp /etc/fail2ban/fail2ban.conf /etc/fail2ban/fail2ban.local 2>&1 | tee -a "${currentlog}" + sudo truncate -s 0 /etc/fail2ban/fail2ban.local 2>&1 | tee -a "${currentlog}" + + # Write default app config override + sudo chmod 777 /etc/fail2ban/fail2ban.local 2>&1 | tee -a "${currentlog}" + sudo printf '[Definition]' >> /etc/fail2ban/fail2ban.local 2>&1 | tee -a "${currentlog}" + sudo printf "\ndbpurgeage = 31579200" >> /etc/fail2ban/fail2ban.local 2>&1 | tee -a "${currentlog}" + sudo printf "\nloglevel = INFO" >> /etc/fail2ban/fail2ban.local 2>&1 | tee -a "${currentlog}" + sudo chmod 644 /etc/fail2ban/fail2ban.local 2>&1 | tee -a "${currentlog}" + + # Add T-RMM definition + sudo echo '[Definition]' | sudo tee /etc/fail2ban/filter.d/tacticalrmm.conf 2>&1 | tee -a "${currentlog}" + sudo chmod 777 /etc/fail2ban/filter.d/tacticalrmm.conf 2>&1 | tee -a "${currentlog}" + sudo printf "\n" >> /etc/fail2ban/filter.d/tacticalrmm.conf 2>&1 | tee -a "${currentlog}" + sudo printf 'failregex = ^.*400.17.*$' >> /etc/fail2ban/filter.d/tacticalrmm.conf 2>&1 | tee -a "${currentlog}" + sudo printf "\n" >> /etc/fail2ban/filter.d/tacticalrmm.conf 2>&1 | tee -a "${currentlog}" + sudo printf 'ignoreregex = ^.*200.*$' >> /etc/fail2ban/filter.d/tacticalrmm.conf 2>&1 | tee -a "${currentlog}" + sudo chmod 644 /etc/fail2ban/filter.d/tacticalrmm.conf 2>&1 | tee -a "${currentlog}" + + # Restart Fail2ban + sudo systemctl restart fail2ban 2>&1 | tee -a "${currentlog}" +} \ No newline at end of file diff --git a/script-cfg/07-DatabaseFunctions.cfg b/script-cfg/07-DatabaseFunctions.cfg new file mode 100644 index 0000000000..cc5e0cf8f9 --- /dev/null +++ b/script-cfg/07-DatabaseFunctions.cfg @@ -0,0 +1,21 @@ +################### +# CFG file info # +################### + +CFG_VERSION="8" + + +######################## +# Database Functions # +######################## + +# Postgres DB creation +createPGDB() +{ + sudo -u postgres psql -c "CREATE DATABASE tacticalrmm" 2>&1 | tee -a "${currentlog}" + sudo -u postgres psql -c "CREATE USER ${pgusername} WITH PASSWORD '${pgpw}'" 2>&1 | tee -a "${currentlog}" + sudo -u postgres psql -c "ALTER ROLE ${pgusername} SET client_encoding TO 'utf8'" 2>&1 | tee -a "${currentlog}" + sudo -u postgres psql -c "ALTER ROLE ${pgusername} SET default_transaction_isolation TO 'read committed'" 2>&1 | tee -a "${currentlog}" + sudo -u postgres psql -c "ALTER ROLE ${pgusername} SET timezone TO 'UTC'" 2>&1 | tee -a "${currentlog}" + sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE tacticalrmm TO ${pgusername}" 2>&1 | tee -a "${currentlog}" +} \ No newline at end of file diff --git a/script-cfg/08-CertificateFunctions.cfg b/script-cfg/08-CertificateFunctions.cfg new file mode 100644 index 0000000000..c427a465ff --- /dev/null +++ b/script-cfg/08-CertificateFunctions.cfg @@ -0,0 +1,235 @@ +################### +# CFG file info # +################### + +CFG_VERSION="8" + + +########################### +# Certificate Functions # +########################### + + +# Check that acme challenge dns entry exists +acmeChallengeCheck() +{ + local acmegood="n" + + until [ "$acmegood" == "y" ]; do + if [[ $(dig +noall +answer -t TXT _acme-challenge."$1") ]] 2>/dev/null; then + acmegood="y" + echo -e "${GREEN} Acme Challenge TXT record available. ${NC}\n" | tee -a "${currentlog}" + echo -e "${GREEN} Continuing... ${NC}\n" | tee -a "${currentlog}" + else + echo -e "${YELLOW} Acme Challenge TXT record not available yet. ${NC}\n" | tee -a "${currentlog}" + echo -e "${YELLOW} Trying again in 30 sec... ${NC}\n" | tee -a "${currentlog}" + acmegood="n" + sleep 30 + fi + done +} + +# Import certs +importCerts() +{ + dialog --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Importing Certificates" --msgbox "A VIM window will open for you to paste the contents of your SSL certificate, private key, and CA Chain file, in that order.\n\nTo paste the contents, type i , then shift+insert.\n\nAfter pasting the contents, type esc , then shift-: , then wq , then enter to close the window and continue.\n\nPress enter when you're ready to begin:" 0 0 + + # Create directory if it doesn't exist + if [ ! -d "/etc/letsencrypt/live/${rootdomain}" ]; then + sudo mkdir -p /etc/letsencrypt/live/"${rootdomain}" 2>&1 | tee -a "${currentlog}" + fi + + # Check if cert file exists, if so, wipe and import data + if [ -f /etc/letsencrypt/live/"${rootdomain}"/fullchain.pem ]; then + sudo truncate -s 0 /etc/letsencrypt/live/"${rootdomain}"/fullchain.pem 2>&1 | tee -a "${currentlog}" + fi + sudo vim /etc/letsencrypt/live/"${rootdomain}"/fullchain.pem 2>&1 | tee -a "${currentlog}" + + # Check if key file exists, if so, wipe and import data + if [ -f /etc/letsencrypt/live/"${rootdomain}"/privkey.pem ]; then + sudo truncate -s 0 /etc/letsencrypt/live/"${rootdomain}"/privkey.pem 2>&1 | tee -a "${currentlog}" + fi + sudo vim /etc/letsencrypt/live/"${rootdomain}"/privkey.pem 2>&1 | tee -a "${currentlog}" + + # Check if ca file exists, if so, wipe and import data + if [ -f /etc/letsencrypt/live/"${rootdomain}"/chain.pem ]; then + sudo truncate -s 0 /etc/letsencrypt/live/"${rootdomain}"/chain.pem 2>&1 | tee -a "${currentlog}" + fi + sudo vim /etc/letsencrypt/live/"${rootdomain}"/chain.pem 2>&1 | tee -a "${currentlog}" + + # Fix dir/file perms and ownership + sudo chown root:"${USER}" -R /etc/letsencrypt 2>&1 | tee -a "${currentlog}" + sudo chmod 755 -R /etc/letsencrypt 2>&1 | tee -a "${currentlog}" +} + +# Enable webroot nginx conf +enableWebrootNginxConf() +{ + sudo sed -i "s/\#location/location/" /etc/nginx/sites-available/frontend.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/\#root/root/" /etc/nginx/sites-available/frontend.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/\#\}/\}/" /etc/nginx/sites-available/frontend.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/\#location/location/" /etc/nginx/sites-available/rmm.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/\#root/root/" /etc/nginx/sites-available/rmm.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/\#\}/\}/" /etc/nginx/sites-available/rmm.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/\#location/location/" /etc/nginx/sites-available/meshcentral.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/\#root/root/" /etc/nginx/sites-available/meshcentral.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/\#\}/\}/" /etc/nginx/sites-available/meshcentral.conf 2>&1 | tee -a "${currentlog}" +} + +# Generate certs +generateCerts() +{ + # Manual DNS method + if [ "$certtype" == "dns" ]; then + echo -e "\n${GREEN} Getting wildcard certificate... ${NC}\n" | tee -a "${currentlog}" + + # Get initial DNS text entry + sudo certbot certonly --manual -d *."${rootdomain}" --agree-tos --no-bootstrap --preferred-challenges dns -m "${letsemail}" --no-eff-email 2>&1 | tee -a "${currentlog}" + + # Ensure TXT record has populated + acmeChallengeCheck "$rootdomain"; + + # Keep going until successful cert issue after adding DNS txt entry + while [[ $? -ne 0 ]]; do + sudo certbot certonly --manual -d *."${rootdomain}" --agree-tos --no-bootstrap --preferred-challenges dns -m "${letsemail}" --no-eff-email 2>&1 | tee -a "${currentlog}" + done + + # Fix dir/file perms and ownership + sudo chown root:"${USER}" -R /etc/letsencrypt 2>&1 | tee -a "${currentlog}" + sudo chmod 755 -R /etc/letsencrypt 2>&1 | tee -a "${currentlog}" + + # Webroot method + elif [ "$certtype" == "webroot" ]; then + echo -e "\n${GREEN} Getting webroot certificates... ${NC}\n" | tee -a "${currentlog}" + + # Edit nginx conf files + enableWebrootNginxConf; + sudo sed -i "s/ssl_stapling/\#ssl_stapling/" /etc/nginx/sites-available/frontend.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/ssl_stapling/\#ssl_stapling/" /etc/nginx/sites-available/rmm.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/ssl_stapling/\#ssl_stapling/" /etc/nginx/sites-available/meshcentral.conf 2>&1 | tee -a "${currentlog}" + + # Create folder + sudo mkdir -p /var/www/letsencrypt 2>&1 | tee -a "${currentlog}" + + # Start nginx + sudo systemctl start nginx 2>&1 | tee -a "${currentlog}" + + # Get webroot certs + sudo certbot certonly --webroot --webroot-path /var/www/letsencrypt/ --agree-tos -m "${letsemail}" --no-eff-email -d "${rmmdomain}" -d "${frontenddomain}" -d "${meshdomain}" 2>&1 | tee -a "${currentlog}" + + # Stop nginx + sudo systemctl stop nginx 2>&1 | tee -a "${currentlog}" + + # Switch from snakeoil to webroot + sudo sed -i "s/ssl\/certs\/ssl-cert-snakeoil.pem/letsencrypt\/live\/${rootdomain}\/fullchain.pem/" /etc/nginx/sites-available/rmm.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/ssl\/private\/ssl-cert-snakeoil.key/letsencrypt\/live\/${rootdomain}\/privkey.pem/" /etc/nginx/sites-available/rmm.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/\#ssl_stapling/ssl_stapling/" /etc/nginx/sites-available/rmm.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/ssl\/certs\/ssl-cert-snakeoil.pem/letsencrypt\/live\/${rootdomain}\/fullchain.pem/" /etc/nginx/sites-available/frontend.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/ssl\/private\/ssl-cert-snakeoil.key/letsencrypt\/live\/${rootdomain}\/privkey.pem/" /etc/nginx/sites-available/frontend.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/\#ssl_stapling/ssl_stapling/" /etc/nginx/sites-available/frontend.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/ssl\/certs\/ssl-cert-snakeoil.pem/letsencrypt\/live\/${rootdomain}\/fullchain.pem/" /etc/nginx/sites-available/meshcentral.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/ssl\/private\/ssl-cert-snakeoil.key/letsencrypt\/live\/${rootdomain}\/privkey.pem/" /etc/nginx/sites-available/meshcentral.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/\#ssl_stapling/ssl_stapling/" /etc/nginx/sites-available/meshcentral.conf 2>&1 | tee -a "${currentlog}" + + # Create dns type folder if it doesn't exist + if [ ! -d "/etc/letsencrypt/live/${rootdomain}" ]; then + sudo mkdir -p /etc/letsencrypt/live/"${rootdomain}" 2>&1 | tee -a "${currentlog}" + fi + + # Create symlinks so cert paths don't need to be edited + sudo ln -s /etc/letsencrypt/live/"${rmmdomain}"/fullchain.pem /etc/letsencrypt/live/"${rootdomain}"/fullchain.pem 2>&1 | tee -a "${currentlog}" + sudo ln -s /etc/letsencrypt/live/"${rmmdomain}"/privkey.pem /etc/letsencrypt/live/"${rootdomain}"/privkey.pem 2>&1 | tee -a "${currentlog}" + sudo ln -s /etc/letsencrypt/live/"${rmmdomain}"/chain.pem /etc/letsencrypt/live/"${rootdomain}"/chain.pem 2>&1 | tee -a "${currentlog}" + + # Create renewal post hook to restart services after renewal + echo '#!/usr/bin/env bash' | sudo tee /etc/letsencrypt/renewal-hooks/post/001-restart-services.sh + sudo sed -i "/\#\!\/usr\/bin\/env bash/ a systemctl restart nginx meshcentral rmm celery celerybeat nats" /etc/letsencrypt/renewal-hooks/post/001-restart-services.sh 2>&1 | tee -a "${currentlog}" + sudo chmod +x /etc/letsencrypt/renewal-hooks/post/001-restart-services.sh 2>&1 | tee -a "${currentlog}" + + # Fix dir/file ownership and perms + sudo chown root:"${USER}" -R /etc/letsencrypt 2>&1 | tee -a "${currentlog}" + sudo chmod 755 -R /etc/letsencrypt 2>&1 | tee -a "${currentlog}" + sudo systemctl start nginx 2>&1 | tee -a "${currentlog}" + fi +} + +# Renew Certs +renewCerts() +{ + # generate certs and restart services + certtype="dns" + + # 04-UserInput + getEmailAddress; + + # 02-MiscFunctions + getExistingDomainInfo; + + generateCerts; + + # Restart services after cert generation + sudo systemctl restart nginx.service rmm.service celery celerybeat.service nats-api.service nats.service meshcentral.service 2>&1 | tee -a "${currentlog}" +} + +# Select DNS or Webroot +dnsOrWebroot() +{ + dialog --cr-wrap --clear --yes-label "Manual-DNS" --no-label "Webroot" --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Certificate Generation" --yesno "LetsEncrypt Manual-DNS method, or Webroot method?\n\nWebroot requires port 80 to be open publicly but allows auto-renewal.\n\nManual-DNS requires manual renewal, but can be used on internal only servers." 0 0 + case $? in + # Manual-DNS certs + 0 ) certtype="dns" + generateCerts;; + + # Webroot certs + 1 ) certtype="webroot";; + esac +} + +# Install Certbot and get initial certs +installCertbot() +{ + # Install Certbot + sudo apt-get install -y certbot 2>&1 | tee -a "${currentlog}" + if [ "$1" == "restore" ]; then + # Generate DH if it doesn't exist + if [ ! -f /etc/ssl/certs/dhparam.pem ]; then + sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048 2>&1 | tee -a "${currentlog}" + fi + return + fi + + # Skip if devinstall + if [ "$1" == "devinstall" ]; then + echo -e "\n${GREEN} Certificates should be in place. ${NC}\n" | tee -a "${currentlog}" + # Get preferred method if not automated + elif [ "$autoinstall" != "1" ]; then + dialog --cr-wrap --clear --yes-label "LetsEncrypt" --no-label "Import" --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Certificate Generation" --yesno "Would you like to have certificates generated using LetsEncrypt, or import your own existing certificates?" 0 0 + case $? in + # Get LetsEncrypt type + 0 ) dnsOrWebroot;; + + # Import certs using vim + 1 ) importCerts;; + esac + # Determine type and import or generate certs + elif [ "$autoinstall" == "1" ]; then + if [ "$certtype" == "import" ]; then + if [ ! -d "/etc/letsencrypt/live/${rootdomain}" ]; then + sudo mkdir -p /etc/letsencrypt/live/"${rootdomain}" 2>&1 | tee -a "${currentlog}" + fi + echo -e "\n${GREEN} Importing certificates... ${NC}\n" | tee -a "${currentlog}" + sudo cp "$sslcert" /etc/letsencrypt/live/"${rootdomain}"/fullchain.pem 2>&1 | tee -a "${currentlog}" + sudo cp "$sslkey" /etc/letsencrypt/live/"${rootdomain}"/privkey.pem 2>&1 | tee -a "${currentlog}" + sudo cp "$sslcacert" /etc/letsencrypt/live/"${rootdomain}"/chain.pem 2>&1 | tee -a "${currentlog}" + sudo chown root:"${USER}" -R /etc/letsencrypt 2>&1 | tee -a "${currentlog}" + sudo chmod 755 -R /etc/letsencrypt 2>&1 | tee -a "${currentlog}" + elif [ "$certtype" == "webroot" ]; then + echo -e "" + fi + fi + + # Generate DH if it doesn't exist + if [ ! -f /etc/ssl/certs/dhparam.pem ]; then + sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048 2>&1 | tee -a "${currentlog}" + fi +} \ No newline at end of file diff --git a/script-cfg/09-ConfigAndServiceFunctions.cfg b/script-cfg/09-ConfigAndServiceFunctions.cfg new file mode 100644 index 0000000000..b68921fda1 --- /dev/null +++ b/script-cfg/09-ConfigAndServiceFunctions.cfg @@ -0,0 +1,366 @@ +################### +# CFG file info # +################### + +CFG_VERSION="8" + + +####################################### +# Config file and service functions # +####################################### + +# Generate mesh configuration +createMeshConfig() +{ + # Copy default config file into place + sudo cp /rmm/default-configs/mesh/config.json /meshcentral/meshcentral-data/config.json 2>&1 | tee -a "${currentlog}" + + # Swap out generic placeholders + sudo sed -i "s/mesh.example.com/$meshdomain/" /meshcentral/meshcentral-data/config.json 2>&1 | tee -a "${currentlog}" +} + +# Generate local settings file +createLocalSettings() +{ + # Copy default config file into place + sudo cp /rmm/default-configs/python/local_settings.py /rmm/api/tacticalrmm/tacticalrmm/local_settings.py 2>&1 | tee -a "${currentlog}" + + # Swap out generic placeholders + sudo sed -i "s/DJANGO_SEKRET/$DJANGO_SEKRET/" /rmm/api/tacticalrmm/tacticalrmm/local_settings.py 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/api.example.com/$rmmdomain/" /rmm/api/tacticalrmm/tacticalrmm/local_settings.py 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/ADMINURL/$ADMINURL/" /rmm/api/tacticalrmm/tacticalrmm/local_settings.py 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/rmm.example.com/$frontenddomain/" /rmm/api/tacticalrmm/tacticalrmm/local_settings.py 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/pgusername/$pgusername/" /rmm/api/tacticalrmm/tacticalrmm/local_settings.py 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/pgpw/$pgpw/" /rmm/api/tacticalrmm/tacticalrmm/local_settings.py 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/meshusername/$meshusername/" /rmm/api/tacticalrmm/tacticalrmm/local_settings.py 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/mesh.example.com/$meshdomain/" /rmm/api/tacticalrmm/tacticalrmm/local_settings.py 2>&1 | tee -a "${currentlog}" +} + +# Backend configuration +configureBackend() +{ + SETUPTOOLS_VER=$(grep "^SETUPTOOLS_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}') + WHEEL_VER=$(grep "^WHEEL_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}') + userconfirm="n" + + if [ "$1" == "update" ]; then + CHECK_ADMIN_ENABLED=$(grep ADMIN_ENABLED /rmm/api/tacticalrmm/tacticalrmm/local_settings.py) + if ! [[ $CHECK_ADMIN_ENABLED ]]; then + sudo sed -i '$ a ADMIN_ENABLED = False' /rmm/api/tacticalrmm/tacticalrmm/local_settings.py 2>&1 | tee -a "${currentlog}" + fi + sudo cp /rmm/natsapi/bin/nats-api /usr/local/bin 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" /usr/local/bin/nats-api 2>&1 | tee -a "${currentlog}" + sudo chmod +x /usr/local/bin/nats-api 2>&1 | tee -a "${currentlog}" + if [ "${CURRENT_PIP_VER}" != "${LATEST_PIP_VER}" ] || [ "$UPDATE_TYPE" == "forced" ]; then + rm -rf /rmm/api/env 2>&1 | tee -a "${currentlog}" + cd /rmm/api + python3.10 -m venv env + source /rmm/api/env/bin/activate + cd /rmm/api/tacticalrmm + pip install --no-cache-dir --upgrade pip 2>&1 | tee -a "${currentlog}" + pip install --no-cache-dir setuptools=="${SETUPTOOLS_VER}" wheel=="${WHEEL_VER}" 2>&1 | tee -a "${currentlog}" + pip install --no-cache-dir -r requirements.txt 2>&1 | tee -a "${currentlog}" + else + source /rmm/api/env/bin/activate + cd /rmm/api/tacticalrmm + pip install -r /rmm/api/tacticalrmm/requirements.txt 2>&1 | tee -a "${currentlog}" + fi + python manage.py pre_update_tasks 2>&1 | tee -a "${currentlog}" + celery -A tacticalrmm purge -f 2>&1 | tee -a "${currentlog}" + python manage.py migrate 2>&1 | tee -a "${currentlog}" + python manage.py delete_tokens 2>&1 | tee -a "${currentlog}" + python manage.py collectstatic --no-input 2>&1 | tee -a "${currentlog}" + python manage.py reload_nats 2>&1 | tee -a "${currentlog}" + python manage.py load_chocos 2>&1 | tee -a "${currentlog}" + python manage.py create_installer_user 2>&1 | tee -a "${currentlog}" + python manage.py create_natsapi_conf 2>&1 | tee -a "${currentlog}" + python manage.py post_update_tasks 2>&1 | tee -a "${currentlog}" + rmmdomain=$(python manage.py get_config api) + WEB_VERSION=$(python manage.py get_config webversion) + deactivate + elif [ "$1" == "restore" ]; then + cd /rmm/api + python3.10 -m venv env + source /rmm/api/env/bin/activate + cd /rmm/api/tacticalrmm + pip install --no-cache-dir --upgrade pip 2>&1 | tee -a "${currentlog}" + pip install --no-cache-dir setuptools=="${SETUPTOOLS_VER}" wheel=="${WHEEL_VER}" 2>&1 | tee -a "${currentlog}" + pip install --no-cache-dir -r /rmm/api/tacticalrmm/requirements.txt 2>&1 | tee -a "${currentlog}" + python manage.py migrate 2>&1 | tee -a "${currentlog}" + python manage.py collectstatic --no-input 2>&1 | tee -a "${currentlog}" + python manage.py create_natsapi_conf 2>&1 | tee -a "${currentlog}" + python manage.py reload_nats 2>&1 | tee -a "${currentlog}" + python manage.py post_update_tasks 2>&1 | tee -a "${currentlog}" + rmmdomain=$(python manage.py get_config api) + WEB_VERSION=$(python manage.py get_config webversion) + deactivate + elif [ "$1" == "install" ] || [ "$1" == "devinstall" ]; then + cd /rmm/api + python3.10 -m venv env + source /rmm/api/env/bin/activate + cd /rmm/api/tacticalrmm + pip install --no-cache-dir --upgrade pip 2>&1 | tee -a "${currentlog}" + pip install --no-cache-dir setuptools=="${SETUPTOOLS_VER}" wheel=="${WHEEL_VER}" 2>&1 | tee -a "${currentlog}" + pip install --no-cache-dir -r /rmm/api/tacticalrmm/requirements.txt 2>&1 | tee -a "${currentlog}" + python manage.py migrate 2>&1 | tee -a "${currentlog}" + python manage.py collectstatic --no-input 2>&1 | tee -a "${currentlog}" + python manage.py create_natsapi_conf 2>&1 | tee -a "${currentlog}" + python manage.py load_chocos 2>&1 | tee -a "${currentlog}" + python manage.py load_community_scripts 2>&1 | tee -a "${currentlog}" + WEB_VERSION=$(python manage.py get_config webversion) + if [ "$autoinstall" == "1" ]; then + djangousername="$trmmuser" + echo "from accounts.models import User; User.objects.create_superuser('${trmmuser}', '${letsemail}', '${trmmpass}') if not User.objects.filter(username='${trmmuser}').exists() else 0;" | python manage.py shell + else + until [ "$userconfirm" == "y" ]; do + djangousername=$(dialog --cr-wrap --no-cancel --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Create Site/Django Admin Login" --inputbox "Please enter the RMM website and django admin username you wish to use:" 10 90 3>&1 1>&2 2>&3) + dialog --cr-wrap --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Create Site/Django Admin Login" --yesno "Is this correct?\n$djangousername" 0 0 + case $? in + 0 ) userconfirm="y";; + + 1 ) userconfirm="n" + derpDerp;; + esac + done + + userconfirm="n" + clear -x + python manage.py createsuperuser --username "${djangousername}" --email "${letsemail}" 2>&1 | tee -a "${currentlog}" + python manage.py create_installer_user 2>&1 | tee -a "${currentlog}" + RANDBASE=$(python manage.py generate_totp) + clear -x + python manage.py generate_barcode "${RANDBASE}" "${djangousername}" "${frontenddomain}" + deactivate + read -n 1 -s -r -p "Press any key to continue..." + echo -e "\n" + fi + fi +} + +# Create UWSGI config +createUwsgiConf() +{ + # Copy default config file into place + sudo cp /rmm/default-configs/uwsgi/app.ini /rmm/api/tacticalrmm/app.ini 2>&1 | tee -a "${currentlog}" +} + +# Create UWSGI service +createUwsgiService() +{ + # Copy default service file into place + sudo cp /rmm/service-definitions/rmm.service /etc/systemd/system/rmm.service 2>&1 | tee -a "${currentlog}" + + # Swap out generic placeholders + sudo sed -i "s/REPLACEME/${USER}/" /etc/systemd/system/rmm.service 2>&1 | tee -a "${currentlog}" +} + +# Create Daphne service +createDaphneService() +{ + # Copy default service file into place + sudo cp /rmm/service-definitions/daphne.service /etc/systemd/system/daphne.service 2>&1 | tee -a "${currentlog}" + + # Swap out generic placeholders + sudo sed -i "s/REPLACEME/${USER}/" /etc/systemd/system/daphne.service 2>&1 | tee -a "${currentlog}" +} + +# Create NATS service +createNatsService() +{ + # Copy default service file into place + sudo cp /rmm/service-definitions/nats.service /etc/systemd/system/nats.service 2>&1 | tee -a "${currentlog}" + + # Swap out generic placeholders + sudo sed -i "s/REPLACEME/${USER}/" /etc/systemd/system/nats.service 2>&1 | tee -a "${currentlog}" +} + +# Create NATS service +createNatsApiService() +{ + # Copy default service file into place + sudo cp /rmm/service-definitions/nats-api.service /etc/systemd/system/nats-api.service 2>&1 | tee -a "${currentlog}" + + # Swap out generic placeholders + sudo sed -i "s/REPLACEME/${USER}/" /etc/systemd/system/nats-api.service 2>&1 | tee -a "${currentlog}" +} + +# Create backend nginx conf +createDefaultNginxConf() +{ + # Backup original conf file + sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak-"$rundate" 2>&1 | tee -a "${currentlog}" + + # Copy default config file into place + sudo cp /rmm/default-configs/nginx/nginx.conf /etc/nginx/nginx.conf 2>&1 | tee -a "${currentlog}" +} + +# Create backend nginx conf +createBackendNginxConf() +{ + # Copy default config file into place + sudo cp /rmm/default-configs/nginx/rmm.conf /etc/nginx/sites-available/rmm.conf 2>&1 | tee -a "${currentlog}" + + # Swap out generic placeholders + sudo sed -i "s/api.example.com/$rmmdomain/" /etc/nginx/sites-available/rmm.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/rmm.example.com/$frontenddomain/" /etc/nginx/sites-available/rmm.conf 2>&1 | tee -a "${currentlog}" + if [ "$1" == "install" ] || [ "$1" == "devinstall" ]; then + if [ "$certtype" == "dns" ]; then + sudo sed -i "s/rootdomain/$rootdomain/" /etc/nginx/sites-available/rmm.conf 2>&1 | tee -a "${currentlog}" + elif [ "$certtype" == "webroot" ]; then + sudo sed -i "s/letsencrypt\/live\/rootdomain\/fullchain.pem/ssl\/certs\/ssl-cert-snakeoil.pem/" /etc/nginx/sites-available/rmm.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/letsencrypt\/live\/rootdomain\/privkey.pem/ssl\/private\/ssl-cert-snakeoil.key/" /etc/nginx/sites-available/rmm.conf 2>&1 | tee -a "${currentlog}" + fi + elif [ "$1" == "update" ] || [ "$1" == "restore" ]; then + sudo sed -i "s/rootdomain/$rootdomain/" /etc/nginx/sites-available/rmm.conf 2>&1 | tee -a "${currentlog}" + fi +} + +# Create Mesh nginx conf +createMeshNginxConf() +{ + # Copy default config file into place + sudo cp /rmm/default-configs/nginx/meshcentral.conf /etc/nginx/sites-available/meshcentral.conf 2>&1 | tee -a "${currentlog}" + + # Swap out generic placeholders + sudo sed -i "s/mesh.example.com/$meshdomain/" /etc/nginx/sites-available/meshcentral.conf 2>&1 | tee -a "${currentlog}" + if [ "$1" == "install" ] || [ "$1" == "devinstall" ]; then + if [ "$certtype" == "dns" ]; then + sudo sed -i "s/rootdomain/$rootdomain/" /etc/nginx/sites-available/meshcentral.conf 2>&1 | tee -a "${currentlog}" + elif [ "$certtype" == "webroot" ]; then + sudo sed -i "s/letsencrypt\/live\/rootdomain\/fullchain.pem/ssl\/certs\/ssl-cert-snakeoil.pem/" /etc/nginx/sites-available/meshcentral.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/letsencrypt\/live\/rootdomain\/privkey.pem/ssl\/private\/ssl-cert-snakeoil.key/" /etc/nginx/sites-available/meshcentral.conf 2>&1 | tee -a "${currentlog}" + fi + elif [ "$1" == "update" ] || [ "$1" == "restore" ]; then + sudo sed -i "s/rootdomain/$rootdomain/" /etc/nginx/sites-available/meshcentral.conf 2>&1 | tee -a "${currentlog}" + fi +} + +# Create Celery service +createCeleryService() +{ + # Copy default service file into place + sudo cp /rmm/service-definitions/celery.service /etc/systemd/system/celery.service 2>&1 | tee -a "${currentlog}" + + # Swap out generic placeholders + sudo sed -i "s/REPLACEME/${USER}/" /etc/systemd/system/celery.service 2>&1 | tee -a "${currentlog}" +} + +# Create Celery config +createCeleryConf() +{ + # Copy default config file into place + sudo cp /rmm/default-configs/celery/celery.conf /etc/conf.d/celery.conf 2>&1 | tee -a "${currentlog}" +} + +# Create CeleryBeat service +createCeleryBeatService() +{ + # Copy default service file into place + sudo cp /rmm/service-definitions/celerybeat.service /etc/systemd/system/celerybeat.service 2>&1 | tee -a "${currentlog}" + + # Swap out generic placeholders + sudo sed -i "s/REPLACEME/${USER}/" /etc/systemd/system/celerybeat.service 2>&1 | tee -a "${currentlog}" +} + +# Create MeshCentral service +createMeshCentralService() +{ + # Copy default service file into place + sudo cp /rmm/service-definitions/meshcentral.service /etc/systemd/system/meshcentral.service 2>&1 | tee -a "${currentlog}" + + # Swap out generic placeholders + sudo sed -i "s/REPLACEME/${USER}/" /etc/systemd/system/meshcentral.service 2>&1 | tee -a "${currentlog}" +} + +# Create Frontend Nginx config +createFrontendNginxConf() +{ + # Copy default config file into place + sudo cp /rmm/default-configs/nginx/frontend.conf /etc/nginx/sites-available/frontend.conf 2>&1 | tee -a "${currentlog}" + + # Swap out generic placeholders + sudo sed -i "s/rmm.example.com/$frontenddomain/" /etc/nginx/sites-available/frontend.conf 2>&1 | tee -a "${currentlog}" + if [ "$1" == "install" ] || [ "$1" == "devinstall" ]; then + if [ "$certtype" == "dns" ]; then + sudo sed -i "s/rootdomain/$rootdomain/" /etc/nginx/sites-available/frontend.conf 2>&1 | tee -a "${currentlog}" + elif [ "$certtype" == "webroot" ]; then + sudo sed -i "s/letsencrypt\/live\/rootdomain\/fullchain.pem/ssl\/certs\/ssl-cert-snakeoil.pem/" /etc/nginx/sites-available/frontend.conf 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/letsencrypt\/live\/rootdomain\/privkey.pem/ssl\/private\/ssl-cert-snakeoil.key/" /etc/nginx/sites-available/frontend.conf 2>&1 | tee -a "${currentlog}" + fi + elif [ "$1" == "update" ] || [ "$1" == "restore" ]; then + sudo sed -i "s/rootdomain/$rootdomain/" /etc/nginx/sites-available/frontend.conf 2>&1 | tee -a "${currentlog}" + fi +} + +# Enable MeshCentral service +enableMeshService() +{ + if [ "$1" != "update" ]; then + sudo systemctl enable meshcentral 2>&1 | tee -a "${currentlog}" + fi + sudo systemctl restart meshcentral 2>&1 | tee -a "${currentlog}" + sleep 3 + + # The first time we start meshcentral, it will need some time to generate certs and install plugins. + # This will take anywhere from a few seconds to a few minutes depending on the server's hardware + # We will know it's ready once the last line of the systemd service stdout is 'MeshCentral HTTP server running on port.....' + while ! [[ $CHECK_MESH_READY ]]; do + CHECK_MESH_READY=$(sudo journalctl -u meshcentral.service -b --no-pager | grep "MeshCentral HTTP server running on port") + echo -e "${YELLOW} Mesh Central not ready yet... ${NC}" | tee -a "${currentlog}" + sleep 3 + done +} + +# Generate Mesh Token +generateMeshToken() +{ + MESHTOKENKEY="$(node /meshcentral/node_modules/meshcentral --logintokenkey)" + + sudo sed -i '$ a MESH_TOKEN_KEY = "MESHTOKENKEY"' /rmm/api/tacticalrmm/tacticalrmm/local_settings.py 2>&1 | tee -a "${currentlog}" + sudo sed -i "s/MESHTOKENKEY/$MESHTOKENKEY/" /rmm/api/tacticalrmm/tacticalrmm/local_settings.py 2>&1 | tee -a "${currentlog}" +} + +# Configure Mesh user and group, restart service +configMeshUserGroup() +{ + # Stop service and switch dir + sudo systemctl stop meshcentral 2>&1 | tee -a "${currentlog}" + sleep 1 + cd /meshcentral + + # Create mesh user and make admin + node node_modules/meshcentral --createaccount "${meshusername}" --pass "${meshpasswd}" --email "${letsemail}" 2>&1 | tee -a "${currentlog}" + sleep 1 + node node_modules/meshcentral --adminaccount "${meshusername}" 2>&1 | tee -a "${currentlog}" + + # Restart mesh + sudo systemctl start meshcentral 2>&1 | tee -a "${currentlog}" + sleep 5 + + # Wait for mesh to be ready + while ! [[ $CHECK_MESH_READY2 ]]; do + CHECK_MESH_READY2=$(sudo journalctl -u meshcentral.service -b --no-pager | grep "MeshCentral HTTP server running on port") + echo -e "${YELLOW} Mesh Central not ready yet... ${NC}" | tee -a "${currentlog}" + sleep 3 + done + + # Create mesh device group + node node_modules/meshcentral/meshctrl.js --url wss://"${meshdomain}" --loginuser "${meshusername}" --loginpass "${meshpasswd}" AddDeviceGroup --name TacticalRMM 2>&1 | tee -a "${currentlog}" + sleep 1 +} + +# Configure and enable NATS service +enableNatsService() +{ + sudo systemctl enable nats.service 2>&1 | tee -a "${currentlog}" + cd /rmm/api/tacticalrmm + source /rmm/api/env/bin/activate + python manage.py initial_db_setup 2>&1 | tee -a "${currentlog}" + python manage.py reload_nats 2>&1 | tee -a "${currentlog}" + deactivate + sudo systemctl start nats.service 2>&1 | tee -a "${currentlog}" + + sleep 1 + sudo systemctl enable nats-api.service 2>&1 | tee -a "${currentlog}" + sudo systemctl start nats-api.service 2>&1 | tee -a "${currentlog}" +} \ No newline at end of file diff --git a/script-cfg/10-UpdateRestoreFunctions.cfg b/script-cfg/10-UpdateRestoreFunctions.cfg new file mode 100644 index 0000000000..60c751c5db --- /dev/null +++ b/script-cfg/10-UpdateRestoreFunctions.cfg @@ -0,0 +1,136 @@ +################### +# CFG file info # +################### + +CFG_VERSION="8" + + +########################################### +# Update and Restore specific functions # +########################################### + +# Check that user is same as during install +checkSameUser() +{ + strip="User=" + if [ "$1" == "update" ]; then + ORIGUSER=$(grep ${strip} /etc/systemd/system/rmm.service | sed -e "s/^${strip}//") + elif [ "$1" == "restore" ]; then + ORIGUSER=$(grep ${strip} "$tmp_dir"/systemd/rmm.service | sed -e "s/^${strip}//") + fi + if [ "$ORIGUSER" != "$USER" ]; then + if [ "$autoinstall" == "1" ]; then + echo -e "${RED} You must run this update script from the same user account used during install: ${ORIGUSER} ${NC}" | tee -a "${currentlog}" + echo -e "${RED} Exiting... ${NC}" | tee -a "${currentlog}" + if [ "$1" == "restore" ]; then + rm -rf "$tmp_dir" 2>&1 | tee -a "${currentlog}" + fi + exit 1 + elif [ "$autoinstall" != "1" ]; then + dialog --keep-tite --cr-wrap --backtitle "Tactical RMM Installation and Maintenance Utility" --title "ERROR" --msgbox "You must run this update script from the same user account used during install: ${ORIGUSER}\n\nExiting." 0 0 + if [ "$1" == "restore" ]; then + rm -rf "$tmp_dir" 2>&1 | tee -a "${currentlog}" + fi + clear -x + exit 1 + fi + fi +} + +# Check if T-RMM update is necessary +checkIfUpdate() +{ + TMP_SETTINGS=$(mktemp -p "" "rmmsettings_XXXXXXXXXX") + curl -s -L "${LATEST_SETTINGS_URL}" > "$TMP_SETTINGS" + + LATEST_TRMM_VER=$(grep "^TRMM_VERSION" "$TMP_SETTINGS" | awk -F'[= "]' '{print $5}') + CURRENT_TRMM_VER=$(grep "^TRMM_VERSION" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}') + + if [ "${CURRENT_TRMM_VER}" == "${LATEST_TRMM_VER}" ] && [ "$UPDATE_TYPE" == "standard" ]; then + echo -e "${GREEN} Already on latest version. Current version:${NC} ${YELLOW}${CURRENT_TRMM_VER}${NC} ${GREEN}Latest version:${NC} ${YELLOW}${LATEST_TRMM_VER} ${NC}" | tee -a "${currentlog}" + rm -f "$TMP_SETTINGS" 2>&1 | tee -a "${currentlog}" + exit 0 + fi +} + +# Get current versions of necessary included apps +checkAdditionalAppsVers() +{ + LATEST_MESH_VER=$(grep "^MESH_VER" "$TMP_SETTINGS" | awk -F'[= "]' '{print $5}') + LATEST_PIP_VER=$(grep "^PIP_VER" "$TMP_SETTINGS" | awk -F'[= "]' '{print $5}') + NATS_SERVER_VER=$(grep "^NATS_SERVER_VER" "$TMP_SETTINGS" | awk -F'[= "]' '{print $5}') + CURRENT_PIP_VER=$(grep "^PIP_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}') +} + +# Check CHECK_NATS_LIMITNOFILE, whatever that means +checkNatsLimitNoFile() +{ + CHECK_NATS_LIMITNOFILE=$(grep LimitNOFILE /etc/systemd/system/nats.service) + if ! [ "$CHECK_NATS_LIMITNOFILE" ]; then + sudo rm -f /etc/systemd/system/nats.service 2>&1 | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createNatsService; + sudo systemctl daemon-reload 2>&1 | tee -a "${currentlog}" + fi +} + +# Disable Redis append only +turnOffRedisAppendOnly() +{ + echo -e "${GREEN} Turning off redis aof ${NC}\n" | tee -a "${currentlog}" + sudo redis-cli config set appendonly no 2>&1 | tee -a "${currentlog}" + sudo redis-cli config rewrite 2>&1 | tee -a "${currentlog}" + sudo rm -f /var/lib/redis/appendonly.aof 2>&1 | tee -a "${currentlog}" +} + +# Update MeshCentral +updateMeshCentral() +{ + CURRENT_MESH_VER=$(cd /meshcentral/node_modules/meshcentral && node -p -e "require('./package.json').version") + if [ "${CURRENT_MESH_VER}" != "${LATEST_MESH_VER}" ] || [ "$UPDATE_TYPE" = "forced" ]; then + echo -e "${GREEN} Updating meshcentral from ${CURRENT_MESH_VER} to ${LATEST_MESH_VER} ${NC}\n" | tee -a "${currentlog}" + sudo systemctl stop meshcentral 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" -R /meshcentral 2>&1 | tee -a "${currentlog}" + cd /meshcentral + rm -rf node_modules/ 2>&1 | tee -a "${currentlog}" + npm install meshcentral@"${LATEST_MESH_VER}" 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" -R /meshcentral 2>&1 | tee -a "${currentlog}" + fi +} + +# Get backup location +getBackupFileLocation() +{ + if [ "$autoinstall" == "1" ]; then + return + elif [ "$autoinstall" != "1" ]; then + userconfirm="n" + + until [ "$userconfirm" == "y" ]; do + backupfile=$(dialog --cr-wrap --no-cancel --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Enter Path to Backup File" --inputbox "Enter the full path to the backup file, including filename:" 10 90 3>&1 1>&2 2>&3) + dialog --cr-wrap --clear --backtitle "Tactical RMM Installation and Maintenance Utility" --title "Backup File Path" --yesno "Is this correct?\n$backupfile" 0 0 + case $? in + 0 ) userconfirm="y";; + + 1 ) userconfirm="n" + derpDerp;; + esac + if [ ! -f "$backupfile" ]; then + userconfirm="n" + derpDerp; + else + userconfirm="y" + fi + done + userconfirm="n" + fi +} + +# Extract backup +extractBackup() +{ + echo -e "\n${GREEN} Unpacking backup... ${NC}\n" | tee -a "${currentlog}" + tmp_dir=$(mktemp -d -t tacticalrmm-XXXXXXXXXXXXXXXXXXXXX) + + tar -xf "$1" -C "$tmp_dir" 2>&1 | tee -a "${currentlog}" +} \ No newline at end of file diff --git a/script-cfg/11-TroubleshootingFunctions.cfg b/script-cfg/11-TroubleshootingFunctions.cfg new file mode 100644 index 0000000000..02b1dcab32 --- /dev/null +++ b/script-cfg/11-TroubleshootingFunctions.cfg @@ -0,0 +1,128 @@ +################### +# CFG file info # +################### + +CFG_VERSION="8" + + +############################### +# Troubleshooting functions # +############################### + +# Ping to test if domain is live +pingDomain() +{ + if ( ping -c 1 "$1" &> /dev/null ); then + echo -e "${GREEN} Verified $1 by ping. ${NC}\n" | tee -a "${currentlog}" + else + echo -e "${RED} $1 cannot be verified by ping. ${NC}\n" | tee -a "${currentlog}" + fi +} + +# Check IPs +checkIPisLive() +{ + # Resolve Locally used DNS server + locdns=$(resolvectl | grep 'Current DNS Server:' | cut -d: -f2 | awk '{ print $1}') + + locinputip=$(dig @"$locdns" +short "$1") + reminputip=$(dig @8.8.8.8 +short "$1") + + if [ "$locinputip" == "$reminputip" ]; then + echo -e "${GREEN} Success $1 is Locally Resolved: ${locinputip} Remotely Resolved: ${reminputip} ${NC}\n" | tee -a "${currentlog}" + else + echo -e "${YELLOW} Locally Resolved: ${locinputip} Remotely Resolved: ${reminputip} ${NC}\n" | tee -a "${currentlog}" + echo -e "${YELLOW} Your Local and Remote IP for $1 all agents will require non-public DNS to find TRMM server. ${NC}\n" | tee -a "${currentlog}" + fi +} + +# Check services status +readServicesStatus() +{ + rmmstatus=$(systemctl is-active rmm) + daphnestatus=$(systemctl is-active daphne) + celerystatus=$(systemctl is-active celery) + celerybeatstatus=$(systemctl is-active celerybeat) + nginxstatus=$(systemctl is-active nginx) + natsstatus=$(systemctl is-active nats) + natsapistatus=$(systemctl is-active nats-api) + meshcentralstatus=$(systemctl is-active meshcentral) + mongodstatus=$(systemctl is-active mongod) + postgresqlstatus=$(systemctl is-active postgresql) + redisserverstatus=$(systemctl is-active redis-server) +} + +# Verify services active +checkIfServiceActive() +{ + if [ "$1" = active ]; then + echo -e "${GREEN} Success $2 is Running. ${NC}\n" | tee -a "${currentlog}" + else + echo -e "${RED} $2 is not running. \(Tactical will not work without this\) ${NC}\n" | tee -a "${currentlog}" + fi +} + +# Check for open ports +isPortOpen() +{ + if ( nc -zv "$wanip" "$1" 2>&1 >/dev/null ); then + echo -e "${GREEN} $2 Port is open. ${NC}\n" | tee -a "${currentlog}" + else + echo -e "${YELLOW} $2 port is closed. \(you may want this if running locally only\) ${NC}\n" | tee -a "${currentlog}" + fi +} + +# Check proxy +checkProxy() +{ + echo -e "${GREEN} Checking For Proxy. ${NC}\n" | tee -a "${currentlog}" + echo -e "${YELLOW} ......this might take a while!! ${NC}" + + # Detect Proxy via cert + proxyext="$(openssl s_client -showcerts -servername "$remapiip" -connect "$remapiip":443 2>/dev/null | openssl x509 -inform pem -noout -text)" + proxyint="$(openssl s_client -showcerts -servername 127.0.1.1 -connect 127.0.1.1:443 2>/dev/null | openssl x509 -inform pem -noout -text)" + + if [[ "$proxyext" == "$proxyint" ]]; then + echo -e "${GREEN} No Proxy detected using Certificate. ${NC}\n" | tee -a "${currentlog}" + else + echo -e "${YELLOW} Proxy detected using Certificate. ${NC}\n" | tee -a "${currentlog}" + fi + + # Detect Proxy via IP + if [ "$wanip" != "$remrmmip" ]; then + echo -e "${YELLOW} Proxy detected using IP. ${NC}\n" | tee -a "${currentlog}" + else + echo -e "${GREEN} No Proxy detected using IP. ${NC}\n" | tee -a "${currentlog}" + fi +} + +# Check for valid cert +checkIfCertIsValid() +{ + echo -e "${YELLOW} Checking if SSL Certificate is up to date.${NC}\n" | tee -a "${currentlog}" + + # SSL Certificate check + # Check DNS-Manual or imported certs + if [ ! -f /etc/letsencrypt/renewal-hooks/post/001-restart-services.sh ]; then + cert="$(openssl verify -partial_chain -CAfile /etc/letsencrypt/live/"${rootdomain}"/chain.pem /etc/letsencrypt/live/"${rootdomain}"/fullchain.pem)" + certexp="$(openssl x509 -enddate -noout -in /etc/letsencrypt/live/"${rootdomain}"/fullchain.pem | cut -d "=" -f2)" + + if [[ "$cert" == *"OK"* ]]; then + echo -e "${GREEN} SSL Certificate for $rootdomain is fine. ${NC}\n" | tee -a "${currentlog}" + echo -e "${GREEN} SSL Certificate expires $certexp ${NC}\n" | tee -a "${currentlog}" + else + echo -e "${RED} SSL Certificate has expired or doesnt exist for $rootdomain. ${NC}\n" | tee -a "${currentlog}" + fi + # Check Webroot authenticated certs + elif [ -f /etc/letsencrypt/renewal-hooks/post/001-restart-services.sh ]; then + cert="$(openssl verify -partial_chain -CAfile /etc/letsencrypt/live/"${rootdomain}"/chain.pem /etc/letsencrypt/live/"${rootdomain}"/fullchain.pem)" + certexp="$(openssl x509 -enddate -noout -in /etc/letsencrypt/live/"${rootdomain}"/fullchain.pem | cut -d "=" -f2)" + + if [[ "$cert" == *"OK"* ]]; then + echo -e "${GREEN} SSL Certificate for $rmmdomain , $frontenddomain , and $meshdomain is fine. ${NC}\n" | tee -a "${currentlog}" + echo -e "${GREEN} SSL Certificate expires $certexp ${NC}\n" | tee -a "${currentlog}" + else + echo -e "${RED} SSL Certificate has expired or doesnt exist for $rmmdomain , $frontenddomain , and $meshdomain. ${NC}\n" | tee -a "${currentlog}" + fi + fi +} diff --git a/script-cfg/12-ParentFunctions.cfg b/script-cfg/12-ParentFunctions.cfg new file mode 100644 index 0000000000..2acdd09edb --- /dev/null +++ b/script-cfg/12-ParentFunctions.cfg @@ -0,0 +1,791 @@ +################### +# CFG file info # +################### + +CFG_VERSION="9" + + +###################### +# Parent Functions # +###################### + +# Main install function +mainInstall() +{ + # Set log file + currentlog="${installlog}" + + # Repo info for Postegres and Mongo + # 06-InstallFunctions + setInstallRepos; + + # Create usernames and passwords + # 04-UserInput + generateUsersAndPass "$INSTALL_TYPE"; + + # Clear screen + clear -x + + # Get host/domain info + # 04-UserInput + getHostAndDomainInfo; + + # Configure hosts file + echo -e "\n${GREEN} Configuring Hosts file... ${NC}\n" | tee -a "${currentlog}" + # 05-NetworkFunctions + configHosts "$INSTALL_TYPE"; + + # Certificate generation + echo -e "\n${GREEN} Installing Certbot... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installCertbot "$INSTALL_TYPE"; + + # Install Nginx + echo -e "\n${GREEN} Installing Nginx... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installNginx "$INSTALL_TYPE"; + + # Install NodeJS + echo -e "\n${GREEN} Installing NodeJS... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installNodeJS "$INSTALL_TYPE"; + + # Install and enable MongoDB + echo -e "\n${GREEN} Installing MongoDB... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installMongo "$INSTALL_TYPE"; + + # Install Python + echo -e "\n${GREEN} Installing Python ${PYTHON_VER}... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installPython "$INSTALL_TYPE"; + + # Installing Redis + echo -e "\n${GREEN} Installing redis... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installRedis; + + # Install and enable Postgresql + echo -e "\n${GREEN} Installing postgresql... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installPostgresql; + + # Postgres DB creation + echo -e "\n${GREEN} Creating database for the rmm... ${NC}\n" | tee -a "${currentlog}" + # 07-DatabaseFunctions + createPGDB; + + # Clone main repo + echo -e "\n${GREEN} Cloning primary repo... ${NC}\n" | tee -a "${currentlog}" + # 02-MiscFunctions + clonePrimaryRepo "$INSTALL_TYPE" "$REPO_URL" "$BRANCH"; + + # Clone scripts repo + echo -e "\n${GREEN} Cloning community scripts repo... ${NC}\n" | tee -a "${currentlog}" + # 02-MiscFunctions + cloneScriptsRepo "$INSTALL_TYPE" "$SCRIPTS_REPO_URL"; + + # Installing NATS + echo -e "\n${GREEN} Installing NATS... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installNats "$INSTALL_TYPE"; + + # Install MeshCentral + echo -e "\n${GREEN} Installing MeshCentral... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installMeshCentral "$INSTALL_TYPE"; + + # Create MeshCentral config + echo -e "\n${GREEN} Generating MeshCentral Config... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createMeshConfig; + + # Create local settings file + echo -e "\n${GREEN} Generating Local Settings... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createLocalSettings; + + # Install NATS-API and correct permissions + echo -e "\n${GREEN} Installing NATS API... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installNatsApi; + + # Install backend, configure primary admin user, setup admin 2fa + echo -e "\n${GREEN} Installing the backend... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + configureBackend "$INSTALL_TYPE"; + + # Create UWSGI config + echo -e "\n${GREEN} Creating UWSGI configuration... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createUwsgiConf; + + # Create RMM UWSGI systemd service + echo -e "\n${GREEN} Creating UWSGI service... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createUwsgiService; + + # Create Daphne systemd service + echo -e "\n${GREEN} Creating Daphne service... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createDaphneService; + + # Create NATS systemd service + echo -e "\n${GREEN} Creating NATS service... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createNatsService; + + # Create NATS-api systemd service + echo -e "\n${GREEN} Creating NATS-API service... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createNatsApiService; + + # Create Default Nginx site config + echo -e "\n${GREEN} Creating Default Nginx config... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createDefaultNginxConf "$INSTALL_TYPE"; + + # Create Backend Nginx site config + echo -e "\n${GREEN} Creating Backend Nginx config... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createBackendNginxConf "$INSTALL_TYPE"; + + # Create MeshCentral Nginx configuration + echo -e "\n${GREEN} Creating MeshCentral Nginx config... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createMeshNginxConf "$INSTALL_TYPE"; + + # Enable Mesh and RMM sites + sudo ln -s /etc/nginx/sites-available/rmm.conf /etc/nginx/sites-enabled/rmm.conf 2>&1 | tee -a "${currentlog}" + sudo ln -s /etc/nginx/sites-available/meshcentral.conf /etc/nginx/sites-enabled/meshcentral.conf 2>&1 | tee -a "${currentlog}" + + # Create conf directory + sudo mkdir /etc/conf.d 2>&1 | tee -a "${currentlog}" + + # Create Celery systemd service + echo -e "\n${GREEN} Creating Celery service... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createCeleryService; + + # Configure Celery service + echo -e "\n${GREEN} Creating Celery config... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createCeleryConf; + + # Create CeleryBeat systemd service + echo -e "\n${GREEN} Creating CeleryBeat service... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createCeleryBeatService; + + # Correct conf dir ownership + sudo chown "${USER}:${USER}" -R /etc/conf.d/ 2>&1 | tee -a "${currentlog}" + + # Create MeshCentral systemd service + echo -e "\n${GREEN} Creating MeshCentral service... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createMeshCentralService; + + # Update services info + sudo systemctl daemon-reload 2>&1 | tee -a "${currentlog}" + + # Verify and correct permissions + if [ -d ~/.npm ]; then + sudo chown -R "${USER}:${GROUP}" ~/.npm 2>&1 | tee -a "${currentlog}" + fi + + if [ -d ~/.config ]; then + sudo chown -R "${USER}:${GROUP}" ~/.config 2>&1 | tee -a "${currentlog}" + fi + + # Install frontend + echo -e "\n${GREEN} Installing the frontend... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installFrontEnd "$INSTALL_TYPE"; + + # Set front end Nginx config and enable + echo -e "\n${GREEN} Creating Frontend Nginx config... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createFrontendNginxConf "$INSTALL_TYPE"; + + # Enable Frontend site + sudo ln -s /etc/nginx/sites-available/frontend.conf /etc/nginx/sites-enabled/frontend.conf 2>&1 | tee -a "${currentlog}" + + # Webroot certificate fix + # 08-CertificateFunctions + if [ "$certtype" == "webroot" ]; then + generateCerts; + fi + + # Enable RMM, Daphne, Celery, and Nginx services + echo -e "\n${GREEN} Enabling Services... ${NC}\n" | tee -a "${currentlog}" + + for i in rmm.service daphne.service celery.service celerybeat.service nginx + do + sudo systemctl enable ${i} 2>&1 | tee -a "${currentlog}" + sudo systemctl stop ${i} 2>&1 | tee -a "${currentlog}" + sudo systemctl start ${i} 2>&1 | tee -a "${currentlog}" + done + sleep 5 + + # Enable MeshCentral service + echo -e "\n${GREEN} Starting meshcentral and waiting for it to install plugins... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + enableMeshService; + + # Generating MeshCentral key + echo -e "\n${GREEN} Generating meshcentral login token key... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + generateMeshToken; + + # Configuring MeshCentral admin user and device group, restart service + echo -e "\n${GREEN} Creating meshcentral account and group... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + configMeshUserGroup; + + # Enable and configure NATS service + echo -e "\n${GREEN} Starting NATS service... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + enableNatsService; + + # Disable django admin + sed -i 's/ADMIN_ENABLED = True/ADMIN_ENABLED = False/g' /rmm/api/tacticalrmm/tacticalrmm/local_settings.py 2>&1 | tee -a "${currentlog}" + + # Restart core services + echo -e "\n${GREEN} Restarting services... ${NC}\n" | tee -a "${currentlog}" + + for i in rmm.service daphne.service celery.service celerybeat.service + do + sudo systemctl stop ${i} 2>&1 | tee -a "${currentlog}" + sudo systemctl start ${i} 2>&1 | tee -a "${currentlog}" + done + + # Yay, we're done! + # 02-MiscFunctions + print_yellow "Installation complete!" + echo -e "\n${YELLOW} Access your rmm at: ${GREEN}https://${frontenddomain}${NC}\n" | tee -a no-peeking.log + echo -e "${YELLOW} Django admin url (disabled by default): ${GREEN}https://${rmmdomain}/${ADMINURL}/${NC}\n" | tee -a no-peeking.log + echo -e "${YELLOW} MeshCentral username: ${GREEN}${meshusername}${NC}" | tee -a no-peeking.log + echo -e "${YELLOW} MeshCentral password: ${GREEN}${meshpasswd}${NC}\n" | tee -a no-peeking.log + + sudo chmod 600 "$PWD"/no-peeking.log 2>&1 | tee -a "${currentlog}" + + if [ "$BEHIND_NAT" = true ]; then + echo -e "\n${YELLOW} Read below if your router does NOT support Hairpin NAT:${NC}\n" + echo -e "${GREEN} If you will be accessing the web interface of the RMM from the same LAN as this server,\n you'll need to make sure your 3 subdomains resolve to ${IPV4}${NC}\n" + echo -e "${GREEN} This also applies to any agents that will be on the same local network as the rmm.${NC}\n" + if [ "$certtype" == "webroot" ]; then + echo -e "\n${GREEN} You'll also need to setup port forwarding in your router on ports 80 and 443 if you have not done so already.${NC}\n" + else + echo -e "\n${GREEN} You'll also need to setup port forwarding in your router on port 443.${NC}\n" + fi + fi + # 02-MiscFunctions + print_yellow "Please refer to the github README for next steps." + + return +} + +# Update function +updateTRMM() +{ + # Set log file + currentlog="${updatelog}" + + # Check if user is same as during installation + # 10-UpdateRestoreFunctions + checkSameUser "$INSTALL_TYPE"; + + # Get current release version and check if update is necessary + # 10-UpdateRestoreFunctions + checkIfUpdate; + + # Get current versions of necessary included apps + # 10-UpdateRestoreFunctions + checkAdditionalAppsVers; + + # Clear screen + clear -x + + # Check CHECK_NATS_LIMITNOFILE, whatever that means + # 10-UpdateRestoreFunctions + checkNatsLimitNoFile; + + # Stop services + for i in nginx nats-api nats rmm daphne celery celerybeat + do + echo -e "${GREEN} Stopping ${i} service... ${NC}\n" | tee -a "${currentlog}" + sudo systemctl stop "${i}" 2>&1 | tee -a "${currentlog}" + done + + # Check if Python is up to date, if not, update + # 06-InstallFunctions + installPython "$INSTALL_TYPE"; + + # Check if NATS is up to date, if not, update + # 06-InstallFunctions + installNats "$INSTALL_TYPE"; + + # This does stuff + if [ -d ~/.npm ]; then + sudo rm -rf ~/.npm 2>&1 | tee -a "${currentlog}" + fi + + if [ -d ~/.cache ]; then + sudo rm -rf ~/.cache 2>&1 | tee -a "${currentlog}" + fi + + if [ -d ~/.config ]; then + sudo chown -R "${USER}:${GROUP}" ~/.config 2>&1 | tee -a "${currentlog}" + fi + + # Check NodeJS version, update if needed and update MeshCentral + echo -e "\n${GREEN} Updating NodeJS... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installNodeJS "$INSTALL_TYPE"; + + # Pull domain info from existing Nginx confs + # 02-MiscFunctions + getExistingDomainInfo; + + # Update from main repo + echo -e "\n${GREEN} Cloning primary repo... ${NC}\n" | tee -a "${currentlog}" + # 02-MiscFunctions + clonePrimaryRepo "$INSTALL_TYPE" "$REPO_URL" "$BRANCH"; + + # Update from community-scripts repo + echo -e "\n${GREEN} Cloning community scripts repo... ${NC}\n" | tee -a "${currentlog}" + # 02-MiscFunctions + cloneScriptsRepo "$INSTALL_TYPE" "$SCRIPTS_REPO_URL"; + + # Rebuild uwsgi config + rm -f /rmm/api/tacticalrmm/app.ini 2>&1 | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createUwsgiConf; + + # Apply updated Ownership and perms + sudo chown "${USER}:${USER}" -R /rmm 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" -R "${SCRIPTS_DIR}" 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" /var/log/celery 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" -R /etc/conf.d/ 2>&1 | tee -a "${currentlog}" + sudo chown "root:${USER}" -R /etc/letsencrypt 2>&1 | tee -a "${currentlog}" + sudo chmod 755 -R /etc/letsencrypt 2>&1 | tee -a "${currentlog}" + + # Backup Nginx settings + # 06-InstallFunctions + installNginx "updatepart1"; + + # Update Nginx conf files + # 09-ConfigAndServiceFunctions + createDefaultNginxConf "$INSTALL_TYPE"; + # 09-ConfigAndServiceFunctions + createMeshNginxConf "$INSTALL_TYPE"; + # 09-ConfigAndServiceFunctions + createFrontendNginxConf "$INSTALL_TYPE"; + # 09-ConfigAndServiceFunctions + createBackendNginxConf "$INSTALL_TYPE"; + + # Enable webroot cert method nginx conf if necessary + if [ -f /etc/letsencrypt/renewal-hooks/post/001-restart-services.sh ]; then + # 08-CertificateFunctions + enableWebrootNginxConf; + fi + + # Check additional Nginx settings and update + # 06-InstallFunctions + installNginx "updatepart2"; + + # Reconfigure backend + # 09-ConfigAndServiceFunctions + createCeleryConf; + # 09-ConfigAndServiceFunctions + configureBackend "$INSTALL_TYPE"; + + # Disable Redis append only + # 10-UpdateRestoreFunctions + turnOffRedisAppendOnly; + + # Update Frontend + # 06-InstallFunctions + installFrontEnd "$INSTALL_TYPE"; + + # Start services + for i in nats nats-api rmm daphne celery celerybeat nginx + do + echo -e "${GREEN} Starting ${i} service... ${NC}\n" | tee -a "${currentlog}" + sudo systemctl start "${i}" 2>&1 | tee -a "${currentlog}" + done + sleep 3 + + # Push agent updates + /rmm/api/env/bin/python /rmm/api/tacticalrmm/manage.py update_agents 2>&1 | tee -a "${currentlog}" + + # Update MeshCentral if necessary + updateMeshCentral; + # 09-ConfigAndServiceFunctions + createMeshConfig; + # 09-ConfigAndServiceFunctions + enableMeshService "$INSTALL_TYPE"; + + # Cleanup + rm -f "$TMP_SETTINGS" 2>&1 | tee -a "${currentlog}" + + # Bye-bye + # 02-MiscFunctions + print_green "Update finished!" + + return +} + +# Backup Function +backupTRMM() +{ + # Set log file + currentlog="${backuplog}" + + # Pull Postgres info + POSTGRES_USER=$(grep -w USER /rmm/api/tacticalrmm/tacticalrmm/local_settings.py | sed 's/^.*: //' | sed 's/.//' | sed -r 's/.{2}$//') + POSTGRES_PW=$(grep -w PASSWORD /rmm/api/tacticalrmm/tacticalrmm/local_settings.py | sed 's/^.*: //' | sed 's/.//' | sed -r 's/.{2}$//') + + # Check if rmmbackup folder exists, if not create it + if [ ! -d /rmmbackups ]; then + sudo mkdir /rmmbackups 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" /rmmbackups 2>&1 | tee -a "${currentlog}" + fi + + # Remove old MeshCentral backups + if [ -d /meshcentral/meshcentral-backup ]; then + rm -rf /meshcentral/meshcentral-backup/* 2>&1 | tee -a "${currentlog}" + fi + + # Remove old MeshCentral DB backups + if [ -d /meshcentral/meshcentral-coredumps ]; then + rm -f /meshcentral/meshcentral-coredumps/* 2>&1 | tee -a "${currentlog}" + fi + + # Set info for backup and folders + dt_now=$(date '+%Y_%m_%d__%H_%M_%S') + tmp_dir=$(mktemp -d -t tacticalrmm-XXXXXXXXXXXXXXXXXXXXX) + sysd="/etc/systemd/system" + + # Create temp backup subdirectories + mkdir -p "$tmp_dir"/meshcentral/mongo 2>&1 | tee -a "${currentlog}" + mkdir "$tmp_dir"/postgres 2>&1 | tee -a "${currentlog}" + mkdir "$tmp_dir"/certs 2>&1 | tee -a "${currentlog}" + mkdir "$tmp_dir"/nginx 2>&1 | tee -a "${currentlog}" + mkdir "$tmp_dir"/systemd 2>&1 | tee -a "${currentlog}" + mkdir "$tmp_dir"/rmm 2>&1 | tee -a "${currentlog}" + mkdir "$tmp_dir"/confd 2>&1 | tee -a "${currentlog}" + + # Dump Postgres database + pg_dump --dbname=postgresql://"${POSTGRES_USER}":"${POSTGRES_PW}"@127.0.0.1:5432/tacticalrmm | gzip -9 > "$tmp_dir"/postgres/db-"$dt_now".psql.gz 2>&1 | tee -a "${currentlog}" + + # Backup Mesh stuff + tar -czvf "$tmp_dir"/meshcentral/mesh.tar.gz --exclude=/meshcentral/node_modules /meshcentral 2>&1 | tee -a "${currentlog}" + mongodump --gzip --out="$tmp_dir"/meshcentral/mongo 2>&1 | tee -a "${currentlog}" + + # Backup certs + sudo tar -czvf "$tmp_dir"/certs/etc-letsencrypt.tar.gz -C /etc/letsencrypt . 2>&1 | tee -a "${currentlog}" + + # Backup Nginx configs + sudo tar -czvf "$tmp_dir"/nginx/etc-nginx.tar.gz -C /etc/nginx . 2>&1 | tee -a "${currentlog}" + + # Backup other config files + sudo tar -czvf "$tmp_dir"/confd/etc-confd.tar.gz -C /etc/conf.d . 2>&1 | tee -a "${currentlog}" + + # Copy service files + sudo cp "$sysd"/rmm.service "$sysd"/celery.service "$sysd"/celerybeat.service "$sysd"/meshcentral.service "$sysd"/nats.service "$sysd"/daphne.service "$tmp_dir"/systemd/ 2>&1 | tee -a "${currentlog}" + if [ -f "$sysd/nats-api.service" ]; then + sudo cp "$sysd"/nats-api.service "$tmp_dir"/systemd/ 2>&1 | tee -a "${currentlog}" + fi + + cat /rmm/api/tacticalrmm/tacticalrmm/private/log/django_debug.log | gzip -9 > "$tmp_dir"/rmm/debug.log.gz 2>&1 | tee -a "${currentlog}" + cp /rmm/api/tacticalrmm/tacticalrmm/local_settings.py "$tmp_dir"/rmm/ 2>&1 | tee -a "${currentlog}" + + tar -cf /rmmbackups/rmm-backup-"$dt_now".tar -C "$tmp_dir" . 2>&1 | tee -a "${currentlog}" + + # Remove temp files/folders + rm -rf "$tmp_dir" 2>&1 | tee -a "${currentlog}" + + echo -e "\n${GREEN} Backup saved to /rmmbackups/rmm-backup-$dt_now.tar ${NC}\n" | tee -a "${currentlog}" + + return +} + +# Restore T-RMM +restoreTRMM() +{ + # Set log file + currentlog="${restorelog}" + + # Repo info for Postegres and Mongo + # 06-InstallFunctions + setInstallRepos; + + # Get backup file location + # 10-UpdateRestoreFunctions + getBackupFileLocation; + + # Extract backup + # 10-UpdateRestoreFunctions + extractBackup "$backupfile"; + + # Check if original user + # 10-UpdateRestoreFunctions + checkSameUser "$INSTALL_TYPE"; + + # Install NodeJS + echo -e "\n${GREEN} Installing NodeJS... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installNodeJS; + + # Clone main repo + echo -e "\n${GREEN} Cloning primary repo... ${NC}\n" | tee -a "${currentlog}" + # 02-MiscFunctions + clonePrimaryRepo "$INSTALL_TYPE" "$REPO_URL" "$BRANCH"; + + # Clone scripts repo + echo -e "\n${GREEN} Cloning community scripts repo... ${NC}\n" | tee -a "${currentlog}" + # 02-MiscFunctions + cloneScriptsRepo "$INSTALL_TYPE" "$SCRIPTS_REPO_URL"; + + # Install Nginx and restore Nginx configuration + echo -e "\n${GREEN} Installing Nginx and restoring configuration... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installNginx "$INSTALL_TYPE"; + + # Restore hosts config + # 05-NetworkFunctions + configHosts; + + # Restore Certbot + echo -e "\n${GREEN} Installing Certbot... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installCertbot "$INSTALL_TYPE"; + + # Restoring existing certs + echo -e "\n${GREEN} Restoring certs... ${NC}\n" | tee -a "${currentlog}" + + sudo rm -rf /etc/letsencrypt 2>&1 | tee -a "${currentlog}" + sudo mkdir /etc/letsencrypt 2>&1 | tee -a "${currentlog}" + sudo tar -xzf "$tmp_dir"/certs/etc-letsencrypt.tar.gz -C /etc/letsencrypt 2>&1 | tee -a "${currentlog}" + sudo chown root:"$USER" -R /etc/letsencrypt 2>&1 | tee -a "${currentlog}" + sudo chmod 755 -R /etc/letsencrypt 2>&1 | tee -a "${currentlog}" + + # Recreate Nginx conf files + # 09-ConfigAndServiceFunctions + createDefaultNginxConf "$INSTALL_TYPE"; + # 09-ConfigAndServiceFunctions + createMeshNginxConf "$INSTALL_TYPE"; + # 09-ConfigAndServiceFunctions + createFrontendNginxConf "$INSTALL_TYPE"; + # 09-ConfigAndServiceFunctions + createBackendNginxConf "$INSTALL_TYPE"; + + # Enable webroot cert method nginx conf if necessary + if [ -f /etc/letsencrypt/renewal-hooks/post/001-restart-services.sh ]; then + # 08-CertificateFunctions + enableWebrootNginxConf; + fi + + # Restore Celery configs + echo -e "\n${GREEN} Restoring celery configs... ${NC}\n" | tee -a "${currentlog}" + + sudo mkdir /etc/conf.d 2>&1 | tee -a "${currentlog}" + sudo tar -xzf "$tmp_dir"/confd/etc-confd.tar.gz -C /etc/conf.d 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" -R /etc/conf.d 2>&1 | tee -a "${currentlog}" + + # Restoring services + echo -e "\n${GREEN} Restoring systemd services... ${NC}\n" | tee -a "${currentlog}" + + sudo cp "$tmp_dir"/systemd/* /etc/systemd/system/ 2>&1 | tee -a "${currentlog}" + sudo systemctl daemon-reload 2>&1 | tee -a "${currentlog}" + + # Install Python + echo -e "\n${GREEN} Installing Python ${PYTHON_VER}... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installPython; + + # Installing Redis + echo -e "\n${GREEN} Installing redis... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installRedis; + + # Install and enable Postgresql + echo -e "\n${GREEN} Installing postgresql... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installPostgresql; + + # Install and enable MongoDB + echo -e "\n${GREEN} Installing MongoDB... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installMongo; + + # Restore Mongo database + echo -e "\n${GREEN} Restoring MongoDB... ${NC}\n" | tee -a "${currentlog}" + mongorestore --gzip "$tmp_dir"/meshcentral/mongo 2>&1 | tee -a "${currentlog}" + + # Installing NATS + echo -e "\n${GREEN} Installing NATS... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installNats "$INSTALL_TYPE"; + + # Restore MeshCentral + echo -e "\n${GREEN} Restoring MeshCentral... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installMeshCentral "$INSTALL_TYPE"; + + # Restore UWSGI + echo -e "\n${GREEN} Restoring UWSGI configuration... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + createUwsgiConf; + + # Restoring other misc stuff + cp "$tmp_dir"/rmm/local_settings.py /rmm/api/tacticalrmm/tacticalrmm/ 2>&1 | tee -a "${currentlog}" + cp "$tmp_dir"/rmm/env /rmm/web/.env 2>&1 | tee -a "${currentlog}" + gzip -d "$tmp_dir"/rmm/debug.log.gz 2>&1 | tee -a "${currentlog}" + cp "$tmp_dir"/rmm/django_debug.log /rmm/api/tacticalrmm/tacticalrmm/private/log/ 2>&1 | tee -a "${currentlog}" + + # Install NATS-API + echo -e "\n${GREEN} Installing NATS API... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installNatsApi; + + # Restore Postgres database + echo -e "\n${GREEN} Restoring the Postgres database... ${NC}\n" | tee -a "${currentlog}" + + pgusername="$(grep -w USER /rmm/api/tacticalrmm/tacticalrmm/local_settings.py | sed 's/^.*: //' | sed 's/.//' | sed -r 's/.{2}$//')" + pgpw="$(grep -w PASSWORD /rmm/api/tacticalrmm/tacticalrmm/local_settings.py | sed 's/^.*: //' | sed 's/.//' | sed -r 's/.{2}$//')" + + sudo -u postgres psql -c "DROP DATABASE IF EXISTS tacticalrmm" 2>&1 | tee -a "${currentlog}" + # 07-DatabaseFunctions + createPGDB; + + gzip -d "$tmp_dir"/postgres/*.psql.gz 2>&1 | tee -a "${currentlog}" + PGPASSWORD=${pgpw} psql -h localhost -U "${pgusername}" -d tacticalrmm < "$tmp_dir"/postgres/db*.psql 2>&1 | tee -a "${currentlog}" + + # Restore Backend + echo -e "\n${GREEN} Restoring the backend... ${NC}\n" | tee -a "${currentlog}" + # 09-ConfigAndServiceFunctions + configureBackend "$INSTALL_TYPE"; + + # Start NATS + echo -e "\n${GREEN} Starting NATS... ${NC}\n" | tee -a "${currentlog}" + sudo systemctl enable nats.service 2>&1 | tee -a "${currentlog}" + sudo systemctl start nats.service 2>&1 | tee -a "${currentlog}" + + # Install frontend + echo -e "\n${GREEN} Installing the frontend... ${NC}\n" | tee -a "${currentlog}" + # 06-InstallFunctions + installFrontEnd "$INSTALL_TYPE"; + + # reset perms + sudo chown "${USER}:${USER}" -R /rmm 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" /var/log/celery 2>&1 | tee -a "${currentlog}" + sudo chown "${USER}:${USER}" -R /etc/conf.d/ 2>&1 | tee -a "${currentlog}" + sudo chown -R "${USER}:${GROUP}" /home/"${USER}"/.npm 2>&1 | tee -a "${currentlog}" + + # Update services info + sudo systemctl daemon-reload 2>&1 | tee -a "${currentlog}" + + # Enable RMM, Daphne, Celery, Nats-api, and Nginx services + echo -e "\n${GREEN} Enabling Services... ${NC}\n" | tee -a "${currentlog}" + + for i in celery.service celerybeat.service rmm.service daphne.service nats-api.service nginx + do + sudo systemctl enable ${i} 2>&1 | tee -a "${currentlog}" + sudo systemctl stop ${i} 2>&1 | tee -a "${currentlog}" + sudo systemctl start ${i} 2>&1 | tee -a "${currentlog}" + done + sleep 5 + + # Start MeshCentral + echo -e "\n${GREEN} Starting meshcentral... ${NC}\n" | tee -a "${currentlog}" + sudo systemctl enable meshcentral 2>&1 | tee -a "${currentlog}" + sudo systemctl start meshcentral 2>&1 | tee -a "${currentlog}" + + # Done!!!! + # 02-MiscFunctions + print_green 'Restore complete!' + + return +} + +# Troubleshooting utility +troubleShoot() +{ + # Set log file + currentlog="${checklog}" + + # Get existing domain info + if [ -f /etc/nginx/sites-available/rmm.conf ] && [ -f /etc/nginx/sites-available/rmm.conf ] && [ -f /etc/nginx/sites-available/rmm.conf ]; then + # 02-MiscFunctions + getExistingDomainInfo; + else + # 04-UserInput + getHostAndDomainInfo; + fi + + # Verify domains are live + # 11-TroubleshootingFunctions + pingDomain "$rmmdomain"; + pingDomain "$frontenddomain"; + pingDomain "$meshdomain"; + + # Verify IPs + echo -e "\n${GREEN} Checking IPs... ${NC}\n" | tee -a "${currentlog}" + + # 11-TroubleshootingFunctions + checkIPisLive "$rmmdomain"; + remapiip="${reminputip}" + checkIPisLive "$frontenddomain"; + checkIPisLive "$meshdomain"; + + # Get services status + # 11-TroubleshootingFunctions + readServicesStatus; + + # Verify services active + # 11-TroubleshootingFunctions + checkIfServiceActive "$rmmstatus" "RMM Service"; + checkIfServiceActive "$daphnestatus" "Daphne Service"; + checkIfServiceActive "$celerystatus" "Celery Service"; + checkIfServiceActive "$celerybeatstatus" "CeleryBeat Service"; + checkIfServiceActive "$nginxstatus" "Nginx Service"; + checkIfServiceActive "$natsstatus" "NATS Service"; + checkIfServiceActive "$natsapistatus" "NATS-API Service"; + checkIfServiceActive "$meshcentralstatus" "MeshCentral Service"; + checkIfServiceActive "$mongodstatus" "MongoD Service"; + checkIfServiceActive "$postgresqlstatus" "Postgresql Service"; + checkIfServiceActive "$redisserverstatus" "Redis-Server Service"; + + # Get WAN IP + wanip=$(dig @resolver4.opendns.com myip.opendns.com +short) + echo -e "\n${GREEN} WAN IP is $wanip. ${NC}\n" | tee -a "${currentlog}" + + # Check if ports are open + # 11-TroubleshootingFunctions + if [ -f /etc/letsencrypt/renewal-hooks/post/001-restart-services.sh ]; then + isPortOpen "80" "HTTP"; + fi + isPortOpen "443" "HTTPS"; + + # Checking Proxy + # 11-TroubleshootingFunctions + checkProxy; + + # Check for valid cert + # 11-TroubleshootingFunctions + checkIfCertIsValid; + + # Generate log summary + echo -e "\n${GREEN} Getting summary output of logs... ${NC}\n" | tee -a "${currentlog}" + + tail /rmm/api/tacticalrmm/tacticalrmm/private/log/django_debug.log | tee -a "${currentlog}" + echo -e "\n" | tee -a "${currentlog}" + tail /rmm/api/tacticalrmm/tacticalrmm/private/log/error.log | tee -a "${currentlog}" + echo -e "\n" | tee -a "${currentlog}" + # 02-MiscFunctions + print_yellow "You will have a log file called checklog_$rundate.log in the directory you ran this script from." + + return +} \ No newline at end of file diff --git a/service-definitions/celery.service b/service-definitions/celery.service new file mode 100644 index 0000000000..1a895ea010 --- /dev/null +++ b/service-definitions/celery.service @@ -0,0 +1,18 @@ +[Unit] +Description=Celery Service V2 +After=network.target redis-server.service postgresql.service + +[Service] +Type=forking +User=REPLACEME +Group=REPLACEME +EnvironmentFile=/etc/conf.d/celery.conf +WorkingDirectory=/rmm/api/tacticalrmm +ExecStart=/bin/sh -c '${CELERY_BIN} -A $CELERY_APP multi start $CELERYD_NODES --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel="${CELERYD_LOG_LEVEL}" $CELERYD_OPTS' +ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait $CELERYD_NODES --pidfile=${CELERYD_PID_FILE} --loglevel="${CELERYD_LOG_LEVEL}"' +ExecReload=/bin/sh -c '${CELERY_BIN} -A $CELERY_APP multi restart $CELERYD_NODES --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel="${CELERYD_LOG_LEVEL}" $CELERYD_OPTS' +Restart=always +RestartSec=10s + +[Install] +WantedBy=multi-user.target diff --git a/service-definitions/celerybeat.service b/service-definitions/celerybeat.service new file mode 100644 index 0000000000..5582be73fb --- /dev/null +++ b/service-definitions/celerybeat.service @@ -0,0 +1,16 @@ +[Unit] +Description=Celery Beat Service V2 +After=network.target redis-server.service postgresql.service + +[Service] +Type=simple +User=REPLACEME +Group=REPLACEME +EnvironmentFile=/etc/conf.d/celery.conf +WorkingDirectory=/rmm/api/tacticalrmm +ExecStart=/bin/sh -c '${CELERY_BIN} -A ${CELERY_APP} beat --pidfile=${CELERYBEAT_PID_FILE} --logfile=${CELERYBEAT_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL}' +Restart=always +RestartSec=10s + +[Install] +WantedBy=multi-user.target diff --git a/service-definitions/daphne.service b/service-definitions/daphne.service new file mode 100644 index 0000000000..8fbee4fded --- /dev/null +++ b/service-definitions/daphne.service @@ -0,0 +1,17 @@ +[Unit] +Description=django channels daemon +After=network.target + +[Service] +User=REPLACEME +Group=www-data +WorkingDirectory=/rmm/api/tacticalrmm +Environment="PATH=/rmm/api/env/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" +ExecStart=/rmm/api/env/bin/daphne -u /rmm/daphne.sock tacticalrmm.asgi:application +ExecStartPre=rm -f /rmm/daphne.sock +ExecStartPre=rm -f /rmm/daphne.sock.lock +Restart=always +RestartSec=3s + +[Install] +WantedBy=multi-user.target diff --git a/service-definitions/meshcentral.service b/service-definitions/meshcentral.service new file mode 100644 index 0000000000..bd5a528624 --- /dev/null +++ b/service-definitions/meshcentral.service @@ -0,0 +1,16 @@ +[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=/meshcentral +User=REPLACEME +Group=REPLACEME +Restart=always +RestartSec=10s + +[Install] +WantedBy=multi-user.target diff --git a/service-definitions/nats-api.service b/service-definitions/nats-api.service new file mode 100644 index 0000000000..90e26f8ec2 --- /dev/null +++ b/service-definitions/nats-api.service @@ -0,0 +1,14 @@ +[Unit] +Description=TacticalRMM Nats Api v1 +After=nats.service + +[Service] +Type=simple +ExecStart=/usr/local/bin/nats-api +User=REPLACEME +Group=REPLACEME +Restart=always +RestartSec=5s + +[Install] +WantedBy=multi-user.target diff --git a/service-definitions/nats.service b/service-definitions/nats.service new file mode 100644 index 0000000000..7d69c0e108 --- /dev/null +++ b/service-definitions/nats.service @@ -0,0 +1,18 @@ +[Unit] +Description=NATS Server +After=network.target + +[Service] +PrivateTmp=true +Type=simple +ExecStart=/usr/local/bin/nats-server -c /rmm/api/tacticalrmm/nats-rmm.conf +ExecReload=/usr/bin/kill -s HUP $MAINPID +ExecStop=/usr/bin/kill -s SIGINT $MAINPID +User=REPLACEME +Group=www-data +Restart=always +RestartSec=5s +LimitNOFILE=1000000 + +[Install] +WantedBy=multi-user.target diff --git a/service-definitions/rmm.service b/service-definitions/rmm.service new file mode 100644 index 0000000000..8967bc243a --- /dev/null +++ b/service-definitions/rmm.service @@ -0,0 +1,15 @@ +[Unit] +Description=tacticalrmm uwsgi daemon +After=network.target postgresql.service + +[Service] +User=REPLACEME +Group=www-data +WorkingDirectory=/rmm/api/tacticalrmm +Environment="PATH=/rmm/api/env/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" +ExecStart=/rmm/api/env/bin/uwsgi --ini app.ini +Restart=always +RestartSec=10s + +[Install] +WantedBy=multi-user.target