#!/usr/bin/env bash #: Free TAK Server Installation Script #: Original Author: John #: Maintainers: #: - Sypher #: - nailshard #: #: This is an unmodified copy of a script from: #: https://github.com/FreeTAKTeam/FreeTAKHub-Installation/blob/main/scripts/easy_install.sh #: #: Licensed under the Eclipse Public License 2.0 (EPL-2.0) #: SPDX-License-Identifier: EPL-2.0 #: https://www.eclipse.org/legal/epl-2.0/ ############################################################################### # enforce failfast set -o errexit set -o nounset set -o pipefail # This disables Apt's "restart services" interactive dialog export DEBIAN_FRONTEND=noninteractive export NEEDRESTART_SUSPEND=1 NEEDRESTART= # trap or catch signals and direct execution to cleanup trap cleanup SIGINT SIGTERM ERR EXIT DEFAULT_REPO="https://github.com/FreeTAKTeam/FreeTAKHub-Installation.git" REPO=${REPO:-$DEFAULT_REPO} DEFAULT_BRANCH="main" BRANCH=${BRANCH:-$DEFAULT_BRANCH} CBRANCH=${CBRANCH:-} STABLE_OS_REQD="Ubuntu" STABLE_OS_VER_REQD="22.04" STABLE_CODENAME_REQD="jammy" LEGACY_OS_REQD="Ubuntu" LEGACY_OS_VER_REQD="20.04" LEGACY_CODENAME_REQD="focal" # the specific versions will be set later based on INSTALL_TYPE DEFAULT_INSTALL_TYPE="latest" INSTALL_TYPE="${INSTALL_TYPE:-$DEFAULT_INSTALL_TYPE}" PY3_VER_LEGACY="3.8" PY3_VER_STABLE="3.11" STABLE_FTS_VERSION="2.0.66" LEGACY_FTS_VERSION="1.9.9.6" LATEST_FTS_VERSION=$(curl -s https://pypi.org/pypi/FreeTAKServer/json | python3 -c "import sys, json; print(json.load(sys.stdin)['info']['version'])") FTS_VENV="${HOME}/fts.venv" DRY_RUN=0 hsep="*********************" # ############################################################################### # Add coloration to output for highlighting or emphasizing words ############################################################################### function setup_colors() { if [[ -t 2 ]] && [[ -z "${NO_COLOR-}" ]] && [[ "${TERM-}" != "dumb" ]]; then NOFORMAT='\033[0m' RED='\033[0;31m' GREEN='\033[0;32m' # ORANGE='\033[0;33m' # unused BLUE='\033[0;34m' # PURPLE='\033[0;35m' # unused # CYAN='\033[0;36m' # unused YELLOW='\033[1;33m' else NOFORMAT='' RED='' GREEN='' # ORANGE='' # unused BLUE='' # PURPLE='' # unused # CYAN='' # unused YELLOW='' fi } ############################################################################### # Print out helpful message. # Outputs: # Writes usage message to stdout ############################################################################### function usage() { cat <] Install Free TAK Server and components. Available options: -h, --help Print help -v, --verbose Print script debug info -c, --check Check for compatibility issues while installing --core Install FreeTAKServer, UI, and Web Map --latest [DEFAULT] Install latest version (v$LATEST_FTS_VERSION) -s, --stable Install latest stable version (v$STABLE_FTS_VERSION) -l, --legacy Install legacy version (v$LEGACY_FTS_VERSION) --repo Replaces with specified ZT Installer repository [DEFAULT ${DEFAULT_REPO}] --branch Use specified ZT Installer repository branch [DEFAULT main] --dev-test Sets TEST Envar to 1 --dry-run Sets up dependencies but exits before running any playbooks --ip-addr Explicitly set IP address (when http://ifconfig.me/ip is wrong) USAGE_TEXT exit } ############################################################################### # Cleanup here ############################################################################### function cleanup() { trap - SIGINT SIGTERM ERR EXIT # script cleanup here } ############################################################################### # Echo a message ############################################################################### function msg() { echo >&2 -e "${1-}" } ############################################################################### # Exit gracefully ############################################################################### function die() { local msg=$1 local code=${2-1} msg "$msg" [[ $code -eq 0 ]] || echo -e "Exiting. Installation NOT successful." # default exit status 1 exit "$code" } ############################################################################### # Parse parameters ############################################################################### function parse_params() { # The default 'apt verbosity' is verbose. Set it to quiet, since that's what our script assumes # unset this later if we want verbosity APT_VERBOSITY="-qq" while true; do case "${1-}" in --help | -h) usage exit 0 shift ;; --verbose | -v) echo "Verbose output" set -x NO_COLOR=1 GIT_TRACE=true GIT_CURL_VERBOSE=true GIT_SSH_COMMAND="ssh -vvv" unset APT_VERBOSITY # verbose is the default ANSIBLE_VERBOSITY="-vvvvv" shift ;; --check | -c) CHECK=1 shift ;; --core) CORE=1 shift ;; --stable | -s) INSTALL_TYPE="stable" shift ;; --latest) INSTALL_TYPE="latest" shift ;; --legacy | -l) INSTALL_TYPE="legacy" shift ;; -B) echo "${RED}${hsep}${hsep}${hsep}" echo -e "This option is not supported for public use.\n\ It will alter the version of this installer, which means:\n\ 1. it may make breaking system alterations\n\ 2. use at your own risk\n\ It is highly recommended that you do not continue\n\ unless you've selected this option for a specific reason" echo "${hsep}${hsep}${hsep}${NOFORMAT}" CBRANCH=$2 shift 2 ;; --repo) REPO=$2 shift 2 if [[ -d ~/FreeTAKHub-Installation ]] then rm -rf ~/FreeTAKHub-Installation fi ;; --branch) BRANCH=$2 shift 2 ;; --dev-test) TEST=1 shift ;; --dry-run) DRY_RUN=1 shift ;; --ip-addr) FTS_IP_CUSTOM=$2 shift 2 echo "Using the IP of ${FTS_IP_CUSTOM}" ;; --no-color) NO_COLOR=1 shift ;; -?*) die "ERROR: unknown option $1" ;; *) break ;; esac done } ############################################################################### # Update variables from defaults, user inputs or implied values ############################################################################### function set_versions() { case $INSTALL_TYPE in latest) export PY3_VER=$PY3_VER_STABLE export FTS_VERSION=$LATEST_FTS_VERSION export CFG_RPATH="core/configuration" export OS_REQD=$STABLE_OS_REQD export OS_VER_REQD=$STABLE_OS_VER_REQD export CODENAME=$STABLE_CODENAME_REQD ;; legacy) export PY3_VER=$PY3_VER_LEGACY export FTS_VERSION=$LEGACY_FTS_VERSION export CFG_RPATH="controllers/configuration" export OS_REQD=$LEGACY_OS_REQD export OS_VER_REQD=$LEGACY_OS_VER_REQD export CODENAME=$LEGACY_CODENAME_REQD ;; stable) export PY3_VER=$PY3_VER_STABLE export FTS_VERSION=$STABLE_FTS_VERSION export CFG_RPATH="core/configuration" export OS_REQD=$STABLE_OS_REQD export OS_VER_REQD=$STABLE_OS_VER_REQD export CODENAME=$STABLE_CODENAME_REQD ;; *) die "Unsupport install type: $INSTALL_TYPE" ;; esac } ############################################################################### # Do checks or skip unnecessary ones if non-interactive ############################################################################### function do_checks() { check_root if [[ -n "${CHECK-}" ]]; then check_os # check_architecture else WEBMAP_FORCE_INSTALL="webmap_force_install=true" fi if [[ -n "${TEST-}" ]]; then REPO="https://github.com/janseptaugust/FreeTAKHub-Installation.git" fi } ############################################################################### # Check if script was ran as root. This script requires root execution. ############################################################################### function check_root() { echo -e -n "${BLUE}Checking if this script is running as root...${NOFORMAT}" # check Effective User ID (EUID) for root user, which has an EUID of 0. if [[ "$EUID" -ne 0 ]]; then echo -e "${RED}ERROR${NOFORMAT}" die "This script requires running as root. Use sudo before the command." else echo -e "${GREEN}Success!${NOFORMAT}" fi } ############################################################################### # Check for supported operating system and warn user if not supported. ############################################################################### function check_os() { which apt-get >/dev/null if [[ $? -ne 0 ]]; then die "Could not locate apt... this installation method will not work" fi echo -e -n "${BLUE}Checking for supported OS...${NOFORMAT}" # freedesktop.org and systemd if [[ -f /etc/os-release ]]; then . /etc/os-release OS=${NAME:-unknown} VER=${VERSION_ID:-unknown} CODENAME=${VERSION_CODENAME} # linuxbase.org elif type lsb_release >/dev/null 2>&1; then OS=$(lsb_release -si) VER=$(lsb_release -sr) # for some Debian-based distros elif [[ -f /etc/lsb-release ]]; then . /etc/lsb-release OS=${DISTRIB_ID} VER=${DISTRIB_RELEASE} # older Debian-based distros elif [[ -f /etc/debian_version ]]; then OS=Debian VER=$(cat /etc/debian_version) # fallback else OS=$(uname -s) VER=$(uname -r) fi # check for supported OS and version and warn if not supported if [[ "${OS}" != "${OS_REQD}" ]] || [[ "${VER}" != "${OS_VER_REQD}" ]]; then echo -e "${YELLOW}WARNING${NOFORMAT}" echo "FreeTAKServer has only been tested on ${GREEN}${OS_REQD} ${OS_VER_REQD}${NOFORMAT}." echo -e "This machine is currently running: ${YELLOW}${OS} ${VER}${NOFORMAT}" echo "Errors may arise during installation or execution." read -r -e -p "Do you want to continue? [y/n]: " PROCEED # Default answer is "n" for NO. DEFAULT="n" # Set user-inputted value and apply default if user input is null. PROCEED="${PROCEED:-${DEFAULT}}" # Check user input to proceed or not. if [[ "${PROCEED}" != "y" ]]; then die "Answer was not y. Not proceeding." else echo -e "${GREEN}Proceeding...${NOFORMAT}" fi else echo -e "${GREEN}Success!${NOFORMAT}" echo -e "This machine is currently running: ${GREEN}${OS} ${VER}${NOFORMAT}" echo -e "Selected install type is: ${GREEN}${DEFAULT_INSTALL_TYPE}" fi } ############################################################################### # Check for supported architecture ############################################################################### function check_architecture() { echo -e -n "${BLUE}Checking for supported architecture...${NOFORMAT}" # check for non-Intel-based architecture here arch=$(uname --hardware-platform) # uname is non-portable, but we only target Ubuntu 20.04/22.04 if ! grep --ignore-case x86 <<<"${arch}" >/dev/null; then echo -e "${YELLOW}WARNING${NOFORMAT}" echo "Possible non-Intel architecture detected, ${name}" echo "Non-intel architectures may cause problems. The web map might not install." read -r -e -p "Do you want to force web map installation? [y/n]: " USER_INPUT # Default answer is "n" for NO. DEFAULT="n" # Set user-inputted value and apply default if user input is null. FORCE_WEBMAP_INSTALL_INPUT="${USER_INPUT:-${DEFAULT}}" # Check user input to force install web map or not if [[ "${FORCE_WEBMAP_INSTALL_INPUT}" != "y" ]]; then echo -e "${YELLOW}WARNING${NOFORMAT}: installer may skip web map installation." else WEBMAP_FORCE_INSTALL="webmap_force_install=true" echo -e "${YELLOW}WARNING${NOFORMAT}: forcing web map installation!" fi else # good architecture to install webmap echo -e "${GREEN}Success!${NOFORMAT}" echo "Intel architecture detected, ${name}" fi } ############################################################################### # Download dependencies ############################################################################### function download_dependencies() { echo -e "${BLUE}Downloading dependencies...${NOFORMAT}" echo -e "${BLUE}Adding the Ansible Personal Package Archive (PPA)...${NOFORMAT}" # dpkg --list | grep -q needrestart && NEEDRESTART=1 # [[ 0 -eq $NEEDRESTART ]] || apt-get remove --yes needrestart x=$(find /etc/apt/apt.conf.d -name "*needrestart*") if [[ -f $x ]]; then NEEDRESTART=$x mv $x $HOME/nr-conf-temp fi # Some programs need predictable names for certain libraries, so symlink x="pkg inst" for y in $x; do z=$(find /usr/lib -name apt_${y}.so) if [[ -z $z ]]; then z=$(find /usr/lib -name "apt_${y}.cpython*.so") ln -sf $z $(dirname $z)/apt_${y}.so fi done # Some Ubuntu installations do not have the software-properties-common # package by default, so install it if not installed which apt-add-repository >/dev/null || apt-get --yes install software-properties-common apt-add-repository -y ppa:ansible/ansible echo -e "${BLUE}Downloading package information from configured sources...${NOFORMAT}" apt-get -y ${APT_VERBOSITY--qq} update echo -e "${BLUE}Installing Ansible...${NOFORMAT}" apt-get -y ${APT_VERBOSITY--qq} install ansible echo -e "${BLUE}Installing Git...${NOFORMAT}" apt-get -y ${APT_VERBOSITY--qq} install git } ############################################################################### # We can install the python virtual environment here including the python interpreter. # This removes any need to deal with any circular requirement between # the installer, Ansible, and its dependencies (e.g. jinja2) and # the application being installed, FTS, and its dependencies. ############################################################################### function install_python_environment() { apt-get update apt-get install -y python3-pip python3-setuptools apt-get install -y python${PY3_VER}-dev python${PY3_VER}-venv libpython${PY3_VER}-dev /usr/bin/python${PY3_VER} -m venv ${FTS_VENV} source ${FTS_VENV}/bin/activate python3 -m pip install --upgrade pip python3 -m pip install --force-reinstall jinja2 python3 -m pip install --force-reinstall pyyaml python3 -m pip install --force-reinstall psutil deactivate } ############################################################################### # Handle git repository ############################################################################### function handle_git_repository() { echo -e -n "${BLUE}Checking for FreeTAKHub-Installation in home directory..." cd ~ [[ -n $CBRANCH ]] && BRANCH=$CBRANCH # check for FreeTAKHub-Installation repository if [[ ! -d ~/FreeTAKHub-Installation ]]; then echo -e "local working git tree NOT FOUND" echo -e "Cloning the FreeTAKHub-Installation repository...${NOFORMAT}" git clone --branch "${BRANCH}" ${REPO} ~/FreeTAKHub-Installation cd ~/FreeTAKHub-Installation else echo -e "FOUND" cd ~/FreeTAKHub-Installation echo -e \ "Pulling latest from the FreeTAKHub-Installation repository...${NOFORMAT}" git pull git checkout "${BRANCH}" fi git pull } ############################################################################### # Add passwordless Ansible execution ############################################################################### function add_passwordless_ansible_execution() { echo -e \ "${BLUE}Adding passwordless Ansible execution for the current user...${NOFORMAT}" # line to add LINE="${USER} ALL=(ALL) NOPASSWD:/usr/bin/ansible-playbook" # file to create for passwordless FILE="/etc/sudoers.d/dont-prompt-${USER}-for-sudo-password" # only add if non-existent grep -qF -- "${LINE}" "${FILE}" || echo "${LINE}" >>"${FILE}" } ############################################################################### # Generate public and private keys ############################################################################### function generate_key_pair() { echo -e \ "${BLUE}Creating a public and private keys if non-existent...${NOFORMAT}" # check for public and private keys if [[ ! -e ${HOME}/.ssh/id_rsa.pub ]]; then # generate keys ssh-keygen -t rsa -f "${HOME}/.ssh/id_rsa" -N "" fi } ############################################################################### # Run Ansible playbook to install # https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#defining-variables-at-runtime ############################################################################### function run_playbook() { export CODENAME export INSTALL_TYPE export FTS_VERSION env_vars="python3_version=$PY3_VER codename=$CODENAME itype=$INSTALL_TYPE" env_vars="$env_vars fts_version=$FTS_VERSION cfg_rpath=$CFG_RPATH fts_venv=${FTS_VENV}" [[ -n "${FTS_IP_CUSTOM:-}" ]] && env_vars="$env_vars fts_ip_addr_extra=$FTS_IP_CUSTOM" [[ -n "${WEBMAP_FORCE_INSTALL:-}" ]] && env_vars="$env_vars $WEBMAP_FORCE_INSTALL" [[ -n "${CORE:-}" ]] && pb=install_mainserver || pb=install_all echo -e "${BLUE}Running Ansible Playbook ${GREEN}$pb${BLUE}...${NOFORMAT}" ansible-playbook -u root ${pb}.yml \ --connection=local \ --inventory localhost, \ --extra-vars "$env_vars" \ ${ANSIBLE_VERBOSITY-} } function cleanup() { if [[ -n $NEEDRESTART ]] then cp $HOME/nr-conf-temp $NEEDRESTART fi } ############################################################################### # MAIN BUSINESS LOGIC HERE ############################################################################### setup_colors parse_params "${@}" set_versions check_os # do_checks download_dependencies [[ "$DEFAULT_INSTALL_TYPE" == "$INSTALL_TYPE" ]] && install_python_environment handle_git_repository add_passwordless_ansible_execution generate_key_pair [[ 0 -eq $DRY_RUN ]] || die "Dry run complete. Not running Ansible" 0 run_playbook cleanup