#!/usr/bin/env php require ?? [] as $package => $version) { $output[] = "$package:$version"; } echo implode(' ', $output); return; } /** * Scripts with dependencies */ $init_modules = ['alerts']; require __DIR__ . '/includes/init.php'; if (isset($options['d'])) { echo "DEBUG\n"; Debug::set(); } if ($options['f'] === 'update') { if (! Config::get('update')) { exit(0); } if (Config::get('update_channel') == 'master') { exit(1); } elseif (Config::get('update_channel') == 'release') { exit(3); } exit(0); } if ($options['f'] === 'rrd_purge') { $lock = Cache::lock('rrd_purge', 86000); if ($lock->get()) { $rrd_purge = Config::get('rrd_purge'); $rrd_dir = Config::get('rrd_dir'); if (is_numeric($rrd_purge) && $rrd_purge > 0) { $cmd = "find $rrd_dir -name .gitignore -prune -o -type f -mtime +$rrd_purge -print -exec rm -f {} +"; $purge = `$cmd`; if (! empty($purge)) { echo "Purged the following RRD files due to old age (over $rrd_purge days old):\n"; echo $purge; } } $lock->release(); } } if ($options['f'] === 'syslog') { $lock = Cache::lock('syslog_purge', 86000); if ($lock->get()) { $syslog_purge = Config::get('syslog_purge'); if (is_numeric($syslog_purge)) { $rows = (int) dbFetchCell('SELECT MIN(seq) FROM syslog'); $initial_rows = $rows; while (true) { $limit = dbFetchCell('SELECT seq FROM syslog WHERE seq >= ? ORDER BY seq LIMIT 1000,1', [$rows]); if (empty($limit)) { break; } // Deletes are done in blocks of 1000 to avoid a single very large operation. if (dbDelete('syslog', 'seq >= ? AND seq < ? AND timestamp < DATE_SUB(NOW(), INTERVAL ? DAY)', [$rows, $limit, $syslog_purge]) > 0) { $rows = $limit; } else { break; } } dbDelete('syslog', 'seq >= ? AND timestamp < DATE_SUB(NOW(), INTERVAL ? DAY)', [$rows, $syslog_purge]); $final_rows = $rows - $initial_rows; echo "Syslog cleared for entries over $syslog_purge days (about $final_rows rows)\n"; } $lock->release(); } } if ($options['f'] === 'ports_fdb') { $ret = lock_and_purge('ports_fdb', 'updated_at < DATE_SUB(NOW(), INTERVAL ? DAY)'); exit($ret); } if ($options['f'] === 'ports_nac') { $ret = lock_and_purge('ports_nac', 'updated_at < DATE_SUB(NOW(), INTERVAL ? DAY)'); exit($ret); } if ($options['f'] === 'route') { $ret = lock_and_purge('route', 'updated_at < DATE_SUB(NOW(), INTERVAL ? DAY)'); exit($ret); } if ($options['f'] === 'eventlog') { $ret = lock_and_purge('eventlog', 'datetime < DATE_SUB(NOW(), INTERVAL ? DAY)'); exit($ret); } if ($options['f'] === 'authlog') { $ret = lock_and_purge('authlog', 'datetime < DATE_SUB(NOW(), INTERVAL ? DAY)'); exit($ret); } if ($options['f'] === 'callback') { \LibreNMS\Util\Stats::submit(); } if ($options['f'] === 'ports_purge') { if (Config::get('ports_purge')) { $lock = Cache::lock('ports_purge', 86000); if ($lock->get()) { \App\Models\Port::query()->with(['device' => function ($query) { $query->select('device_id', 'hostname'); }])->isDeleted()->chunkById(100, function ($ports) { foreach ($ports as $port) { $port->delete(); } }); echo "All deleted ports now purged\n"; $lock->release(); } } } if ($options['f'] === 'handle_notifiable') { if ($options['t'] === 'update') { $title = 'Error: Daily update failed'; $poller_name = Config::get('distributed_poller_name'); if ($options['r']) { // result was a success (1), remove the notification Notifications::remove($title); } else { // result was a failure (0), create the notification Notifications::create($title, "The daily update script (daily.sh) has failed on $poller_name." . 'Please check output by hand. If you need assistance, ' . 'visit the LibreNMS Website to find out how.', 'daily.sh', 2 ); } } elseif ($options['t'] === 'phpver') { $error_title = 'Error: PHP version too low'; // if update is not set to false and version is min or newer if (Config::get('update') && $options['r']) { if (preg_match('/^php\d{2}/', $options['r'])) { $phpver = Php::PHP_MIN_VERSION; $eol_date = Php::PHP_MIN_VERSION_DATE; Notifications::create($error_title, "PHP version $phpver is the minimum supported version as of $eol_date. We recommend you update to PHP a supported version of PHP (" . Php::PHP_RECOMMENDED_VERSION . ' suggested) to continue to receive updates. If you do not update PHP, LibreNMS will continue to function but stop receiving bug fixes and updates.', 'daily.sh', 2 ); exit(1); } } Notifications::remove($error_title); exit(0); } elseif ($options['t'] === 'pythonver') { $error_title = 'Error: Python requirements not met'; // if update is not set to false and version is min or newer if (Config::get('update') && $options['r']) { if ($options['r'] === 'python3-missing') { Notifications::create($error_title, 'Python 3 is required to run LibreNMS as of May, 2020. You need to install Python 3 to continue to receive updates. If you do not install Python 3 and required packages, LibreNMS will continue to function but stop receiving bug fixes and updates.', 'daily.sh', 2 ); exit(1); } elseif ($options['r'] === 'python3-deps') { Notifications::create($error_title, 'Python 3 dependencies are missing. You need to install them via pip3 install -r requirements.txt or system packages to continue to receive updates. If you do not install Python 3 and required packages, LibreNMS will continue to function but stop receiving bug fixes and updates.', 'daily.sh', 2 ); exit(1); } } Notifications::remove($error_title); exit(0); } } if ($options['f'] === 'notifications') { $lock = Cache::lock('notifications', 86000); if ($lock->get()) { Notifications::post(); $lock->release(); } } if ($options['f'] === 'bill_data') { // Deletes data older than XX months before the start of the last complete billing period $msg = "Deleting billing data more than %d month before the last completed billing cycle\n"; $table = 'bill_data'; $sql = 'DELETE bill_data FROM bill_data INNER JOIN (SELECT bill_id, SUBDATE( SUBDATE( ADDDATE( subdate(curdate(), (day(curdate())-1)), # Start of this month bill_day - 1), # Billing anniversary INTERVAL IF(bill_day > DAY(curdate()), 1, 0) MONTH), # Deal with anniversary not yet happened this month INTERVAL ? MONTH) AS threshold # Adjust based on config threshold FROM bills) q ON bill_data.bill_id = q.bill_id AND bill_data.timestamp < q.threshold;'; lock_and_purge_query($table, $sql, $msg); } if ($options['f'] === 'alert_log') { $msg = "Deleting alert_logs more than %d days that are not active\n"; $table = 'alert_log'; $sql = 'DELETE alert_log FROM alert_log INNER JOIN alerts ON alerts.device_id=alert_log.device_id AND alerts.rule_id=alert_log.rule_id WHERE alerts.state=0 AND alert_log.time_logged < DATE_SUB(NOW(),INTERVAL ? DAY) '; lock_and_purge_query($table, $sql, $msg); // alert_log older than $config['alert_log_purge'] days match now only the alert_log of active alerts // in case of flapping of an alert, many entries are kept in alert_log // we want only to keep the last alert_log that contains the alert details $msg = "Deleting history of active alert_logs more than %d days\n"; $sql = 'DELETE FROM alert_log WHERE id IN( SELECT id FROM( SELECT id FROM alert_log a1 WHERE time_logged < DATE_SUB(NOW(),INTERVAL ? DAY) AND (device_id, rule_id, time_logged) NOT IN ( SELECT device_id, rule_id, max(time_logged) FROM alert_log a2 WHERE a1.device_id = a2.device_id AND a1.rule_id = a2.rule_id AND a2.time_logged < DATE_SUB(NOW(),INTERVAL ? DAY) ) ) as c ) '; $purge_duration = Config::get('alert_log_purge'); if (! (is_numeric($purge_duration) && $purge_duration > 0)) { return -2; } $sql = preg_replace('/\?/', strval($purge_duration), $sql, 1); lock_and_purge_query($table, $sql, $msg); } if ($options['f'] === 'purgeusers') { $lock = Cache::lock('purgeusers', 86000); if ($lock->get()) { $purge = 0; if (is_numeric(\LibreNMS\Config::get('radius.users_purge')) && Config::get('auth_mechanism') === 'radius') { $purge = \LibreNMS\Config::get('radius.users_purge'); } if (is_numeric(\LibreNMS\Config::get('active_directory.users_purge')) && Config::get('auth_mechanism') === 'active_directory') { $purge = \LibreNMS\Config::get('active_directory.users_purge'); } if ($purge > 0) { $users = \App\Models\AuthLog::where('datetime', '>=', \Carbon\Carbon::now()->subDays($purge)) ->distinct()->pluck('user') ->merge(\App\Models\User::has('apiTokens')->pluck('username')) // don't purge users with api tokens ->unique(); if (\App\Models\User::thisAuth()->whereNotIn('username', $users)->delete()) { echo "Removed users that haven't logged in for $purge days\n"; } } $lock->release(); } } if ($options['f'] === 'refresh_alert_rules') { $lock = Cache::lock('refresh_alert_rules', 86000); if ($lock->get()) { echo 'Refreshing alert rules queries' . PHP_EOL; $rules = dbFetchRows('SELECT `id`, `rule`, `builder`, `extra` FROM `alert_rules`'); foreach ($rules as $rule) { $rule_options = json_decode($rule['extra'], true); if ($rule_options['options']['override_query'] !== 'on') { $data['query'] = AlertDB::genSQL($rule['rule'], $rule['builder']); if (! empty($data['query'])) { dbUpdate($data, 'alert_rules', 'id=?', [$rule['id']]); unset($data); } } } $lock->release(); } } if ($options['f'] === 'refresh_device_groups') { $lock = Cache::lock('refresh_device_groups', 86000); if ($lock->get()) { echo 'Refreshing device group table relationships' . PHP_EOL; DeviceGroup::all()->each(function ($deviceGroup) { if ($deviceGroup->type == 'dynamic') { /** @var DeviceGroup $deviceGroup */ $deviceGroup->rules = $deviceGroup->getParser()->generateJoins()->toArray(); $deviceGroup->save(); } }); $lock->release(); } } if ($options['f'] === 'notify') { if (\LibreNMS\Config::has('alert.default_mail')) { \LibreNMS\Util\Mail::send(\LibreNMS\Config::get('alert.default_mail'), '[LibreNMS] Auto update has failed for ' . Config::get('distributed_poller_name'), "We just attempted to update your install but failed. The information below should help you fix this.\r\n\r\n" . $options['o'], false); } } if ($options['f'] === 'peeringdb') { $lock = Cache::lock('peeringdb', 86000); if ($lock->get()) { cache_peeringdb(); $lock->release(); } } if ($options['f'] === 'refresh_os_cache') { echo 'Clearing OS cache' . PHP_EOL; if (is_file(Config::get('install_dir') . '/cache/os_defs.cache')) { unlink(Config::get('install_dir') . '/cache/os_defs.cache'); } } if ($options['f'] === 'recalculate_device_dependencies') { // fix broken dependency max_depth calculation in case things weren't done though eloquent $lock = Cache::lock('recalculate_device_dependencies', 86000); if ($lock->get()) { // update all root nodes and recurse, chunk so we don't blow up Device::doesntHave('parents')->with('children')->chunkById(100, function (Collection $devices) { // anonymous recursive function $recurse = function (Device $device) use (&$recurse) { $device->updateMaxDepth(); $device->children->each($recurse); }; $devices->each($recurse); }); $lock->release(); } }