freetak/freetak_install.sh
2025-07-04 22:12:40 +01:00

655 lines
No EOL
18 KiB
Bash

#!/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 <<USAGE_TEXT
Usage: $(basename "${BASH_SOURCE[0]}") [<optional-arguments>]
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