From 77fa7573cfdeaad4ffa40312f17fb1216d1909c4 Mon Sep 17 00:00:00 2001 From: Jellyfrog Date: Tue, 10 Sep 2024 09:59:20 +0200 Subject: [PATCH] Revert "Convert Config to a singleton (#16349)" (#16382) This reverts commit f1e7a218f047d71d17952dcddcfa1acebbd9400c. --- LibreNMS/Config.php | 453 ++++++++++++++++-- LibreNMS/IRCBot.php | 5 +- LibreNMS/Util/Git.php | 6 +- LibreNMS/Util/Version.php | 14 +- app/ConfigRepository.php | 576 ----------------------- app/Facades/LibrenmsConfig.php | 43 -- app/Providers/AppServiceProvider.php | 10 +- app/Providers/ConfigServiceProvider.php | 24 +- app/Providers/ErrorReportingProvider.php | 14 +- config/app.php | 1 - config/librenms.php | 9 - config/logging.php | 4 +- phpunit.xml | 1 - tests/ConfigTest.php | 19 +- tests/OSDiscoveryTest.php | 2 +- tests/bootstrap.php | 2 + 16 files changed, 470 insertions(+), 713 deletions(-) delete mode 100644 app/ConfigRepository.php delete mode 100644 app/Facades/LibrenmsConfig.php diff --git a/LibreNMS/Config.php b/LibreNMS/Config.php index 39a70c9119..2a631fd306 100644 --- a/LibreNMS/Config.php +++ b/LibreNMS/Config.php @@ -25,31 +25,119 @@ namespace LibreNMS; -use App\Facades\LibrenmsConfig; +use App\Models\Callback; +use App\Models\GraphType; +use Exception; +use Illuminate\Database\QueryException; +use Illuminate\Support\Arr; +use Illuminate\Support\Str; +use LibreNMS\Data\Store\Rrd; +use LibreNMS\Util\Debug; +use LibreNMS\Util\Version; +use Log; -// not yet: @deprecated Please use the facade App\Facades\LibrenmsConfig instead class Config { + private static $config; + + /** + * Load the config, if the database connected, pull in database settings. + * + * return &array + */ + public static function load() + { + // don't reload the config if it is already loaded, reload() should be used for that + if (self::isLoaded()) { + return self::$config; + } + + // merge all config sources together config_definitions.json > db config > config.php + self::loadDefaults(); + self::loadDB(); + self::loadUserConfigFile(self::$config); + + // final cleanups and validations + self::processConfig(); + + // set to global for legacy/external things (is this needed?) + global $config; + $config = self::$config; + + return self::$config; + } + + /** + * Reload the config from files/db + * + * @return mixed + */ + public static function reload() + { + self::$config = null; + + return self::load(); + } + /** * Get the config setting definitions * * @return array */ - public static function getDefinitions(): array + public static function getDefinitions() { - return LibrenmsConfig::getDefinitions(); + return json_decode(file_get_contents(base_path('misc/config_definitions.json')), true)['config']; + } + + private static function loadDefaults() + { + self::$config['install_dir'] = base_path(); + $definitions = self::getDefinitions(); + + foreach ($definitions as $path => $def) { + if (array_key_exists('default', $def)) { + Arr::set(self::$config, $path, $def['default']); + } + } + + // load macros from json + $macros = json_decode(file_get_contents(base_path('misc/macros.json')), true); + Arr::set(self::$config, 'alert.macros.rule', $macros); + + self::processDefaults(); } /** - * Get a config value, if non-existent null (or default if set) will be returned + * Load the user config from config.php + * + * @param array $config (this should be self::$config) + */ + private static function loadUserConfigFile(&$config) + { + // Load user config file + if (is_file(base_path('config.php'))) { + @include base_path('config.php'); + } + } + + /** + * Get a config value, if non existent null (or default if set) will be returned * * @param string $key period separated config variable name * @param mixed $default optional value to return if the setting is not set * @return mixed */ - public static function get($key, $default = null): mixed + public static function get($key, $default = null) { - return LibrenmsConfig::get($key, $default); + if (isset(self::$config[$key])) { + return self::$config[$key]; + } + + if (! Str::contains($key, '.')) { + return $default; + } + + return Arr::get(self::$config, $key, $default); } /** @@ -58,9 +146,9 @@ class Config * * @param string|array $key */ - public static function forget($key): void + public static function forget($key) { - LibrenmsConfig::forget($key); + Arr::forget(self::$config, $key); } /** @@ -74,9 +162,17 @@ class Config * @param mixed $default will be returned if the setting is not set on the device or globally * @return mixed */ - public static function getDeviceSetting($device, $key, $global_prefix = null, $default = null): mixed + public static function getDeviceSetting($device, $key, $global_prefix = null, $default = null) { - return LibrenmsConfig::getDeviceSetting($device, $key, $global_prefix, $default); + if (isset($device[$key])) { + return $device[$key]; + } + + if (isset($global_prefix)) { + $key = "$global_prefix.$key"; + } + + return self::get($key, $default); } /** @@ -87,9 +183,22 @@ class Config * @param mixed $default optional value to return if the setting is not set * @return mixed */ - public static function getOsSetting($os, $key, $default = null): mixed + public static function getOsSetting($os, $key, $default = null) { - return LibrenmsConfig::getOsSetting($os, $key, $default); + if ($os) { + \LibreNMS\Util\OS::loadDefinition($os); + + if (isset(self::$config['os'][$os][$key])) { + return self::$config['os'][$os][$key]; + } + + $os_key = "os.$os.$key"; + if (self::has($os_key)) { + return self::get($os_key); + } + } + + return $default; } /** @@ -105,7 +214,25 @@ class Config */ public static function getCombined(?string $os, string $key, string $global_prefix = '', array $default = []): array { - return LibrenmsConfig::getCombined($os, $key, $global_prefix, $default); + $global_key = $global_prefix . $key; + + if (! isset(self::$config['os'][$os][$key])) { + if (! Str::contains($global_key, '.')) { + return (array) self::get($global_key, $default); + } + if (! self::has("os.$os.$key")) { + return (array) self::get($global_key, $default); + } + } + + if (! self::has("os.$os.$key")) { + return (array) self::get($global_key, $default); + } + + return array_unique(array_merge( + (array) self::get($global_key), + (array) self::getOsSetting($os, $key) + )); } /** @@ -114,9 +241,9 @@ class Config * @param mixed $key period separated config variable name * @param mixed $value */ - public static function set($key, $value): void + public static function set($key, $value) { - LibrenmsConfig::set($key, $value); + Arr::set(self::$config, $key, $value); } /** @@ -126,9 +253,29 @@ class Config * @param mixed $value * @return bool if the save was successful */ - public static function persist($key, $value): bool + public static function persist($key, $value) { - return LibrenmsConfig::persist($key, $value); + try { + Arr::set(self::$config, $key, $value); + \App\Models\Config::updateOrCreate(['config_name' => $key], [ + 'config_name' => $key, + 'config_value' => $value, + ]); + + // delete any children (there should not be any unless it is legacy) + \App\Models\Config::query()->where('config_name', 'like', "$key.%")->delete(); + + return true; + } catch (Exception $e) { + if (class_exists(Log::class)) { + Log::error($e); + } + if (Debug::isEnabled()) { + echo $e; + } + + return false; + } } /** @@ -138,9 +285,14 @@ class Config * @param string $key * @return int|false */ - public static function erase($key): bool|int + public static function erase($key) { - return LibrenmsConfig::erase($key); + self::forget($key); + try { + return \App\Models\Config::withChildren($key)->delete(); + } catch (Exception $e) { + return false; + } } /** @@ -149,9 +301,17 @@ class Config * @param string $key period separated config variable name * @return bool */ - public static function has($key): bool + public static function has($key) { - return LibrenmsConfig::has($key); + if (isset(self::$config[$key])) { + return true; + } + + if (! Str::contains($key, '.')) { + return false; + } + + return Arr::has(self::$config, $key); } /** @@ -159,9 +319,9 @@ class Config * * @return string */ - public static function toJson(): string + public static function toJson() { - return LibrenmsConfig::toJson(); + return json_encode(self::$config); } /** @@ -169,9 +329,193 @@ class Config * * @return array */ - public static function getAll(): array + public static function getAll() { - return LibrenmsConfig::getAll(); + return self::$config; + } + + /** + * merge the database config with the global config + * Global config overrides db + */ + private static function loadDB() + { + try { + \App\Models\Config::get(['config_name', 'config_value']) + ->each(function ($item) { + Arr::set(self::$config, $item->config_name, $item->config_value); + }); + } catch (QueryException $e) { + // possibly table config doesn't exist yet + } + + // load graph types from the database + self::loadGraphsFromDb(self::$config); + } + + private static function loadGraphsFromDb(&$config) + { + try { + $graph_types = GraphType::all()->toArray(); + } catch (QueryException $e) { + // possibly table config doesn't exist yet + $graph_types = []; + } + + // load graph types from the database + foreach ($graph_types as $graph) { + $g = []; + foreach ($graph as $k => $v) { + if (strpos($k, 'graph_') == 0) { + // remove leading 'graph_' from column name + $key = str_replace('graph_', '', $k); + } else { + $key = $k; + } + $g[$key] = $v; + } + + $config['graph_types'][$g['type']][$g['subtype']] = $g; + } + } + + /** + * Handle defaults that are set programmatically + */ + private static function processDefaults() + { + Arr::set(self::$config, 'log_dir', base_path('logs')); + Arr::set(self::$config, 'distributed_poller_name', php_uname('n')); + + // set base_url from access URL + if (isset($_SERVER['SERVER_NAME']) && isset($_SERVER['SERVER_PORT'])) { + $port = $_SERVER['SERVER_PORT'] != 80 ? ':' . $_SERVER['SERVER_PORT'] : ''; + // handle literal IPv6 + $server = Str::contains($_SERVER['SERVER_NAME'], ':') ? "[{$_SERVER['SERVER_NAME']}]" : $_SERVER['SERVER_NAME']; + Arr::set(self::$config, 'base_url', "http://$server$port/"); + } + + // graph color copying + Arr::set(self::$config, 'graph_colours.mega', array_merge( + (array) Arr::get(self::$config, 'graph_colours.psychedelic', []), + (array) Arr::get(self::$config, 'graph_colours.manycolours', []), + (array) Arr::get(self::$config, 'graph_colours.default', []), + (array) Arr::get(self::$config, 'graph_colours.mixed', []) + )); + } + + /** + * Process the config after it has been loaded. + * Make sure certain variables have been set properly and + */ + private static function processConfig() + { + // If we're on SSL, let's properly detect it + if ( + isset($_SERVER['HTTPS']) || + (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') + ) { + self::set('base_url', preg_replace('/^http:/', 'https:', self::get('base_url', ''))); + } + + self::set('base_url', Str::finish(self::get('base_url', ''), '/')); + + if (! self::get('email_from')) { + self::set('email_from', '"' . self::get('project_name') . '" <' . self::get('email_user') . '@' . php_uname('n') . '>'); + } + + // Define some variables if they aren't set by user definition in config_definitions.json + self::set('applied_site_style', self::get('site_style')); + self::setDefault('html_dir', '%s/html', ['install_dir']); + self::setDefault('rrd_dir', '%s/rrd', ['install_dir']); + self::setDefault('mib_dir', '%s/mibs', ['install_dir']); + self::setDefault('log_dir', '%s/logs', ['install_dir']); + self::setDefault('log_file', '%s/%s.log', ['log_dir', 'project_id']); + self::setDefault('plugin_dir', '%s/plugins', ['html_dir']); + self::setDefault('temp_dir', sys_get_temp_dir() ?: '/tmp'); + self::setDefault('irc_nick', '%s', ['project_name']); + self::setDefault('irc_chan.0', '##%s', ['project_id']); + self::setDefault('page_title_suffix', '%s', ['project_name']); +// self::setDefault('email_from', '"%s" <%s@' . php_uname('n') . '>', ['project_name', 'email_user']); // FIXME email_from set because alerting config + + // deprecated variables + self::deprecatedVariable('rrdgraph_real_95th', 'rrdgraph_real_percentile'); + self::deprecatedVariable('fping_options.millisec', 'fping_options.interval'); + self::deprecatedVariable('discovery_modules.cisco-vrf', 'discovery_modules.vrf'); + self::deprecatedVariable('discovery_modules.toner', 'discovery_modules.printer-supplies'); + self::deprecatedVariable('poller_modules.toner', 'poller_modules.printer-supplies'); + self::deprecatedVariable('discovery_modules.cisco-sla', 'discovery_modules.slas'); + self::deprecatedVariable('poller_modules.cisco-sla', 'poller_modules.slas'); + self::deprecatedVariable('oxidized.group', 'oxidized.maps.group'); + + // migrate device display + if (! self::has('device_display_default')) { + $display_value = '{{ $hostname }}'; + if (self::get('force_hostname_to_sysname')) { + $display_value = '{{ $sysName }}'; + } elseif (self::get('force_ip_to_sysname')) { + $display_value = '{{ $sysName_fallback }}'; + } + + self::persist('device_display_default', $display_value); + } + + // make sure we have full path to binaries in case PATH isn't set + foreach (['fping', 'fping6', 'snmpgetnext', 'rrdtool', 'traceroute'] as $bin) { + if (! is_executable(self::get($bin))) { + self::persist($bin, self::locateBinary($bin)); + } + } + + if (! self::has('rrdtool_version')) { + self::persist('rrdtool_version', Rrd::version()); + } + if (! self::has('snmp.unescape')) { + self::persist('snmp.unescape', version_compare(Version::get()->netSnmp(), '5.8.0', '<')); + } + if (! self::has('reporting.usage')) { + self::persist('reporting.usage', (bool) Callback::get('enabled')); + } + + self::populateTime(); + + // populate legacy DB credentials, just in case something external uses them. Maybe remove this later + self::populateLegacyDbCredentials(); + } + + /** + * Set default values for defaults that depend on other settings, if they are not already loaded + * + * @param string $key + * @param string $value value to set to key or vsprintf() format string for values below + * @param array $format_values array of keys to send to vsprintf() + */ + private static function setDefault($key, $value, $format_values = []) + { + if (! self::has($key)) { + if (is_string($value)) { + $format_values = array_map('\LibreNMS\Config::get', $format_values); + self::set($key, vsprintf($value, $format_values)); + } else { + self::set($key, $value); + } + } + } + + /** + * Copy data from old variables to new ones. + * + * @param string $old + * @param string $new + */ + private static function deprecatedVariable($old, $new) + { + if (self::has($old)) { + if (Debug::isEnabled()) { + echo "Copied deprecated config $old to $new\n"; + } + self::set($new, self::get($old)); + } } /** @@ -180,13 +524,62 @@ class Config * @param string $binary * @return mixed */ - public static function locateBinary($binary): mixed + public static function locateBinary($binary) { - return LibrenmsConfig::locateBinary($binary); + if (! Str::contains($binary, '/')) { + $output = `whereis -b $binary`; + $list = trim(substr($output, strpos($output, ':') + 1)); + $targets = explode(' ', $list); + foreach ($targets as $target) { + if (is_executable($target)) { + return $target; + } + } + } + + return $binary; } - public static function populateLegacyDbCredentials(): void + private static function populateTime() { - LibrenmsConfig::populateLegacyDbCredentials(); + $now = time(); + $now -= $now % 300; + self::set('time.now', $now); + self::set('time.onehour', $now - 3600); // time() - (1 * 60 * 60); + self::set('time.fourhour', $now - 14400); // time() - (4 * 60 * 60); + self::set('time.sixhour', $now - 21600); // time() - (6 * 60 * 60); + self::set('time.twelvehour', $now - 43200); // time() - (12 * 60 * 60); + self::set('time.day', $now - 86400); // time() - (24 * 60 * 60); + self::set('time.twoday', $now - 172800); // time() - (2 * 24 * 60 * 60); + self::set('time.week', $now - 604800); // time() - (7 * 24 * 60 * 60); + self::set('time.twoweek', $now - 1209600); // time() - (2 * 7 * 24 * 60 * 60); + self::set('time.month', $now - 2678400); // time() - (31 * 24 * 60 * 60); + self::set('time.twomonth', $now - 5356800); // time() - (2 * 31 * 24 * 60 * 60); + self::set('time.threemonth', $now - 8035200); // time() - (3 * 31 * 24 * 60 * 60); + self::set('time.sixmonth', $now - 16070400); // time() - (6 * 31 * 24 * 60 * 60); + self::set('time.year', $now - 31536000); // time() - (365 * 24 * 60 * 60); + self::set('time.twoyear', $now - 63072000); // time() - (2 * 365 * 24 * 60 * 60); + } + + public static function populateLegacyDbCredentials() + { + $db = config('database.default'); + + self::set('db_host', config("database.connections.$db.host", 'localhost')); + self::set('db_name', config("database.connections.$db.database", 'librenms')); + self::set('db_user', config("database.connections.$db.username", 'librenms')); + self::set('db_pass', config("database.connections.$db.password")); + self::set('db_port', config("database.connections.$db.port", 3306)); + self::set('db_socket', config("database.connections.$db.unix_socket")); + } + + /** + * Check if the config has been loaded yet + * + * @return bool + */ + public static function isLoaded(): bool + { + return ! is_null(self::$config); } } diff --git a/LibreNMS/IRCBot.php b/LibreNMS/IRCBot.php index 7b0fcfad90..467937d99a 100644 --- a/LibreNMS/IRCBot.php +++ b/LibreNMS/IRCBot.php @@ -20,7 +20,6 @@ namespace LibreNMS; -use App\Facades\LibrenmsConfig; use App\Models\Device; use App\Models\Eventlog; use App\Models\Port; @@ -729,9 +728,9 @@ class IRCBot return $this->loadExternal(); } - LibrenmsConfig::reload(); + $new_config = Config::load(); $this->respond('Reloading configuration & defaults'); - if (LibrenmsConfig::getAll() != $this->config) { + if ($new_config != $this->config) { $this->__construct(); return; diff --git a/LibreNMS/Util/Git.php b/LibreNMS/Util/Git.php index 98d8b8d349..348f86af43 100644 --- a/LibreNMS/Util/Git.php +++ b/LibreNMS/Util/Git.php @@ -25,10 +25,10 @@ namespace LibreNMS\Util; -use App\Facades\LibrenmsConfig; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Http\Client\ConnectionException; use Illuminate\Support\Str; +use LibreNMS\Config; use LibreNMS\Traits\RuntimeClassCache; use Symfony\Component\Process\Process; @@ -42,7 +42,7 @@ class Git public function __construct(int $cache = 0) { $this->runtimeCacheExternalTTL = $cache; - $this->install_dir = realpath(__DIR__ . '/../..'); + $this->install_dir = Config::get('install_dir', realpath(__DIR__ . '/../..')); } public static function make(int $cache = 0): Git @@ -196,7 +196,7 @@ class Git return $this->cacheGet('remoteCommit', function () { if ($this->isAvailable()) { try { - return (array) Http::client()->get(LibrenmsConfig::get('github_api') . 'commits/master')->json(); + return (array) Http::client()->get(Config::get('github_api') . 'commits/master')->json(); } catch (ConnectionException $e) { } } diff --git a/LibreNMS/Util/Version.php b/LibreNMS/Util/Version.php index 53a6e671c3..f1d2a96dfd 100644 --- a/LibreNMS/Util/Version.php +++ b/LibreNMS/Util/Version.php @@ -25,9 +25,9 @@ namespace LibreNMS\Util; -use App\ConfigRepository; use Illuminate\Support\Arr; use Illuminate\Support\Facades\DB; +use LibreNMS\Config; use LibreNMS\DB\Eloquent; use Symfony\Component\Process\Process; @@ -38,22 +38,20 @@ class Version /** @var Git convenience instance */ public $git; - private ConfigRepository $config; - public function __construct(ConfigRepository $config) + public function __construct() { - $this->config = $config; $this->git = Git::make(); } public static function get(): Version { - return new static(app('librenms-config')); + return new static; } public function release(): string { - return $this->config->get('update_channel') == 'master' ? 'master' : self::VERSION; + return Config::get('update_channel') == 'master' ? 'master' : self::VERSION; } public function date(string $format = 'c'): string @@ -137,7 +135,7 @@ class Version public function rrdtool(): string { - $process = new Process([$this->config->get('rrdtool', 'rrdtool'), '--version']); + $process = new Process([Config::get('rrdtool', 'rrdtool'), '--version']); $process->run(); preg_match('/^RRDtool ([\w.]+) /', $process->getOutput(), $matches); @@ -146,7 +144,7 @@ class Version public function netSnmp(): string { - $process = new Process([$this->config->get('snmpget', 'snmpget'), '-V']); + $process = new Process([Config::get('snmpget', 'snmpget'), '-V']); $process->run(); preg_match('/[\w.]+$/', $process->getErrorOutput(), $matches); diff --git a/app/ConfigRepository.php b/app/ConfigRepository.php deleted file mode 100644 index 73f35ff251..0000000000 --- a/app/ConfigRepository.php +++ /dev/null @@ -1,576 +0,0 @@ -. - * - * @link https://www.librenms.org - * - * @copyright 2017 Tony Murray - * @author Tony Murray - */ - -namespace App; - -use App\Models\Callback; -use App\Models\GraphType; -use Exception; -use Illuminate\Database\QueryException; -use Illuminate\Support\Arr; -use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Str; -use LibreNMS\DB\Eloquent; -use LibreNMS\Util\Debug; -use LibreNMS\Util\Version; -use Log; - -class ConfigRepository -{ - private array $config; - - /** - * Load the config, if the database connected, pull in database settings. - * - * return &array - */ - public function __construct() - { - // load config settings that can be cached - $cache_ttl = config('librenms.config_cache_ttl'); - $this->config = Cache::driver($cache_ttl == 0 ? 'null' : 'file')->remember('librenms-config', $cache_ttl, function () { - $this->config = []; - // merge all config sources together config_definitions.json > db config > config.php - $this->loadPreUserConfigDefaults(); - $this->loadDB(); - $this->loadUserConfigFile($this->config); - $this->loadPostUserConfigDefaults(); - - return $this->config; - }); - - // set config settings that must change every run - $this->loadRuntimeSettings(); - } - - /** - * Get the config setting definitions - * - * @return array - */ - public function getDefinitions(): array - { - return json_decode(file_get_contents($this->get('install_dir') . '/misc/config_definitions.json'), true)['config']; - } - - /** - * Load the user config from config.php - * - * @param array $config (this should be $this->config) - */ - private function loadUserConfigFile(&$config): void - { - // Load user config file - $file = $this->get('install_dir') . '/config.php'; - if (is_file($file)) { - @include $file; - } - } - - /** - * Get a config value, if non existent null (or default if set) will be returned - * - * @param string $key period separated config variable name - * @param mixed $default optional value to return if the setting is not set - * @return mixed - */ - public function get($key, $default = null): mixed - { - if (isset($this->config[$key])) { - return $this->config[$key]; - } - - if (! Str::contains($key, '.')) { - return $default; - } - - return Arr::get($this->config, $key, $default); - } - - /** - * Unset a config setting - * or multiple - * - * @param string|array $key - */ - public function forget($key): void - { - Arr::forget($this->config, $key); - } - - /** - * Get a setting from a device, if that is not set, - * fall back to the global config setting prefixed by $global_prefix - * The key must be the same for the global setting and the device setting. - * - * @param array $device Device array - * @param string $key Name of setting to fetch - * @param string $global_prefix specify where the global setting lives in the global config - * @param mixed $default will be returned if the setting is not set on the device or globally - * @return mixed - */ - public function getDeviceSetting($device, $key, $global_prefix = null, $default = null): mixed - { - if (isset($device[$key])) { - return $device[$key]; - } - - if (isset($global_prefix)) { - $key = "$global_prefix.$key"; - } - - return $this->get($key, $default); - } - - /** - * Get a setting from the $config['os'] array using the os of the given device - * - * @param string $os The os name - * @param string $key period separated config variable name - * @param mixed $default optional value to return if the setting is not set - * @return mixed - */ - public function getOsSetting($os, $key, $default = null): mixed - { - if ($os) { - \LibreNMS\Util\OS::loadDefinition($os); - - if (isset($this->config['os'][$os][$key])) { - return $this->config['os'][$os][$key]; - } - - $os_key = "os.$os.$key"; - if ($this->has($os_key)) { - return $this->get($os_key); - } - } - - return $default; - } - - /** - * Get the merged array from the global and os settings for the specified key. - * Removes any duplicates. - * When the arrays have keys, os settings take precedence over global settings - * - * @param string|null $os The os name - * @param string $key period separated config variable name - * @param string $global_prefix prefix for global setting - * @param array $default optional array to return if the setting is not set - * @return array - */ - public function getCombined(?string $os, string $key, string $global_prefix = '', array $default = []): array - { - $global_key = $global_prefix . $key; - - if (! isset($this->config['os'][$os][$key])) { - if (! Str::contains($global_key, '.')) { - return (array) $this->get($global_key, $default); - } - if (! $this->has("os.$os.$key")) { - return (array) $this->get($global_key, $default); - } - } - - if (! $this->has("os.$os.$key")) { - return (array) $this->get($global_key, $default); - } - - return array_unique(array_merge( - (array) $this->get($global_key), - (array) $this->getOsSetting($os, $key) - )); - } - - /** - * Set a variable in the global config - * - * @param mixed $key period separated config variable name - * @param mixed $value - */ - public function set($key, $value): void - { - Arr::set($this->config, $key, $value); - } - - /** - * Save setting to persistent storage. - * - * @param mixed $key period separated config variable name - * @param mixed $value - * @return bool if the save was successful - */ - public function persist($key, $value): bool - { - try { - Arr::set($this->config, $key, $value); - - if (! Eloquent::isConnected()) { - return false; // can't save it if there is no DB - } - - \App\Models\Config::updateOrCreate(['config_name' => $key], [ - 'config_name' => $key, - 'config_value' => $value, - ]); - - // delete any children (there should not be any unless it is legacy) - \App\Models\Config::query()->where('config_name', 'like', "$key.%")->delete(); - - return true; - } catch (Exception $e) { - if (class_exists(Log::class)) { - Log::error($e); - } - if (Debug::isEnabled()) { - echo $e; - } - - if ($e instanceof \Illuminate\Database\QueryException && $e->getCode() !== '42S02') { - // re-throw, else Config service provider get stuck in a loop - // if there is an error (database not connected) - // unless it is table not found (migrations have not been run yet) - - throw $e; - } - - return false; - } - } - - /** - * Forget a key and all it's descendants from persistent storage. - * This will effectively set it back to default. - * - * @param string $key - * @return int|false - */ - public function erase($key): bool|int - { - $this->forget($key); - try { - return \App\Models\Config::withChildren($key)->delete(); - } catch (Exception $e) { - return false; - } - } - - /** - * Check if a setting is set - * - * @param string $key period separated config variable name - * @return bool - */ - public function has($key): bool - { - if (isset($this->config[$key])) { - return true; - } - - if (! Str::contains($key, '.')) { - return false; - } - - return Arr::has($this->config, $key); - } - - /** - * Serialise the whole configuration to json for use in external processes. - * - * @return string - */ - public function toJson(): string - { - return json_encode($this->config); - } - - /** - * Get the full configuration array - * - * @return array - */ - public function getAll(): array - { - return $this->config; - } - - /** - * merge the database config with the global config, - * global config overrides db - */ - private function loadDB(): void - { - if (! Eloquent::isConnected()) { - return; // don't even try if no DB - } - - try { - \App\Models\Config::get(['config_name', 'config_value']) - ->each(function ($item) { - Arr::set($this->config, $item->config_name, $item->config_value); - }); - } catch (QueryException $e) { - // possibly table config doesn't exist yet - } - - // load graph types from the database - $this->loadGraphsFromDb($this->config); - } - - private function loadGraphsFromDb(&$config): void - { - try { - $graph_types = GraphType::all()->toArray(); - } catch (QueryException $e) { - // possibly table config doesn't exist yet - $graph_types = []; - } - - // load graph types from the database - foreach ($graph_types as $graph) { - $g = []; - foreach ($graph as $k => $v) { - if (strpos($k, 'graph_') == 0) { - // remove leading 'graph_' from column name - $key = str_replace('graph_', '', $k); - } else { - $key = $k; - } - $g[$key] = $v; - } - - $config['graph_types'][$g['type']][$g['subtype']] = $g; - } - } - - /** - * Handle defaults that are set programmatically - */ - private function loadPreUserConfigDefaults(): void - { - $this->config['install_dir'] = realpath(__DIR__ . '/..'); - $definitions = $this->getDefinitions(); - - foreach ($definitions as $path => $def) { - if (array_key_exists('default', $def)) { - Arr::set($this->config, $path, $def['default']); - } - } - - // load macros from json - $macros = json_decode(file_get_contents($this->get('install_dir') . '/misc/macros.json'), true); - Arr::set($this->config, 'alert.macros.rule', $macros); - - Arr::set($this->config, 'log_dir', $this->get('install_dir') . '/logs'); - Arr::set($this->config, 'distributed_poller_name', php_uname('n')); - - // set base_url from access URL - if (isset($_SERVER['SERVER_NAME']) && isset($_SERVER['SERVER_PORT'])) { - $port = $_SERVER['SERVER_PORT'] != 80 ? ':' . $_SERVER['SERVER_PORT'] : ''; - // handle literal IPv6 - $server = Str::contains($_SERVER['SERVER_NAME'], ':') ? "[{$_SERVER['SERVER_NAME']}]" : $_SERVER['SERVER_NAME']; - Arr::set($this->config, 'base_url', "http://$server$port/"); - } - - // graph color copying - Arr::set($this->config, 'graph_colours.mega', array_merge( - (array) Arr::get($this->config, 'graph_colours.psychedelic', []), - (array) Arr::get($this->config, 'graph_colours.manycolours', []), - (array) Arr::get($this->config, 'graph_colours.default', []), - (array) Arr::get($this->config, 'graph_colours.mixed', []) - )); - } - - private function loadPostUserConfigDefaults(): void - { - if (! $this->get('email_from')) { - $this->set('email_from', '"' . $this->get('project_name') . '" <' . $this->get('email_user') . '@' . php_uname('n') . '>'); - } - // Define some variables if they aren't set by user definition in config_definitions.json - $this->setDefault('html_dir', '%s/html', ['install_dir']); - $this->setDefault('rrd_dir', '%s/rrd', ['install_dir']); - $this->setDefault('mib_dir', '%s/mibs', ['install_dir']); - $this->setDefault('log_dir', '%s/logs', ['install_dir']); - $this->setDefault('log_file', '%s/%s.log', ['log_dir', 'project_id']); - $this->setDefault('plugin_dir', '%s/plugins', ['html_dir']); - $this->setDefault('temp_dir', sys_get_temp_dir() ?: '/tmp'); - $this->setDefault('irc_nick', '%s', ['project_name']); - $this->setDefault('irc_chan.0', '##%s', ['project_id']); - $this->setDefault('page_title_suffix', '%s', ['project_name']); -// $this->setDefault('email_from', '"%s" <%s@' . php_uname('n') . '>', ['project_name', 'email_user']); // FIXME email_from set because alerting config - - // deprecated variables - $this->deprecatedVariable('rrdgraph_real_95th', 'rrdgraph_real_percentile'); - $this->deprecatedVariable('fping_options.millisec', 'fping_options.interval'); - $this->deprecatedVariable('discovery_modules.cisco-vrf', 'discovery_modules.vrf'); - $this->deprecatedVariable('discovery_modules.toner', 'discovery_modules.printer-supplies'); - $this->deprecatedVariable('poller_modules.toner', 'poller_modules.printer-supplies'); - $this->deprecatedVariable('discovery_modules.cisco-sla', 'discovery_modules.slas'); - $this->deprecatedVariable('poller_modules.cisco-sla', 'poller_modules.slas'); - $this->deprecatedVariable('oxidized.group', 'oxidized.maps.group'); - - // migrate device display - if (! $this->has('device_display_default')) { - $display_value = '{{ $hostname }}'; - if ($this->get('force_hostname_to_sysname')) { - $display_value = '{{ $sysName }}'; - } elseif ($this->get('force_ip_to_sysname')) { - $display_value = '{{ $sysName_fallback }}'; - } - - $this->persist('device_display_default', $display_value); - } - - // make sure we have full path to binaries in case PATH isn't set - foreach (['fping', 'fping6', 'snmpgetnext', 'rrdtool', 'traceroute'] as $bin) { - if (! is_executable($this->get($bin))) { - $this->persist($bin, $this->locateBinary($bin)); - } - } - - if (! $this->has('rrdtool_version')) { - $this->persist('rrdtool_version', (new Version($this))->rrdtool()); - } - if (! $this->has('snmp.unescape')) { - $this->persist('snmp.unescape', version_compare((new Version($this))->netSnmp(), '5.8.0', '<')); - } - if (! self::has('reporting.usage')) { - self::persist('reporting.usage', (bool) Callback::get('enabled')); - } - - // populate legacy DB credentials, just in case something external uses them. Maybe remove this later - $this->populateLegacyDbCredentials(); - } - - private function loadRuntimeSettings(): void - { - // If we're on SSL, let's properly detect it - if ( - isset($_SERVER['HTTPS']) || - (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') - ) { - $this->set('base_url', preg_replace('/^http:/', 'https:', $this->get('base_url', ''))); - } - $this->set('base_url', Str::finish($this->get('base_url', ''), '/')); - - $this->set('applied_site_style', $this->get('site_style')); - - $this->populateTime(); - } - - /** - * Set default values for defaults that depend on other settings, if they are not already loaded - * - * @param string $key - * @param string $value value to set to key or vsprintf() format string for values below - * @param array $format_values array of keys to send to vsprintf() - */ - private function setDefault($key, $value, $format_values = []): void - { - if (! $this->has($key)) { - if (is_string($value)) { - $format_values = array_map([$this, 'get'], $format_values); - $this->set($key, vsprintf($value, $format_values)); - } else { - $this->set($key, $value); - } - } - } - - /** - * Copy data from old variables to new ones. - * - * @param string $old - * @param string $new - */ - private function deprecatedVariable($old, $new): void - { - if ($this->has($old)) { - if (Debug::isEnabled()) { - echo "Copied deprecated config $old to $new\n"; - } - $this->set($new, $this->get($old)); - } - } - - /** - * Locate the actual path of a binary - * - * @param string $binary - * @return mixed - */ - public function locateBinary($binary): mixed - { - if (! Str::contains($binary, '/')) { - $output = `whereis -b $binary`; - $list = trim(substr($output, strpos($output, ':') + 1)); - $targets = explode(' ', $list); - foreach ($targets as $target) { - if (is_executable($target)) { - return $target; - } - } - } - - return $binary; - } - - private function populateTime(): void - { - $now = time(); - $now -= $now % 300; - $this->set('time.now', $now); - $this->set('time.onehour', $now - 3600); // time() - (1 * 60 * 60); - $this->set('time.fourhour', $now - 14400); // time() - (4 * 60 * 60); - $this->set('time.sixhour', $now - 21600); // time() - (6 * 60 * 60); - $this->set('time.twelvehour', $now - 43200); // time() - (12 * 60 * 60); - $this->set('time.day', $now - 86400); // time() - (24 * 60 * 60); - $this->set('time.twoday', $now - 172800); // time() - (2 * 24 * 60 * 60); - $this->set('time.week', $now - 604800); // time() - (7 * 24 * 60 * 60); - $this->set('time.twoweek', $now - 1209600); // time() - (2 * 7 * 24 * 60 * 60); - $this->set('time.month', $now - 2678400); // time() - (31 * 24 * 60 * 60); - $this->set('time.twomonth', $now - 5356800); // time() - (2 * 31 * 24 * 60 * 60); - $this->set('time.threemonth', $now - 8035200); // time() - (3 * 31 * 24 * 60 * 60); - $this->set('time.sixmonth', $now - 16070400); // time() - (6 * 31 * 24 * 60 * 60); - $this->set('time.year', $now - 31536000); // time() - (365 * 24 * 60 * 60); - $this->set('time.twoyear', $now - 63072000); // time() - (2 * 365 * 24 * 60 * 60); - } - - public function populateLegacyDbCredentials(): void - { - if (! class_exists('config')) { - return; - } - - $db = config('database.default'); - - $this->set('db_host', config("database.connections.$db.host", 'localhost')); - $this->set('db_name', config("database.connections.$db.database", 'librenms')); - $this->set('db_user', config("database.connections.$db.username", 'librenms')); - $this->set('db_pass', config("database.connections.$db.password")); - $this->set('db_port', config("database.connections.$db.port", 3306)); - $this->set('db_socket', config("database.connections.$db.unix_socket")); - } -} diff --git a/app/Facades/LibrenmsConfig.php b/app/Facades/LibrenmsConfig.php deleted file mode 100644 index 946cbabbe6..0000000000 --- a/app/Facades/LibrenmsConfig.php +++ /dev/null @@ -1,43 +0,0 @@ -. - * - * @link https://www.librenms.org - * - * @copyright 2019 Tony Murray - * @author Tony Murray - */ - -namespace App\Facades; - -use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\Facade; - -class LibrenmsConfig extends Facade -{ - protected static function getFacadeAccessor(): string - { - return 'librenms-config'; - } - - public static function reload(): void - { - App::forgetInstance('librenms-config'); // clear singleton - self::clearResolvedInstances(); // clear facade resolved instances cache - } -} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index aaa617269a..ebf95f2201 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,7 +2,6 @@ namespace App\Providers; -use App\Facades\LibrenmsConfig; use App\Models\Sensor; use Illuminate\Contracts\Foundation\Application; use Illuminate\Database\Eloquent\Relations\Relation; @@ -10,6 +9,7 @@ use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Log; use Illuminate\Support\ServiceProvider; use LibreNMS\Cache\PermissionsCache; +use LibreNMS\Config; use LibreNMS\Util\IP; use LibreNMS\Util\Validate; use Validator; @@ -61,13 +61,13 @@ class AppServiceProvider extends ServiceProvider $this->bootObservers(); } - private function bootCustomBladeDirectives(): void + private function bootCustomBladeDirectives() { Blade::if('config', function ($key, $value = true) { - return LibrenmsConfig::get($key) == $value; + return \LibreNMS\Config::get($key) == $value; }); Blade::if('notconfig', function ($key) { - return ! LibrenmsConfig::get($key); + return ! \LibreNMS\Config::get($key); }); Blade::if('admin', function () { return auth()->check() && auth()->user()->isAdmin(); @@ -114,7 +114,7 @@ class AppServiceProvider extends ServiceProvider { $this->app->alias(\LibreNMS\Interfaces\Geocoder::class, 'geocoder'); $this->app->bind(\LibreNMS\Interfaces\Geocoder::class, function ($app) { - $engine = LibrenmsConfig::get('geoloc.engine'); + $engine = Config::get('geoloc.engine'); switch ($engine) { case 'mapquest': diff --git a/app/Providers/ConfigServiceProvider.php b/app/Providers/ConfigServiceProvider.php index 51d09ae795..b7cd216b0c 100644 --- a/app/Providers/ConfigServiceProvider.php +++ b/app/Providers/ConfigServiceProvider.php @@ -2,10 +2,8 @@ namespace App\Providers; -use App\ConfigRepository; -use App\Facades\LibrenmsConfig; -use Illuminate\Support\Facades\Log; use Illuminate\Support\ServiceProvider; +use LibreNMS\Config; class ConfigServiceProvider extends ServiceProvider { @@ -16,16 +14,16 @@ class ConfigServiceProvider extends ServiceProvider */ public function register(): void { - $this->app->singleton('librenms-config', function () { - return new ConfigRepository; - }); + // + } - // if we skipped loading the DB the first time config was called, load it when it is available - $this->callAfterResolving('db', function () { - if ($this->app->resolved('librenms-config')) { - Log::error('Loaded config twice due to bad initialization order'); - LibrenmsConfig::reload(); - } - }); + /** + * Bootstrap services. + * + * @return void + */ + public function boot(): void + { + Config::load(); } } diff --git a/app/Providers/ErrorReportingProvider.php b/app/Providers/ErrorReportingProvider.php index 0d486ee931..6ed871ed5d 100644 --- a/app/Providers/ErrorReportingProvider.php +++ b/app/Providers/ErrorReportingProvider.php @@ -25,7 +25,6 @@ namespace App\Providers; -use App\Facades\LibrenmsConfig; use App\Logging\Reporting\Middleware\AddGitInformation; use App\Logging\Reporting\Middleware\CleanContext; use App\Logging\Reporting\Middleware\SetGroups; @@ -35,6 +34,7 @@ use ErrorException; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; +use LibreNMS\Config; use LibreNMS\Util\Git; use Spatie\FlareClient\Report; use Spatie\LaravelIgnition\Facades\Flare; @@ -50,10 +50,12 @@ class ErrorReportingProvider extends \Spatie\LaravelIgnition\IgnitionServiceProv /** @var string|null */ private static $instanceId; - private ?int $throttle = null; + private $throttle = 300; public function boot(): void { + $this->throttle = Config::get('reporting.throttle', 300); + /* @phpstan-ignore-next-line */ if (! method_exists(\Spatie\FlareClient\Flare::class, 'filterReportsUsing')) { Log::debug("Flare client too old, disabling Ignition to avoid bug.\n"); @@ -62,7 +64,7 @@ class ErrorReportingProvider extends \Spatie\LaravelIgnition\IgnitionServiceProv } Flare::filterExceptionsUsing(function (\Exception $e) { - if (LibrenmsConfig::get('reporting.dump_errors')) { + if (Config::get('reporting.dump_errors')) { \Log::critical('%RException: ' . get_class($e) . ' ' . $e->getMessage() . '%n @ %G' . $e->getFile() . ':' . $e->getLine() . '%n' . PHP_EOL . $e->getTraceAsString(), ['color' => true]); } @@ -106,14 +108,14 @@ class ErrorReportingProvider extends \Spatie\LaravelIgnition\IgnitionServiceProv } // safety check so we don't leak early reports (but reporting should not be loaded before the config is) - if (! app()->bound('librenms-config')) { + if (! Config::isLoaded()) { return false; } $this->reportingEnabled = false; // don't cache before config is loaded // check the user setting - if (LibrenmsConfig::get('reporting.error') !== true) { + if (Config::get('reporting.error') !== true) { \Log::debug('Reporting disabled by user setting'); return false; @@ -155,8 +157,6 @@ class ErrorReportingProvider extends \Spatie\LaravelIgnition\IgnitionServiceProv private function isThrottled(): bool { - $this->throttle ??= LibrenmsConfig::get('reporting.throttle', 300); - if ($this->throttle) { $this->reportingEnabled = false; // disable future reporting (to avoid this cache check) diff --git a/config/app.php b/config/app.php index 84e19c319e..d709e5d4ef 100644 --- a/config/app.php +++ b/config/app.php @@ -211,7 +211,6 @@ return [ 'PluginManager' => \App\Facades\PluginManager::class, 'Rrd' => \App\Facades\Rrd::class, 'SnmpQuery' => \App\Facades\FacadeAccessorSnmp::class, - 'LibrenmsConfig' => \App\Facades\LibrenmsConfig::class, ])->forget([ 'Http', // don't use Laravel Http facade, LibreNMS has its own wrapper ])->toArray(), diff --git a/config/librenms.php b/config/librenms.php index 3cff19b25e..43c3cd3fdb 100644 --- a/config/librenms.php +++ b/config/librenms.php @@ -53,13 +53,4 @@ 'node_id' => env('NODE_ID'), - /* - |-------------------------------------------------------------------------- - | Config Cache TTL - |-------------------------------------------------------------------------- - | - | Amount of seconds to allow the config to be cached. 0 means no cache. - */ - - 'config_cache_ttl' => env('CONFIG_CACHE_TTL', 0), ]; diff --git a/config/logging.php b/config/logging.php index ff39e7a945..c9370d12da 100644 --- a/config/logging.php +++ b/config/logging.php @@ -80,7 +80,7 @@ return [ 'single' => [ 'driver' => 'single', - 'path' => env('APP_LOG', base_path('logs/librenms.log')), + 'path' => env('APP_LOG', \LibreNMS\Config::get('log_file', base_path('logs/librenms.log'))), 'formatter' => \App\Logging\NoColorFormatter::class, 'level' => env('LOG_LEVEL', 'error'), 'replace_placeholders' => true, @@ -88,7 +88,7 @@ return [ 'daily' => [ 'driver' => 'daily', - 'path' => env('APP_LOG', base_path('logs/librenms.log')), + 'path' => env('APP_LOG', \LibreNMS\Config::get('log_file', base_path('logs/librenms.log'))), 'formatter' => \App\Logging\NoColorFormatter::class, 'level' => env('LOG_LEVEL', 'error'), 'days' => 14, diff --git a/phpunit.xml b/phpunit.xml index 4259b2b7c5..7573685a79 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -40,7 +40,6 @@ - diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php index 9d35fad8b8..6dd39f4622 100644 --- a/tests/ConfigTest.php +++ b/tests/ConfigTest.php @@ -2,7 +2,7 @@ /** * ConfigTest.php * - * Tests for App\Facades\Config + * Tests for LibreNMS\Config * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,17 +25,17 @@ namespace LibreNMS\Tests; -use App\ConfigRepository; use LibreNMS\Config; class ConfigTest extends TestCase { - private \ReflectionProperty $config; + private $config; protected function setUp(): void { parent::setUp(); - $this->config = new \ReflectionProperty(ConfigRepository::class, 'config'); + $this->config = new \ReflectionProperty(Config::class, 'config'); + $this->config->setAccessible(true); } public function testGetBasic(): void @@ -46,9 +46,8 @@ class ConfigTest extends TestCase public function testSetBasic(): void { - $instance = $this->app->make('librenms-config'); Config::set('basics', 'first'); - $this->assertEquals('first', $this->config->getValue($instance)['basics']); + $this->assertEquals('first', $this->config->getValue()['basics']); } public function testGet(): void @@ -138,10 +137,9 @@ class ConfigTest extends TestCase public function testSet(): void { - $instance = $this->app->make('librenms-config'); Config::set('you.and.me', "I'll be there"); - $this->assertEquals("I'll be there", $this->config->getValue($instance)['you']['and']['me']); + $this->assertEquals("I'll be there", $this->config->getValue()['you']['and']['me']); } public function testSetPersist(): void @@ -208,10 +206,9 @@ class ConfigTest extends TestCase */ private function setConfig($function) { - $instance = $this->app->make('librenms-config'); - $config = $this->config->getValue($instance); + $config = $this->config->getValue(); $function($config); - $this->config->setValue($instance, $config); + $this->config->setValue($config); } public function testForget(): void diff --git a/tests/OSDiscoveryTest.php b/tests/OSDiscoveryTest.php index dfdf06eafd..2a926c74b3 100644 --- a/tests/OSDiscoveryTest.php +++ b/tests/OSDiscoveryTest.php @@ -42,7 +42,7 @@ class OSDiscoveryTest extends TestCase { parent::setUpBeforeClass(); - $glob = realpath(__DIR__ . '/..') . '/tests/snmpsim/*.snmprec'; + $glob = Config::get('install_dir') . '/tests/snmpsim/*.snmprec'; self::$unchecked_files = array_flip(array_filter(array_map(function ($file) { return basename($file, '.snmprec'); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index b1d47f28f3..5e4aac38c4 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -23,6 +23,7 @@ * @author Tony Murray */ +use LibreNMS\Config; use LibreNMS\Util\Snmpsim; $install_dir = realpath(__DIR__ . '/..'); @@ -83,6 +84,7 @@ if (getenv('DBTEST')) { unset($db_config); } +Config::reload(); // reload the config including database config \LibreNMS\Util\OS::updateCache(true); // Force update of OS Cache app()->terminate(); // destroy the bootstrap Laravel application