├── .gitignore ├── LICENSE ├── README.md ├── easy-ubnt.sh ├── extras ├── deb │ └── original │ │ ├── control │ │ ├── control │ │ ├── postinst │ │ ├── postrm │ │ ├── preinst │ │ ├── prerm │ │ └── templates │ │ └── data │ │ └── lib │ │ └── unifi │ │ └── bin │ │ └── unifi.init └── unifi-controller_azure-network-security-group.json ├── lib ├── unifi-controller_certbot-deploy.sh └── unifi-controller_mongodb-prune.js └── unifi-installer.sh /.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | !.gitignore 3 | !LICENSE 4 | !README.md 5 | !easy-ubnt.sh 6 | !unifi-installer.sh 7 | !/deb/ 8 | !/extras/ 9 | !/lib/ 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2019 SprockTech, LLC and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### How to use the Easy UBNT (deprecated) script 2 | You can run the script this way: 3 | ```console 4 | wget https://raw.githubusercontent.com/sprockteam/ubi-tools/master/easy-ubnt.sh -O easy-ubnt.sh 5 | sudo bash easy-ubnt.sh 6 | ``` 7 | 8 | ### Script command-line useage 9 | ```console 10 | Note: 11 | This script currently requires root access. 12 | 13 | Usage: 14 | sudo bash easy-ubnt.sh [options] 15 | 16 | Options: 17 | -a Accept and skip the license agreement screen 18 | -c [arg] Specify a command to issue to a product, used with -p 19 | The script will execute the specified command only and then exit 20 | Currently supported commands: 21 | 'get-installed-version' - Show currently installed package version 22 | 'get-available-version' - Show latest available version number 23 | 'get-available-download' - Show latest available download URL 24 | 'archive-alerts' - Archive controller alerts for all sites 25 | -d [arg] Specify the domain name (FQDN) to use in the script 26 | -f [arg] Specify an option for the firewall setup 27 | If not specified, the firewall (UFW) will be enabled 28 | Currently supported options: 29 | 'off' - Disable the firewall 30 | 'skip' - Don't make any firewall changes 31 | -h Show this help screen 32 | -i [arg] Specify a UBNT product version to install, used with -p 33 | Currently supported syntax examples: 34 | '5.9.29', 'stable', '5.7' 35 | Can also use 'skip' to bypass any UBNT product changes 36 | -l [arg] Specify an option for the Let's Encrypt setup 37 | Currently supported options: 38 | 'skip' - Don't do any Let's Encrypt setup 39 | -p [arg] Specify which UBNT product to administer 40 | Currently supported products: 41 | 'unifi-controller' (default) 42 | -q Run the script in quick mode, accepting all default answers 43 | -s [arg] Specify an option for the SSH server setup 44 | Currently supported options: 45 | '' - Specify a port number to use 46 | 'off' - Disable SSH 47 | 'skip' - Don't do anything with SSH 48 | -t Bypass normal script execution and run tests 49 | -v Enable verbose screen output 50 | -x Enable script execution tracing 51 | -z Bypass initial system checks, common fixes and updates 52 | ``` 53 | 54 | ### Quick mode example 55 | You can run the script this way to quickly deploy a server with a Let's Encrypt cert and a basic firewall: 56 | ```console 57 | wget https://raw.githubusercontent.com/sprockteam/ubi-tools/master/easy-ubnt.sh -qO easy-ubnt.sh && sudo bash easy-ubnt.sh -aqd unifi.fqdn.com 58 | ``` 59 | 60 | ### Script Logging 61 | The last 10 logs are saved in `/var/log/easy-ubnt` and the latest script log is symlinked as `latest.log`: 62 | ```console 63 | more /var/log/easy-ubnt/latest.log 64 | ``` 65 | -------------------------------------------------------------------------------- /extras/deb/original/control/control: -------------------------------------------------------------------------------- 1 | Package: unifi 2 | Version: 5.10.20-11657-1 3 | Section: java 4 | Priority: optional 5 | Architecture: all 6 | Depends: binutils, coreutils, adduser, libcap2, curl, 7 | mongodb-server (>= 2.4.10) | mongodb-10gen (>= 2.4.14) | mongodb-org-server (>= 2.6.0), 8 | mongodb-server (<< 1:3.6.0) | mongodb-10gen (<< 3.6.0) | mongodb-org-server (<< 3.6.0), 9 | java8-runtime-headless, jsvc (>=1.0.8) 10 | Pre-Depends: debconf (>= 0.5) | debconf-2.0 11 | Conflicts: unifi-controller 12 | Provides: unifi-controller 13 | Replaces: unifi-controller 14 | Installed-Size: 125425 15 | Maintainer: UniFi developers 16 | Description: Ubiquiti UniFi server 17 | Ubiquiti UniFi server is a centralized management system for UniFi suite of devices. 18 | After the UniFi server is installed, the UniFi controller can be accessed on any 19 | web browser. The UniFi controller allows the operator to instantly provision thousands 20 | of UniFi devices, map out network topology, quickly manage system traffic, and further 21 | provision individual UniFi devices. 22 | Homepage: http://www.ubnt.com/unifi 23 | -------------------------------------------------------------------------------- /extras/deb/original/control/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NAME=unifi 4 | BASEDIR=/usr/lib/${NAME} 5 | 6 | [ -f /etc/default/${NAME} ] && . /etc/default/${NAME} 7 | 8 | DATADIR=${UNIFI_DATA_DIR:-/var/lib/$NAME} 9 | LOGDIR=${UNIFI_LOG_DIR:-/var/log/$NAME} 10 | CODEPATH=${BASEDIR} 11 | RUNDIR=${UNIFI_RUN_DIR:-/var/run/$NAME} 12 | 13 | for i in $(seq 1 10); do 14 | [ -z "$(pgrep -f ${CODEPATH}/lib/ace.jar)" ] && break 15 | # graceful shutdown 16 | [ $i -gt 1 ] && [ -d ${RUNDIR} ] && touch ${RUNDIR}/server.stop || true 17 | # savage shutdown 18 | [ $i -gt 7 ] && pkill -f ${CODEPATH}/lib/ace.jar || true 19 | sleep 1 20 | done 21 | 22 | service_ctrl() { 23 | if which invoke-rc.d >/dev/null 2>&1; then 24 | invoke-rc.d $1 $2 25 | else 26 | /etc/init.d/$1 $2 27 | fi 28 | } 29 | 30 | systemd_setup() { 31 | local systemd_helper 32 | systemd_helper=/usr/bin/deb-systemd-helper 33 | if [ ! -x ${systemd_helper} ]; then 34 | return 0; 35 | fi 36 | 37 | ${systemd_helper} unmask ${NAME}.service >/dev/null || true 38 | if ${systemd_helper} --quiet was-enabled ${NAME}.service >/dev/null; then 39 | ${systemd_helper} enable ${NAME}.service >/dev/null || true 40 | ${systemd_helper} update-state ${NAME}.service >/dev/null || true 41 | fi 42 | } 43 | 44 | UMASK=027 45 | umask ${UMASK} 46 | 47 | if [ "$1" = "configure" ] ; then 48 | UNIFI_USER=${UNIFI_USER:-unifi} 49 | FILE_MODE=$(printf '%x' $((0x7777 - 0x${UMASK} & 0x0666))) 50 | DIR_MODE=$(printf '%x' $((0x7777 - 0x${UMASK} & 0x0777))) 51 | 52 | if ! id ${UNIFI_USER} >/dev/null 2>&1; then 53 | adduser --system --home ${DATADIR} --no-create-home --group --disabled-password --quiet ${UNIFI_USER} 54 | fi 55 | UNIFI_GROUP=$(id -gn ${UNIFI_USER}) 56 | 57 | [ -e ${DATADIR} ] || install -o ${UNIFI_USER} -g ${UNIFI_GROUP} -m ${DIR_MODE} -d ${DATADIR} 58 | [ -e ${LOGDIR} ] || install -o ${UNIFI_USER} -g ${UNIFI_GROUP} -m ${DIR_MODE} -d ${LOGDIR} 59 | [ -e ${RUNDIR} ] || install -o ${UNIFI_USER} -g ${UNIFI_GROUP} -m ${DIR_MODE} -d ${RUNDIR} 60 | 61 | chown -h ${UNIFI_USER}:${UNIFI_GROUP} ${DATADIR} ${LOGDIR} ${RUNDIR} && chown -RH ${UNIFI_USER}:${UNIFI_GROUP} ${DATADIR} ${LOGDIR} ${RUNDIR} 62 | 63 | update-rc.d unifi defaults 92 08 64 | ln -sf $(which mongod) ${CODEPATH}/bin/mongod 65 | 66 | rm -rf ${CODEPATH}/conf 67 | fi 68 | 69 | systemd_setup 70 | service_ctrl ${NAME} start 71 | 72 | exit 0 73 | -------------------------------------------------------------------------------- /extras/deb/original/control/postrm: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | NAME=unifi 4 | BASEDIR=/usr/lib/${NAME} 5 | 6 | [ -f /etc/default/${NAME} ] && . /etc/default/${NAME} 7 | 8 | DATADIR=${UNIFI_DATA_DIR:-/var/lib/$NAME} 9 | LOGDIR=${UNIFI_LOG_DIR:-/var/log/$NAME} 10 | RUNDIR=${UNIFI_RUN_DIR:-/var/run/$NAME} 11 | 12 | [ -z "${UNIFI_DATA_DIR}" ] || DATADIR=${UNIFI_DATA_DIR} 13 | [ -z "${UNIFI_LOG_DIR}" ] || LOGDIR=${UNIFI_LOG_DIR} 14 | [ -z "${UNIFI_RUN_DIR}" ] || RUNDIR=${UNIFI_RUN_DIR} 15 | 16 | systemd_helper=/usr/bin/deb-systemd-helper 17 | 18 | systemd_remove() { 19 | [ -x ${systemd_helper} ] || return 0 20 | ${systemd_helper} mask ${NAME}.service > /dev/null 21 | } 22 | 23 | systemd_purge() { 24 | [ -x ${systemd_helper} ] || return 0 25 | ${systemd_helper} purge ${NAME}.service > /dev/null 26 | ${systemd_helper} unmask ${NAME}.service > /dev/null 27 | } 28 | 29 | case "$1" in 30 | failed-upgrade|abort-install|abort-upgrade|disappear) 31 | echo "$1: please reinstall previous version" 32 | echo "" 33 | echo "sudo apt-get install --reinstall unifi=$2" 34 | echo "" 35 | exit 2 36 | ;; 37 | 38 | remove|upgrade) 39 | systemd_remove 40 | update-rc.d -f unifi remove 41 | ;; 42 | 43 | purge) 44 | update-rc.d -f unifi remove 45 | systemd_purge 46 | [ ! -d ${DATADIR} ] || rm -rf ${DATADIR} 47 | [ ! -d ${LOGDIR} ] || rm -rf ${LOGDIR} 48 | [ ! -d ${RUNDIR} ] || rm -rf ${RUNDIR} 49 | 50 | . /usr/share/debconf/confmodule 51 | db_purge 52 | 53 | deluser ${UNIFI_USER:-unifi} 54 | ;; 55 | 56 | *) 57 | echo "postrm called with unknown argument \`$1'" >&2 58 | exit 1 59 | ;; 60 | esac 61 | 62 | exit 0 63 | 64 | 65 | -------------------------------------------------------------------------------- /extras/deb/original/control/preinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | . /usr/share/debconf/confmodule 4 | 5 | if [ -f /var/lib/unifi/db/version ]; then 6 | echo "Previous setting (UniFi $(cat /var/lib/unifi/db/version)) is found." 7 | 8 | db_fset unifi/has_backup seen false 9 | db_input critical unifi/has_backup || true 10 | db_go 11 | 12 | db_get unifi/has_backup 13 | if [ "$RET" = "false" ]; then 14 | db_input critical unifi/err_no_backup || true 15 | db_go 16 | exit 2 17 | fi 18 | fi 19 | -------------------------------------------------------------------------------- /extras/deb/original/control/prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | CODEPATH=/usr/lib/unifi 4 | 5 | if [ "$1" = "remove" ] || [ "$1" = "upgrade" ] ; then 6 | if which invoke-rc.d >/dev/null 2>&1; then 7 | invoke-rc.d unifi stop 8 | else 9 | /etc/init.d/unifi stop 10 | fi 11 | for i in $(seq 1 30); do 12 | [ -z "$(pgrep -f ${CODEPATH}/lib/ace.jar)" ] && break 13 | # graceful shutdown 14 | [ $i -gt 1 ] && touch ${CODEPATH}/run/server.stop || true 15 | # savage shutdown 16 | [ $i -gt 15 ] && pkill -f ${CODEPATH}/lib/ace.jar || true 17 | sleep 1 18 | done 19 | [ ! -d ${CODEPATH}/webapps/ROOT ] || rm -rf ${CODEPATH}/webapps/ROOT 20 | [ ! -d ${CODEPATH}/work ] || rm -rf ${CODEPATH}/work 21 | rm -rf ${CODEPATH}/data ${CODEPATH}/logs ${CODEPATH}/run ${CODEPATH}/bin/mongod 22 | fi 23 | exit 0 24 | -------------------------------------------------------------------------------- /extras/deb/original/control/templates: -------------------------------------------------------------------------------- 1 | Template: unifi/has_backup 2 | Type: boolean 3 | Description: Do you have a backup? 4 | It is recommended that you create a backup before installing a new version. 5 | 6 | Template: unifi/err_no_backup 7 | Type: error 8 | Description: The installation will be canceled. 9 | 10 | -------------------------------------------------------------------------------- /extras/deb/original/data/lib/unifi/bin/unifi.init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # /etc/init.d/UniFi -- startup script for Ubiquiti UniFi 4 | # 5 | # 6 | ### BEGIN INIT INFO 7 | # Provides: unifi 8 | # Required-Start: $local_fs $remote_fs $network 9 | # Required-Stop: $local_fs $remote_fs $network 10 | # Default-Start: 2 3 4 5 11 | # Default-Stop: 0 1 6 12 | # Short-Description: Ubiquiti UniFi 13 | # Description: Ubiquiti UniFi Controller 14 | ### END INIT INFO 15 | 16 | set_java_home () { 17 | arch=`dpkg --print-architecture 2>/dev/null` 18 | support_java_ver='8' 19 | java_list='' 20 | for v in ${support_java_ver}; do 21 | java_list=`echo ${java_list} java-$v-openjdk-${arch}` 22 | java_list=`echo ${java_list} java-$v-openjdk` 23 | done 24 | 25 | # appending Oracle JDK8 (make-jpkg version) to list 26 | for a in i386 amd64 armhf arm64; do 27 | java_list=$(echo ${java_list} oracle-java8-jdk-${a}/jre) 28 | done 29 | # appending Oracle JRE8 (make-jpkg version) to list 30 | for a in i386 amd64; do 31 | java_list=$(echo ${java_list} oracle-java8-jre-${a}) 32 | done 33 | # appending Oracle JDK8 (old make-jpkg version) to list 34 | for a in x64 i586 arm32-vfp-hflt arm64-vfp-hflt; do 35 | java_list=$(echo ${java_list} jdk-8-oracle-${a}/jre) 36 | done 37 | # appending Oracle JRE8 (old make-jpkg version) to list 38 | for a in x64 i586; do 39 | java_list=$(echo ${java_list} jre-8-oracle-${a}) 40 | done 41 | 42 | # appending Oracle JDK8 (PPA version https://launchpad.net/~webupd8team/+archive/ubuntu/java) to list 43 | java_list=$(echo ${java_list} java-8-oracle/jre) 44 | 45 | cur_java=`update-alternatives --query java | awk '/^Value: /{print $2}'` 46 | cur_real_java=`readlink -f ${cur_java} 2>/dev/null` 47 | for jvm in ${java_list}; do 48 | jvm_real_java=`readlink -f /usr/lib/jvm/${jvm}/bin/java 2>/dev/null` 49 | [ "${jvm_real_java}" != "" ] || continue 50 | if [ "${jvm_real_java}" == "${cur_real_java}" ]; then 51 | JAVA_HOME="/usr/lib/jvm/${jvm}" 52 | return 53 | fi 54 | done 55 | 56 | alts_java=`update-alternatives --query java | awk '/^Alternative: /{print $2}'` 57 | for cur_java in ${alts_java}; do 58 | cur_real_java=`readlink -f ${cur_java} 2>/dev/null` 59 | for jvm in ${java_list}; do 60 | jvm_real_java=`readlink -f /usr/lib/jvm/${jvm}/bin/java 2>/dev/null` 61 | [ "${jvm_real_java}" != "" ] || continue 62 | if [ "${jvm_real_java}" == "${cur_real_java}" ]; then 63 | JAVA_HOME="/usr/lib/jvm/${jvm}" 64 | return 65 | fi 66 | done 67 | done 68 | 69 | JAVA_HOME=/usr/lib/jvm/java-8-openjdk-${arch} 70 | } 71 | 72 | 73 | dir_symlink_fix() { 74 | local DSTDIR=$1 75 | local SYMLINK=$2 76 | local MYUSER=$3 77 | local MYGROUP=$4 78 | local MYMODE=$5 79 | 80 | [ -d ${DSTDIR} ] || install -o ${MYUSER} -g ${MYGROUP} -m ${MYMODE} -d ${DSTDIR} 81 | [ -d ${SYMLINK} -a ! -L ${SYMLINK} ] && mv ${SYMLINK} `mktemp -u ${SYMLINK}.XXXXXXXX` 82 | [ "$(readlink ${SYMLINK})" = "${DSTDIR}" ] || (rm -f ${SYMLINK} && ln -sf ${DSTDIR} ${SYMLINK}) 83 | } 84 | 85 | file_symlink_fix() { 86 | local DSTFILE=$1 87 | local SYMLINK=$2 88 | 89 | if [ -f ${DSTFILE} ]; then 90 | [ -f ${SYMLINK} -a ! -L ${SYMLINK} ] && mv ${SYMLINK} `mktemp -u ${SYMLINK}.XXXXXXXX` 91 | [ "$(readlink ${SYMLINK})" = "${DSTFILE}" ] || (rm -f ${SYMLINK} && ln -sf ${DSTFILE} ${SYMLINK}) 92 | fi 93 | } 94 | 95 | manual_stop_unifi() { 96 | local MYDIR=$1 97 | local MYUSER=$2 98 | local MYGROUP=$3 99 | local MYMODE=$4 100 | 101 | TMP_UNIFI_STOP=$(mktemp) 102 | rm -f ${MYDIR}/launcher.looping 103 | install -o ${MYUSER} -g ${MYGROUP} -m ${MYMODE} ${TMP_UNIFI_STOP} ${MYDIR}/server.stop 104 | rm -f ${TMP_UNIFI_STOP} 105 | } 106 | 107 | NAME="unifi" 108 | DESC="Ubiquiti UniFi Controller" 109 | 110 | BASEDIR="/usr/lib/unifi" 111 | MAINCLASS="com.ubnt.ace.Launcher" 112 | 113 | PATH=/bin:/usr/bin:/sbin:/usr/sbin 114 | 115 | UMASK=027 116 | FILE_MODE=$(printf '%x' $((0x7777 - 0x${UMASK} & 0x0666))) 117 | DIR_MODE=$(printf '%x' $((0x7777 - 0x${UMASK} & 0x0777))) 118 | 119 | [ -f /etc/default/rcS ] && . /etc/default/rcS 120 | . /lib/lsb/init-functions 121 | 122 | MONGOPORT=27117 123 | 124 | CODEPATH=${BASEDIR} 125 | DATALINK=${BASEDIR}/data 126 | LOGLINK=${BASEDIR}/logs 127 | RUNLINK=${BASEDIR}/run 128 | 129 | JAVA_ENTROPY_GATHER_DEVICE= 130 | JVM_MAX_HEAP_SIZE=1024M 131 | JVM_INIT_HEAP_SIZE= 132 | UNIFI_JVM_EXTRA_OPTS= 133 | 134 | ENABLE_UNIFI=yes 135 | JVM_EXTRA_OPTS= 136 | JSVC_EXTRA_OPTS= 137 | [ -f /etc/default/${NAME} ] && . /etc/default/${NAME} 138 | 139 | [ "x${ENABLE_UNIFI}" != "xyes" ] && exit 0 140 | 141 | DATADIR=${UNIFI_DATA_DIR:-/var/lib/${NAME}} 142 | LOGDIR=${UNIFI_LOG_DIR:-/var/log/${NAME}} 143 | RUNDIR=${UNIFI_RUN_DIR:-/var/run/${NAME}} 144 | 145 | JVM_EXTRA_OPTS="${JVM_EXTRA_OPTS} -Dunifi.datadir=${DATADIR} -Dunifi.logdir=${LOGDIR} -Dunifi.rundir=${RUNDIR}" 146 | PIDFILE="/var/run/${NAME}.pid" 147 | 148 | if [ ! -z "${JAVA_ENTROPY_GATHER_DEVICE}" ]; then 149 | JVM_EXTRA_OPTS="${JVM_EXTRA_OPTS} -Djava.security.egd=${JAVA_ENTROPY_GATHER_DEVICE}" 150 | fi 151 | 152 | if [ ! -z "${JVM_MAX_HEAP_SIZE}" ]; then 153 | JVM_EXTRA_OPTS="${JVM_EXTRA_OPTS} -Xmx${JVM_MAX_HEAP_SIZE}" 154 | fi 155 | 156 | if [ ! -z "${JVM_INIT_HEAP_SIZE}" ]; then 157 | JVM_EXTRA_OPTS="${JVM_EXTRA_OPTS} -Xms${JVM_INIT_HEAP_SIZE}" 158 | fi 159 | 160 | if [ ! -z "${UNIFI_JVM_EXTRA_OPTS}" ]; then 161 | JVM_EXTRA_OPTS="${JVM_EXTRA_OPTS} ${UNIFI_JVM_EXTRA_OPTS}" 162 | fi 163 | 164 | JVM_OPTS="${JVM_EXTRA_OPTS} -Djava.awt.headless=true -Dfile.encoding=UTF-8" 165 | 166 | [ "x${JAVA_HOME}" != "x" ] || set_java_home 167 | 168 | # JSVC - for running java apps as services 169 | JSVC=$(command -v jsvc) 170 | if [ -z ${JSVC} -o ! -x ${JSVC} ]; then 171 | log_failure_msg "${DESC}: jsvc is missing!" 172 | exit 1 173 | fi 174 | 175 | UNIFI_USER=${UNIFI_USER:-unifi} 176 | UNIFI_GROUP=$(id -gn ${UNIFI_USER}) 177 | 178 | umask ${UMASK} 179 | 180 | # fix path for ace 181 | dir_symlink_fix ${DATADIR} ${DATALINK} ${UNIFI_USER} ${UNIFI_GROUP} ${DIR_MODE} 182 | dir_symlink_fix ${LOGDIR} ${LOGLINK} ${UNIFI_USER} ${UNIFI_GROUP} ${DIR_MODE} 183 | dir_symlink_fix ${RUNDIR} ${RUNLINK} ${UNIFI_USER} ${UNIFI_GROUP} ${DIR_MODE} 184 | [ -z "${UNIFI_SSL_KEYSTORE}" ] || file_symlink_fix ${UNIFI_SSL_KEYSTORE} ${DATALINK}/keystore 185 | 186 | MONGOLOCK="${DATADIR}/db/mongod.lock" 187 | # check whether jsvc requires -cwd option 188 | ${JSVC} -java-home ${JAVA_HOME} -cwd / -help >/dev/null 2>&1 189 | if [ $? -eq 0 ] ; then 190 | JSVC_OPTS="${JSVC_OPTS} -cwd ${BASEDIR}" 191 | fi 192 | 193 | UNIFI_UID=$(id -u ${UNIFI_USER}) 194 | DATADIR_UID=$(stat ${DATADIR} -Lc %u) 195 | if [ ${UNIFI_UID} -ne ${DATADIR_UID} ]; then 196 | msg="${NAME} cannot start. Please create ${UNIFI_USER} user, and chown -R ${UNIFI_USER} ${DATADIR} ${LOGDIR} ${RUNDIR}" 197 | logger $msg 198 | echo $msg >&2 199 | exit 1 200 | fi 201 | 202 | #JSVC_OPTS="-debug" 203 | 204 | JSVC_OPTS="${JSVC_OPTS}\ 205 | -home ${JAVA_HOME} \ 206 | -cp /usr/share/java/commons-daemon.jar:${BASEDIR}/lib/ace.jar \ 207 | -pidfile ${PIDFILE} \ 208 | -procname ${NAME} \ 209 | -outfile SYSLOG \ 210 | -errfile SYSLOG \ 211 | -umask ${UMASK} \ 212 | -user ${UNIFI_USER} \ 213 | ${JSVC_EXTRA_OPTS} \ 214 | ${JVM_OPTS}" 215 | 216 | [ -f /etc/default/rcS ] && . /etc/default/rcS 217 | . /lib/lsb/init-functions 218 | 219 | cd ${BASEDIR} 220 | 221 | is_not_running() { 222 | start-stop-daemon --test --start --pidfile "${PIDFILE}" \ 223 | --startas "${JAVA_HOME}/bin/java" >/dev/null 224 | RC=$? 225 | return ${RC} 226 | } 227 | 228 | case "$1" in 229 | start) 230 | log_daemon_msg "Starting ${DESC}" "${NAME}" 231 | [ ! -f ${DATADIR}/system.properties ] || api_port=$(grep "^[^#;]" ${DATADIR}/system.properties | sed -n 's/unifi.http.port=\([0-9]\+\)/\1/p') 232 | api_port=${api_port:-8080} 233 | if is_not_running; then 234 | ${JSVC} ${JSVC_OPTS} ${MAINCLASS} start 235 | sleep 1 236 | if is_not_running; then 237 | log_end_msg 1 238 | else 239 | MAX_WAIT=60 240 | http_code=$(curl -s --connect-timeout 1 -o /dev/null -w "%{http_code}" htttp://localhost:${api_port}/status) 241 | for i in `seq 1 ${MAX_WAIT}` ; do 242 | if [ "${http_code}" != "200" ]; then 243 | sleep 1 244 | http_code=$(curl -s --connect-timeout 1 -o /dev/null -w "%{http_code}" http://localhost:${api_port}/status) 245 | else 246 | break 247 | fi 248 | done 249 | if [ "${http_code}" != "200" ]; then 250 | log_end_msg 1 251 | else 252 | log_end_msg 0 253 | fi 254 | fi 255 | else 256 | log_progress_msg "(already running)" 257 | log_end_msg 1 258 | fi 259 | ;; 260 | stop) 261 | log_daemon_msg "Stopping ${DESC}" "${NAME}" 262 | if is_not_running; then 263 | log_progress_msg "(not running)" 264 | log_end_msg 0 265 | exit 0 266 | fi 267 | 268 | IS_STOPPED=0 269 | MAX_WAIT=10 270 | ${JSVC} ${JSVC_OPTS} -stop ${MAINCLASS} stop 271 | for i in `seq 1 ${MAX_WAIT}` ; do 272 | sleep 1 273 | if [ -z "$(pgrep -f ${BASEDIR}/lib/ace.jar)" ]; then 274 | IS_STOPPED=1 275 | break 276 | fi 277 | ${JSVC} ${JSVC_OPTS} -stop ${MAINCLASS} stop 278 | done 279 | if [ ${IS_STOPPED} -ge 1 ] ; then 280 | log_end_msg 0 281 | exit 0 282 | fi 283 | 284 | # if jsvc can't stop it 285 | [ -z "$(pgrep -f ${BASEDIR}/lib/ace.jar)" ] || manual_stop_unifi ${RUNDIR} ${UNIFI_USER} ${UNIFI_GROUP} ${FILE_MODE} 286 | for i in `seq 1 ${MAX_WAIT}` ; do 287 | sleep 1 288 | if [ -z "$(pgrep -f ${BASEDIR}/lib/ace.jar)" ]; then 289 | IS_STOPPED=1 290 | break 291 | fi 292 | done 293 | if [ ${IS_STOPPED} -ge 1 ] ; then 294 | log_end_msg 0 295 | exit 0 296 | fi 297 | 298 | # force stop 299 | pkill -f ${BASEDIR}/lib/ace.jar || true 300 | sleep 2 301 | if [ -f ${MONGOLOCK} ]; then 302 | mongo localhost:${MONGOPORT} --eval "db.getSiblingDB('admin').shutdownServer()" >/dev/null 2>&1 || true 303 | fi 304 | log_end_msg 0 305 | ;; 306 | status) 307 | status_of_proc -p ${PIDFILE} unifi unifi && exit 0 || exit $? 308 | ;; 309 | restart|reload|force-reload) 310 | if ! is_not_running ; then 311 | if which invoke-rc.d >/dev/null 2>&1; then 312 | invoke-rc.d ${NAME} stop 313 | else 314 | /etc/init.d/${NAME} stop 315 | fi 316 | fi 317 | if which invoke-rc.d >/dev/null 2>&1; then 318 | invoke-rc.d ${NAME} start 319 | else 320 | /etc/init.d/${NAME} start 321 | fi 322 | ;; 323 | *) 324 | log_success_msg "Usage: $0 {start|stop|restart|reload|force-reload}" 325 | exit 1 326 | ;; 327 | esac 328 | 329 | exit 0 330 | -------------------------------------------------------------------------------- /extras/unifi-controller_azure-network-security-group.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "UnifiNetworkInformPort": { 6 | "defaultValue": "8080", 7 | "type": "String" 8 | }, 9 | "UnifiNetworkStunPort": { 10 | "defaultValue": "3478", 11 | "type": "String" 12 | }, 13 | "UnifiNetworkSpeedPort": { 14 | "defaultValue": "6789", 15 | "type": "String" 16 | }, 17 | "UnifiNetworkPortalPort": { 18 | "defaultValue": "8880", 19 | "type": "String" 20 | }, 21 | "UnifiNetworkPortalSecurePort": { 22 | "defaultValue": "8843", 23 | "type": "String" 24 | }, 25 | "UnifiNetworkAdminPort": { 26 | "defaultValue": "8443", 27 | "type": "String" 28 | }, 29 | "WebPort": { 30 | "defaultValue": "80", 31 | "type": "String" 32 | }, 33 | "WebSecurePort": { 34 | "defaultValue": "443", 35 | "type": "String" 36 | }, 37 | "SecureShellPort": { 38 | "defaultValue": "22", 39 | "type": "String" 40 | }, 41 | "UnifiNetworkInformPriority": { 42 | "defaultValue": "100", 43 | "type": "String" 44 | }, 45 | "UnifiNetworkStunPriority": { 46 | "defaultValue": "110", 47 | "type": "String" 48 | }, 49 | "UnifiNetworkSpeedPriority": { 50 | "defaultValue": "120", 51 | "type": "String" 52 | }, 53 | "UnifiNetworkPortalPriority": { 54 | "defaultValue": "130", 55 | "type": "String" 56 | }, 57 | "UnifiNetworkPortalSecurePriority": { 58 | "defaultValue": "140", 59 | "type": "String" 60 | }, 61 | "UnifiNetworkAdminPriority": { 62 | "defaultValue": "150", 63 | "type": "String" 64 | }, 65 | "WebPriority": { 66 | "defaultValue": "200", 67 | "type": "String" 68 | }, 69 | "WebSecurePriority": { 70 | "defaultValue": "210", 71 | "type": "String" 72 | }, 73 | "SecureShellPriority": { 74 | "defaultValue": "300", 75 | "type": "String" 76 | } 77 | }, 78 | "resources": [ 79 | { 80 | "type": "Microsoft.Network/networkSecurityGroups", 81 | "name": "UniFiNetworkController", 82 | "apiVersion": "2018-12-01", 83 | "location": "southcentralus", 84 | "scale": null, 85 | "properties": { 86 | "securityRules": [ 87 | { 88 | "name": "UniFiNetworkInform", 89 | "properties": { 90 | "description": "Required to allow devices to communicate with the Controller", 91 | "protocol": "TCP", 92 | "sourcePortRange": "*", 93 | "destinationPortRange": "[parameters('UnifiNetworkInformPort')]", 94 | "sourceAddressPrefix": "*", 95 | "destinationAddressPrefix": "*", 96 | "access": "Allow", 97 | "priority": "[parameters('UnifiNetworkInformPriority')]", 98 | "direction": "Inbound" 99 | } 100 | }, 101 | { 102 | "name": "UniFiNetworkStun", 103 | "properties": { 104 | "description": "Required to allow the Controller to communicate back to devices", 105 | "protocol": "UDP", 106 | "sourcePortRange": "*", 107 | "destinationPortRange": "[parameters('UnifiNetworkStunPort')]", 108 | "sourceAddressPrefix": "*", 109 | "destinationAddressPrefix": "*", 110 | "access": "Allow", 111 | "priority": "[parameters('UnifiNetworkStunPriority')]", 112 | "direction": "Inbound" 113 | } 114 | }, 115 | { 116 | "name": "UniFiNetworkSpeed", 117 | "properties": { 118 | "description": "Used to test throughput speed using the UniFi mobile app", 119 | "protocol": "TCP", 120 | "sourcePortRange": "*", 121 | "destinationPortRange": "[parameters('UnifiNetworkSpeedPort')]", 122 | "sourceAddressPrefix": "*", 123 | "destinationAddressPrefix": "*", 124 | "access": "Allow", 125 | "priority": "[parameters('UnifiNetworkSpeedPriority')]", 126 | "direction": "Inbound" 127 | } 128 | }, 129 | { 130 | "name": "UniFiNetworkPortal", 131 | "properties": { 132 | "description": "Used to allow for guest portal access to the Controller", 133 | "protocol": "TCP", 134 | "sourcePortRange": "*", 135 | "destinationPortRange": "[parameters('UnifiNetworkPortalPort')]", 136 | "sourceAddressPrefix": "*", 137 | "destinationAddressPrefix": "*", 138 | "access": "Allow", 139 | "priority": "[parameters('UnifiNetworkPortalPriority')]", 140 | "direction": "Inbound" 141 | } 142 | }, 143 | { 144 | "name": "UniFiNetworkPortalSecure", 145 | "properties": { 146 | "description": "Used to allow for guest secure portal access to the Controller", 147 | "protocol": "TCP", 148 | "sourcePortRange": "*", 149 | "destinationPortRange": "[parameters('UnifiNetworkPortalSecurePort')]", 150 | "sourceAddressPrefix": "*", 151 | "destinationAddressPrefix": "*", 152 | "access": "Allow", 153 | "priority": "[parameters('UnifiNetworkPortalSecurePriority')]", 154 | "direction": "Inbound" 155 | } 156 | }, 157 | { 158 | "name": "UniFiNetworkAdmin", 159 | "properties": { 160 | "description": "Required for Controller administration", 161 | "protocol": "TCP", 162 | "sourcePortRange": "*", 163 | "destinationPortRange": "[parameters('UnifiNetworkAdminPort')]", 164 | "sourceAddressPrefix": "*", 165 | "destinationAddressPrefix": "*", 166 | "access": "Allow", 167 | "priority": "[parameters('UnifiNetworkAdminPriority')]", 168 | "direction": "Inbound" 169 | } 170 | }, 171 | { 172 | "name": "Web", 173 | "properties": { 174 | "description": "Used by nginx reverse proxy and/or Let's Encrypt challenges", 175 | "protocol": "TCP", 176 | "sourcePortRange": "*", 177 | "destinationPortRange": "[parameters('WebPort')]", 178 | "sourceAddressPrefix": "*", 179 | "destinationAddressPrefix": "*", 180 | "access": "Allow", 181 | "priority": "[parameters('WebPriority')]", 182 | "direction": "Inbound" 183 | } 184 | }, 185 | { 186 | "name": "WebSecure", 187 | "properties": { 188 | "description": "Secure port used by nginx reverse proxy and/or Let's Encrypt challenges", 189 | "protocol": "TCP", 190 | "sourcePortRange": "*", 191 | "destinationPortRange": "[parameters('WebSecurePort')]", 192 | "sourceAddressPrefix": "*", 193 | "destinationAddressPrefix": "*", 194 | "access": "Allow", 195 | "priority": "[parameters('WebSecurePriority')]", 196 | "direction": "Inbound" 197 | } 198 | }, 199 | { 200 | "name": "SecureShell", 201 | "properties": { 202 | "description": "Required for SSH access", 203 | "protocol": "TCP", 204 | "sourcePortRange": "*", 205 | "destinationPortRange": "[parameters('SecureShellPort')]", 206 | "sourceAddressPrefix": "*", 207 | "destinationAddressPrefix": "*", 208 | "access": "Allow", 209 | "priority": "[parameters('SecureShellPriority')]", 210 | "direction": "Inbound" 211 | } 212 | } 213 | ], 214 | "defaultSecurityRules": [ 215 | { 216 | "name": "AllowVnetInBound", 217 | "properties": { 218 | "description": "Allow inbound traffic from all VMs in VNET", 219 | "protocol": "*", 220 | "sourcePortRange": "*", 221 | "destinationPortRange": "*", 222 | "sourceAddressPrefix": "VirtualNetwork", 223 | "destinationAddressPrefix": "VirtualNetwork", 224 | "access": "Allow", 225 | "priority": 65000, 226 | "direction": "Inbound" 227 | } 228 | }, 229 | { 230 | "name": "AllowAzureLoadBalancerInBound", 231 | "properties": { 232 | "description": "Allow inbound traffic from azure load balancer", 233 | "protocol": "*", 234 | "sourcePortRange": "*", 235 | "destinationPortRange": "*", 236 | "sourceAddressPrefix": "AzureLoadBalancer", 237 | "destinationAddressPrefix": "*", 238 | "access": "Allow", 239 | "priority": 65001, 240 | "direction": "Inbound" 241 | } 242 | }, 243 | { 244 | "name": "DenyAllInBound", 245 | "properties": { 246 | "description": "Deny all inbound traffic", 247 | "protocol": "*", 248 | "sourcePortRange": "*", 249 | "destinationPortRange": "*", 250 | "sourceAddressPrefix": "*", 251 | "destinationAddressPrefix": "*", 252 | "access": "Deny", 253 | "priority": 65500, 254 | "direction": "Inbound" 255 | } 256 | }, 257 | { 258 | "name": "AllowVnetOutBound", 259 | "properties": { 260 | "description": "Allow outbound traffic from all VMs to all VMs in VNET", 261 | "protocol": "*", 262 | "sourcePortRange": "*", 263 | "destinationPortRange": "*", 264 | "sourceAddressPrefix": "VirtualNetwork", 265 | "destinationAddressPrefix": "VirtualNetwork", 266 | "access": "Allow", 267 | "priority": 65000, 268 | "direction": "Outbound" 269 | } 270 | }, 271 | { 272 | "name": "AllowInternetOutBound", 273 | "properties": { 274 | "provisioningState": "Succeeded", 275 | "description": "Allow outbound traffic from all VMs to Internet", 276 | "protocol": "*", 277 | "sourcePortRange": "*", 278 | "destinationPortRange": "*", 279 | "sourceAddressPrefix": "*", 280 | "destinationAddressPrefix": "Internet", 281 | "access": "Allow", 282 | "priority": 65001, 283 | "direction": "Outbound" 284 | } 285 | }, 286 | { 287 | "name": "DenyAllOutBound", 288 | "properties": { 289 | "description": "Deny all outbound traffic", 290 | "protocol": "*", 291 | "sourcePortRange": "*", 292 | "destinationPortRange": "*", 293 | "sourceAddressPrefix": "*", 294 | "destinationAddressPrefix": "*", 295 | "access": "Deny", 296 | "priority": 65500, 297 | "direction": "Outbound" 298 | } 299 | } 300 | ] 301 | } 302 | } 303 | ] 304 | } -------------------------------------------------------------------------------- /lib/unifi-controller_certbot-deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Author: Klint Van Tassel (SprockTech) 3 | # Credits: Frank Gabriel (Frankedinven) and others 4 | # Script location: /etc/letsencrypt/renewal-hooks/deploy/unifi-controller.sh (important for auto renewal) 5 | # Note: The certbot deploy hook only runs if a new cert is generated 6 | 7 | # Exit on error, append "|| true" if an error is expected 8 | set -o errexit 9 | trap 'echo "Uncaught error on line ${LINENO}"' ERR 10 | # Exit on error inside any functions or subshells 11 | set -o errtrace 12 | # Do not allow use of undefined vars, use ${var:-} if a variable might be undefined 13 | set -o nounset 14 | 15 | # Change these if needed 16 | __unifi_data_dir="/usr/lib/unifi/data" 17 | __unifi_system_properties="${__unifi_data_dir}/system.properties" 18 | __unifi_hostname="" 19 | __cert_live_dir="/etc/letsencrypt/live" 20 | __keypass="aircontrolenterprise" 21 | __mongodb_host="localhost" 22 | __mongodb_port="27117" 23 | __mongodb_ace="ace" 24 | __regex_fingerprint='^[0-9A-Za-z:]+$' 25 | 26 | if command -v easy-ubnt &>/dev/null; then 27 | __unifi_hostname="$(easy-ubnt -p unifi-controller -c get-hostname)" 28 | fi 29 | 30 | function do_fingerprints_match() { 31 | local domain="${1:-}" 32 | 33 | # Find the SHA1 fingerprint of the domain cert from certbot 34 | certbot_fingerprint="$(openssl x509 -in ${__cert_live_dir:-}/${domain:-}/fullchain.pem -noout -sha1 -fingerprint 2>/dev/null | sed 's/.*=//')" 35 | # Find the SHA1 fingerprint of the domain cert in the UniFi keystore 36 | keystore_fingerprint="$(keytool -list -keystore ${__unifi_data_dir:-}/keystore -storepass ${__keypass:-} 2>/dev/null | grep "fingerprint" | sed 's/.*(SHA1): //')" 37 | 38 | # If the fingerprints match then return success 39 | if [[ "${certbot_fingerprint:-}" =~ ${__regex_fingerprint} && "${keystore_fingerprint:-}" =~ ${__regex_fingerprint} && "${certbot_fingerprint}" = "${keystore_fingerprint}" ]]; then 40 | return 0 41 | fi 42 | 43 | # Default is to return error 44 | return 1 45 | } 46 | 47 | function deploy_cert_to_unifi() { 48 | 49 | # Make sure a domain has been passed to this function 50 | if [[ -z "${1:-}" ]]; then 51 | echo "No domain has been specified" 52 | return 1 53 | fi 54 | local domain="${1}" 55 | 56 | # Make sure we have a valid cert folder 57 | if [[ ! -d "${__cert_live_dir}/${domain}" ]]; then 58 | echo "Unable to find cert folder: ${__cert_live_dir}/${domain}" 59 | return 1 60 | fi 61 | 62 | # If the keystore can't be found then we can't proceed 63 | if [[ ! -f "${__unifi_data_dir:-}/keystore" ]]; then 64 | echo "Unable to find UniFi keystore" 65 | return 1 66 | fi 67 | 68 | # We really only want to import a new cert 69 | if do_fingerprints_match ${domain}; then 70 | echo "The certificate is already in the UniFi keystore" 71 | return 1 72 | fi 73 | 74 | # Backup existing keystore to fallback if needed 75 | if cp --force ${__unifi_data_dir}/keystore ${__unifi_data_dir}/keystore.backup &>/dev/null; then 76 | 77 | # Convert cert to PKCS12 format 78 | if openssl pkcs12 -export -inkey ${__cert_live_dir}/${domain}/privkey.pem -in ${__cert_live_dir}/${domain}/fullchain.pem -out ${__cert_live_dir}/${domain}/fullchain.p12 -name unifi -password pass:${__keypass} &>/dev/null; then 79 | 80 | # Delete the existing 'unifi' cert in the keystore 81 | if keytool -delete -alias unifi -keystore ${__unifi_data_dir}/keystore -storepass aircontrolenterprise &>/dev/null 82 | 83 | # Finally import the new key 84 | if keytool -importkeystore -deststorepass ${__keypass} -destkeypass ${__keypass} -destkeystore ${__unifi_data_dir}/keystore -srckeystore ${__cert_live_dir}/${domain}/fullchain.p12 -srcstoretype PKCS12 -srcstorepass ${__keypass} -alias unifi -noprompt &>/dev/null; then 85 | 86 | # Import seemed successful, let's check 87 | if do_fingerprints_match ${domain}; then 88 | 89 | # Make sure permissions are right 90 | chown unifi:unifi ${__unifi_data_dir}/keystore &>/dev/null 91 | 92 | # Restart UniFi with the updated cert 93 | if service unifi restart &>/dev/null; then 94 | # Cleanup the PKC12 file and backup file then return success 95 | rm --force ${__cert_live_dir}/${domain}/fullchain.p12 &>/dev/null 96 | rm --force ${__unifi_data_dir}/keystore.backup &>/dev/null 97 | return 0 98 | fi 99 | fi 100 | fi 101 | fi 102 | 103 | # Something didn't go right, revert to the backup if needed 104 | if ! cmp --silent ${__unifi_data_dir}/keystore ${__unifi_data_dir}/keystore.backup &>/dev/null; then 105 | if cp --force ${__unifi_data_dir}/keystore.backup ${__unifi_data_dir}/keystore &>/dev/null; then 106 | rm --force ${__unifi_data_dir}/keystore.backup &>/dev/null 107 | fi 108 | else 109 | rm --force ${__unifi_data_dir}/keystore.backup &>/dev/null 110 | fi 111 | 112 | # Cleanup the PKC12 file 113 | rm --force ${__cert_live_dir}/${domain}/fullchain.p12 &>/dev/null 114 | fi 115 | 116 | # Make sure permissions are right 117 | chown unifi:unifi ${__unifi_data_dir}/keystore &>/dev/null 118 | fi 119 | echo "Unknown error trying to import certificate for domain: ${domain}" 120 | return 1 121 | } 122 | 123 | # RENEWED_DOMAINS should be passed to this script when called by certbot 124 | for domain in "${RENEWED_DOMAINS:-}"; do 125 | 126 | # If UniFi has been configure with a hostname, then only proceed if the renewed cert domain matches 127 | if [[ -n "${__unifi_hostname:-}" && "${__unifi_hostname}" != "${domain}" ]]; then 128 | continue 129 | fi 130 | 131 | # Try once to deploy the cert to UniFi 132 | if deploy_cert_to_unifi "${domain}"; then 133 | echo "New certificate imported to UniFi keystore for domain: ${domain}" 134 | exit 0 135 | else 136 | echo "Unable to import certificate to UniFi keystore for domain: ${domain}" 137 | exit 1 138 | fi 139 | done 140 | -------------------------------------------------------------------------------- /lib/unifi-controller_mongodb-prune.js: -------------------------------------------------------------------------------- 1 | // keep N-day worth of data 2 | var days=180; 3 | 4 | // change to false to have the script to really exclude old records 5 | // from the database. While true, no change at all will be made to the DB 6 | var dryrun=false; 7 | 8 | var now = new Date().getTime(), 9 | time_criteria = now - days * 86400 * 1000, 10 | time_criteria_in_seconds = time_criteria / 1000; 11 | 12 | print((dryrun ? "[dryrun] " : "") + "pruning data older than " + days + " days (" + time_criteria + ")... "); 13 | 14 | use ace; 15 | var collectionNames = db.getCollectionNames(); 16 | for (i=0; i/dev/null; then 36 | exec bash "$0" "$@" 37 | else 38 | echo -e "\\nUnable to find Bash. Is it installed?\\n" 39 | fi 40 | fi 41 | 42 | # This script has not been tested when called by another program 43 | if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then 44 | echo -e "\\nPlease run this script directly\\n" 45 | exit 46 | fi 47 | 48 | # This script requires root or sudo privilege to run properly 49 | if [[ $(id --user) -ne 0 ]]; then 50 | echo -e "\\nPlease run this script as root or use sudo\\n" 51 | echo -e "For example in Debian:" 52 | echo -e "su root\\nbash unifi-installer.sh\\n" 53 | echo -e "\\nFor example in Ubuntu (or Debian with sudo installed):" 54 | echo -e "sudo bash unifi-installer.sh\\n" 55 | exit 56 | fi 57 | 58 | ### Setup options and initialize variables 59 | ############################################################################## 60 | 61 | # Exit on error, append "|| true" if an error is expected 62 | set -o errexit 63 | # Exit on error inside any functions or subshells 64 | set -o errtrace 65 | # Do not allow use of undefined vars, use ${var:-} if a variable might be undefined 66 | set -o nounset 67 | 68 | # Set magic variables for script and environment 69 | __script_time=$(date +%s) 70 | __script_name=$(basename "${0}" .sh) 71 | __dir=$(cd "$(dirname "${0}")" && pwd) 72 | __file="${__dir}/${__script_name}" 73 | __base=$(basename "${__file}" .sh) 74 | __eubnt_dir=$(mkdir --parents /usr/lib/easy-ubnt && echo "/usr/lib/easy-ubnt") 75 | __script_log_dir=$(mkdir --parents /var/log/easy-ubnt && echo "/var/log/easy-ubnt") 76 | __script_log=$(touch "${__script_log_dir}/${__script_name}-${__script_time}.log" && echo "${__script_log_dir}/${__script_name}-${__script_time}.log") 77 | 78 | # Set script time, get system information 79 | __architecture=$(uname --machine) 80 | __os_all_info=$(uname --all) 81 | __os_kernel=$(uname --release) 82 | __os_kernel_version=$(uname --release | sed 's/[-][a-z].*//g') 83 | __os_version=$(lsb_release --release --short) 84 | __os_version_name=$(lsb_release --codename --short) 85 | __os_name=$(lsb_release --id --short) 86 | __is_user_sudo=$([[ -n "${SUDO_USER:-}" ]] && echo "true") 87 | __disk_total_space="$(df . | awk '/\//{printf "%.0fGB", $2/1024/1024}')" 88 | __disk_free_space="$(df . | awk '/\//{printf "%.0fGB", $4/1024/1024}')" 89 | __disk_free_space_mb="$(df . | awk '/\//{printf "%.0fMB", $4/1024}')" 90 | __memory_total="$(grep "MemTotal" /proc/meminfo | awk '{printf "%.0fMB", $2/1024}')" 91 | __swap_total="$(grep "SwapTotal" /proc/meminfo | awk '{printf "%.0fMB", $2/1024}')" 92 | __nameservers=$(awk '/nameserver/{print $2}' /etc/resolv.conf | xargs) 93 | 94 | # Initialize miscellaneous variables 95 | __machine_ip_address= 96 | __os_version_name_ubuntu_equivalent= 97 | __unifi_version_installed= 98 | __unifi_update_available= 99 | __unifi_domain_name= 100 | __unifi_tcp_port_admin= 101 | 102 | # Set various base folders and files 103 | # TODO: Make these dynamic 104 | __apt_sources_dir=$(find /etc -type d -name "sources.list.d") 105 | __unifi_base_dir="/usr/lib/unifi" 106 | __unifi_data_dir="${__unifi_base_dir}/data" 107 | __unifi_system_properties="${__unifi_data_dir}/system.properties" 108 | __letsencrypt_dir="/etc/letsencrypt" 109 | __sshd_config="/etc/ssh/sshd_config" 110 | 111 | # Recommendations and minimum requirements and misc variables 112 | __recommended_disk_free_space="10GB" 113 | __recommended_memory_total="2048MB" 114 | __recommended_memory_total_gb="2GB" 115 | __recommended_swap_total="2048MB" 116 | __recommended_swap_total_gb="2GB" 117 | __os_bit_recommended="64-bit" 118 | __java_version_recommended="8" 119 | __mongo_version_recommended="3.4.x" 120 | __unifi_version_stable="5.9" 121 | __recommended_nameserver="9.9.9.9" 122 | __ubnt_dns="dl.ubnt.com" 123 | 124 | # Initialize "boolean" variables as "false" 125 | __is_32= 126 | __is_64= 127 | __is_ubuntu= 128 | __is_debian= 129 | __is_experimental= 130 | __is_unifi_installed= 131 | __setup_source_java= 132 | __setup_source_mongo= 133 | __setup_source_certbot= 134 | __purge_mongo= 135 | __hold_java= 136 | __hold_mongo= 137 | __hold_unifi= 138 | __install_mongo= 139 | __install_java= 140 | __install_webupd8_java= 141 | __accept_license= 142 | __quick_mode= 143 | __verbose_output= 144 | __script_debug= 145 | __restart_ssh_server= 146 | __run_autoremove= 147 | __reboot_system= 148 | 149 | # Setup script colors and special text to use 150 | __colors_bold_text="$(tput bold)" 151 | __colors_warning_text="${__colors_bold_text}$(tput setaf 1)" 152 | __colors_error_text="${__colors_bold_text}$(tput setaf 1)" 153 | __colors_notice_text="${__colors_bold_text}$(tput setaf 6)" 154 | __colors_success_text="${__colors_bold_text}$(tput setaf 2)" 155 | __colors_default="$(tput sgr0)" 156 | __spinner="-\\|/" 157 | 158 | ### Error/cleanup handling 159 | ############################################################################## 160 | 161 | # Run miscellaneous tasks before exiting 162 | ### 163 | # Restart services if needed 164 | # Fix UniFi source list if needed 165 | # Auto clean and remove un-needed apt-get info/packages 166 | # Show UniFi SDN Controller information post-setup 167 | # Unset script variables 168 | # Reboot system if needed 169 | function __eubnt_cleanup_before_exit() { 170 | local log_files_to_delete= 171 | echo -e "${__colors_default}\\nCleaning up script, please wait...\\n" 172 | if [[ -n "${__restart_ssh_server:-}" ]]; then 173 | __eubnt_run_command "service ssh restart" 174 | fi 175 | if [[ -n "${__unifi_version_installed:-}" ]]; then 176 | __unifi_update_available=$(apt-cache policy "unifi" | grep "Candidate" | awk '{print $2}' | sed 's/-.*//g') 177 | if [[ "${__unifi_update_available:0:3}" != "${__unifi_version_installed:0:3}" ]]; then 178 | if __eubnt_add_source "http://www.ubnt.com/downloads/unifi/debian unifi-${__unifi_version_installed:0:3} ubiquiti" "100-ubnt-unifi.list"; then 179 | __eubnt_run_command "apt-get update" 180 | fi 181 | fi 182 | fi 183 | __eubnt_run_command "apt-get autoclean --yes" 184 | if [[ -n "${__run_autoremove:-}" ]]; then 185 | __eubnt_run_command "apt-get autoremove --yes" 186 | fi 187 | if [[ -f "/lib/systemd/system/unifi.service" && -z "${__reboot_system:-}" ]]; then 188 | __eubnt_show_header "Collecting UniFi SDN Controller info..." 189 | local controller_status= 190 | if [[ $(service unifi status | wc --lines) -gt 1 ]]; then 191 | controller_status=$(service unifi status | grep --only-matching "Active: .*" | sed 's/Active:/Service status:/') 192 | else 193 | controller_status="Service status: $(service unifi status)" 194 | fi 195 | __eubnt_show_notice "\\n${controller_status}" 196 | if [[ -n "${__unifi_tcp_port_admin:-}" ]]; then 197 | local controller_address 198 | if [[ -n "${__unifi_domain_name:-}" ]]; then 199 | controller_address="${__unifi_domain_name}" 200 | else 201 | controller_address="${__machine_ip_address}" 202 | fi 203 | __eubnt_show_notice "\\nWeb address: https://${controller_address}:${__unifi_tcp_port_admin}/manage/" 204 | fi 205 | echo 206 | fi 207 | if [[ -d "${__script_log_dir:-}" ]]; then 208 | log_files_to_delete=$(find "${__script_log_dir}" -maxdepth 1 -type f -print0 | xargs -0 --exit ls -t | awk 'NR>5') 209 | if [[ -n "${log_files_to_delete:-}" ]]; then 210 | echo "${log_files_to_delete}" | xargs --max-lines=1 rm 211 | fi 212 | fi 213 | if [[ "${__script_debug:-}" != "true" ]]; then 214 | for var_name in ${!__*}; do 215 | if [[ "${var_name}" != "__reboot_system" ]]; then 216 | unset -v "${var_name}" 217 | fi 218 | done 219 | fi 220 | if [[ -n "${__reboot_system:-}" ]]; then 221 | shutdown -r now 222 | fi 223 | } 224 | trap __eubnt_cleanup_before_exit EXIT 225 | trap '__script_debug=true' ERR 226 | 227 | ### Screen display functions 228 | ############################################################################## 229 | 230 | # Set script colors 231 | function __eubnt_script_colors() { 232 | echo "${__colors_default}" 233 | } 234 | 235 | # Print an error to the screen 236 | # $1: The error text to display 237 | function __eubnt_show_error() { 238 | echo -e "${__colors_error_text}##############################################################################\\n" 239 | __eubnt_echo_and_log "ERROR! ${1:-}${__colors_default}\\n" 240 | } 241 | 242 | # Print a header that informs the user what task is running 243 | # $1: Can be set with a string to display additional details about the current task 244 | ### 245 | # If the script is not in debug mode, then the screen will be cleared first 246 | # The script header will then be displayed 247 | # If $1 is set then it will be displayed under the header 248 | function __eubnt_show_header() { 249 | if [[ -z "${__script_debug:-}" ]]; then 250 | clear 251 | fi 252 | echo -e "${__colors_notice_text}### Easy UBNT: UniFi SDN Installer ${__script_version}" 253 | echo -e "##############################################################################${__colors_default}\\n" 254 | __eubnt_show_notice "${1:-}" 255 | } 256 | 257 | # Print text to the screen 258 | # $1: The text to display 259 | function __eubnt_show_text() { 260 | if [[ -n "${1:-}" ]]; then 261 | __eubnt_echo_and_log "${__colors_default}${1}${__colors_default}\\n" 262 | fi 263 | } 264 | 265 | # Print a notice to the screen 266 | # $1: The notice to display 267 | function __eubnt_show_notice() { 268 | if [[ -n "${1:-}" ]]; then 269 | __eubnt_echo_and_log "${__colors_notice_text}${1}${__colors_default}\\n" 270 | fi 271 | } 272 | 273 | # Print a success message to the screen 274 | # $1: The message to display 275 | function __eubnt_show_success() { 276 | if [[ -n "${1:-}" ]]; then 277 | __eubnt_echo_and_log "${__colors_success_text}${1}${__colors_default}\\n" 278 | fi 279 | } 280 | 281 | # Print a warning to the screen 282 | # $1: The warning to display 283 | # $2: Can be set to "none" to not show the "WARNING:" prefix 284 | function __eubnt_show_warning() { 285 | if [[ -n "${1:-}" ]]; then 286 | local warning_prefix= 287 | if [[ "${2:-}" != "none" ]]; then 288 | warning_prefix="WARNING: " 289 | fi 290 | __eubnt_echo_and_log "${__colors_warning_text}${warning_prefix}${1}${__colors_default}\\n" 291 | fi 292 | } 293 | 294 | # Print the license and disclaimer for this script to the screen 295 | function __eubnt_show_license() { 296 | __eubnt_show_text "MIT License\\nCopyright (c) 2018 SprockTech, LLC and contributors\\n" 297 | __eubnt_show_notice "${__script_contributors:-}\\n" 298 | __eubnt_show_warning "This script will guide you through installing and upgrading 299 | the UniFi SDN Controller from UBNT, and securing this system 300 | according to best practices. It is intended to work on systems that 301 | will be dedicated to running the UniFi SDN Controller.\\n 302 | THIS SCRIPT IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND!\\n" 303 | __eubnt_show_text "Read the full MIT License for this script here: 304 | https://github.com/sprockteam/easy-ubnt/raw/master/LICENSE\\n" 305 | } 306 | 307 | ### User input functions 308 | ############################################################################## 309 | 310 | # Display a yes or know question and proceed accordingly based on the answer 311 | # $1: The question to use instead of the default question 312 | # $2: Can be set to "return" if an error should be returned instead of exiting 313 | # $3: Can be set to "n" if the default answer should be no instead of yes 314 | ### 315 | # If no answer is given, the default answer is used 316 | # If the script it running in "quiet mode" then the default answer is used without prompting 317 | function __eubnt_question_prompt() { 318 | local yes_no= 319 | local default_question="Do you want to proceed?" 320 | local default_answer="y" 321 | if [[ "${3:-}" = "n" ]]; then 322 | default_answer="n" 323 | fi 324 | if [[ -n "${__quick_mode:-}" ]]; then 325 | yes_no="${default_answer}" 326 | fi 327 | while [[ ! "${yes_no}" =~ (^[Yy]([Ee]?|[Ee][Ss])?$)|(^[Nn][Oo]?$) ]]; do 328 | echo -e -n "${__colors_notice_text}${1:-$default_question} (y/n, default ${default_answer})${__colors_default} " 329 | read -r yes_no 330 | echo -e -n "\\r" 331 | if [[ "${yes_no}" = "" ]]; then 332 | yes_no="${default_answer}" 333 | fi 334 | done 335 | __eubnt_add_to_log "${1:-$default_question} ${yes_no}" 336 | case "${yes_no}" in 337 | [Nn]*) 338 | echo 339 | if [[ "${2:-}" = "return" ]]; then 340 | return 1 341 | else 342 | exit 343 | fi;; 344 | [Yy]*) 345 | echo 346 | return 0;; 347 | esac 348 | } 349 | 350 | # Display a question and return full user input 351 | # $1: The question to ask, there is no default question so one must be set 352 | # $2: The variable to assign the answer to, this must also be set 353 | # $3: Can be set to "optional" to allow for an empty response to bypass the question 354 | ### 355 | # No validation is done on use the input within this function, must be done after the answer has been returned 356 | function __eubnt_get_user_input() { 357 | local user_input= 358 | if [[ -n "${1:-}" && -n "${2:-}" ]]; then 359 | while [[ -z "${user_input}" ]]; do 360 | echo -e -n "${__colors_notice_text}${1}${__colors_default} " 361 | read -r user_input 362 | echo -e -n "\\r" 363 | if [[ "${3:-}" = "optional" ]]; then 364 | break 365 | fi 366 | done 367 | __eubnt_add_to_log "${1} ${user_input}" 368 | eval "${2}=\"${user_input}\"" 369 | fi 370 | } 371 | 372 | ### Logging and task functions 373 | ############################################################################## 374 | 375 | # Add to the log file 376 | # $1: The message to log 377 | function __eubnt_add_to_log() { 378 | if [[ -n "${1:-}" ]]; then 379 | echo "${1}" | sed -r 's/\^\[.*m//g' >>"${__script_log}" 380 | fi 381 | } 382 | 383 | # Echo to the screen and log file 384 | # $1: The message to echo 385 | # $2: Optional file to pipe echo output to 386 | # $3: If set to "append" then the message is appended to file specified in $2 387 | function __eubnt_echo_and_log() { 388 | if [[ -n "${1:-}" ]]; then 389 | if [[ -n "${2:-}" ]]; then 390 | if [[ "${3:-}" = "append" ]]; then 391 | echo "${1}" | tee -a "${2}" 392 | else 393 | echo "${1}" | tee "${2}" 394 | fi 395 | else 396 | echo -e -n "${1}" 397 | fi 398 | __eubnt_add_to_log "${1}" 399 | fi 400 | } 401 | 402 | # Get the latest UniFi SDN controller minor version for the major version given 403 | # $1: The major version number to check (i.e. "5.9") 404 | # $2: The variable to assign the returned minor version to 405 | # $3: If set to "url" then return the full URL to the download file instead of just the version number 406 | function __eubnt_get_latest_unifi_version() { 407 | if [[ -n "${1:-}" && -n "${2:-}" ]]; then 408 | local ubnt_download="http://dl.ubnt.com/unifi/debian/dists" 409 | local unifi_version_full=$(wget --quiet --output-document - "${ubnt_download}/unifi-${1}/ubiquiti/binary-amd64/Packages" | grep "Version" | sed 's/Version: //') 410 | if [[ "${3:-}" = "url" ]]; then 411 | local deb_url="${ubnt_download}/pool/ubiquiti/u/unifi/unifi_${unifi_version_full}_all.deb" 412 | eval "${2}=\"${deb_url}\"" 413 | else 414 | local unifi_version_short=$(echo "${unifi_version_full}" | sed 's/-.*//') 415 | eval "${2}=\"${unifi_version_short}\"" 416 | fi 417 | fi 418 | } 419 | 420 | # Try to get the release notes for the given UniFi SDN version 421 | # $1: The full version number to check (i.e. "5.9.29") 422 | # $2: The variable to assign the filename with the release notes 423 | function __eubnt_get_unifi_release_notes() { 424 | if [[ -z "${1:-}" && -z "${2:-}" ]]; then 425 | __eubnt_show_warning "Invalid check for release notes at $(caller)" 426 | return 1 427 | fi 428 | local download_url="" 429 | local found_version="" 430 | local version_major="&filter=eq~~version_major~~$(echo "${1}" | cut --fields 1 --delimiter '.')" 431 | local version_minor="&filter=eq~~version_minor~~$(echo "${1}" | cut --fields 2 --delimiter '.')" 432 | local version_patch="&filter=eq~~version_patch~~$(echo "${1}" | cut --fields 3 --delimiter '.')" 433 | local ubnt_update_api="https://fw-update.ubnt.com/api/firmware" 434 | local update_url="${ubnt_update_api:-}?filter=eq~~product~~unifi-controller&filter=eq~~platform~~document${version_major}${version_minor}${version_patch}&limit=1" 435 | local release_notes_url="$(wget --quiet --output-document - "${update_url:-}" | grep --max-count=1 "changelog/unifi-controller" | sed 's|.*"href": "||' | sed 's|"||')" 436 | local release_notes_file="$(mktemp)" 437 | __eubnt_add_to_log "Trying to get release notes from: ${release_notes_url:-}" 438 | if wget --quiet --output-document - "${release_notes_url:-}" | sed '/#### Recommended Firmware:/,$d' 1>"${release_notes_file:-}"; then 439 | if [[ -f "${release_notes_file:-}" && -s "${release_notes_file:-}" ]]; then 440 | eval "${2}=\"${release_notes_file}\"" 441 | return 0 442 | fi 443 | fi 444 | return 1 445 | } 446 | 447 | # A wrapper to run commands, display a nice message and handle errors gracefully 448 | # $1: The full command to run as a string 449 | # $2: If set to "foreground" then the command will run in the foreground, if set to "quiet" the output will be directed to the log file, if set to "return" then output will be assigned to variable named in $3 450 | # $3: Name of variable to assign output value of the command if $2 is set to "return" 451 | ### 452 | # Make sure the command seems valid 453 | # Run the command in the background and show a spinner (https://unix.stackexchange.com/a/225183) 454 | # Run the command in the foreground when in verbose mode 455 | # Wait for the command to finish and get the exit code (https://stackoverflow.com/a/1570356) 456 | function __eubnt_run_command() { 457 | if [[ -n "${1:-}" ]]; then 458 | local background_pid= 459 | local command_output= 460 | local command_return= 461 | declare -a full_command=() 462 | IFS=' ' read -r -a full_command <<< "${1}" 463 | if [[ ! $(command -v "${full_command[0]}") ]]; then 464 | local found_package= 465 | local unknown_command="${full_command[0]}" 466 | __eubnt_install_package "apt-file" 467 | if [[ "${unknown_command}" != "apt-file" ]]; then 468 | __eubnt_run_command "apt-file update" 469 | __eubnt_run_command "apt-file --package-only --regexp search .*bin\\/${unknown_command}$" "return" "found_package" 470 | if [[ -n "${found_package:-}" ]]; then 471 | __eubnt_install_package "${found_package}" 472 | else 473 | __eubnt_show_error "Unknown command ${unknown_command} at $(caller)" 474 | return 1 475 | fi 476 | fi 477 | fi 478 | if [[ "${full_command[0]}" != "echo" ]]; then 479 | __eubnt_add_to_log "${1}" 480 | fi 481 | if [[ ( -n "${__verbose_output:-}" && "${2:-}" != "quiet" ) || "${2:-}" = "foreground" || "${full_command[1]}" = "echo" || ( "${2:-}" != "return" && -n "${__is_experimental:-}" ) ]]; then 482 | if [[ -n "${__is_experimental:-}" ]]; then 483 | echo "${1}" 484 | fi 485 | "${full_command[@]}" | tee -a "${__script_log}" 486 | command_return=$? 487 | elif [[ "${2:-}" = "quiet" ]]; then 488 | "${full_command[@]}" &>>"${__script_log}" || __eubnt_show_warning "Unable to run ${1} at $(caller)\\n" 489 | command_return=$? 490 | elif [[ "${2:-}" = "return" ]]; then 491 | command_output=$(mktemp) 492 | if [[ -n "${__is_experimental:-}" ]]; then 493 | echo "${1}" 494 | "${full_command[@]}" &>>"${command_output}" 495 | command_return=$? 496 | else 497 | "${full_command[@]}" &>>"${command_output}" & 498 | background_pid=$! 499 | fi 500 | else 501 | "${full_command[@]}" &>>"${__script_log}" & 502 | background_pid=$! 503 | fi 504 | if [[ -n "${background_pid:-}" ]]; then 505 | local i=0 506 | while [[ -d /proc/$background_pid ]]; do 507 | echo -e -n "\\rRunning ${1} [${__spinner:i++%${#__spinner}:1}]" 508 | sleep 0.5 509 | if [[ $i -gt 360 ]]; then 510 | break 511 | fi 512 | done 513 | wait $background_pid 514 | command_return=$? 515 | if [[ ${command_return} -gt 0 ]]; then 516 | __eubnt_echo_and_log "\\rRunning ${1} [x]\\n" 517 | else 518 | __eubnt_echo_and_log "\\rRunning ${1} [\\xE2\\x9C\\x93]\\n" 519 | fi 520 | fi 521 | if [[ "${2:-}" = "return" && -n "${3:-}" && -e "${command_output:-}" && -s "${command_output:-}" && ${command_return} -eq 0 ]]; then 522 | # shellcheck disable=SC2086 523 | eval "${3}=$(cat ${command_output})" 524 | rm "${command_output}" 525 | fi 526 | if [[ ${command_return} -gt 0 ]]; then 527 | return 1 528 | else 529 | return 0 530 | fi 531 | fi 532 | __eubnt_show_warning "No command given at $(caller)\\n" 533 | return 1 534 | } 535 | 536 | # Add a source list to the system 537 | # $1: The source information to use 538 | # $2: The name of the source list file to make on the local machine 539 | # $3: A search term to use when checking if the source list should be added 540 | function __eubnt_add_source() { 541 | if [[ "${1:-}" && "${2:-}" && "${3:-}" ]]; then 542 | if [[ ! $(find /etc/apt -name "*.list" -exec grep "${3}" {} \;) ]]; then 543 | __eubnt_echo_and_log "deb ${1}" "${__apt_sources_dir}/${2}" 544 | return 0 545 | else 546 | __eubnt_add_to_log "Skipping add source for ${1}" 547 | return 1 548 | fi 549 | fi 550 | } 551 | 552 | # Add a package signing key to the system if needed 553 | # $1: The 32-bit hex fingerprint of the key to add 554 | function __eubnt_add_key() { 555 | if [[ "${1:-}" ]]; then 556 | if ! apt-key list 2>/dev/null | grep --quiet "${1:0:4}.*${1:4:4}"; then 557 | __eubnt_run_command "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-key ${1}" 558 | fi 559 | fi 560 | } 561 | 562 | # Check if is package is installed 563 | # $1: The name of the package to check 564 | function __eubnt_is_package_installed() { 565 | if [[ -z "${1:-}" ]]; then 566 | return 1 567 | fi 568 | local package_name=$(echo "${1}" | sed 's/=.*//') 569 | if dpkg --list "${package_name}" 2>/dev/null | grep --quiet "^i"; then 570 | return 0 571 | else 572 | return 1 573 | fi 574 | } 575 | 576 | # Install package if needed and handle errors gracefully 577 | # $1: The name of the package to install 578 | # $2: An optional target release to use 579 | # $3: If set to "return" then return a status 580 | function __eubnt_install_package() { 581 | if [[ "${1:-}" ]]; then 582 | local target_release 583 | if ! apt-get install --simulate "${1}" &>/dev/null; then 584 | __eubnt_setup_sources "os" 585 | __eubnt_run_command "apt-get update" 586 | __eubnt_run_command "apt-get install --fix-broken --yes" 587 | __eubnt_run_command "apt-get autoremove --yes" 588 | fi 589 | if apt-get install --simulate "${1}" &>/dev/null; then 590 | if ! __eubnt_is_package_installed "${1}"; then 591 | local i=0 592 | while lsof /var/lib/dpkg/lock &>/dev/null; do 593 | echo -e -n "\\rWaiting for package manager to become available... [${__spinner:i++%${#__spinner}:1}]" 594 | sleep 0.5 595 | done 596 | __eubnt_echo_and_log "\\rWaiting for package manager to become available... [\\xE2\\x9C\\x93]\\n" 597 | export DEBIAN_FRONTEND=noninteractive 598 | if [[ -n "${2:-}" ]]; then 599 | __eubnt_run_command "apt-get install --quiet --no-install-recommends --yes --target-release ${2} ${1}" "${3:-}" 600 | else 601 | __eubnt_run_command "apt-get install --quiet --no-install-recommends --yes ${1}" "${3:-}" 602 | fi 603 | else 604 | __eubnt_echo_and_log "Package ${1} already installed [\\xE2\\x9C\\x93]\\n" 605 | if [[ "${3:-}" = "return" ]]; then 606 | return 0 607 | fi 608 | fi 609 | else 610 | __eubnt_show_error "Unable to install package ${1} at $(caller)" 611 | if [[ "${3:-}" = "return" ]]; then 612 | return 1 613 | fi 614 | fi 615 | fi 616 | } 617 | 618 | ### Parse commandline options 619 | ############################################################################## 620 | 621 | # Basic way to get command line options 622 | # TODO: Incorporate B3BP methods here 623 | while getopts ":aqvx" options; do 624 | case "${options}" in 625 | a) 626 | __eubnt_add_to_log "Accepted license via command line option" 627 | __accept_license=true;; 628 | q) 629 | __eubnt_add_to_log "Running script in quick mode" 630 | __quick_mode=true;; 631 | v) 632 | __eubnt_add_to_log "Running script with verbose screen output" 633 | __verbose_output=true;; 634 | x) 635 | __eubnt_add_to_log "Running script with tracing turned on for debugging" 636 | set -o xtrace 637 | __script_debug=true;; 638 | *) 639 | break;; 640 | esac 641 | done 642 | 643 | ### Main script functions 644 | ############################################################################## 645 | 646 | # Setup source lists for later use in the script 647 | # $1: If set to "os" then only setup core repos for the OS 648 | ### 649 | # Ubuntu: Setup alternative source lists to get certain packages 650 | # Debian: Make sure the dirmngr package is installed so keys can be validated 651 | # Certbot: Debian distribution sources include it, add sources for Ubuntu except Precise 652 | # Java: Use WebUpd8 repository for Precise and Trust era OSes (https://gist.github.com/pyk/19a619b0763d6de06786 | https://askubuntu.com/a/190674) 653 | # Java: Use the core distribution sources to get Java for all others 654 | # Mongo: Official repository only distributes 64-bit packages, not compatible with Wheezy 655 | # Mongo: UniFi will install it from distribution sources if needed 656 | # UniFi: Add UBNT package signing key here, add source list later depending on the chosen version 657 | # shellcheck disable=SC2120 658 | function __eubnt_setup_sources() { 659 | local do_apt_update= 660 | if [[ -n "${__is_ubuntu:-}" ]]; then 661 | __eubnt_add_source "http://archive.ubuntu.com/ubuntu ${__os_version_name} main universe" "${__os_version_name}-archive.list" "archive\\.ubuntu\\.com.*${__os_version_name}.*main" && do_apt_update=true 662 | __eubnt_add_source "http://security.ubuntu.com/ubuntu ${__os_version_name}-security main universe" "${__os_version_name}-security.list" "security\\.ubuntu\\.com.*${__os_version_name}-security main" && do_apt_update=true 663 | __eubnt_add_source "http://mirrors.kernel.org/ubuntu ${__os_version_name} main universe" "${__os_version_name}-mirror.list" "mirrors\\.kernel\\.org.*${__os_version_name}.*main" && do_apt_update=true 664 | elif [[ -n "${__is_debian:-}" ]]; then 665 | __eubnt_install_package "dirmngr" 666 | __eubnt_add_source "http://ftp.debian.org/debian ${__os_version_name}-backports main" "${__os_version_name}-backports.list" "ftp\\.debian\\.org.*${__os_version_name}-backports.*main" && do_apt_update=true 667 | fi 668 | if [[ "${1:-}" != "os" ]]; then 669 | if [[ -n "${__setup_source_java:-}" ]]; then 670 | if [[ "${__os_version_name_ubuntu_equivalent:-}" = "precise" || "${__os_version_name:-}" = "trusty" ]]; then 671 | __install_webupd8_java=true 672 | echo "oracle-java8-installer shared/accepted-oracle-license-v1-1 select true" | debconf-set-selections 673 | __eubnt_add_source "http://ppa.launchpad.net/webupd8team/java/ubuntu ${__os_version_name_ubuntu_equivalent} main" "webupd8team-java.list" "ppa\\.launchpad\\.net.*${__os_version_name_ubuntu_equivalent}.*main" && do_apt_update=true 674 | __eubnt_add_key "EEA14886" # WebUpd8 package signing key 675 | else 676 | __install_java=true 677 | fi 678 | fi 679 | if [[ -n "${__setup_source_mongo:-}" ]]; then 680 | local mongo_repo_distro= 681 | local mongo_repo_url= 682 | if [[ -n "${__is_64:-}" && -n "${__is_ubuntu:-}" ]]; then 683 | if [[ "${__os_version_name}" = "precise" ]]; then 684 | mongo_repo_distro="trusty" 685 | elif [[ "${__os_version_name}" = "bionic" ]]; then 686 | mongo_repo_distro="xenial" 687 | else 688 | mongo_repo_distro="${__os_version_name}" 689 | fi 690 | mongo_repo_url="http://repo.mongodb.org/apt/ubuntu ${mongo_repo_distro}/mongodb-org/3.4 multiverse" 691 | elif [[ -n "${__is_64:-}" && -n "${__is_debian:-}" ]]; then 692 | if [[ "${__os_version_name:-}" != "wheezy" ]]; then 693 | mongo_repo_url="http://repo.mongodb.org/apt/debian jessie/mongodb-org/3.4 main" 694 | __eubnt_add_source "http://ftp.debian.org/debian jessie-backports main" "jessie-backports.list" "ftp\\.debian\\.org.*jessie-backports.*main" && do_apt_update=true 695 | fi 696 | fi 697 | if [[ -n "${mongo_repo_url:-}" ]]; then 698 | if __eubnt_add_source "${mongo_repo_url}" "mongodb-org-3.4.list" "repo\\.mongodb\\.org.*3\\.4"; then 699 | do_apt_update=true 700 | fi 701 | __eubnt_add_key "A15703C6" # Mongo package signing key 702 | __install_mongo=true 703 | fi 704 | fi 705 | if [[ -n "${__setup_source_certbot:-}" && -n "${__is_ubuntu:-}" ]]; then 706 | if [[ "${__os_version_name:-}" != "precise" ]]; then 707 | __eubnt_add_source "http://ppa.launchpad.net/certbot/certbot/ubuntu ${__os_version_name} main" "certbot-ubuntu-certbot-${__os_version_name}.list" "ppa\\.laundpad\\.net.*${__os_version_name}.*main" && do_apt_update=true 708 | __eubnt_add_key "75BCA694" # Certbot package signing key 709 | fi 710 | fi 711 | __eubnt_add_key "C0A52C50" # UBNT package signing key 712 | fi 713 | if [[ -n "${do_apt_update:-}" ]]; then 714 | __eubnt_run_command "apt-get update" 715 | fi 716 | } 717 | 718 | # Collection of different fixes to do pre/post apt install/upgrade 719 | ### 720 | # Try to fix broken installs 721 | # Remove un-needed packages 722 | # Remove cached source list information 723 | # Fix for kernel files filling /boot in Ubuntu (https://askubuntu.com/a/90219) 724 | # Update apt-get and apt-file 725 | function __eubnt_install_fixes { 726 | __eubnt_show_header "Running common pre-install fixes...\\n" 727 | __eubnt_run_command "apt-get install --fix-broken --yes" 728 | __eubnt_run_command "apt-get autoremove --yes" 729 | __eubnt_run_command "apt-get clean --yes" 730 | __eubnt_run_command "rm -rf /var/lib/apt/lists/*" 731 | if [[ -n "${__is_ubuntu:-}" && -d /boot ]]; then 732 | local configured_localhost= 733 | if __eubnt_run_command "hostname --short" "return" "configured_localhost"; then 734 | if ! grep --quiet "127\.0\.1\.1.*{configured_localhost}" /etc/hosts; then 735 | sed -i "1s/^/127.0.1.1\t${configured_localhost}\n/" /etc/hosts 736 | fi 737 | fi 738 | if [[ $(df /boot | awk '/\/boot/{gsub("%", ""); print $5}') -gt 50 ]]; then 739 | declare -a files_in_boot=() 740 | declare -a kernel_packages=() 741 | __eubnt_show_text "Removing old kernel files from /boot" 742 | while IFS=$'\n' read -r found_file; do files_in_boot+=("$found_file"); done < <(find /boot -maxdepth 1 -type f) 743 | for boot_file in "${!files_in_boot[@]}"; do 744 | kernel_version=$(echo "${files_in_boot[$boot_file]}" | grep --extended-regexp --only-matching "[0-9]+\\.[0-9]+(\\.[0-9]+)?(\\-{1}[0-9]+)?") 745 | if [[ "${kernel_version}" = *"-"* && "${__os_kernel_version}" = *"-"* && "${kernel_version//-*/}" = "${__os_kernel_version//-*/}" && "${kernel_version//*-/}" -lt "${__os_kernel_version//*-/}" ]]; then 746 | # shellcheck disable=SC2227 747 | find /boot -maxdepth 1 -type f -name "*${kernel_version}*" -exec rm {} \; -exec echo Removing {} >>"${__script_log}" \; 748 | fi 749 | done 750 | __eubnt_run_command "apt-get install --fix-broken --yes" 751 | __eubnt_run_command "apt-get autoremove --yes" 752 | while IFS=$'\n' read -r found_package; do kernel_packages+=("$found_package"); done < <(dpkg --list linux-{image,headers}-"[0-9]*" | awk '/linux/{print $2}') 753 | for kernel in "${!kernel_packages[@]}"; do 754 | kernel_version=$(echo "${kernel_packages[$kernel]}" | sed --regexp-extended 's/linux-(image|headers)-//g' | sed 's/[-][a-z].*//g') 755 | if [[ "${kernel_version}" = *"-"* && "${__os_kernel_version}" = *"-"* && "${kernel_version//-*/}" = "${__os_kernel_version//-*/}" && "${kernel_version//*-/}" -lt "${__os_kernel_version//*-/}" ]]; then 756 | __eubnt_run_command "apt-get purge --yes ${kernel_packages[$kernel]}" 757 | fi 758 | done 759 | fi 760 | fi 761 | __eubnt_run_command "apt-get update" 762 | __eubnt_run_command "apt-file update" 763 | } 764 | 765 | # Install basic system utilities and dependencies needed for successful script run 766 | function __eubnt_install_updates_utils() { 767 | __eubnt_show_header "Installing utilities and updates...\\n" 768 | __eubnt_install_package "software-properties-common" 769 | __eubnt_install_package "apt-transport-https" 770 | __eubnt_install_package "unattended-upgrades" 771 | __eubnt_install_package "sudo" 772 | __eubnt_install_package "curl" 773 | __eubnt_install_package "net-tools" 774 | __eubnt_install_package "dnsutils" 775 | __eubnt_install_package "psmisc" 776 | __eubnt_install_package "binutils" 777 | if [[ -n "${__hold_java:-}" ]]; then 778 | __eubnt_run_command "apt-mark hold ${__hold_java}" 779 | fi 780 | if [[ -n "${__hold_mongo:-}" ]]; then 781 | __eubnt_run_command "apt-mark hold ${__hold_mongo}" 782 | fi 783 | if [[ -n "${__hold_unifi:-}" ]]; then 784 | __eubnt_run_command "apt-mark hold ${__hold_unifi}" 785 | fi 786 | echo 787 | if apt-get dist-upgrade --simulate | grep --quiet "^Inst"; then 788 | if __eubnt_question_prompt "Do you want to upgrade all currently installed packages?" "return"; then 789 | __eubnt_run_command "apt-get dist-upgrade --yes" 790 | fi 791 | fi 792 | if [[ -n "${__hold_java:-}" ]]; then 793 | __eubnt_run_command "apt-mark unhold ${__hold_java}" 794 | fi 795 | if [[ -n "${__hold_mongo:-}" ]]; then 796 | __eubnt_run_command "apt-mark unhold ${__hold_mongo}" 797 | fi 798 | if [[ -n "${__hold_unifi:-}" ]]; then 799 | __eubnt_run_command "apt-mark unhold ${__hold_unifi}" 800 | fi 801 | __run_autoremove=true 802 | } 803 | 804 | # Use haveged for better entropy generation from @ssawyer (https://community.ubnt.com/t5/UniFi-Wireless/UniFi-Controller-Linux-Install-Issues/m-p/1324455/highlight/true#M116452) 805 | # Virtual memory tweaks from @adrianmmiller 806 | function __eubnt_system_tweaks() { 807 | __eubnt_show_header "Tweaking system for performance and security...\\n" 808 | if ! __eubnt_is_package_installed "haveged"; then 809 | if __eubnt_question_prompt "Do you want to install a better entropy generator?" "return"; then 810 | __eubnt_install_package "haveged" 811 | fi 812 | fi 813 | echo 814 | if [[ $(cat /proc/sys/vm/swappiness) -ne 10 || $(cat /proc/sys/vm/vfs_cache_pressure) -ne 50 ]]; then 815 | if __eubnt_question_prompt "Do you want adjust the system to prefer RAM over virtual memory?" "return"; then 816 | __eubnt_run_command "sysctl vm.swappiness=10" 817 | __eubnt_run_command "sysctl vm.vfs_cache_pressure=50" 818 | fi 819 | fi 820 | } 821 | 822 | # Install OpenJDK Java 8 if available from distribution sources 823 | # Install WebUpd8 Java if OpenJDK is not available from the distribution 824 | function __eubnt_install_java() { 825 | if [[ -n "${__install_webupd8_java:-}" || -n "${__install_java:-}" ]]; then 826 | __eubnt_show_header "Installing Java...\\n" 827 | if [[ -n "${__install_webupd8_java:-}" ]]; then 828 | __eubnt_install_package "oracle-java8-installer" 829 | __eubnt_install_package "oracle-java8-set-default" 830 | else 831 | local target_release= 832 | if [[ "${__os_version_name:-}" = "jessie" ]]; then 833 | target_release="${__os_version_name}-backports" 834 | fi 835 | __eubnt_install_package "ca-certificates-java" "${target_release:-}" 836 | __eubnt_install_package "openjdk-8-jre-headless" "${target_release:-}" 837 | fi 838 | __eubnt_install_package "jsvc" 839 | __eubnt_install_package "libcommons-daemon-java" 840 | fi 841 | } 842 | 843 | # Purge MongoDB if desired and UniFi SDN is not installed 844 | function __eubnt_purge_mongo() { 845 | if [[ -n "${__purge_mongo:-}" && -z "${__is_unifi_installed:-}" ]]; then 846 | __eubnt_show_header "Purging MongoDB...\\n" 847 | apt-get purge --yes "mongodb*" 848 | rm "${__apt_sources_dir}/mongodb"* 849 | __eubnt_run_command "apt-get update" 850 | fi 851 | } 852 | 853 | # Install MongoDB 3.4 from the official MongoDB repo 854 | # Only available for 64-bit 855 | function __eubnt_install_mongo() 856 | { 857 | if [[ -n "${__is_64:-}" && -n "${__install_mongo:-}" ]]; then 858 | __eubnt_show_header "Installing MongoDB...\\n" 859 | __eubnt_install_package "mongodb-org=3.4.*" 860 | fi 861 | } 862 | 863 | # Show install/reinstall/update options for UniFi SDN 864 | function __eubnt_install_unifi() 865 | { 866 | __eubnt_show_header "Installing UniFi SDN Controller...\\n" 867 | local selected_unifi_version= 868 | local latest_unifi_version= 869 | declare -a unifi_supported_versions=(5.6 5.8 5.9) 870 | declare -a unifi_historical_versions=(5.4 5.5 5.6 5.8 5.9) 871 | declare -a unifi_versions_to_install=() 872 | declare -a unifi_versions_to_select=() 873 | if [[ -n "${__unifi_version_installed:-}" ]]; then 874 | __eubnt_show_notice "Version ${__unifi_version_installed} is currently installed\\n" 875 | fi 876 | if [[ -n "${__quick_mode:-}" ]]; then 877 | if [[ -n "${__unifi_version_installed:-}" ]]; then 878 | selected_unifi_version="${__unifi_version_installed:0:3}" 879 | else 880 | selected_unifi_version="${__unifi_version_stable}" 881 | fi 882 | else 883 | for version in "${!unifi_supported_versions[@]}"; do 884 | if [[ -n "${__unifi_version_installed:-}" ]]; then 885 | if [[ "${unifi_supported_versions[$version]:0:3}" = "${__unifi_version_installed:0:3}" ]]; then 886 | if [[ -n "${__unifi_update_available:-}" ]]; then 887 | unifi_versions_to_select+=("${__unifi_update_available}") 888 | else 889 | unifi_versions_to_select+=("${__unifi_version_installed}") 890 | fi 891 | elif [[ "${unifi_supported_versions[$version]:2:1}" -gt "${__unifi_version_installed:2:1}" ]]; then 892 | __eubnt_get_latest_unifi_version "${unifi_supported_versions[$version]}" "latest_unifi_version" 893 | unifi_versions_to_select+=("${latest_unifi_version}") 894 | fi 895 | else 896 | __eubnt_get_latest_unifi_version "${unifi_supported_versions[$version]}" "latest_unifi_version" 897 | unifi_versions_to_select+=("${latest_unifi_version}") 898 | fi 899 | done 900 | unifi_versions_to_select+=("Skip") 901 | __eubnt_show_notice "Which controller do you want to (re)install or upgrade to?\\n" 902 | select version in "${unifi_versions_to_select[@]}"; do 903 | case "${version}" in 904 | "") 905 | selected_unifi_version="${__unifi_version_stable}" 906 | break;; 907 | *) 908 | if [[ "${version}" = "Skip" ]]; then 909 | return 0 910 | fi 911 | selected_unifi_version="${version:0:3}" 912 | break;; 913 | esac 914 | done 915 | fi 916 | if [[ -n "${__unifi_version_installed:-}" ]]; then 917 | for step in "${!unifi_historical_versions[@]}"; do 918 | __eubnt_get_latest_unifi_version "${unifi_historical_versions[$step]}" "latest_unifi_version" 919 | if [[ (("${unifi_historical_versions[$step]:2:1}" -eq "${__unifi_version_installed:2:1}" && "${latest_unifi_version}" != "${__unifi_version_installed}") || "${unifi_historical_versions[$step]:2:1}" -gt "${__unifi_version_installed:2:1}") && "${unifi_historical_versions[$step]:2:1}" -le "${selected_unifi_version:2:1}" ]]; then 920 | unifi_versions_to_install+=("${unifi_historical_versions[$step]}") 921 | fi 922 | done 923 | if [[ "${#unifi_versions_to_install[@]}" -eq 0 ]]; then 924 | unifi_versions_to_install=("${__unifi_version_installed:0:3}") 925 | fi 926 | else 927 | unifi_versions_to_install=("${selected_unifi_version}") 928 | fi 929 | for version in "${!unifi_versions_to_install[@]}"; do 930 | __eubnt_install_unifi_version "${unifi_versions_to_install[$version]}" 931 | done 932 | __eubnt_run_command "service unifi start" 933 | } 934 | 935 | # Installs the latest minor version for the given major UniFi SDN version 936 | # $1: The major version number to install 937 | # TODO: Try to recover if install fails 938 | function __eubnt_install_unifi_version() 939 | { 940 | if [[ "${1:-}" ]]; then 941 | unifi_install_this_version="${1}" 942 | else 943 | __eubnt_show_error "No UniFi SDN version specified to install" 944 | fi 945 | if __eubnt_add_source "http://www.ubnt.com/downloads/unifi/debian unifi-${unifi_install_this_version} ubiquiti" "100-ubnt-unifi.list" "www\\.ubnt\\.com.*unifi-${unifi_install_this_version}"; then 946 | __eubnt_run_command "apt-get update" "quiet" 947 | fi 948 | unifi_updated_version=$(apt-cache policy unifi | grep "Candidate" | awk '{print $2}' | sed 's/-.*//g') 949 | if [[ "${__unifi_version_installed}" = "${unifi_updated_version}" ]]; then 950 | __eubnt_show_notice "\\nUniFi SDN version ${__unifi_version_installed} is already installed\\n" 951 | if __eubnt_question_prompt "Do you want to reinstall?" "return" "n"; then 952 | echo "unifi unifi/has_backup boolean true" | debconf-set-selections 953 | DEBIAN_FRONTEND=noninteractive apt-get install --reinstall --yes unifi 954 | fi 955 | return 0 956 | fi 957 | __eubnt_show_header "Installing UniFi SDN version ${unifi_updated_version}...\\n" 958 | if [[ -n "${__unifi_version_installed:-}" ]]; then 959 | __eubnt_show_warning "Make sure you have a backup!\\n" 960 | __eubnt_question_prompt 961 | fi 962 | local release_notes= 963 | if __eubnt_get_unifi_release_notes "${unifi_updated_version}" "release_notes"; then 964 | if __eubnt_question_prompt "Do you want to view the release notes?" "return" "n"; then 965 | more "${release_notes}" 966 | __eubnt_question_prompt 967 | fi 968 | fi 969 | if [[ -f "/lib/systemd/system/unifi.service" ]]; then 970 | __eubnt_run_command "service unifi restart" 971 | fi 972 | echo "unifi unifi/has_backup boolean true" | debconf-set-selections 973 | if DEBIAN_FRONTEND=noninteractive apt-get install --yes unifi; then 974 | __unifi_version_installed="${unifi_updated_version}" 975 | tail --follow /var/log/unifi/server.log --lines=50 | while read -r log_line 976 | do 977 | if [[ "${log_line}" = *"${unifi_updated_version}"* ]] 978 | then 979 | __eubnt_show_success "\\n${log_line}\\n" 980 | pkill --full tail 981 | fi 982 | done 983 | sleep 1 984 | else 985 | if [[ -n "${__unifi_version_installed:-}" ]]; then 986 | if __eubnt_add_source "http://www.ubnt.com/downloads/unifi/debian unifi-${__unifi_version_installed:0:3} ubiquiti" "100-ubnt-unifi.list" "www\\.ubnt\\.com.*unifi-${__unifi_version_installed:0:3}"; then 987 | __eubnt_run_command "apt-get update" "quiet" 988 | fi 989 | fi 990 | fi 991 | } 992 | 993 | # Based on solution by @Frankedinven (https://community.ubnt.com/t5/UniFi-Wireless/Lets-Encrypt-on-Hosted-Controller/m-p/2463220/highlight/true#M318272) 994 | function __eubnt_setup_certbot() { 995 | if [[ "${__os_version_name}" = "precise" || "${__os_version_name}" = "wheezy" ]]; then 996 | return 0 997 | fi 998 | local source_backports= 999 | local skip_certbot_questions= 1000 | local domain_name= 1001 | local email_address= 1002 | local resolved_domain_name= 1003 | local email_option= 1004 | local days_to_renewal= 1005 | __eubnt_show_header "Setting up Let's Encrypt...\\n" 1006 | if __eubnt_question_prompt "Do you want to (re)setup Let's Encrypt?" "return" "n"; then 1007 | if [[ ! $(command -v certbot) ]]; then 1008 | if [[ -n "${__is_ubuntu:-}" ]]; then 1009 | __setup_source_certbot=true 1010 | __eubnt_setup_sources 1011 | fi 1012 | if [[ "${__os_version_name}" = "jessie" ]]; then 1013 | __eubnt_run_command "apt-get install --yes --target-release jessie-backports certbot" 1014 | else 1015 | __eubnt_install_package "certbot" 1016 | fi 1017 | fi 1018 | else 1019 | return 0 1020 | fi 1021 | if [[ ! $(command -v certbot) ]]; then 1022 | echo 1023 | __eubnt_show_warning "Unable to setup certbot!" 1024 | echo 1025 | sleep 3 1026 | return 0 1027 | fi 1028 | if [[ "${__os_version_name}" = "jessie" ]]; then 1029 | __eubnt_run_command "apt-get install --yes --target-release jessie-backports python-cffi python-cryptography" 1030 | fi 1031 | domain_name= 1032 | if __eubnt_run_command "hostname --fqdn" "return" "domain_name"; then 1033 | echo 1034 | if ! __eubnt_question_prompt "Do you want to use ${domain_name} as the domain name?" "return" "y"; then 1035 | __eubnt_get_user_input "\\nDomain name to use for the UniFi SDN Controller: " "domain_name" 1036 | fi 1037 | else 1038 | __eubnt_get_user_input "\\nDomain name to use for the UniFi SDN Controller: " "domain_name" 1039 | fi 1040 | resolved_domain_name=$(dig +short "${domain_name}") 1041 | if [[ "${__machine_ip_address}" != "${resolved_domain_name}" ]]; then 1042 | echo; __eubnt_show_warning "The domain ${domain_name} does not resolve to ${__machine_ip_address}\\n" 1043 | if ! __eubnt_question_prompt "" "return"; then 1044 | return 0 1045 | fi 1046 | fi 1047 | days_to_renewal=0 1048 | if certbot certificates --domain "${domain_name:-}" | grep --quiet "Domains: "; then 1049 | __eubnt_run_command "certbot certificates --domain ${domain_name}" "foreground" 1050 | __eubnt_show_notice "\\nLet's Encrypt has been setup previously\\n" 1051 | days_to_renewal=$(certbot certificates --domain "${domain_name}" | grep --only-matching --max-count=1 "VALID: .*" | awk '{print $2}') 1052 | skip_certbot_questions=true 1053 | fi 1054 | if [[ -z "${skip_certbot_questions:-}" ]]; then 1055 | __eubnt_get_user_input "\\nEmail address for renewal notifications (optional): " "email_address" "optional" 1056 | fi 1057 | echo 1058 | __eubnt_show_warning "Let's Encrypt will verify your domain using HTTP (TCP port 80). This\\nscript will automatically allow HTTP through the firewall on this machine only.\\nPlease make sure firewalls external to this machine are set to allow HTTP.\\n" 1059 | if [[ -n "${email_address:-}" ]]; then 1060 | email_option="--email ${email_address}" 1061 | else 1062 | email_option="--register-unsafely-without-email" 1063 | fi 1064 | if [[ -n "${domain_name:-}" ]]; then 1065 | local letsencrypt_scripts_dir=$(mkdir --parents "${__eubnt_dir}/letsencrypt" && echo "${__eubnt_dir}/letsencrypt") 1066 | local pre_hook_script="${letsencrypt_scripts_dir}/pre-hook_${domain_name}.sh" 1067 | local post_hook_script="${letsencrypt_scripts_dir}/post-hook_${domain_name}.sh" 1068 | local letsencrypt_live_dir="${__letsencrypt_dir}/live/${domain_name}" 1069 | local letsencrypt_renewal_dir="${__letsencrypt_dir}/renewal" 1070 | local letsencrypt_renewal_conf="${letsencrypt_renewal_dir}/${domain_name}.conf" 1071 | local letsencrypt_privkey="${letsencrypt_live_dir}/privkey.pem" 1072 | local letsencrypt_fullchain="${letsencrypt_live_dir}/fullchain.pem" 1073 | tee "${pre_hook_script}" &>/dev/null </dev/null 1077 | if netstat -tulpn | grep ":80 " --quiet; then 1078 | http_process=$(netstat -tulpn | awk '/:80 /{print $7}' | sed 's/[0-9]*\///') 1079 | service "\${http_process}" stop &>/dev/null 1080 | echo "\${http_process}" >"\${http_process_file}" 1081 | fi 1082 | if [[ \$(dpkg --status "ufw" 2>/dev/null | grep "ok installed") && \$(ufw status | grep " active") ]]; then 1083 | ufw allow http &>/dev/null 1084 | fi 1085 | EOF 1086 | # End of output to file 1087 | chmod +x "${pre_hook_script}" 1088 | tee "${post_hook_script}" &>/dev/null </dev/null 1094 | fi 1095 | rm "\${http_process_file}" &>/dev/null 1096 | if [[ \$(dpkg --status "ufw" 2>/dev/null | grep "ok installed") && \$(ufw status | grep " active") && ! \$(netstat -tulpn | grep ":80 ") ]]; then 1097 | ufw delete allow http &>/dev/null 1098 | fi 1099 | if [[ -f ${letsencrypt_privkey} && -f ${letsencrypt_fullchain} ]]; then 1100 | if ! md5sum -c ${letsencrypt_fullchain}.md5 &>/dev/null; then 1101 | md5sum ${letsencrypt_fullchain} >${letsencrypt_fullchain}.md5 1102 | cp ${__unifi_data_dir}/keystore ${__unifi_data_dir}/keystore.backup.\$(date +%s) &>/dev/null 1103 | openssl pkcs12 -export -inkey ${letsencrypt_privkey} -in ${letsencrypt_fullchain} -out ${letsencrypt_live_dir}/fullchain.p12 -name unifi -password pass:aircontrolenterprise &>/dev/null 1104 | keytool -delete -alias unifi -keystore ${__unifi_data_dir}/keystore -deststorepass aircontrolenterprise &>/dev/null 1105 | keytool -importkeystore -deststorepass aircontrolenterprise -destkeypass aircontrolenterprise -destkeystore ${__unifi_data_dir}/keystore -srckeystore ${letsencrypt_live_dir}/fullchain.p12 -srcstoretype PKCS12 -srcstorepass aircontrolenterprise -alias unifi -noprompt &>/dev/null 1106 | echo "unifi.https.ciphers=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_EMPTY_RENEGOTIATION_INFO_SCSVF" | tee -a "${__unifi_system_properties}" 1107 | echo "unifi.https.sslEnabledProtocols=+TLSv1.1,+TLSv1.2,+SSLv2Hello" | tee -a "${__unifi_system_properties}" 1108 | service unifi restart &>/dev/null 1109 | fi 1110 | fi 1111 | EOF 1112 | # End of output to file 1113 | chmod +x "${post_hook_script}" 1114 | local force_renewal="--keep-until-expiring" 1115 | local run_mode="--keep-until-expiring" 1116 | if [[ "${days_to_renewal}" -ge 30 ]]; then 1117 | if __eubnt_question_prompt "\\nDo you want to force certificate renewal?" "return" "n"; then 1118 | force_renewal="--force-renewal" 1119 | fi 1120 | fi 1121 | if [[ -n "${__script_debug:-}" ]]; then 1122 | run_mode="--dry-run" 1123 | else 1124 | if __eubnt_question_prompt "\\nDo you want to do a dry run?" "return" "n"; then 1125 | run_mode="--dry-run" 1126 | fi 1127 | fi 1128 | # shellcheck disable=SC2086 1129 | if certbot certonly --agree-tos --standalone --preferred-challenges http-01 --http-01-port 80 --pre-hook ${pre_hook_script} --post-hook ${post_hook_script} --domain ${domain_name} ${email_option} ${force_renewal} ${run_mode}; then 1130 | __eubnt_show_success "\\nCertbot succeeded for domain name: ${domain_name}" 1131 | __unifi_domain_name="${domain_name}" 1132 | sleep 5 1133 | else 1134 | echo 1135 | __eubnt_show_warning "Certbot failed for domain name: ${domain_name}" 1136 | sleep 10 1137 | fi 1138 | if [[ -f "${letsencrypt_renewal_conf}" ]]; then 1139 | sed -i "s|^pre_hook.*$|pre_hook = ${pre_hook_script}|" "${letsencrypt_renewal_conf}" 1140 | sed -i "s|^post_hook.*$|post_hook = ${post_hook_script}|" "${letsencrypt_renewal_conf}" 1141 | if crontab -l | grep --quiet "^[^#]"; then 1142 | local found_file crontab_file 1143 | declare -a files_in_crontab 1144 | while IFS=$'\n' read -r found_file; do files_in_crontab+=("$found_file"); done < <(crontab -l | awk '/^[^#]/{print $6}') 1145 | for crontab_file in "${!files_in_crontab[@]}"; do 1146 | if grep --quiet "keystore" "${crontab_file}"; then 1147 | __eubnt_show_warning "Please check your crontab to make sure there aren't any conflicting Let's Encrypt renewal scripts" 1148 | sleep 3 1149 | fi 1150 | done 1151 | fi 1152 | fi 1153 | fi 1154 | } 1155 | 1156 | # Install OpenSSH server and harden the configuration 1157 | ### 1158 | # Hardening the OpenSSH Server config according to best practices (https://gist.github.com/nvnmo/91a20f9e72dffb9922a01d499628040f | https://linux-audit.com/audit-and-harden-your-ssh-configuration/) 1159 | # De-duplicate SSH config file (https://stackoverflow.com/a/1444448) 1160 | function __eubnt_setup_ssh_server() { 1161 | __eubnt_show_header "Setting up OpenSSH Server..." 1162 | if ! __eubnt_is_package_installed "openssh-server"; then 1163 | echo 1164 | if __eubnt_question_prompt "Do you want to install the OpenSSH server?" "return"; then 1165 | __eubnt_run_command "apt-get install --yes openssh-server" 1166 | fi 1167 | fi 1168 | if [[ $(dpkg --list | grep "openssh-server") && -f "${__sshd_config}" ]]; then 1169 | cp "${__sshd_config}" "${__sshd_config}.bak-${__script_time}" 1170 | __eubnt_show_notice "\\nChecking OpenSSH server settings for recommended changes...\\n" 1171 | if [[ $(grep ".*Port 22$" "${__sshd_config}") || ! $(grep ".*Port.*" "${__sshd_config}") ]]; then 1172 | if __eubnt_question_prompt "Change SSH port from the default 22?" "return" "n"; then 1173 | local ssh_port="" 1174 | while [[ ! $ssh_port =~ ^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$ ]]; do 1175 | read -r -p "Port number: " ssh_port 1176 | done 1177 | if grep --quiet ".*Port.*" "${__sshd_config}"; then 1178 | sed -i "s/^.*Port.*$/Port ${ssh_port}/" "${__sshd_config}" 1179 | else 1180 | echo "Port ${ssh_port}" | tee -a "${__sshd_config}" 1181 | fi 1182 | __restart_ssh_server=true 1183 | fi 1184 | fi 1185 | declare -A ssh_changes=( 1186 | ['Protocol 2']='Use SSH protocol version 2 (recommended)?' 1187 | ['UsePrivilegeSeparation yes']='Enable privilege separation (recommended)?' 1188 | ['StrictModes yes']='Enforce strict security checks for SSH server (recommended)?' 1189 | ['PermitEmptyPasswords no']='Disallow empty passwords (recommended)?' 1190 | ['PermitRootLogin no']='Disallow root user to log into SSH (optional)?' 1191 | ['IgnoreRhosts yes']='Disable legacy rhosts authentication (recommended)?' 1192 | ['MaxAuthTries 5']='Limit authentication attempts to 5 (recommended)?' 1193 | #['TCPKeepAlive yes']='Enable TCP keep alive (optional)?' 1194 | ) 1195 | for recommended_setting in "${!ssh_changes[@]}"; do 1196 | if [[ "${recommended_setting}" = "PermitRootLogin no" && -z "${__is_user_sudo:-}" ]]; then 1197 | continue 1198 | fi 1199 | if ! grep --quiet "^${recommended_setting}" "${__sshd_config}"; then 1200 | setting_name=$(echo "${recommended_setting}" | awk '{print $1}') 1201 | echo 1202 | if __eubnt_question_prompt "${ssh_changes[$recommended_setting]}" "return"; then 1203 | if grep --quiet ".*${setting_name}.*" "${__sshd_config}"; then 1204 | sed -i "s/^.*${setting_name}.*$/${recommended_setting}/" "${__sshd_config}" 1205 | else 1206 | echo "${recommended_setting}" | tee -a "${__sshd_config}" 1207 | fi 1208 | __restart_ssh_server=true 1209 | fi 1210 | fi 1211 | done 1212 | awk '!seen[$0]++' "${__sshd_config}" &>/dev/null 1213 | fi 1214 | } 1215 | 1216 | # Install and setup UFW 1217 | # Adds an app profile that includes all UniFi SDN ports to allow for easy rule management in UFW 1218 | # Checks if ports appear to be open/accessible from the Internet 1219 | function __eubnt_setup_ufw() { 1220 | __eubnt_show_header "Setting up UFW (Uncomplicated Firewall)...\\n" 1221 | unifi_local_udp_port_discoverable_controller="1900" 1222 | unifi_local_udp_port_ap_discovery="10001" 1223 | if [[ -f "${__unifi_system_properties}" ]]; then 1224 | local unifi_tcp_port_inform=$(grep "^unifi.http.port" "${__unifi_system_properties}" | sed 's/.*=//g') 1225 | if [[ -z "${unifi_tcp_port_inform:-}" ]]; then 1226 | unifi_tcp_port_inform="8080" 1227 | fi 1228 | local unifi_tcp_port_admin=$(grep "^unifi.https.port" "${__unifi_system_properties}" | sed 's/.*=//g') 1229 | if [[ -z "${unifi_tcp_port_admin:-}" ]]; then 1230 | unifi_tcp_port_admin="8443" 1231 | fi 1232 | local unifi_tcp_port_http_portal=$(grep "^portal.http.port" "${__unifi_system_properties}" | sed 's/.*=//g') 1233 | if [[ -z "${unifi_tcp_port_http_portal:-}" ]]; then 1234 | unifi_tcp_port_http_portal="8880" 1235 | fi 1236 | local unifi_tcp_port_https_portal=$(grep "^portal.https.port" "${__unifi_system_properties}" | sed 's/.*=//g') 1237 | if [[ ! $unifi_tcp_port_https_portal ]]; then 1238 | unifi_tcp_port_https_portal="8843" 1239 | fi 1240 | unifi_tcp_port_throughput=$(grep "^unifi.throughput.port" "${__unifi_system_properties}" | sed 's/.*=//g') 1241 | if [[ ! $unifi_tcp_port_throughput ]]; then 1242 | unifi_tcp_port_throughput="6789" 1243 | fi 1244 | unifi_udp_port_stun=$(grep "^unifi.stun.port" "${__unifi_system_properties}" | sed 's/.*=//g') 1245 | if [[ ! $unifi_udp_port_stun ]]; then 1246 | unifi_udp_port_stun="3478" 1247 | fi 1248 | fi 1249 | if [[ ! $(dpkg --list "ufw" | grep "^i") || ( $(command -v ufw) && $(ufw status | grep "inactive") ) ]]; then 1250 | if ! __eubnt_question_prompt "Do you want to use UFW?" "return"; then 1251 | return 0 1252 | fi 1253 | fi 1254 | __eubnt_install_package "ufw" 1255 | if [[ -f "${__sshd_config}" ]]; then 1256 | ssh_port=$(grep "Port" "${__sshd_config}" --max-count=1 | awk '{print $NF}') 1257 | fi 1258 | if [[ -n "${unifi_tcp_port_inform:-}" && -n "${unifi_tcp_port_admin:-}" && -n "${unifi_tcp_port_http_portal:-}" && -n "${unifi_tcp_port_https_portal:-}" && -n "${unifi_tcp_port_throughput:-}" && -n "${unifi_udp_port_stun:-}" ]]; then 1259 | tee "/etc/ufw/applications.d/unifi" &>/dev/null <>"${__script_log}" 1346 | __eubnt_run_command "ufw reload" 1347 | __eubnt_show_notice "\\nUpdated UFW status:\\n" 1348 | __eubnt_run_command "ufw status" "foreground" 1349 | sleep 1 1350 | } 1351 | 1352 | # Recommended by CrossTalk Solutions (https://crosstalksolutions.com/15-minute-hosted-unifi-controller-setup/) 1353 | function __eubnt_setup_swap_file() { 1354 | __eubnt_run_command "fallocate -l 2G /swapfile" 1355 | __eubnt_run_command "chmod 600 /swapfile" 1356 | __eubnt_run_command "mkswap /swapfile" 1357 | if swapon /swapfile; then 1358 | if grep --quiet "^/swapfile " "/etc/fstab"; then 1359 | sed -i "s|^/swapfile.*$|/swapfile none swap sw 0 0|" "/etc/fstab" 1360 | else 1361 | echo "/swapfile none swap sw 0 0" >>/etc/fstab 1362 | fi 1363 | __eubnt_show_success "\\nCreated swap file!\\n" 1364 | else 1365 | rm -rf /swapfile 1366 | __eubnt_show_warning "Unable to create swap file!\\n" 1367 | fi 1368 | echo 1369 | } 1370 | 1371 | # Perform various system checks, display system information and warnings about potential issues 1372 | # Check for currently installed versions of Java, MongoDB and UniFi SDN 1373 | function __eubnt_check_system() { 1374 | local os_version_name_display= 1375 | local os_version_supported= 1376 | local os_version_recommended_display= 1377 | local os_version_recommended= 1378 | local os_bit= 1379 | local have_space_for_swap= 1380 | declare -a ubuntu_supported_versions=("precise" "trusty" "xenial" "bionic") 1381 | declare -a debian_supported_versions=("wheezy" "jessie" "stretch") 1382 | __eubnt_show_header "Checking system...\\n" 1383 | if [[ "${__architecture}" = "i686" ]]; then 1384 | __is_32=true 1385 | os_bit="32-bit" 1386 | elif [[ "${__architecture}" = "x86_64" ]]; then 1387 | __is_64=true 1388 | os_bit="64-bit" 1389 | else 1390 | __eubnt_show_warning "Architecture ${__architecture} is not officially supported\\n" 1391 | __eubnt_question_prompt 1392 | __is_experimental=true 1393 | if [[ "${__architecture:0:3}" = "arm" ]]; then 1394 | if [[ "${__architecture:4:1}" -ge 8 ]]; then 1395 | __is_64=true 1396 | os_bit="64-bit" 1397 | else 1398 | __is_32=true 1399 | os_bit="32-bit" 1400 | fi 1401 | else 1402 | os_bit="Unknown" 1403 | fi 1404 | fi 1405 | if [[ "${__os_name:-}" = "Ubuntu" ]]; then 1406 | __is_ubuntu=true 1407 | os_version_recommended_display="16.04 Xenial" 1408 | os_version_recommended="xenial" 1409 | for version in "${!ubuntu_supported_versions[@]}"; do 1410 | if [[ "${ubuntu_supported_versions[$version]}" = "${__os_version_name}" ]]; then 1411 | __os_version_name_ubuntu_equivalent="${__os_version_name}" 1412 | os_version_name_display=$(echo "${__os_version_name}" | sed 's/./\u&/') 1413 | os_version_supported=true 1414 | break 1415 | fi 1416 | done 1417 | elif [[ "${__os_name:-}" = "Debian" || "${__os_name:-}" = "Raspbian" ]]; then 1418 | __is_debian=true 1419 | os_version_recommended_display="9.x Stretch" 1420 | os_version_recommended="stretch" 1421 | for version in "${!debian_supported_versions[@]}"; do 1422 | if [[ "${debian_supported_versions[$version]}" = "${__os_version_name}" ]]; then 1423 | __os_version_name_ubuntu_equivalent="${ubuntu_supported_versions[$version]}" 1424 | os_version_name_display=$(echo "${__os_version_name}" | sed 's/./\u&/') 1425 | os_version_supported=true 1426 | break 1427 | fi 1428 | done 1429 | else 1430 | __eubnt_show_error "This script is for Ubuntu, Debian or Raspbian\\nYou appear to have: ${__os_all_info}\\n" 1431 | fi 1432 | if [[ -z "${os_version_supported:-}" ]]; then 1433 | __eubnt_show_warning "${__os_name} ${__os_version} is not officially supported\\n" 1434 | __eubnt_question_prompt 1435 | __is_experimental=true 1436 | os_version_name_display=$(echo "${__os_version_name}" | sed 's/./\u&/') 1437 | if [[ -n "${__is_debian:-}" ]]; then 1438 | __os_version_name="stretch" 1439 | __os_version_name_ubuntu_equivalent="xenial" 1440 | else 1441 | __os_version_name="bionic" 1442 | __os_version_name_ubuntu_equivalent="bionic" 1443 | fi 1444 | fi 1445 | if [[ -z "${__os_version:-}" || ( -z "${__is_ubuntu:-}" && -z "${__is_debian:-}" ) ]]; then 1446 | __eubnt_show_error "Unable to detect system information\\n" 1447 | fi 1448 | local show_disk_free_space= 1449 | if [[ "${__disk_free_space%G*}" -le 2 ]]; then 1450 | show_disk_free_space="${__disk_free_space_mb}" 1451 | else 1452 | show_disk_free_space="${__disk_free_space}" 1453 | fi 1454 | __eubnt_show_text "Disk free space is ${__colors_bold_text}${show_disk_free_space}${__colors_default}\\n" 1455 | if [[ "${__disk_free_space%G*}" -lt "${__recommended_disk_free_space%G*}" ]]; then 1456 | __eubnt_show_warning "UBNT recommends at least ${__recommended_disk_free_space} of free space\\n" 1457 | else 1458 | if [[ "${__disk_free_space%G*}" -gt $(( ${__recommended_disk_free_space%G*} + ${__recommended_swap_total_gb%G*} )) ]]; then 1459 | have_space_for_swap=true 1460 | fi 1461 | fi 1462 | __eubnt_show_text "Memory total size is ${__colors_bold_text}${__memory_total}${__colors_default}\\n" 1463 | if [[ "${__memory_total%M*}" -lt "${__recommended_memory_total%M*}" ]]; then 1464 | __eubnt_show_warning "UBNT recommends at least ${__recommended_memory_total_gb} of memory\\n" 1465 | fi 1466 | __eubnt_show_text "Swap total size is ${__colors_bold_text}${__swap_total}\\n" 1467 | if [[ "${__swap_total%M*}" -eq 0 && "${have_space_for_swap:-}" ]]; then 1468 | if __eubnt_question_prompt "Do you want to setup a ${__recommended_swap_total_gb} swap file?" "return"; then 1469 | __eubnt_setup_swap_file 1470 | fi 1471 | fi 1472 | __eubnt_show_text "Operating system is ${__colors_bold_text}${__os_name} ${__os_version} ${os_version_name_display:-} ${os_bit:-}\\n" 1473 | if [[ ( -n "${__is_ubuntu:-}" || -n "${__is_debian:-}" ) && ( "${__os_version_name:-}" != "${os_version_recommended:-}" || "${os_bit:-}" != "${__os_bit_recommended:-}" ) ]]; then 1474 | __eubnt_show_warning "UBNT recommends ${__os_name} ${os_version_recommended_display} ${__os_bit_recommended}\\n" 1475 | fi 1476 | declare -a all_ip_addresses=() 1477 | local apparent_public_ip_address=$(wget --quiet --output-document - http://dynamicdns.park-your-domain.com/getip) 1478 | #shellcheck disable=SC2207 1479 | all_ip_addresses=($(hostname --all-ip-addresses | xargs)) 1480 | all_ip_addresses+=("${apparent_public_ip_address}") 1481 | #shellcheck disable=SC2207 1482 | all_ip_addresses=($(printf "%s\\n" "${all_ip_addresses[@]}" | sort --unique)) 1483 | if [[ "${#all_ip_addresses[@]}" -gt 1 ]]; then 1484 | if [[ -n "${__quick_mode:-}" && -n "${apparent_public_ip_address:-}" ]]; then 1485 | __machine_ip_address="${apparent_public_ip_address}" 1486 | else 1487 | __eubnt_show_notice "Which IP address will the controller be using?\\n" 1488 | select ip_address in "${all_ip_addresses[@]}"; do 1489 | case "${ip_address}" in 1490 | *) 1491 | __machine_ip_address="${ip_address}" 1492 | echo 1493 | break;; 1494 | esac 1495 | done 1496 | fi 1497 | else 1498 | __machine_ip_address="${all_ip_addresses[0]}" 1499 | fi 1500 | __eubnt_show_text "Machine IP address to use is ${__colors_bold_text}${__machine_ip_address}${__colors_default}\\n" 1501 | local configured_fqdn= 1502 | if __eubnt_run_command "hostname --fqdn" "return" "configured_fqdn"; then 1503 | __eubnt_show_text "\\nConfigured hostname is ${__colors_bold_text}${configured_fqdn}${__colors_default}\\n" 1504 | else 1505 | __eubnt_show_text "\\nNo configured hostname (FQDN) found\\n" 1506 | fi 1507 | if [[ -z "${__nameservers:-}" ]]; then 1508 | __eubnt_show_warning "No nameservers found!\\n" 1509 | else 1510 | if [[ $(echo "${__nameservers}" | awk '{print $2}') ]]; then 1511 | __eubnt_show_text "Current nameservers in use are ${__colors_bold_text}${__nameservers}${__colors_default}\\n" 1512 | else 1513 | __eubnt_show_text "Current nameserver in use is ${__colors_bold_text}${__nameservers}${__colors_default}\\n" 1514 | fi 1515 | fi 1516 | if ! __eubnt_run_command "dig +short ${__ubnt_dns}" "return"; then 1517 | echo 1518 | if __eubnt_question_prompt "Unable to resolve ${__ubnt_dns}, do you want to use ${__recommended_nameserver} as your nameserver?" "return"; then 1519 | if __eubnt_install_package "resolvconf" "return"; then 1520 | echo "nameserver ${__recommended_nameserver}" | tee "/etc/resolvconf/resolv.conf.d/head" 1521 | __eubnt_run_command "resolvconf -u" 1522 | __nameservers=$(awk '/nameserver/{print $2}' /etc/resolv.conf | xargs) 1523 | if ! __eubnt_run_command "dig +short ${__ubnt_dns}" "return"; then 1524 | echo 1525 | if ! __eubnt_question_prompt "${__colors_warning_text}Still unable to resolve ${__ubnt_dns}, do you want to continue anyway?" "return" "n"; then 1526 | echo 1527 | __eubnt_show_error "Unable to resolve ${__ubnt_dns} using ${__nameservers}, is DNS blocked?" 1528 | return 1 1529 | fi 1530 | fi 1531 | fi 1532 | fi 1533 | else 1534 | __eubnt_show_success "\\nDNS appears to be working!\\n" 1535 | fi 1536 | __eubnt_show_text "Current time is ${__colors_bold_text}$(date)${__colors_default}\\n" 1537 | if ! __eubnt_question_prompt "Does the current time and timezone look correct?" "return"; then 1538 | __eubnt_install_package "ntp" 1539 | __eubnt_install_package "ntpdate" 1540 | __eubnt_run_command "service ntp stop" 1541 | __eubnt_run_command "ntpdate 0.ubnt.pool.ntp.org" 1542 | __eubnt_run_command "service ntp start" 1543 | dpkg-reconfigure --frontend dialog tzdata 1544 | __eubnt_show_success "Updated time is $(date)\\n" 1545 | sleep 3 1546 | fi 1547 | __eubnt_run_command "apt-get update" 1548 | echo 1549 | if [[ $(command -v java) ]]; then 1550 | local java_version_installed="" 1551 | local java_package_installed="" 1552 | local set_java_alternative="" 1553 | if __eubnt_is_package_installed "oracle-java8-installer"; then 1554 | java_package_installed="oracle-java8-installer" 1555 | fi 1556 | if __eubnt_is_package_installed "openjdk-8-jre-headless"; then 1557 | java_package_installed="openjdk-8-jre-headless" 1558 | fi 1559 | if [[ -n "${java_package_installed:-}" ]]; then 1560 | java_version_installed=$(dpkg --list "${java_package_installed}" | awk '/^i/{print $3}' | sed 's/-.*//') 1561 | fi 1562 | fi 1563 | if [[ -n "${java_version_installed:-}" ]]; then 1564 | java_update_available=$(apt-cache policy "${java_package_installed}" | awk '/Candidate/{print $2}' | sed 's/-.*//') 1565 | if [[ -n "${java_update_available}" && "${java_update_available}" != "${java_version_installed}" ]]; then 1566 | __eubnt_show_text "Java ${java_version_installed} is installed, ${__colors_warning_text}version ${java_update_available} is available\\n" 1567 | if ! __eubnt_question_prompt "Do you want to update Java to ${java_update_available}?" "return"; then 1568 | __hold_java="${java_package_installed}" 1569 | fi 1570 | echo 1571 | elif [[ "${java_update_available}" != '' && "${java_update_available}" = "${java_version_installed}" ]]; then 1572 | __eubnt_show_success "Java ${java_version_installed} is the latest\\n" 1573 | fi 1574 | if ! dpkg --list "${java_package_installed}" | grep --quiet "^ii"; then 1575 | __eubnt_show_warning "The Java 8 installation appears to be damaged\\n" 1576 | fi 1577 | else 1578 | __eubnt_show_text "Java 8 will be installed\\n" 1579 | __setup_source_java=true 1580 | fi 1581 | if __eubnt_is_package_installed "mongodb-server" || __eubnt_is_package_installed "mongodb-org-server" ; then 1582 | local mongo_version_installed="" 1583 | local mongo_package_installed="" 1584 | local mongo_update_available="" 1585 | local mongo_version_check="" 1586 | if __eubnt_is_package_installed "mongodb-org-server"; then 1587 | mongo_package_installed="mongodb-org-server" 1588 | fi 1589 | if __eubnt_is_package_installed "mongodb-server"; then 1590 | mongo_package_installed="mongodb-server" 1591 | fi 1592 | if [[ -n "${mongo_package_installed:-}" ]]; then 1593 | mongo_version_installed=$(dpkg --list "${mongo_package_installed}" | awk '/^i/{print $3}' | sed 's/.*://' | sed 's/-.*//') 1594 | fi 1595 | if [[ "${mongo_package_installed:-}" = "mongodb-server" && -n "${__is_64:-}" && ! -f "/lib/systemd/system/unifi.service" ]]; then 1596 | __eubnt_show_notice "Mongo officially maintains 'mongodb-org' packages but you have 'mongodb' packages installed\\n" 1597 | if __eubnt_question_prompt "Do you want to remove the 'mongodb' packages and install 'mongodb-org' packages instead?" "return"; then 1598 | __purge_mongo=true 1599 | __setup_source_mongo=true 1600 | fi 1601 | fi 1602 | fi 1603 | if [[ -n "${mongo_version_installed:-}" && -n "${mongo_package_installed:-}" && ! $__purge_mongo ]]; then 1604 | mongo_update_available=$(apt-cache policy "${mongo_package_installed}" | awk '/Candidate/{print $2}' | sed 's/.*://' | sed 's/-.*//') 1605 | mongo_version_check=$(echo "${mongo_version_installed:0:3}" | sed 's/\.//') 1606 | if [[ "${mongo_version_check:-}" -gt "34" && ! $(dpkg --list 2>/dev/null | grep "^i.*unifi.*") ]]; then 1607 | __eubnt_show_warning "UBNT recommends MongoDB version ${__mongo_version_recommended}\\n" 1608 | if __eubnt_question_prompt "Do you want to downgrade MongoDB to ${__mongo_version_recommended}?" "return"; then 1609 | __purge_mongo=true 1610 | __setup_source_mongo=true 1611 | fi 1612 | fi 1613 | if [[ ! $__purge_mongo && -n "${mongo_update_available:-}" && "${mongo_update_available:-}" != "${mongo_version_installed}" ]]; then 1614 | __eubnt_show_text "MongoDB version ${mongo_version_installed} is installed, ${__colors_warning_text}version ${mongo_update_available} is available\\n" 1615 | if ! __eubnt_question_prompt "Do you want to update MongoDB to ${mongo_update_available}?" "return"; then 1616 | __hold_mongo="${mongo_package_installed}" 1617 | fi 1618 | echo 1619 | elif [[ ! $__purge_mongo && -n "${mongo_update_available:-}" && "${mongo_update_available:-}" = "${mongo_version_installed}" ]]; then 1620 | __eubnt_show_success "MongoDB version ${mongo_version_installed} is the latest\\n" 1621 | fi 1622 | if ! dpkg --list "${mongo_package_installed}" | grep --quiet "^ii"; then 1623 | __eubnt_show_warning "The MongoDB installation appears to be damaged\\n" 1624 | fi 1625 | else 1626 | __eubnt_show_text "MongoDB will be installed\\n" 1627 | __setup_source_mongo=true 1628 | fi 1629 | if [[ -f "/lib/systemd/system/unifi.service" ]]; then 1630 | __is_unifi_installed=true 1631 | __unifi_version_installed=$(dpkg --list "unifi" | awk '/^i/{print $3}' | sed 's/-.*//') 1632 | __unifi_update_available=$(apt-cache policy "unifi" | awk '/Candidate/{print $2}' | sed 's/-.*//') 1633 | if [[ "${__unifi_update_available:0:3}" != "${__unifi_version_installed:0:3}" ]]; then 1634 | __eubnt_add_source "http://www.ubnt.com/downloads/unifi/debian unifi-${__unifi_version_installed:0:3} ubiquiti" "100-ubnt-unifi.list" "www\\.ubnt\\.com.*unifi-${__unifi_version_installed:0:3}" && __eubnt_run_command "apt-get update" "quiet" 1635 | __unifi_update_available=$(apt-cache policy "unifi" | awk '/Candidate/{print $2}' | sed 's/-.*//') 1636 | fi 1637 | if [[ -n "${__unifi_update_available}" && "${__unifi_update_available}" != "${__unifi_version_installed}" ]]; then 1638 | __eubnt_show_text "UniFi SDN version ${__unifi_version_installed} is installed, ${__colors_warning_text}version ${__unifi_update_available} is available\\n" 1639 | __hold_unifi="unifi" 1640 | elif [[ -n "${__unifi_update_available}" && "${__unifi_update_available}" = "${__unifi_version_installed}" ]]; then 1641 | __eubnt_show_success "UniFi SDN version ${__unifi_version_installed} is the latest\\n" 1642 | __unifi_update_available= 1643 | fi 1644 | if ! dpkg --list "unifi" | grep --quiet "^ii"; then 1645 | __eubnt_show_warning "The UniFi SDN Controller installation appears to be damaged\\n" 1646 | fi 1647 | __eubnt_show_warning "Be sure to have a backup before proceeding\\n" 1648 | else 1649 | __eubnt_show_text "UniFi SDN does not appear to be installed yet\\n" 1650 | fi 1651 | } 1652 | 1653 | ### Tests 1654 | ############################################################################## 1655 | 1656 | ### Execution of script 1657 | ############################################################################## 1658 | 1659 | __eubnt_question_prompt "This script is deprecated. Do you want to proceed anyway?" "exit" "n" 1660 | ln --force --symbolic "${__script_log}" "${__script_log_dir}/${__script_name}-latest.log" 1661 | __eubnt_script_colors 1662 | __eubnt_show_header 1663 | __eubnt_show_license 1664 | if [[ -n "${__accept_license:-}" ]]; then 1665 | sleep 3 1666 | else 1667 | __eubnt_question_prompt "Do you agree to the license and want to proceed?" "exit" "n" 1668 | fi 1669 | __eubnt_install_package "apt-transport-https" 1670 | __eubnt_check_system 1671 | __eubnt_question_prompt 1672 | __eubnt_purge_mongo 1673 | # shellcheck disable=SC2119 1674 | __eubnt_setup_sources 1675 | __eubnt_install_fixes 1676 | __eubnt_install_updates_utils 1677 | if [[ -f /var/run/reboot-required ]]; then 1678 | echo 1679 | __eubnt_show_warning "A reboot is recommended.\\nRun this script again after reboot.\\n" 1680 | # TODO: Restart the script automatically after reboot 1681 | if [[ -n "${__quick_mode:-}" ]]; then 1682 | __eubnt_show_warning "The system will automatically reboot in 10 seconds.\\n" 1683 | sleep 10 1684 | fi 1685 | if __eubnt_question_prompt "Do you want to reboot now?" "return"; then 1686 | __eubnt_show_warning "Exiting script and rebooting system now!" 1687 | __reboot_system=true 1688 | exit 0 1689 | fi 1690 | fi 1691 | __eubnt_system_tweaks 1692 | __eubnt_install_java 1693 | __eubnt_install_mongo 1694 | __eubnt_install_unifi 1695 | __eubnt_setup_ssh_server 1696 | __eubnt_setup_certbot 1697 | __eubnt_setup_ufw 1698 | __eubnt_show_success "\\nDone!\\n" 1699 | sleep 3 1700 | --------------------------------------------------------------------------------