1#!/usr/bin/env bash 2################################################################################ 3# Copyright (C) 2015 Daniel Preussker, QuxLabs UG <preussker@quxlabs.com> 4# Copyright (C) 2016 Layne "Gorian" Breitkreutz <Layne.Breitkreutz@thelenon.com> 5# Copyright (C) 2017 Tony Murray <murraytony@gmail.com> 6# This program is free software: you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation, either version 3 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program. If not, see <https://www.gnu.org/licenses/>. 18################################################################################ 19 20####################################### 21# CONSTANTS 22####################################### 23# define DAILY_SCRIPT as the full path to this script and LIBRENMS_DIR as the directory this script is in 24DAILY_SCRIPT=$(readlink -f "$0") 25LIBRENMS_DIR=$(dirname "$DAILY_SCRIPT") 26COMPOSER="php ${LIBRENMS_DIR}/scripts/composer_wrapper.php --no-interaction" 27 28# set log_file, using librenms 'log_dir' config setting, if set 29# otherwise we default to <LibreNMS Install Directory>/logs 30LOG_DIR=$(php -r "@include '${LIBRENMS_DIR}/config.php'; echo isset(\$config['log_dir']) ? \$config['log_dir'] : '${LIBRENMS_DIR}/logs';") 31 32# get the librenms user 33# shellcheck source=.env.example 34source "${LIBRENMS_DIR}/.env" 35LIBRENMS_USER="${LIBRENMS_USER:-librenms}" 36LIBRENMS_USER_ID=$(id -u "$LIBRENMS_USER") 37 38####################################### 39# Fancy-Print and run commands 40# Globals: 41# LOG_DIR 42# Arguments: 43# Text 44# Command 45# Returns: 46# Exit-Code of Command 47####################################### 48status_run() { 49 # Explicitly define our arguments 50 local args arg_text arg_command arg_option log_file exit_code tmp log_file 51 52 args=("$@") 53 arg_text=$1 54 arg_command=$2 55 arg_option=$3 56 log_file=${LOG_DIR}/daily.log 57 58 # set log_file, using librenms $config['log_dir'], if set 59 # otherwise we default to ./logs/daily.log 60 61 printf "%-50s" "${arg_text}" 62 echo "${arg_text}" >> "${log_file}" 63 tmp=$(bash -c "${arg_command}" 2>&1) 64 exit_code=$? 65 echo "${tmp}" >> "${log_file}" 66 echo "Returned: ${exit_code}" >> "${log_file}" 67 68 # print OK if the command ran successfully 69 # or FAIL otherwise (non-zero exit code) 70 if [[ "${exit_code}" == "0" ]]; then 71 printf " \\033[0;32mOK\\033[0m\\n" 72 else 73 printf " \\033[0;31mFAIL\\033[0m\\n" 74 if [[ "${arg_option}" == "update" ]]; then 75 php "${LIBRENMS_DIR}/daily.php" -f notify -o "${tmp}" 76 fi 77 if [[ -n "${tmp}" ]]; then 78 # print output in case of failure 79 echo "${tmp}" 80 fi 81 fi 82 return ${exit_code} 83} 84 85####################################### 86# Call daily.php 87# Globals: 88# LIBRENMS_DIR 89# Arguments: 90# args: 91# Array of arguments to pass to 92# daily.php 93# Returns: 94# Exit-Code of Command 95####################################### 96call_daily_php() { 97 local args 98 99 args=("$@") 100 101 for arg in "${args[@]}"; do 102 php "${LIBRENMS_DIR}/daily.php" -f "${arg}" 103 done 104} 105 106####################################### 107# Send result of a notifiable process to php code for processing 108# Globals: 109# LIBRENMS_DIR 110# Arguments: 111# args: 112# Type: update 113# Result: 1 for success, 0 for failure 114# Returns: 115# Exit-Code of Command 116####################################### 117set_notifiable_result() { 118 local args arg_type arg_result 119 120 args=("$@") 121 arg_type=$1 122 arg_result=$2 123 124 php "${LIBRENMS_DIR}/daily.php" -f handle_notifiable -t "${arg_type}" -r "${arg_result}" 125} 126 127####################################### 128# Check the PHP and Python version and branch and switch to the appropriate branch 129# Returns: 130# Exit-Code: 0 >= min ver, 1 < min ver 131####################################### 132check_dependencies() { 133 local branch ver_56 ver_71 ver_72 ver_73 python3 python_deps phpver pythonver old_branches msg 134 135 branch=$(git rev-parse --abbrev-ref HEAD) 136 scripts/check_requirements.py > /dev/null 2>&1 || pip3 install -r requirements.txt > /dev/null 2>&1 137 138 ver_56=$(php -r "echo (int)version_compare(PHP_VERSION, '5.6.4', '<');") 139 ver_71=$(php -r "echo (int)version_compare(PHP_VERSION, '7.1.3', '<');") 140 ver_72=$(php -r "echo (int)version_compare(PHP_VERSION, '7.2.5', '<');") 141 ver_73=$(php -r "echo (int)version_compare(PHP_VERSION, '7.3', '<');") 142 python3=$(python3 -c "import sys;print(int(sys.version_info < (3, 4)))" 2> /dev/null) 143 python_deps=$("${LIBRENMS_DIR}/scripts/check_requirements.py" > /dev/null 2>&1; echo $?) 144 phpver="master" 145 pythonver="master" 146 147 old_branches="^(php53|php56|php71-python2|php72)$" 148 if [[ $branch =~ $old_branches ]] && [[ "$ver_73" == "0" && "$python3" == "0" && "$python_deps" == "0" ]]; then 149 status_run "Supported PHP and Python version, switched back to master branch." 'git checkout master' 150 elif [[ "$ver_56" != "0" ]]; then 151 phpver="php53" 152 if [[ "$branch" != "php53" ]]; then 153 status_run "Unsupported PHP version, switched to php53 branch." 'git checkout php53' 154 fi 155 elif [[ "$ver_71" != "0" ]]; then 156 phpver="php56" 157 if [[ "$branch" != "php56" ]]; then 158 status_run "Unsupported PHP version, switched to php56 branch." 'git checkout php56' 159 fi 160 elif [[ "$ver_72" != "0" || "$python3" != "0" || "$python_deps" != "0" ]]; then 161 msg="" 162 if [[ "$ver_72" != "0" ]]; then 163 msg="Unsupported PHP version, $msg" 164 phpver="php71" 165 fi 166 if [[ "$python3" != "0" ]]; then 167 msg="python3 is not available, $msg" 168 pythonver="python3-missing" 169 elif [[ "$python_deps" != "0" ]]; then 170 msg="Python 3 dependencies missing, $msg" 171 pythonver="python3-deps" 172 fi 173 174 if [[ "$branch" != "php71-python2" ]]; then 175 status_run "${msg}switched to php71-python2 branch." 'git checkout php71-python2' 176 fi 177 elif [[ "$ver_73" != "0" ]]; then 178 phpver="php72" 179 if [[ "$branch" != "php72" ]]; then 180 status_run "Unsupported PHP version, switched to php72 branch." 'git checkout php72' 181 fi 182 fi 183 184 set_notifiable_result phpver ${phpver} 185 set_notifiable_result pythonver ${pythonver} 186 187 if [[ "$phpver" == "master" && "$pythonver" == "master" ]]; then 188 return 0 189 fi 190 return 1 191} 192 193####################################### 194# Compare two numeric versions 195# Arguments: 196# args: 197# version 1 198# version 2 199# parts: Number of parts to compare, from the left, compares all if unspecified 200# Returns: 201# Exit-Code: 0: if equal 1: if 1 > 2 2: if 1 < 2 202####################################### 203version_compare () { 204 local i ver1 ver2 parts1 parts2 205 206 if [[ "$1" == "$2" ]]; then 207 return 0 208 fi 209 210 IFS=. read -ra ver1 <<< "$1" 211 IFS=. read -ra ver2 <<< "$2" 212 213 parts2=${#ver2[@]} 214 [[ -n $3 ]] && parts2=$3 215 216 # fill empty fields in ver1 with zeros 217 for ((i=${#ver1[@]}; i<parts2; i++)); do 218 ver1[i]=0 219 done 220 221 parts1=${#ver1[@]} 222 [[ -n $3 ]] && parts1=$3 223 224 for ((i=0; i<parts1; i++)); do 225 if [[ -z ${ver2[i]} ]]; then 226 # fill empty fields in ver2 with zeros 227 ver2[i]=0 228 fi 229 if ((10#${ver1[i]} > 10#${ver2[i]})); then 230 return 1 231 fi 232 if ((10#${ver1[i]} < 10#${ver2[i]})); then 233 return 2 234 fi 235 done 236 return 0 237} 238 239 240####################################### 241# Entry into program 242# Globals: 243# LIBRENMS_DIR 244# Arguments: 245# 246# Returns: 247# Exit-Code of Command 248####################################### 249main () { 250 local arg old_version new_version branch options 251 252 arg="$1" 253 old_version="$2" 254 new_version="$3" 255 old_version="${old_version:=unset}" # if $1 is unset, make it mismatch for pre-update daily.sh 256 257 cd "${LIBRENMS_DIR}" || exit 1 258 259 # if not running as $LIBRENMS_USER (unless $LIBRENMS_USER = root), relaunch 260 if [[ "$LIBRENMS_USER" != "root" ]]; then 261 # only try to su if we are root (or sudo) 262 if [[ "$EUID" -eq 0 ]]; then 263 echo "Re-running ${DAILY_SCRIPT} as ${LIBRENMS_USER} user" 264 sudo -u "$LIBRENMS_USER" "$DAILY_SCRIPT" "$@" 265 exit 266 fi 267 268 if [[ "$EUID" -ne "$LIBRENMS_USER_ID" ]]; then 269 printf "\\033[0;93mWARNING\\033[0m: You should run this script as %s\\n" "${LIBRENMS_USER}" 270 fi 271 fi 272 273 # make sure autoload.php exists before trying to run any php that may require it 274 if [ ! -f "${LIBRENMS_DIR}/vendor/autoload.php" ]; then 275 ${COMPOSER} install --no-dev 276 fi 277 278 if [[ -z "$arg" ]]; then 279 up=$(php daily.php -f update >&2; echo $?) 280 if [[ "$up" == "0" ]]; then 281 ${DAILY_SCRIPT} no-code-update 282 set_notifiable_result update 1 # make sure there are no update notifications if update is disabled 283 exit 284 fi 285 286 check_dependencies 287 php_ver_ret=$? 288 289 # make sure the vendor directory is clean 290 git checkout vendor/ --quiet > /dev/null 2>&1 291 292 update_res=0 293 if [[ "$up" == "1" ]] || [[ "$php_ver_ret" == "1" ]]; then 294 # Update current branch to latest 295 branch=$(git rev-parse --abbrev-ref HEAD) 296 if [[ "$branch" == "HEAD" ]]; then 297 # if the branch is HEAD, then we are not on a branch, checkout master 298 git checkout master 299 fi 300 301 old_ver=$(git rev-parse --short HEAD) 302 status_run 'Updating to latest codebase' 'git pull --quiet' 'update' 303 update_res=$? 304 new_ver=$(git rev-parse --short HEAD) 305 else 306 # Update to last Tag 307 old_ver=$(git describe --exact-match --tags "$(git log -n1 --pretty='%h')" 2> /dev/null) 308 309 # fetch new tags 310 status_run 'Fetching new release information' "git fetch --tags" 'update' 311 312 # collect versions full, base, new tag and hash 313 IFS='-' read -ra full_version <<< "$(git describe --tags 2>/dev/null)" 314 base_ver="${full_version[0]}" 315 latest_hash=$(git rev-list --tags --max-count=1) 316 latest_tag=$(git describe --exact-match --tags "${latest_hash}") 317 318 #compare current base and latest version numbers (only the first two sections) 319 version_compare "$base_ver" "$latest_tag" 2 320 newer_check=$? 321 322 if [[ -z $old_ver ]] && [[ $newer_check -eq 0 ]]; then 323 echo 'Between releases, waiting for newer release' 324 else 325 status_run 'Updating to latest release' "git checkout ${latest_hash}" 'update' 326 update_res=$? 327 new_ver=$(git describe --exact-match --tags "$(git log -n1 --pretty='%h')") 328 fi 329 fi 330 331 if (( update_res > 0 )); then 332 set_notifiable_result update 0 333 fi 334 335 # Call ourself again in case above pull changed or added something to daily.sh 336 ${DAILY_SCRIPT} post-pull "${old_ver}" "${new_ver}" 337 else 338 case $arg in 339 no-code-update) 340 # Updates of the code are disabled, just check for schema updates 341 # and clean up the db. 342 status_run 'Updating SQL-Schema' 'php includes/sql-schema/update.php' 343 status_run 'Cleaning up DB' "$DAILY_SCRIPT cleanup" 344 ;; 345 post-pull) 346 # re-check dependencies after pull with the new code 347 check_dependencies 348 349 # Check for missing vendor dir 350 if [ ! -f vendor/autoload.php ]; then 351 git checkout 609676a9f8d72da081c61f82967e1d16defc0c4e -- vendor/ 352 git reset HEAD vendor/ # don't add vendor directory to the index 353 fi 354 355 status_run 'Updating Composer packages' "${COMPOSER} install --no-dev" 'update' 356 357 # Check if we need to revert (Must be in post pull so we can update it) 358 if [[ "$old_version" != "$new_version" ]]; then 359 check_dependencies # check php and python version and switch branches 360 361 # new_version may be incorrect if we just switch branches... ignoring that detail 362 status_run "Updated from $old_version to $new_version" '' 363 set_notifiable_result update 1 # only clear the error if update was a success 364 fi 365 366 # List all tasks to do after pull in the order of execution 367 status_run 'Updating SQL-Schema' 'php includes/sql-schema/update.php' 368 status_run 'Updating submodules' "$DAILY_SCRIPT submodules" 369 status_run 'Cleaning up DB' "$DAILY_SCRIPT cleanup" 370 status_run 'Fetching notifications' "$DAILY_SCRIPT notifications" 371 status_run 'Caching PeeringDB data' "$DAILY_SCRIPT peeringdb" 372 status_run 'Caching Mac OUI data' "$DAILY_SCRIPT mac_oui" 373 ;; 374 cleanup) 375 # Cleanups 376 options=("refresh_alert_rules" 377 "refresh_os_cache" 378 "refresh_device_groups" 379 "recalculate_device_dependencies" 380 "syslog" 381 "eventlog" 382 "authlog" 383 "callback" 384 "device_perf" 385 "purgeusers" 386 "bill_data" 387 "alert_log" 388 "rrd_purge" 389 "ports_fdb" 390 "route" 391 "ports_purge") 392 call_daily_php "${options[@]}" 393 ;; 394 submodules) 395 # Init+Update our submodules 396 git submodule --quiet init 397 git submodule --quiet update 398 ;; 399 notifications) 400 # Get notifications 401 options=("notifications") 402 call_daily_php "${options[@]}" 403 ;; 404 peeringdb) 405 options=("peeringdb") 406 call_daily_php "${options[@]}" 407 ;; 408 mac_oui) 409 options=("mac_oui") 410 call_daily_php "${options[@]}" 411 esac 412 fi 413} 414 415main "$@" 416