tacticalrmm/docker/tactical-cli

440 lines
13 KiB
Bash

#!/usr/bin/env bash
set -o nounset
set -o errexit
set -o pipefail
# FUNCTIONS
function ask_questions {
while [[ -z "$API_HOST" ]] && [[ "$API_HOST" != *[.]*[.]* ]]
do
echo -ne "Enter the subdomain for the backend (e.g. api.example.com): "
read API_HOST
done
echo "API_HOST is set to ${API_HOST}"
while [[ -z "$APP_HOST" ]] && [[ "$APP_HOST" != *[.]*[.]* ]]
do
echo -ne "Enter the subdomain for the frontend (e.g. rmm.example.com): "
read APP_HOST
done
echo "APP_HOST is set to ${APP_HOST}"
while [[ -z "$MESH_HOST" ]] && [[ "$MESH_HOST" != *[.]*[.]* ]]
do
echo -ne "Enter the subdomain for meshcentral (e.g. mesh.example.com): "
read MESH_HOST
done
echo "MESH_HOST is set to ${MESH_HOST}"
while [[ -z "$EMAIL" ]] && [[ "$EMAIL" != *[@]*[.]* ]]
do
echo -ne "Enter a valid email address for django and meshcentral: "
read EMAIL
done
echo "EMAIL is set to ${EMAIL}"
while [[ -z "$USERNAME" ]]
do
echo -ne "Set username for mesh and tactical login: "
read USERNAME
done
echo "USERNAME is set to ${USERNAME}"
while [[ -z "$PASSWORD" ]]
do
echo -ne "Set password for mesh and tactical password: "
read PASSWORD
done
echo "PASSWORD is set"
# check if let's encrypt or cert-keys options were set
if [[ -z "$LETS_ENCRYPT" ]] && [[ -z "$CERT_PRIV_FILE" ]] || [[ -z "$CERT_PUB_FILE" ]]; then
echo -ne "Create a let's encrypt certificate?[Y,n]: "
read USE_LETS_ENCRYPT
[[ "$USE_LETS_ENCRYPT" == "" ]] || [[ "$USE_LETS_ENCRYPT" ~= [Yy] ]] && LETS_ENCRYPT=1
if [[ -z "$LET_ENCRYPT" ]]; then
echo "Let's Encrypt will not be used"
echo -ne "Do you want to specify paths to a certificate public key and private key?[Y,n]: "
read PRIVATE_CERTS
if [[ "$PRIVATE_CERTS" == "" ]] || [[ "$PRIVATE_CERTS" ~= [yY] ]]; then
# check for valid public certificate file
while [[ ! -f $CERT_PUB_FILE ]]
do
echo -ne "Enter a valid full path to public key file: "
read CERT_PUB_FILE
done
# check for valid private key file
while [[ ! -f $CERT_PRIV_FILE ]]
do
echo -ne "Enter a valid full path to private key file: "
read CERT_PRIV_FILE
done
fi
fi
fi
}
function encode_certificates {
echo "Base64 encoding certificates"
CERT_PUB_BASE64="$(sudo base64 -w 0 ${CERT_PUB_FILE})"
CERT_PRIV_BASE64="$(sudo base64 -w 0 ${CERT_PRIV_FILE})"
}
function generate_env {
[[ -f "$ENV_FILE" ]] && echo "Env file already exists"; return 0;
local mongodb_user=$(cat /dev/urandom | tr -dc 'a-z' | fold -w 8 | head -n 1)
local mongodb_pass=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 20 | head -n 1)
local postgres_user=$(cat /dev/urandom | tr -dc 'a-z' | fold -w 8 | head -n 1)
local postgres_pass=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 20 | head -n 1)
echo "Generating env file in ${INSTALL_DIR}"
local config_file="$(cat << EOF
IMAGE_REPO=${REPO}
VERSION=${VERSION}
TRMM_USER=${USERNAME}
TRMM_PASS=${PASSWORD}
APP_HOST=${APP_HOST}
API_HOST=${API_HOST}
MESH_HOST=${MESH_HOST}
MESH_USER=${USERNAME}
MESH_PASS=${PASSWORD}
MONGODB_USER=${mongogb_user}
MONGODB_PASSWORD=${mongodb_pass}
POSTGRES_USER=${postgres_user}
POSTGRES_PASS=${postgres_pass}
EOF
)"
echo "${env_file}" > "$ENV_FILE"
}
function update_env_field {
}
function get_env_field {
local search_field="$1"
awk -F "=" '{if ($1==$search_field) { print $2" } }' $ENV_FILE
}
function initiate_letsencrypt {
echo "Starting Let's Encrypt"
ROOT_DOMAIN=$(echo ${API_HOST} | cut -d "." -f2- )
echo "Root domain is ${ROOTDOMAIN}"
sudo certbot certonly --manual -d *.${ROOT_DOMAIN} --agree-tos --no-bootstrap --manual-public-ip-logging-ok --preferred-challenges dns -m ${EMAIL} --no-eff-email
while [[ $? -ne 0 ]]
do
sudo certbot certonly --manual -d *.${ROOT_DOMAIN} --agree-tos --no-bootstrap --manual-public-ip-logging-ok --preferred-challenges dns -m ${EMAIL} --no-eff-email
done
CERT_PRIV_FILE=/etc/letsencrypt/live/${ROOT_DOMAIN}/privkey.pem
CERT_PUB_FILE=/etc/letsencrypt/live/${ROOT_DOMAIN}/fullchain.pem
}
# setup defaults
# keep track of first arg
FIRST_ARG="$1"
# defaults
REPO="tacticalrmm/"
BRANCH="master"
VERSION="latest"
# file locations
INSTALL_DIR=/opt/tactical
ENV_FILE=/opt/tactical/.env
# check prerequisites
command -v docker >/dev/null 2>&1 || { echo >&2 "Docker must be installed. Exiting..."; exit 1; }
command -v docker-compose >/dev/null 2>&1 || { echo >&2 "Docker Compose must be installed. Exiting..."; exit 1; }
command -v curl >/dev/null 2>&1 || { echo >&2 "Curl must be installed. Exiting..."; exit 1; }
command -v bash >/dev/null 2>&1 || { echo >&2 "Bash must be installed. Exiting..."; exit 1; }
# check for arguments
[ -z "$1" ] && echo >&2 "No arguments supplied. Exiting..."; exit 1;
# parse arguments
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
# install arg
-i|install)
[[ "$key" != "$FIRST_ARG" ]] && echo >&2 "install must be the first argument. Exiting.."; exit 1;
MODE="install"
shift # past argument
;;
# update arg
-u|update)
[[ "$key" != "$FIRST_ARG" ]] && echo >&2 "update must be the first argument. Exiting..."; exit 1;
MODE="update"
shift # past argument
;;
# backup arg
-b|backup)
[[ "$key" != "$FIRST_ARG" ]] && echo >&2 "backup must be the first argument. Exiting..."; exit 1;
MODE="backup"
shift # past argument
;;
# restore arg
-r|restore)
[[ "$key" != "$FIRST_ARG" ]] && echo >&2 "restore must be the first argument. Exiting..."; exit 1;
MODE="restore"
shift # past argument
;;
# update-cert arg
-c|update-cert)
[[ "$key" != "$FIRST_ARG" ]] && echo >&2 "update-cert must be the first argument. Exiting..."; exit 1;
MODE="update-cert"
shift # past argument
;;
# use-lets-encrypt arg
--use-lets-encrypt)
[[ -z "$MODE" ]] && echo >&2 "Missing install or update-cert as first argument. Exiting..."; exit 1;
[[ "$MODE" != "install" ]] || [[ "$MODE" != "update-cert" ]] && \
echo >&2 "--use-lets-encrypt option only valid for install and update-cert. Exiting..."; exit 1;
LETS_ENCRYPT=1
shift # past argument
;;
# cert-priv-file arg
--cert-priv-file)
[[ -z "$MODE" ]] && echo >&2 "Missing install or update-cert first argument. Exiting..."; exit 1;
[[ "$MODE" != "install" ]] || [[ "$MODE" != "update-cert" ]] && \
echo >&2 "--cert-priv-file option only valid for install and update-cert. Exiting..."; exit 1;
shift # past argument
[ ! -f "$key" ] && echo >&2 "Certificate private key file $key does not exist. Use absolute paths. Exiting..."; exit 1;
CERT_PRIV_FILE="$key"
shift # past value
;;
# cert-pub-file arg
--cert-pub-file)
[[ -z "$MODE" ]] && echo >&2 "Missing install or update-cert first argument. Exiting..."; exit 1;
[[ "$MODE" != "install" ]] || [[ "$MODE" != "update-cert" ]] && \
echo >&2 "--cert-pub-file option only valid for install and update-cert. Exiting..."; exit 1;
shift # past argument
[ ! -f "$key" ] && echo >&2 "Certificate public Key file ${key} does not exist. Use absolute paths. Exiting..."; exit 1;
CERT_PUB_FILE="$key"
shift # past value
;;
# local arg
--local)
[[ -z "$MODE" ]] && echo >&2 "Missing install or update first argument. Exiting..."; exit 1;
[[ "$MODE" != "install" ]] || [[ "$MODE" != "update" ]] && \
echo >&2 "--local option only valid for install and update. Exiting..."; exit 1;
REPO=""
shift # past argument
;;
# branch arg
--branch)
[[ -z "$MODE" ]] && echo >&2 "Missing install or update first argument. Exiting..."; exit 1;
[[ "$MODE" != "install" ]] || [[ "$MODE" != "update" ]] && \
echo >&2 "--branch option only valid for install and update. Exiting..."; exit 1;
shift # past argument
BRANCH="$key"
shift # past value
;;
# version arg
--version)
[[ -z "$MODE" ]] && echo >&2 "Missing install or update first argument. Exiting..."; exit 1;
[[ "$MODE" != "install" ]] || [[ "$MODE" != "update" ]] && \
echo ">&2 --version option only valid for install and update. Exiting..."; exit 1;
shift # past argument
VERSION="$key"
shift # past value
;;
# noninteractive arg
--noninteractive)
[[ -z "$MODE" ]] && echo >&2 "Missing install first argument. Exiting..."; exit 1;
[[ "$MODE" != "install" ]] && echo >&2 "--noninteractive option only valid for install. Exiting..."; exit 1;
NONINTERACTIVE=1
shift # past argument
;;
# app host arg
--app-host)
[[ -z "$MODE" ]] && echo >&2 "Missing install first argument. Exiting..."; exit 1;
[[ "$MODE" != "install" ]] && echo >&2 "--app-host option only valid for install. Exiting..."; exit 1;
shift # past argument
APP_HOST="$key"
shift # past value
;;
# api host arg
--api-host)
[[ -z "$MODE" ]] && echo >&2 "Missing install first argument. Exiting..."; exit 1;
[[ "$MODE" != "install" ]] && echo >&2 "--api-host option only valid for install. Exiting..."; exit 1;
shift # past argument
API_HOST="$key"
shift # past value
;;
# mesh host arg
--mesh-host)
[[ -z "$MODE" ]] && echo >&2 "Missing install first argument. Exiting..."; exit 1;
[[ "$MODE" != "install" ]] && echo >&2 "--mesh-host option only valid for install. Exiting..."; exit 1;
shift # past argument
MESH_HOST="$key"
shift # past value
;;
# tactical user arg
--tactical-user)
[[ -z "$MODE" ]] && echo >&2 "Missing install first argument. Exiting..."; exit 1;
[[ "$MODE" != "install" ]] && echo >&2 "--tactical-user option only valid for install. Exiting..."; exit 1;
shift # past argument
USERNAME="$key"
shift # past value
;;
# tactical password arg
--tactical-password)
[[ -z "$MODE" ]] && echo >&2 "Missing install first argument. Exiting..."; exit 1;
[[ "$MODE" != "install" ]] && echo >&2 "--tactical-password option only valid for install. Exiting..."; exit 1;
shift # past argument
PASSWORD="$key"
shift # past value
;;
# email arg
--email)
[[ -z "$MODE" ]] && echo >&2 "Missing install first argument. Exiting..."; exit 1;
[[ "$MODE" != "install" ]] && echo >&2 "--email option only valid for install. Exiting..."; exit 1;
shift # past argument
EMAIL="$key"
shift # past value
;;
# Unknown arg
*)
echo "Unknown argument ${$1}. Exiting..."
exit 1
;;
esac
done
# for install mode
if [[ "$MODE" == "install" ]]; then
echo "Starting installation in ${INSTALL_DIR}"
# move to install dir
mkdir -p "${INSTALL_DIR}"
cd "$INSTALL_DIR"
# pull docker-compose.yml file
echo "Downloading docker-compose.yml from branch ${branch}"
COMPOSE_FILE="https://raw.githubusercontent.com/wh1te909/tacticalrmm/${branch}/docker/docker-compose.yml"
if ! curl -sS "${COMPOSE_FILE}"; then
echo >&2 "Failed to download installation package ${COMPOSE_FILE}"
exit 1
fi
# check if install is noninteractive
if [[ -z "$NONINTERACTIVE" ]]; then
# ask user for information not supplied as arguments
ask_questions
else
echo "NonInteractive mode set."
# check for required noninteractive arguments
[[ -z "$API_HOST" ]] || \
[[ -z "$APP_HOST" ]] || \
[[ -z "$MESH_HOST" ]] || \
[[ -z "$EMAIL" ]] || \
[[ -z "$USERNAME" ]] || \
[[ -z "$PASSWORD" ]] && \
echo "You must supply additional arguments for noninteractive install."; exit 1;
fi
# if certificates are available base64 encode them
if [[ -n "$LET_ENCRYPT" ]] && [[ -z "$NONINTERACTIVE" ]]; then
initiate_letsencrypt
encode_certificates
elif [[ -n "$CERT_PUB_FILE" ]] && [[ -n "$CERT_PRIV_FILE" ]]; then
encode_certificates
# generate config file
generate_config
# generate env file
generate_env
echo "Configuration complete. Starting environment."
# start environment
docker-compose pull
docker-compose up -d
fi
# for update mode
if [[ "$MODE" == "update" ]]; then
[[ "$VERSION" != "latest" ]]
docker-compose pull
docker-compose up -d
fi
# for update cert mode
if [[ "$MODE" == "update-cert" ]]; then
# check for required parameters
[[ -z "$LET_ENCRYPT" ]] || \
[[ -z "$CERT_PUB_FILE" ]] && \
[[ -z "$CERT_PRIV_FILE" ]] && \
echo >&2 "Provide the --lets-encrypt option or use --cert-pub-file and --cert-priv-file. Exiting..."; exit;
if [[ -n "$LET_ENCRYPT" ]]; then
initiate_letsencrypt
encode_certificates
generate_env
elif [[ -n "$CERT_PUB_FILE" ]] && [[ -n "$CERT_PRIV_FILE" ]]; then
encode_certificates
generate_env
docker-compose restart
fi
# for backup mode
if [[ "$MODE" == "backup" ]]; then
echo "backup not yet implemented"
fi
# for restore mode
if [[ "$MODE" == "restore" ]] then;
echo "restore not yet implemented"
fi