Automatic fixes for validation failures (#13930)

* Automatic fixes for validations

* webui

* lint fixes

* Fix an install issue with ConfigSeeder requesting cli input in web page.

* Do not use c_echo in validate.php print_fail()
This commit is contained in:
Tony Murray 2022-06-10 16:25:33 -05:00 committed by GitHub
parent 46899d1643
commit 7a0d604cdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1374 additions and 845 deletions

View File

@ -0,0 +1,43 @@
<?php
/*
* Validation.php
*
* -Description-
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Interfaces;
use LibreNMS\ValidationResult;
interface Validation
{
/**
* Validate this module.
*/
public function validate(): ValidationResult;
/**
* If this validation is enabled or not.
*
* @return bool
*/
public function enabled(): bool;
}

View File

@ -0,0 +1,36 @@
<?php
/*
* ValidationFixer.php
*
* -Description-
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Interfaces;
interface ValidationFixer
{
/**
* Fix the failed validation result. Take care not to break user installs.
*
* @return bool
*/
public function fix(): bool;
}

View File

@ -32,29 +32,21 @@ interface ValidationGroup
/**
* Validate this module.
* To return ValidationResults, call ok, warn, fail, or result methods on the $validator
*
* @param Validator $validator
*/
public function validate(Validator $validator);
public function validate(Validator $validator): void;
/**
* Returns if this test should be run by default or not.
*
* @return bool
*/
public function isDefault();
public function isDefault(): bool;
/**
* Returns true if this group has been run
*
* @return bool
*/
public function isCompleted();
public function isCompleted(): bool;
/**
* Mark this group as completed
*
* @return void
*/
public function markCompleted();
public function markCompleted(): void;
}

View File

@ -44,6 +44,8 @@ class ValidationResult
private $list;
/** @var string|null */
private $fix;
/** @var string|null */
private $fixer;
/**
* ValidationResult constructor.
@ -226,6 +228,7 @@ class ValidationResult
'statusText' => substr($this->getStatusText($resultStatus), 2, -2), // remove console colors
'message' => $this->getMessage(),
'fix' => Arr::wrap($resultFix),
'fixer' => $this->getFixer(),
'listDescription' => $this->getListDescription(),
'list' => is_array($resultList) ? array_values($resultList) : [],
];
@ -249,4 +252,30 @@ class ValidationResult
printf($format, " and $extra more...");
}
}
/**
* Fixer exists
*/
public function hasFixer(): bool
{
return $this->fixer !== null;
}
/**
* @return string|null the class of the fixer
*/
public function getFixer(): ?string
{
return $this->fixer;
}
/**
* Set fixer, optionally denote if this is fixable
*/
public function setFixer(string $fixer, bool $fixable = true): ValidationResult
{
$this->fixer = $fixable ? $fixer : null;
return $this;
}
}

View File

@ -25,39 +25,55 @@
namespace LibreNMS\Validations;
use LibreNMS\Interfaces\Validation;
use LibreNMS\Interfaces\ValidationGroup;
use LibreNMS\Validator;
abstract class BaseValidation implements ValidationGroup
{
/** @var bool */
protected $completed = false;
/** @var bool */
protected static $RUN_BY_DEFAULT = true;
/** @var string */
protected $directory = null;
/** @var string */
protected $name = null;
public function validate(Validator $validator): void
{
if ($this->directory) {
foreach (glob(__DIR__ . "/$this->directory/*.php") as $file) {
$base = basename($file, '.php');
$class = __NAMESPACE__ . "\\$this->directory\\$base";
$validation = new $class;
if ($validation instanceof Validation && $validation->enabled()) {
$validator->result($validation->validate(), $this->name);
}
}
}
}
/**
* Returns if this test should be run by default or not.
*
* @return bool
*/
public function isDefault()
public function isDefault(): bool
{
return static::$RUN_BY_DEFAULT;
}
/**
* Returns true if this group has been run
*
* @return bool
*/
public function isCompleted()
public function isCompleted(): bool
{
return $this->completed;
}
/**
* Mark this group as completed
*
* @return void
*/
public function markCompleted()
public function markCompleted(): void
{
$this->completed = true;
}

View File

@ -38,7 +38,7 @@ class Configuration extends BaseValidation
*
* @param Validator $validator
*/
public function validate(Validator $validator)
public function validate(Validator $validator): void
{
// Test transports
if (Config::get('alerts.email.enable') == true) {

View File

@ -25,439 +25,33 @@
namespace LibreNMS\Validations;
use Carbon\Carbon;
use Carbon\CarbonInterval;
use LibreNMS\Config;
use LibreNMS\DB\Eloquent;
use LibreNMS\DB\Schema;
use LibreNMS\ValidationResult;
use LibreNMS\Validations\Database\CheckDatabaseServerVersion;
use LibreNMS\Validations\Database\CheckDatabaseTableNamesCase;
use LibreNMS\Validations\Database\CheckMysqlEngine;
use LibreNMS\Validations\Database\CheckSqlServerTime;
use LibreNMS\Validator;
use Symfony\Component\Yaml\Yaml;
class Database extends BaseValidation
{
const MYSQL_MIN_VERSION = '5.7.7';
const MYSQL_MIN_VERSION_DATE = 'March, 2021';
const MYSQL_RECOMMENDED_VERSION = '8.0';
public const MYSQL_MIN_VERSION = '5.7.7';
public const MYSQL_MIN_VERSION_DATE = 'March, 2021';
public const MYSQL_RECOMMENDED_VERSION = '8.0';
const MARIADB_MIN_VERSION = '10.2.2';
const MARIADB_MIN_VERSION_DATE = 'March, 2021';
const MARIADB_RECOMMENDED_VERSION = '10.5';
public const MARIADB_MIN_VERSION = '10.2.2';
public const MARIADB_MIN_VERSION_DATE = 'March, 2021';
public const MARIADB_RECOMMENDED_VERSION = '10.5';
public function validate(Validator $validator)
{
if (! Eloquent::isConnected()) {
return;
}
$this->validateSystem($validator);
if ($this->checkSchemaVersion($validator)) {
$this->checkSchema($validator);
$this->checkCollation($validator);
}
}
public function validateSystem(Validator $validator)
{
$this->checkVersion($validator);
$this->checkMode($validator);
$this->checkTime($validator);
$this->checkMysqlEngine($validator);
}
private function checkSchemaVersion(Validator $validator): bool
{
$current = \LibreNMS\DB\Schema::getLegacySchema();
$latest = 1000;
if ($current === 0 || $current === $latest) {
// Using Laravel migrations
if (! Schema::isCurrent()) {
$validator->fail('Your database is out of date!', './lnms migrate');
return false;
}
$migrations = Schema::getUnexpectedMigrations();
if ($migrations->isNotEmpty()) {
$validator->warn('Your database schema has extra migrations (' . $migrations->implode(', ') .
'). If you just switched to the stable release from the daily release, your database is in between releases and this will be resolved with the next release.');
}
} elseif ($current < $latest) {
$validator->fail(
"Your database schema ($current) is older than the latest ($latest).",
'Manually run ./daily.sh, and check for any errors.'
);
return false;
} elseif ($current > $latest) {
$validator->warn("Your database schema ($current) is newer than expected ($latest). If you just switched to the stable release from the daily release, your database is in between releases and this will be resolved with the next release.");
}
return true;
}
private function checkVersion(Validator $validator)
{
$version = \LibreNMS\Util\Version::get()->databaseServer();
$version = explode('-', $version);
if (isset($version[1]) && $version[1] == 'MariaDB') {
if (version_compare($version[0], self::MARIADB_MIN_VERSION, '<=')) {
$validator->fail(
'MariaDB version ' . self::MARIADB_MIN_VERSION . ' is the minimum supported version as of ' .
self::MARIADB_MIN_VERSION_DATE . '.',
'Update MariaDB to a supported version, ' . self::MARIADB_RECOMMENDED_VERSION . ' suggested.'
);
}
} else {
if (version_compare($version[0], self::MYSQL_MIN_VERSION, '<=')) {
$validator->fail(
'MySQL version ' . self::MYSQL_MIN_VERSION . ' is the minimum supported version as of ' .
self::MYSQL_MIN_VERSION_DATE . '.',
'Update MySQL to a supported version, ' . self::MYSQL_RECOMMENDED_VERSION . ' suggested.'
);
}
}
}
private function checkTime(Validator $validator)
{
$raw_time = Eloquent::DB()->selectOne('SELECT NOW() as time')->time;
$db_time = new Carbon($raw_time);
$php_time = Carbon::now();
$diff = $db_time->diffAsCarbonInterval($php_time);
if ($diff->compare(CarbonInterval::minute(1)) > 0) {
$message = "Time between this server and the mysql database is off\n";
$message .= ' Mysql time ' . $db_time->toDateTimeString() . PHP_EOL;
$message .= ' PHP time ' . $php_time->toDateTimeString() . PHP_EOL;
$validator->fail($message);
}
}
private function checkMode(Validator $validator)
{
// Test for lower case table name support
$lc_mode = Eloquent::DB()->selectOne('SELECT @@global.lower_case_table_names as mode')->mode;
if ($lc_mode != 0) {
$validator->fail(
'You have lower_case_table_names set to 1 or true in mysql config.',
'Set lower_case_table_names=0 in your mysql config file in the [mysqld] section.'
);
}
}
private function checkMysqlEngine(Validator $validator)
{
$db = \config('database.connections.' . \config('database.default') . '.database');
$query = "SELECT `TABLE_NAME` FROM information_schema.tables WHERE `TABLE_SCHEMA` = '$db' && `ENGINE` != 'InnoDB'";
$tables = Eloquent::DB()->select($query);
if (! empty($tables)) {
$validator->result(
ValidationResult::warn('Some tables are not using the recommended InnoDB engine, this may cause you issues.')
->setList('Tables', array_column($tables, 'TABLE_NAME'))
);
}
}
private function checkCollation(Validator $validator)
{
$db_name = Eloquent::DB()->selectOne('SELECT DATABASE() as name')->name;
// Test for correct character set and collation
$db_collation_sql = "SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
FROM information_schema.SCHEMATA S
WHERE schema_name = '$db_name' AND
( DEFAULT_CHARACTER_SET_NAME != 'utf8mb4' OR DEFAULT_COLLATION_NAME != 'utf8mb4_unicode_ci')";
$collation = Eloquent::DB()->selectOne($db_collation_sql);
if (empty($collation) !== true) {
$validator->fail(
"MySQL Database collation is wrong: $collation->DEFAULT_CHARACTER_SET_NAME $collation->DEFAULT_COLLATION_NAME",
'Check https://community.librenms.org/t/new-default-database-charset-collation/14956 for info on how to fix.'
);
}
$table_collation_sql = "SELECT T.TABLE_NAME, C.CHARACTER_SET_NAME, C.COLLATION_NAME
FROM information_schema.TABLES AS T, information_schema.COLLATION_CHARACTER_SET_APPLICABILITY AS C
WHERE C.collation_name = T.table_collation AND T.table_schema = '$db_name' AND
( C.CHARACTER_SET_NAME != 'utf8mb4' OR C.COLLATION_NAME != 'utf8mb4_unicode_ci' );";
$collation_tables = Eloquent::DB()->select($table_collation_sql);
if (empty($collation_tables) !== true) {
$result = ValidationResult::fail('MySQL tables collation is wrong: ')
->setFix('Check https://community.librenms.org/t/new-default-database-charset-collation/14956 for info on how to fix.')
->setList('Tables', array_map(function ($row) {
return "$row->TABLE_NAME $row->CHARACTER_SET_NAME $row->COLLATION_NAME";
}, $collation_tables));
$validator->result($result);
}
$column_collation_sql = "SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME
FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '$db_name' AND
( CHARACTER_SET_NAME != 'utf8mb4' OR COLLATION_NAME != 'utf8mb4_unicode_ci' );";
$collation_columns = Eloquent::DB()->select($column_collation_sql);
if (empty($collation_columns) !== true) {
$result = ValidationResult::fail('MySQL column collation is wrong: ')
->setFix('Check https://community.librenms.org/t/new-default-database-charset-collation/14956 for info on how to fix.')
->setList('Columns', array_map(function ($row) {
return "$row->TABLE_NAME: $row->COLUMN_NAME $row->CHARACTER_SET_NAME $row->COLLATION_NAME";
}, $collation_columns));
$validator->result($result);
}
}
private function checkSchema(Validator $validator)
{
$schema_file = Config::get('install_dir') . '/misc/db_schema.yaml';
if (! is_file($schema_file)) {
$validator->warn("We haven't detected the db_schema.yaml file");
return;
}
$master_schema = Yaml::parse(file_get_contents($schema_file));
$current_schema = Schema::dump();
$schema_update = [];
foreach ((array) $master_schema as $table => $data) {
if (empty($current_schema[$table])) {
$validator->fail("Database: missing table ($table)");
$schema_update[] = $this->addTableSql($table, $data);
} else {
$current_columns = array_reduce($current_schema[$table]['Columns'], function ($array, $item) {
$array[$item['Field']] = $item;
return $array;
}, []);
foreach ($data['Columns'] as $index => $cdata) {
$column = $cdata['Field'];
// MySQL 8 fix, remove DEFAULT_GENERATED from timestamp extra columns
if ($cdata['Type'] == 'timestamp') {
$current_columns[$column]['Extra'] = preg_replace('/DEFAULT_GENERATED[ ]*/', '', $current_columns[$column]['Extra']);
}
if (empty($current_columns[$column])) {
$validator->fail("Database: missing column ($table/$column)");
$primary = false;
if ($data['Indexes']['PRIMARY']['Columns'] == [$column]) {
// include the primary index with the add statement
unset($data['Indexes']['PRIMARY']);
$primary = true;
}
$schema_update[] = $this->addColumnSql($table, $cdata, isset($data['Columns'][$index - 1]) ? $data['Columns'][$index - 1]['Field'] : null, $primary);
} elseif ($cdata !== $current_columns[$column]) {
$validator->fail("Database: incorrect column ($table/$column)");
$schema_update[] = $this->updateTableSql($table, $column, $cdata);
}
unset($current_columns[$column]); // remove checked columns
}
foreach ($current_columns as $column => $_unused) {
$validator->fail("Database: extra column ($table/$column)");
$schema_update[] = $this->dropColumnSql($table, $column);
}
$index_changes = [];
if (isset($data['Indexes'])) {
foreach ($data['Indexes'] as $name => $index) {
if (empty($current_schema[$table]['Indexes'][$name])) {
$validator->fail("Database: missing index ($table/$name)");
$index_changes[] = $this->addIndexSql($table, $index);
} elseif ($index != $current_schema[$table]['Indexes'][$name]) {
$validator->fail("Database: incorrect index ($table/$name)");
$index_changes[] = $this->updateIndexSql($table, $name, $index);
}
unset($current_schema[$table]['Indexes'][$name]);
}
}
if (isset($current_schema[$table]['Indexes'])) {
foreach ($current_schema[$table]['Indexes'] as $name => $_unused) {
$validator->fail("Database: extra index ($table/$name)");
$schema_update[] = $this->dropIndexSql($table, $name);
}
}
$schema_update = array_merge($schema_update, $index_changes); // drop before create/update
$constraint_changes = [];
if (isset($data['Constraints'])) {
foreach ($data['Constraints'] as $name => $constraint) {
if (empty($current_schema[$table]['Constraints'][$name])) {
$validator->fail("Database: missing constraint ($table/$name)");
$constraint_changes[] = $this->addConstraintSql($table, $constraint);
} elseif ($constraint != $current_schema[$table]['Constraints'][$name]) {
$validator->fail("Database: incorrect constraint ($table/$name)");
$constraint_changes[] = $this->dropConstraintSql($table, $name);
$constraint_changes[] = $this->addConstraintSql($table, $constraint);
}
unset($current_schema[$table]['Constraints'][$name]);
}
}
if (isset($current_schema[$table]['Constraints'])) {
foreach ($current_schema[$table]['Constraints'] as $name => $_unused) {
$validator->fail("Database: extra constraint ($table/$name)");
$schema_update[] = $this->dropConstraintSql($table, $name);
}
}
$schema_update = array_merge($schema_update, $constraint_changes); // drop before create/update
}
unset($current_schema[$table]); // remove checked tables
}
foreach ($current_schema as $table => $data) {
$validator->fail("Database: extra table ($table)");
$schema_update[] = $this->dropTableSql($table);
}
// set utc timezone if timestamp issues
if (preg_grep('/\d{4}-\d\d-\d\d \d\d:\d\d:\d\d/', $schema_update)) {
array_unshift($schema_update, "SET TIME_ZONE='+00:00';");
}
if (empty($schema_update)) {
$validator->ok('Database schema correct');
} else {
$result = ValidationResult::fail('We have detected that your database schema may be wrong')
->setFix('Run the following SQL statements to fix it')
->setList('SQL Statements', $schema_update);
$validator->result($result);
}
}
private function addTableSql($table, $table_schema)
{
$columns = array_map([$this, 'columnToSql'], $table_schema['Columns']);
$indexes = array_map([$this, 'indexToSql'], isset($table_schema['Indexes']) ? $table_schema['Indexes'] : []);
$def = implode(', ', array_merge(array_values((array) $columns), array_values((array) $indexes)));
return "CREATE TABLE `$table` ($def);";
}
private function addColumnSql($table, $schema, $previous_column, $primary = false)
{
$sql = "ALTER TABLE `$table` ADD " . $this->columnToSql($schema);
if ($primary) {
$sql .= ' PRIMARY KEY';
}
if (empty($previous_column)) {
$sql .= ' FIRST';
} else {
$sql .= " AFTER `$previous_column`";
}
return $sql . ';';
}
private function updateTableSql($table, $column, $column_schema)
{
return "ALTER TABLE `$table` CHANGE `$column` " . $this->columnToSql($column_schema) . ';';
}
private function dropColumnSql($table, $column)
{
return "ALTER TABLE `$table` DROP `$column`;";
}
private function addIndexSql($table, $index_schema)
{
return "ALTER TABLE `$table` ADD " . $this->indexToSql($index_schema) . ';';
}
private function updateIndexSql($table, $name, $index_schema)
{
return "ALTER TABLE `$table` DROP INDEX `$name`, " . $this->indexToSql($index_schema) . ';';
}
private function dropIndexSql($table, $name)
{
return "ALTER TABLE `$table` DROP INDEX `$name`;";
}
private function dropTableSql($table)
{
return "DROP TABLE `$table`;";
}
protected $directory = 'Database';
protected $name = 'database';
/**
* Generate an SQL segment to create the column based on data from Schema::dump()
*
* @param array $column_data The array of data for the column
* @return string sql fragment, for example: "`ix_id` int(10) unsigned NOT NULL"
* Tests used by the installer to validate that SQL server doesn't have any known issues (before migrations)
*/
private function columnToSql($column_data)
public function validateSystem(Validator $validator): void
{
$segments = ["`${column_data['Field']}`", $column_data['Type']];
$segments[] = $column_data['Null'] ? 'NULL' : 'NOT NULL';
if (isset($column_data['Default'])) {
if ($column_data['Default'] === 'CURRENT_TIMESTAMP') {
$segments[] = 'DEFAULT CURRENT_TIMESTAMP';
} elseif ($column_data['Default'] == 'NULL') {
$segments[] = 'DEFAULT NULL';
} else {
$segments[] = "DEFAULT '${column_data['Default']}'";
}
}
if ($column_data['Extra'] == 'on update current_timestamp()') {
$segments[] = 'on update CURRENT_TIMESTAMP';
} else {
$segments[] = $column_data['Extra'];
}
return implode(' ', $segments);
}
/**
* Generate an SQL segment to create the index based on data from Schema::dump()
*
* @param array $index_data The array of data for the index
* @return string sql fragment, for example: "PRIMARY KEY (`device_id`)"
*/
private function indexToSql($index_data)
{
if ($index_data['Name'] == 'PRIMARY') {
$index = 'PRIMARY KEY (%s)';
} elseif ($index_data['Unique']) {
$index = "UNIQUE `{$index_data['Name']}` (%s)";
} else {
$index = "INDEX `{$index_data['Name']}` (%s)";
}
$columns = implode(',', array_map(function ($col) {
return "`$col`";
}, $index_data['Columns']));
return sprintf($index, $columns);
}
private function addConstraintSql($table, $constraint)
{
$sql = "ALTER TABLE `$table` ADD CONSTRAINT `{$constraint['name']}` FOREIGN KEY (`{$constraint['foreign_key']}`) ";
$sql .= " REFERENCES `{$constraint['table']}` (`{$constraint['key']}`)";
if (! empty($constraint['extra'])) {
$sql .= ' ' . $constraint['extra'];
}
$sql .= ';';
return $sql;
}
private function dropConstraintSql($table, $name)
{
return "ALTER TABLE `$table` DROP FOREIGN KEY `$name`;";
$validator->result((new CheckDatabaseServerVersion)->validate(), $this->name);
$validator->result((new CheckMysqlEngine)->validate(), $this->name);
$validator->result((new CheckSqlServerTime)->validate(), $this->name);
$validator->result((new CheckDatabaseTableNamesCase)->validate(), $this->name);
}
}

View File

@ -0,0 +1,95 @@
<?php
/*
* CheckSchemaVersion.php
*
* -Description-
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Validations\Database;
use LibreNMS\DB\Eloquent;
use LibreNMS\DB\Schema;
use LibreNMS\Interfaces\Validation;
use LibreNMS\Interfaces\ValidationFixer;
use LibreNMS\ValidationResult;
class CheckDatabaseSchemaVersion implements Validation, ValidationFixer
{
/** @var bool|null */
private static $current = null;
/**
* @inheritDoc
*/
public function validate(): ValidationResult
{
self::$current = false;
$current = \LibreNMS\DB\Schema::getLegacySchema();
$latest = 1000;
if ($current === 0 || $current === $latest) {
// Using Laravel migrations
if (! Schema::isCurrent()) {
return ValidationResult::fail(trans('validation.validations.database.CheckSchemaVersion.fail_outdated'), './lnms migrate')
->setFixer(__CLASS__);
}
$migrations = Schema::getUnexpectedMigrations();
if ($migrations->isNotEmpty()) {
return ValidationResult::warn(trans('validation.validations.database.CheckSchemaVersion.warn_extra_migrations', ['migrations' => $migrations->implode(', ')]));
}
} elseif ($current < $latest) {
return ValidationResult::fail(
trans('validation.validations.database.CheckSchemaVersion.fail_legacy_outdated', ['current' => $current, 'latest' => $latest]),
trans('validation.validations.database.CheckSchemaVersion.fix_legacy_outdated'),
);
} else {
// latest > current
return ValidationResult::warn(trans('validation.validations.database.CheckSchemaVersion.warn_legacy_newer'));
}
self::$current = true;
return ValidationResult::ok(trans('validation.validations.database.CheckSchemaVersion.ok'));
}
public static function isCurrent(): bool
{
if (self::$current === null) {
(new static)->validate();
}
return self::$current;
}
/**
* @inheritDoc
*/
public function enabled(): bool
{
return Eloquent::isConnected();
}
public function fix(): bool
{
return \Artisan::call('migrate', ['--force' => true]) === 0;
}
}

View File

@ -0,0 +1,70 @@
<?php
/*
* CheckDatabaseServerVersion.php
*
* -Description-
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Validations\Database;
use LibreNMS\DB\Eloquent;
use LibreNMS\Interfaces\Validation;
use LibreNMS\Util\Version;
use LibreNMS\ValidationResult;
use LibreNMS\Validations\Database;
class CheckDatabaseServerVersion implements Validation
{
/**
* @inheritDoc
*/
public function validate(): ValidationResult
{
$version = Version::get()->databaseServer();
$version = explode('-', $version);
if (isset($version[1]) && $version[1] == 'MariaDB') {
if (version_compare($version[0], Database::MARIADB_MIN_VERSION, '<=')) {
return ValidationResult::fail(
trans('validation.validations.database.CheckDatabaseServerVersion.fail', ['server' => 'MariaDB', 'min' => Database::MARIADB_MIN_VERSION, 'date' => Database::MARIADB_MIN_VERSION_DATE]),
trans('validation.validations.database.CheckDatabaseServerVersion.fix', ['server' => 'MariaDB', 'suggested' => Database::MARIADB_RECOMMENDED_VERSION]),
);
}
} else {
if (version_compare($version[0], Database::MYSQL_MIN_VERSION, '<=')) {
return ValidationResult::fail(
trans('validation.validations.database.CheckDatabaseServerVersion.fail', ['server' => 'MySQL', 'min' => Database::MYSQL_MIN_VERSION, 'date' => Database::MYSQL_MIN_VERSION_DATE]),
trans('validation.validations.database.CheckDatabaseServerVersion.fix', ['server' => 'MySQL', 'suggested' => Database::MYSQL_RECOMMENDED_VERSION]),
);
}
}
return ValidationResult::ok(trans('validation.validations.database.CheckDatabaseServerVersion.ok'));
}
/**
* @inheritDoc
*/
public function enabled(): bool
{
return Eloquent::isConnected();
}
}

View File

@ -0,0 +1,59 @@
<?php
/*
* CheckDatabaseTableNamesCase.php
*
* -Description-
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Validations\Database;
use DB;
use LibreNMS\DB\Eloquent;
use LibreNMS\Interfaces\Validation;
use LibreNMS\ValidationResult;
class CheckDatabaseTableNamesCase implements Validation
{
/**
* @inheritDoc
*/
public function validate(): ValidationResult
{
// Test for lower case table name support
$lc_mode = DB::selectOne('SELECT @@global.lower_case_table_names as mode')->mode;
if ($lc_mode != 0) {
ValidationResult::fail(
trans('validation.validations.database.CheckDatabaseTableNamesCase.fail'),
trans('validation.validations.database.CheckDatabaseTableNamesCase.fix')
);
}
return ValidationResult::ok(trans('validation.validations.database.CheckDatabaseTableNamesCase.ok'));
}
/**
* @inheritDoc
*/
public function enabled(): bool
{
return Eloquent::isConnected();
}
}

View File

@ -0,0 +1,95 @@
<?php
/*
* CheckMysqlEngine.php
*
* -Description-
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Validations\Database;
use DB;
use Illuminate\Database\QueryException;
use Illuminate\Support\Collection;
use LibreNMS\DB\Eloquent;
use LibreNMS\Interfaces\Validation;
use LibreNMS\Interfaces\ValidationFixer;
use LibreNMS\ValidationResult;
class CheckMysqlEngine implements Validation, ValidationFixer
{
/**
* @inheritDoc
*/
public function validate(): ValidationResult
{
$tables = $this->findNonInnodbTables();
if ($tables->isNotEmpty()) {
return ValidationResult::warn(trans('validation.validations.database.CheckMysqlEngine.fail'))
->setFixer(__CLASS__)
->setList(trans('validation.validations.database.CheckMysqlEngine.tables'), $tables->all());
}
return ValidationResult::ok(trans('validation.validations.database.CheckMysqlEngine.ok'));
}
/**
* @inheritDoc
*/
public function enabled(): bool
{
return Eloquent::isConnected();
}
/**
* @inheritDoc
*/
public function fix(): bool
{
try {
$db = $this->databaseName();
$tables = $this->findNonInnodbTables();
foreach ($tables as $table) {
DB::statement("ALTER TABLE $db.$table ENGINE=InnoDB;");
}
} catch (QueryException $e) {
return false;
}
return true;
}
private function databaseName(): string
{
return \config('database.connections.' . \config('database.default') . '.database');
}
private function findNonInnodbTables(): Collection
{
$db = $this->databaseName();
return DB::table('information_schema.tables')
->where('TABLE_SCHEMA', $db)
->where('ENGINE', '!=', 'InnoDB')
->pluck('TABLE_NAME');
}
}

View File

@ -0,0 +1,100 @@
<?php
/*
* CheckSchemaCollation.php
*
* -Description-
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Validations\Database;
use LibreNMS\DB\Eloquent;
use LibreNMS\Interfaces\Validation;
use LibreNMS\Interfaces\ValidationFixer;
use LibreNMS\ValidationResult;
class CheckSchemaCollation implements Validation, ValidationFixer
{
/**
* @inheritDoc
*/
public function validate(): ValidationResult
{
$db_name = Eloquent::DB()->selectOne('SELECT DATABASE() as name')->name;
// Test for correct character set and collation
$db_collation_sql = "SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
FROM information_schema.SCHEMATA S
WHERE schema_name = '$db_name' AND
( DEFAULT_CHARACTER_SET_NAME != 'utf8mb4' OR DEFAULT_COLLATION_NAME != 'utf8mb4_unicode_ci')";
$collation = Eloquent::DB()->selectOne($db_collation_sql);
if (empty($collation) !== true) {
return ValidationResult::fail(
"MySQL Database collation is wrong: $collation->DEFAULT_CHARACTER_SET_NAME $collation->DEFAULT_COLLATION_NAME",
'Check https://community.librenms.org/t/new-default-database-charset-collation/14956 for info on how to fix.'
)->setFixer(__CLASS__);
}
$table_collation_sql = "SELECT T.TABLE_NAME, C.CHARACTER_SET_NAME, C.COLLATION_NAME
FROM information_schema.TABLES AS T, information_schema.COLLATION_CHARACTER_SET_APPLICABILITY AS C
WHERE C.collation_name = T.table_collation AND T.table_schema = '$db_name' AND
( C.CHARACTER_SET_NAME != 'utf8mb4' OR C.COLLATION_NAME != 'utf8mb4_unicode_ci' );";
$collation_tables = Eloquent::DB()->select($table_collation_sql);
if (empty($collation_tables) !== true) {
return ValidationResult::fail('MySQL tables collation is wrong: ')
->setFix('Check https://community.librenms.org/t/new-default-database-charset-collation/14956 for info on how to fix.')
->setFixer(__CLASS__)
->setList('Tables', array_map(function ($row) {
return "$row->TABLE_NAME $row->CHARACTER_SET_NAME $row->COLLATION_NAME";
}, $collation_tables));
}
$column_collation_sql = "SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME
FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '$db_name' AND
( CHARACTER_SET_NAME != 'utf8mb4' OR COLLATION_NAME != 'utf8mb4_unicode_ci' );";
$collation_columns = Eloquent::DB()->select($column_collation_sql);
if (empty($collation_columns) !== true) {
return ValidationResult::fail('MySQL column collation is wrong: ')
->setFix('Check https://community.librenms.org/t/new-default-database-charset-collation/14956 for info on how to fix.')
->setFixer(__CLASS__)
->setList('Columns', array_map(function ($row) {
return "$row->TABLE_NAME: $row->COLUMN_NAME $row->CHARACTER_SET_NAME $row->COLLATION_NAME";
}, $collation_columns));
}
return ValidationResult::ok('');
}
/**
* @inheritDoc
*/
public function enabled(): bool
{
return Eloquent::isConnected() && CheckDatabaseSchemaVersion::isCurrent();
}
public function fix(): bool
{
\DB::table('migrations')->where('migration', '2021_02_09_122930_migrate_to_utf8mb4')->delete();
$res = \Artisan::call('migrate', ['--force' => true]);
return $res === 0;
}
}

View File

@ -0,0 +1,329 @@
<?php
/*
* CheckSchemaStructure.php
*
* -Description-
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Validations\Database;
use DB;
use Illuminate\Database\QueryException;
use LibreNMS\Config;
use LibreNMS\DB\Eloquent;
use LibreNMS\DB\Schema;
use LibreNMS\Interfaces\Validation;
use LibreNMS\Interfaces\ValidationFixer;
use LibreNMS\ValidationResult;
use Symfony\Component\Yaml\Yaml;
class CheckSchemaStructure implements Validation, ValidationFixer
{
/** @var array */
private $descriptions = [];
/** @var array */
private $schema_update = [];
/** @var string */
private $schema_file;
public function __construct()
{
$this->schema_file = Config::get('install_dir') . '/misc/db_schema.yaml';
}
/**
* @inheritDoc
*/
public function validate(): ValidationResult
{
if (! is_file($this->schema_file)) {
return ValidationResult::warn("We haven't detected the db_schema.yaml file");
}
$this->checkSchema();
if (empty($this->schema_update)) {
return ValidationResult::ok('Database schema correct');
}
return ValidationResult::fail("We have detected that your database schema may be wrong\n" . implode("\n", $this->descriptions))
->setFix('Run the following SQL statements to fix it')
->setFixer(__CLASS__)
->setList('SQL Statements', $this->schema_update);
}
public function fix(): bool
{
try {
$this->checkSchema();
foreach ($this->schema_update as $query) {
DB::statement($query);
}
} catch (QueryException $e) {
return false;
}
return true;
}
/**
* @inheritDoc
*/
public function enabled(): bool
{
return Eloquent::isConnected() && CheckDatabaseSchemaVersion::isCurrent();
}
private function checkSchema(): void
{
$master_schema = Yaml::parse(file_get_contents($this->schema_file));
$current_schema = Schema::dump();
foreach ((array) $master_schema as $table => $data) {
if (empty($current_schema[$table])) {
$this->descriptions[] = "Database: missing table ($table)";
$this->schema_update[] = $this->addTableSql($table, $data);
} else {
$current_columns = array_reduce($current_schema[$table]['Columns'], function ($array, $item) {
$array[$item['Field']] = $item;
return $array;
}, []);
foreach ($data['Columns'] as $index => $cdata) {
$column = $cdata['Field'];
// MySQL 8 fix, remove DEFAULT_GENERATED from timestamp extra columns
if ($cdata['Type'] == 'timestamp') {
$current_columns[$column]['Extra'] = preg_replace('/DEFAULT_GENERATED */', '', $current_columns[$column]['Extra']);
}
if (empty($current_columns[$column])) {
$this->descriptions[] = "Database: missing column ($table/$column)";
$primary = false;
if ($data['Indexes']['PRIMARY']['Columns'] == [$column]) {
// include the primary index with the add statement
unset($data['Indexes']['PRIMARY']);
$primary = true;
}
$this->schema_update[] = $this->addColumnSql($table, $cdata, isset($data['Columns'][$index - 1]) ? $data['Columns'][$index - 1]['Field'] : null, $primary);
} elseif ($cdata !== $current_columns[$column]) {
$this->descriptions[] = "Database: incorrect column ($table/$column)";
$this->schema_update[] = $this->updateTableSql($table, $column, $cdata);
}
unset($current_columns[$column]); // remove checked columns
}
foreach ($current_columns as $column => $_unused) {
$this->descriptions[] = "Database: extra column ($table/$column)";
$this->schema_update[] = $this->dropColumnSql($table, $column);
}
$index_changes = [];
if (isset($data['Indexes'])) {
foreach ($data['Indexes'] as $name => $index) {
if (empty($current_schema[$table]['Indexes'][$name])) {
$this->descriptions[] = "Database: missing index ($table/$name)";
$index_changes[] = $this->addIndexSql($table, $index);
} elseif ($index != $current_schema[$table]['Indexes'][$name]) {
$this->descriptions[] = "Database: incorrect index ($table/$name)";
$index_changes[] = $this->updateIndexSql($table, $name, $index);
}
unset($current_schema[$table]['Indexes'][$name]);
}
}
if (isset($current_schema[$table]['Indexes'])) {
foreach ($current_schema[$table]['Indexes'] as $name => $_unused) {
$this->descriptions[] = "Database: extra index ($table/$name)";
$this->schema_update[] = $this->dropIndexSql($table, $name);
}
}
$this->schema_update = array_merge($this->schema_update, $index_changes); // drop before create/update
$constraint_changes = [];
if (isset($data['Constraints'])) {
foreach ($data['Constraints'] as $name => $constraint) {
if (empty($current_schema[$table]['Constraints'][$name])) {
$this->descriptions[] = "Database: missing constraint ($table/$name)";
$constraint_changes[] = $this->addConstraintSql($table, $constraint);
} elseif ($constraint != $current_schema[$table]['Constraints'][$name]) {
$this->descriptions[] = "Database: incorrect constraint ($table/$name)";
$constraint_changes[] = $this->dropConstraintSql($table, $name);
$constraint_changes[] = $this->addConstraintSql($table, $constraint);
}
unset($current_schema[$table]['Constraints'][$name]);
}
}
if (isset($current_schema[$table]['Constraints'])) {
foreach ($current_schema[$table]['Constraints'] as $name => $_unused) {
$this->descriptions[] = "Database: extra constraint ($table/$name)";
$this->schema_update[] = $this->dropConstraintSql($table, $name);
}
}
$this->schema_update = array_merge($this->schema_update, $constraint_changes); // drop before create/update
}
unset($current_schema[$table]); // remove checked tables
}
foreach ($current_schema as $table => $data) {
$this->descriptions[] = "Database: extra table ($table)";
$this->schema_update[] = $this->dropTableSql($table);
}
// set utc timezone if timestamp issues
if (preg_grep('/\d{4}-\d\d-\d\d \d\d:\d\d:\d\d/', $this->schema_update)) {
array_unshift($this->schema_update, "SET TIME_ZONE='+00:00';");
}
}
private function addTableSql(string $table, array $table_schema): string
{
$columns = array_map([$this, 'columnToSql'], $table_schema['Columns']);
$indexes = array_map([$this, 'indexToSql'], $table_schema['Indexes'] ?? []);
$def = implode(', ', array_merge(array_values($columns), array_values($indexes)));
return "CREATE TABLE `$table` ($def);";
}
private function addColumnSql(string $table, array $schema, ?string $previous_column, bool $primary = false): string
{
$sql = "ALTER TABLE `$table` ADD " . $this->columnToSql($schema);
if ($primary) {
$sql .= ' PRIMARY KEY';
}
if (empty($previous_column)) {
$sql .= ' FIRST';
} else {
$sql .= " AFTER `$previous_column`";
}
return $sql . ';';
}
private function updateTableSql(string $table, string $column, array $column_schema): string
{
return "ALTER TABLE `$table` CHANGE `$column` " . $this->columnToSql($column_schema) . ';';
}
private function dropColumnSql(string $table, string $column): string
{
return "ALTER TABLE `$table` DROP `$column`;";
}
private function addIndexSql(string $table, array $index_schema): string
{
return "ALTER TABLE `$table` ADD " . $this->indexToSql($index_schema) . ';';
}
private function updateIndexSql(string $table, string $name, array $index_schema): string
{
return "ALTER TABLE `$table` DROP INDEX `$name`, " . $this->indexToSql($index_schema) . ';';
}
private function dropIndexSql(string $table, string $name): string
{
return "ALTER TABLE `$table` DROP INDEX `$name`;";
}
private function dropTableSql(string $table): string
{
return "DROP TABLE `$table`;";
}
/**
* Generate an SQL segment to create the column based on data from Schema::dump()
*
* @param array $column_data The array of data for the column
* @return string sql fragment, for example: "`ix_id` int(10) unsigned NOT NULL"
*/
private function columnToSql(array $column_data): string
{
$segments = ["`${column_data['Field']}`", $column_data['Type']];
$segments[] = $column_data['Null'] ? 'NULL' : 'NOT NULL';
if (isset($column_data['Default'])) {
if ($column_data['Default'] === 'CURRENT_TIMESTAMP') {
$segments[] = 'DEFAULT CURRENT_TIMESTAMP';
} elseif ($column_data['Default'] == 'NULL') {
$segments[] = 'DEFAULT NULL';
} else {
$segments[] = "DEFAULT '${column_data['Default']}'";
}
}
if ($column_data['Extra'] == 'on update current_timestamp()') {
$segments[] = 'on update CURRENT_TIMESTAMP';
} else {
$segments[] = $column_data['Extra'];
}
return implode(' ', $segments);
}
/**
* Generate an SQL segment to create the index based on data from Schema::dump()
*
* @param array $index_data The array of data for the index
* @return string sql fragment, for example: "PRIMARY KEY (`device_id`)"
*/
private function indexToSql(array $index_data): string
{
if ($index_data['Name'] == 'PRIMARY') {
$index = 'PRIMARY KEY (%s)';
} elseif ($index_data['Unique']) {
$index = "UNIQUE `{$index_data['Name']}` (%s)";
} else {
$index = "INDEX `{$index_data['Name']}` (%s)";
}
$columns = implode(',', array_map(function ($col) {
return "`$col`";
}, $index_data['Columns']));
return sprintf($index, $columns);
}
private function addConstraintSql(string $table, array $constraint): string
{
$sql = "ALTER TABLE `$table` ADD CONSTRAINT `{$constraint['name']}` FOREIGN KEY (`{$constraint['foreign_key']}`) ";
$sql .= " REFERENCES `{$constraint['table']}` (`{$constraint['key']}`)";
if (! empty($constraint['extra'])) {
$sql .= ' ' . $constraint['extra'];
}
$sql .= ';';
return $sql;
}
private function dropConstraintSql(string $table, string $name): string
{
return "ALTER TABLE `$table` DROP FOREIGN KEY `$name`;";
}
}

View File

@ -0,0 +1,68 @@
<?php
/*
* CheckSqlServerTime.php
*
* -Description-
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Validations\Database;
use Carbon\Carbon;
use Carbon\CarbonInterval;
use LibreNMS\DB\Eloquent;
use LibreNMS\Interfaces\Validation;
use LibreNMS\ValidationResult;
class CheckSqlServerTime implements Validation
{
/**
* @inheritDoc
*/
public function validate(): ValidationResult
{
$raw_time = Eloquent::DB()->selectOne('SELECT NOW() as time')->time;
$db_time = new Carbon($raw_time);
$php_time = Carbon::now();
$diff = $db_time->diffAsCarbonInterval($php_time);
if ($diff->compare(CarbonInterval::minute(1)) > 0) {
$message = "Time between this server and the mysql database is off\n Mysql time :mysql_time\n PHP time :php_time";
$message .= ' Mysql time ' . $db_time->toDateTimeString() . PHP_EOL;
$message .= ' PHP time ' . $php_time->toDateTimeString() . PHP_EOL;
return ValidationResult::fail(trans('validation.validations.database.CheckSqlServerTime.fail', [
'mysql_time' => $db_time->toDateTimeString(),
'php_time' => $php_time->toDateTimeString(),
]));
}
return ValidationResult::ok(trans('validation.validations.database.CheckSqlServerTime.ok'));
}
/**
* @inheritDoc
*/
public function enabled(): bool
{
return Eloquent::isConnected();
}
}

View File

@ -38,7 +38,7 @@ class Dependencies extends BaseValidation
*
* @param Validator $validator
*/
public function validate(Validator $validator)
public function validate(Validator $validator): void
{
if (EnvHelper::librenmsDocker()) {
$validator->ok('Installed from the official Docker image; no Composer required');

View File

@ -36,7 +36,7 @@ class Disk extends BaseValidation
*
* @param Validator $validator
*/
public function validate(Validator $validator)
public function validate(Validator $validator): void
{
// Disk space and permission checks
$temp_dir = Config::get('temp_dir');

View File

@ -35,11 +35,12 @@ namespace LibreNMS\Validations;
use App\Models\PollerCluster;
use Carbon\Carbon;
use LibreNMS\Config;
use LibreNMS\Validations\Rrd\CheckRrdcachedConnectivity;
use LibreNMS\Validator;
class DistributedPoller extends BaseValidation
{
public function isDefault()
public function isDefault(): bool
{
// run by default if distributed polling is enabled
return Config::get('distributed_poller');
@ -51,7 +52,7 @@ class DistributedPoller extends BaseValidation
*
* @param Validator $validator
*/
public function validate(Validator $validator)
public function validate(Validator $validator): void
{
if (! Config::get('distributed_poller')) {
$validator->fail('You have not enabled distributed_poller', 'lnms config:set distributed_poller true');
@ -64,7 +65,7 @@ class DistributedPoller extends BaseValidation
} elseif (! is_dir(Config::get('rrd_dir'))) {
$validator->fail('You have not configured $config[\'rrd_dir\']');
} else {
Rrd::checkRrdcached($validator);
$validator->result((new CheckRrdcachedConnectivity)->validate(), $this->name);
}
if (PollerCluster::exists()) {
@ -82,7 +83,7 @@ class DistributedPoller extends BaseValidation
$this->checkPythonWrapper($validator);
}
private function checkDispatcherService(Validator $validator)
private function checkDispatcherService(Validator $validator): void
{
$driver = config('cache.default');
if ($driver != 'redis') {
@ -109,7 +110,7 @@ class DistributedPoller extends BaseValidation
}
}
private function checkPythonWrapper(Validator $validator)
private function checkPythonWrapper(Validator $validator): void
{
if (! Config::get('distributed_poller_memcached_host')) {
$validator->fail('You have not configured $config[\'distributed_poller_memcached_host\']');

View File

@ -38,7 +38,7 @@ class Mail extends BaseValidation
*
* @param Validator $validator
*/
public function validate(Validator $validator)
public function validate(Validator $validator): void
{
if (Config::get('alert.transports.mail') === true) {
$run_test = 1;

View File

@ -40,7 +40,7 @@ class Php extends BaseValidation
*
* @param Validator $validator
*/
public function validate(Validator $validator)
public function validate(Validator $validator): void
{
$this->checkVersion($validator);
$this->checkExtensions($validator);

View File

@ -37,7 +37,7 @@ class Programs extends BaseValidation
*
* @param Validator $validator
*/
public function validate(Validator $validator)
public function validate(Validator $validator): void
{
// Check programs
$bins = ['fping', 'rrdtool', 'snmpwalk', 'snmpget', 'snmpgetnext', 'snmpbulkwalk'];

View File

@ -40,7 +40,7 @@ class Python extends BaseValidation
*
* @param Validator $validator
*/
public function validate(Validator $validator)
public function validate(Validator $validator): void
{
$version = Version::get()->python();

View File

@ -25,65 +25,8 @@
namespace LibreNMS\Validations;
use LibreNMS\Config;
use LibreNMS\Util\Version;
use LibreNMS\Validator;
class Rrd extends BaseValidation
{
/**
* Validate this module.
* To return ValidationResults, call ok, warn, fail, or result methods on the $validator
*
* @param Validator $validator
*/
public function validate(Validator $validator)
{
// Check that rrdtool config version is what we see
if (Config::has('rrdtool_version')) {
$rrd_version = Version::get()->rrdtool();
if (version_compare(Config::get('rrdtool_version'), '1.5.5', '<')
&& version_compare(Config::get('rrdtool_version'), $rrd_version, '>')
) {
$validator->fail(
'The rrdtool version you have specified is newer than what is installed.',
"Either comment out \$config['rrdtool_version'] = '" .
Config::get('rrdtool_version') . "'; or set \$config['rrdtool_version'] = '{$rrd_version}';"
);
}
}
if (Config::get('rrdcached')) {
self::checkRrdcached($validator);
} else {
$rrd_dir = Config::get('rrd_dir');
$dir_stat = stat($rrd_dir);
if ($dir_stat[4] == 0 || $dir_stat[5] == 0) {
$validator->warn('Your RRD directory is owned by root, please consider changing over to user a non-root user');
}
if (substr(sprintf('%o', fileperms($rrd_dir)), -3) != 775) {
$validator->warn('Your RRD directory is not set to 0775', "chmod 775 $rrd_dir");
}
}
}
public static function checkRrdcached(Validator $validator)
{
[$host,$port] = explode(':', Config::get('rrdcached'));
if ($host == 'unix') {
// Using socket, check that file exists
if (! file_exists($port)) {
$validator->fail("$port doesn't appear to exist, rrdcached test failed");
}
} else {
$connection = @fsockopen($host, (int) $port);
if (is_resource($connection)) {
fclose($connection);
} else {
$validator->fail('Cannot connect to rrdcached instance');
}
}
}
protected $directory = 'Rrd';
protected $name = 'rrd';
}

View File

@ -0,0 +1,62 @@
<?php
/*
* CheckRrddirPermissions.php
*
* -Description-
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Validations\Rrd;
use LibreNMS\Config;
use LibreNMS\Interfaces\Validation;
use LibreNMS\ValidationResult;
class CheckRrdDirPermissions implements Validation
{
/**
* @inheritDoc
*/
public function validate(): ValidationResult
{
$rrd_dir = Config::get('rrd_dir');
$dir_stat = stat($rrd_dir);
if ($dir_stat[4] == 0 || $dir_stat[5] == 0) {
return ValidationResult::warn(trans('validation.validations.rrd.CheckRrdDirPermissions.fail_root'),
sprintf('chown %s:%s %s', Config::get('user'), Config::get('group'), $rrd_dir)
);
}
if (substr(sprintf('%o', fileperms($rrd_dir)), -3) != 775) {
return ValidationResult::warn(trans('validation.validations.rrd.CheckRrdDirPermissions.fail_mode'), "chmod 775 $rrd_dir");
}
return ValidationResult::ok(trans('validation.validations.rrd.CheckRrdDirPermissions.ok'));
}
/**
* @inheritDoc
*/
public function enabled(): bool
{
return ! Config::get('rrdcached');
}
}

View File

@ -0,0 +1,73 @@
<?php
/*
* CheckRrdVersion.php
*
* -Description-
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Validations\Rrd;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Support\Str;
use LibreNMS\Config;
use LibreNMS\Interfaces\Validation;
use LibreNMS\Interfaces\ValidationFixer;
use LibreNMS\ValidationResult;
use Storage;
class CheckRrdVersion implements Validation, ValidationFixer
{
public function validate(): ValidationResult
{
// Check that rrdtool config version is what we see
$rrd_version = '0.1'; //Version::get()->rrdtool();
if (version_compare(Config::get('rrdtool_version'), '1.5.5', '<')
&& version_compare(Config::get('rrdtool_version'), $rrd_version, '>')
) {
return ValidationResult::fail(
trans('validation.validations.rrd.CheckRrdVersion.fail'),
trans('validation.validations.rrd.CheckRrdVersion.fix', ['version' => Config::get('rrdtool_version')])
)->setFixer(__CLASS__, is_writable(base_path('config.php')));
}
return ValidationResult::ok(trans('validation.validations.rrd.CheckRrdVersion.ok'));
}
public function enabled(): bool
{
return Config::has('rrdtool_version');
}
public function fix(): bool
{
try {
$contents = Storage::disk('base')->get('config.php');
$lines = array_filter(explode("\n", $contents), function ($line) {
return ! Str::contains($line, ['$config[\'rrdtool_version\']', '$config["rrdtool_version"]']);
});
return Storage::disk('base')->put('config.php', implode("\n", $lines));
} catch (FileNotFoundException $e) {
return false;
}
}
}

View File

@ -0,0 +1,64 @@
<?php
/*
* CheckRrdcachedConnectivity.php
*
* -Description-
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Validations\Rrd;
use LibreNMS\Config;
use LibreNMS\Interfaces\Validation;
use LibreNMS\ValidationResult;
class CheckRrdcachedConnectivity implements Validation
{
/**
* @inheritDoc
*/
public function validate(): ValidationResult
{
[$host,$port] = explode(':', Config::get('rrdcached'));
if ($host == 'unix') {
// Using socket, check that file exists
if (! file_exists($port)) {
return ValidationResult::fail(trans('validation.validations.rrd.CheckRrdcachedConnectivity.fail_socket', ['socket' => $port]));
}
} else {
$connection = @fsockopen($host, (int) $port);
if (is_resource($connection)) {
fclose($connection);
} else {
return ValidationResult::fail(trans('validation.validations.rrd.CheckRrdcachedConnectivity.fail_port', ['port' => $port]));
}
}
return ValidationResult::ok(trans('validation.validations.rrd.CheckRrdcachedConnectivity.ok'));
}
/**
* @inheritDoc
*/
public function enabled(): bool
{
return (bool) Config::get('rrdcached');
}
}

View File

@ -41,7 +41,7 @@ class RrdCheck extends BaseValidation
*
* @param Validator $validator
*/
public function validate(Validator $validator)
public function validate(Validator $validator): void
{
// Loop through the rrd_dir
$rrd_directory = new RecursiveDirectoryIterator(Config::get('rrd_dir'));

View File

@ -36,7 +36,7 @@ class System extends BaseValidation
/**
* {@inheritdoc}
*/
public function validate(Validator $validator)
public function validate(Validator $validator): void
{
$install_dir = $validator->getBaseDir();

View File

@ -38,7 +38,7 @@ use LibreNMS\Validator;
class Updates extends BaseValidation
{
public function validate(Validator $validator)
public function validate(Validator $validator): void
{
if (EnvHelper::librenmsDocker()) {
$validator->warn('Updates are managed through the official Docker image');

View File

@ -41,7 +41,7 @@ class User extends BaseValidation
*
* @param Validator $validator
*/
public function validate(Validator $validator)
public function validate(Validator $validator): void
{
// Check we are running this as the root user
$username = $validator->getUsername();

View File

@ -154,6 +154,17 @@ class Validator
foreach ($results as $result) {
$result->consolePrint();
if ($result->hasFixer()) {
$input = readline('Attempt to fix this issue (y or n)?:');
if ($input === 'y') {
$result = app()->make($result->getFixer())->fix();
if ($result) {
echo "Attempted to apply fix.\n";
} else {
echo "Failed to apply fix.\n";
}
}
}
}
}

View File

@ -4,6 +4,8 @@ namespace App\Http\Controllers;
use Illuminate\Contracts\View\View;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use LibreNMS\Interfaces\ValidationFixer;
use LibreNMS\Validator;
class ValidateController extends Controller
@ -20,4 +22,23 @@ class ValidateController extends Controller
return response()->json($validator->toArray());
}
public function runFixer(Request $request): JsonResponse
{
$this->validate($request, [
'fixer' => [
'starts_with:LibreNMS\Validations',
function ($attribute, $value, $fail) {
if (! class_exists($value) || ! in_array(ValidationFixer::class, class_implements($value))) {
$fail(trans('validation.results.invalid_fixer'));
}
},
],
]);
$fixer = $request->get('fixer');
return response()->json([
'result' => (new $fixer)->fix(),
]);
}
}

View File

@ -43,6 +43,11 @@ return [
'root' => storage_path('app'),
],
'base' => [
'driver' => 'local',
'root' => base_path(),
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),

View File

@ -58,7 +58,7 @@ class ConfigSeeder extends Seeder
}
if (\App\Models\Config::exists()) {
if (! $this->command->confirm(trans('commands.db:seed.existing_config'), false)) {
if (! app()->runningInConsole() || ! $this->command->confirm(trans('commands.db:seed.existing_config'), false)) {
return; // don't overwrite existing settings.
}
}

View File

@ -3340,11 +3340,6 @@ parameters:
count: 1
path: LibreNMS/Interfaces/Polling/SlaPolling.php
-
message: "#^Method LibreNMS\\\\Interfaces\\\\ValidationGroup\\:\\:validate\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Interfaces/ValidationGroup.php
-
message: "#^Method LibreNMS\\\\Model\\:\\:clean\\(\\) has no return type specified\\.$#"
count: 1
@ -6540,266 +6535,6 @@ parameters:
count: 1
path: LibreNMS/Util/Validate.php
-
message: "#^Property LibreNMS\\\\Validations\\\\BaseValidation\\:\\:\\$RUN_BY_DEFAULT has no type specified\\.$#"
count: 1
path: LibreNMS/Validations/BaseValidation.php
-
message: "#^Property LibreNMS\\\\Validations\\\\BaseValidation\\:\\:\\$completed has no type specified\\.$#"
count: 1
path: LibreNMS/Validations/BaseValidation.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Configuration\\:\\:validate\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Configuration.php
-
message: "#^Comparison operation \"\\>\" between int\\<1001, max\\> and 1000 is always true\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addColumnSql\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addColumnSql\\(\\) has parameter \\$previous_column with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addColumnSql\\(\\) has parameter \\$primary with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addColumnSql\\(\\) has parameter \\$schema with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addColumnSql\\(\\) has parameter \\$table with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addConstraintSql\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addConstraintSql\\(\\) has parameter \\$constraint with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addConstraintSql\\(\\) has parameter \\$table with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addIndexSql\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addIndexSql\\(\\) has parameter \\$index_schema with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addIndexSql\\(\\) has parameter \\$table with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addTableSql\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addTableSql\\(\\) has parameter \\$table with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:addTableSql\\(\\) has parameter \\$table_schema with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:checkCollation\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:checkMode\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:checkMysqlEngine\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:checkSchema\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:checkTime\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:checkVersion\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropColumnSql\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropColumnSql\\(\\) has parameter \\$column with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropColumnSql\\(\\) has parameter \\$table with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropConstraintSql\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropConstraintSql\\(\\) has parameter \\$name with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropConstraintSql\\(\\) has parameter \\$table with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropIndexSql\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropIndexSql\\(\\) has parameter \\$name with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropIndexSql\\(\\) has parameter \\$table with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropTableSql\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:dropTableSql\\(\\) has parameter \\$table with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateIndexSql\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateIndexSql\\(\\) has parameter \\$index_schema with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateIndexSql\\(\\) has parameter \\$name with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateIndexSql\\(\\) has parameter \\$table with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateTableSql\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateTableSql\\(\\) has parameter \\$column with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateTableSql\\(\\) has parameter \\$column_schema with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:updateTableSql\\(\\) has parameter \\$table with no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:validate\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Database\\:\\:validateSystem\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Database.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Dependencies\\:\\:validate\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Dependencies.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Disk\\:\\:validate\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Disk.php
-
message: "#^Method LibreNMS\\\\Validations\\\\DistributedPoller\\:\\:checkDispatcherService\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/DistributedPoller.php
-
message: "#^Method LibreNMS\\\\Validations\\\\DistributedPoller\\:\\:checkPythonWrapper\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/DistributedPoller.php
-
message: "#^Method LibreNMS\\\\Validations\\\\DistributedPoller\\:\\:validate\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/DistributedPoller.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Mail\\:\\:validate\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Mail.php
-
message: "#^Property LibreNMS\\\\Validations\\\\Mail\\:\\:\\$RUN_BY_DEFAULT has no type specified\\.$#"
count: 1
path: LibreNMS/Validations/Mail.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Php\\:\\:checkExtensions\\(\\) has no return type specified\\.$#"
count: 1
@ -6820,11 +6555,6 @@ parameters:
count: 1
path: LibreNMS/Validations/Php.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Php\\:\\:validate\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Php.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Programs\\:\\:checkFping6\\(\\) has no return type specified\\.$#"
count: 1
@ -6875,11 +6605,6 @@ parameters:
count: 1
path: LibreNMS/Validations/Programs.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Programs\\:\\:validate\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Programs.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Python\\:\\:checkExtensions\\(\\) has no return type specified\\.$#"
count: 1
@ -6905,51 +6630,6 @@ parameters:
count: 1
path: LibreNMS/Validations/Python.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Python\\:\\:validate\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Python.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Rrd\\:\\:checkRrdcached\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Rrd.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Rrd\\:\\:validate\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Rrd.php
-
message: "#^Method LibreNMS\\\\Validations\\\\RrdCheck\\:\\:validate\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/RrdCheck.php
-
message: "#^Property LibreNMS\\\\Validations\\\\RrdCheck\\:\\:\\$RUN_BY_DEFAULT has no type specified\\.$#"
count: 1
path: LibreNMS/Validations/RrdCheck.php
-
message: "#^Method LibreNMS\\\\Validations\\\\System\\:\\:validate\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/System.php
-
message: "#^Property LibreNMS\\\\Validations\\\\System\\:\\:\\$RUN_BY_DEFAULT has no type specified\\.$#"
count: 1
path: LibreNMS/Validations/System.php
-
message: "#^Method LibreNMS\\\\Validations\\\\Updates\\:\\:validate\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/Updates.php
-
message: "#^Method LibreNMS\\\\Validations\\\\User\\:\\:validate\\(\\) has no return type specified\\.$#"
count: 1
path: LibreNMS/Validations/User.php
-
message: "#^Property App\\\\Models\\\\Device\\:\\:\\$authlevel \\('authNoPriv'\\|'authPriv'\\|'noAuthNoPriv'\\) does not accept null\\.$#"
count: 1

View File

@ -160,12 +160,66 @@ return [
'attributes' => [],
'results' => [
'autofix' => 'Attempt to automatically fix',
'fix' => 'Fix',
'fixed' => 'Fix has completed, refresh to re-run validations.',
'fetch_failed' => 'Failed to fetch validation results',
'backend_failed' => 'Failed to load data from backend, check webserver.',
'invalid_fixer' => 'Invalid Fixer',
'show_all' => 'Show all',
'show_less' => 'Show less',
'validate' => 'Validate',
'validating' => 'Validating',
],
'validations' => [
'rrd' => [
'CheckRrdVersion' => [
'fail' => 'The rrdtool version you have specified is newer than what is installed.',
'fix' => 'Either comment out or delete $config[\'rrdtool_version\'] = \':version\'; from your config.php file',
'ok' => 'rrdtool version ok',
],
'CheckRrdcachedConnectivity' => [
'fail_socket' => ':socket does not appear to exist, rrdcached connectivity test failed',
'fail_port' => 'Cannot connect to rrdcached server on port :port',
'ok' => 'Connected to rrdcached',
],
'CheckRrdDirPermissions' => [
'fail_root' => 'Your RRD directory is owned by root, please consider changing over to user a non-root user',
'fail_mode' => 'Your RRD directory is not set to 0775',
'ok' => 'rrd_dir is writable',
],
],
'database' => [
'CheckDatabaseTableNamesCase' => [
'fail' => 'You have lower_case_table_names set to 1 or true in mysql config.',
'fix' => 'Set lower_case_table_names=0 in your mysql config file in the [mysqld] section.',
'ok' => 'lower_case_table_names is enabled',
],
'CheckDatabaseServerVersion' => [
'fail' => ':server version :min is the minimum supported version as of :date.',
'fix' => 'Update :server to a supported version, :suggested suggested.',
'ok' => 'SQL Server meets minimum requirements',
],
'CheckMysqlEngine' => [
'fail' => 'Some tables are not using the recommended InnoDB engine, this may cause you issues.',
'tables' => 'Tables',
'ok' => 'MySQL engine is optimal',
],
'CheckSqlServerTime' => [
'fail' => "Time between this server and the mysql database is off\n Mysql time :mysql_time\n PHP time :php_time",
'ok' => 'MySQl and PHP time match',
],
'CheckSchemaVersion' => [
'fail_outdated' => 'Your database is out of date!',
'fail_legacy_outdated' => 'Your database schema (:current) is older than the latest (:latest).',
'fix_legacy_outdated' => 'Manually run ./daily.sh, and check for any errors.',
'warn_extra_migrations' => 'Your database schema has extra migrations (:migrations). If you just switched to the stable release from the daily release, your database is in between releases and this will be resolved with the next release.',
'warn_legacy_newer' => 'Your database schema (:current) is newer than expected (:latest). If you just switched to the stable release from the daily release, your database is in between releases and this will be resolved with the next release.',
'ok' => 'Database Schema is current',
],
'CheckSchemaCollation' => [
],
],
],
];

View File

@ -36,7 +36,13 @@
<div class="panel-heading"
x-text="result.statusText + ': ' + result.message"
></div>
<div class="panel-body" x-show="result.fix.length || result.list.length">
<div class="panel-body" x-show="result.fix.length || result.list.length || result.fixer">
<div x-show="result.fixer" class="tw-mb-2" x-data="fixerData(result.fixer)">
<button class="btn btn-success" x-on:click="runFixer" x-bind:disabled="running" x-show="! fixed">
<i class="fa-solid" x-bind:class="running ? 'fa-spinner fa-spin' : 'fa-wrench'"></i> {{ __('validation.results.autofix') }}
</button>
<div x-show="fixed">{{ __('validation.results.fixed') }}</div>
</div>
<div x-show="result.fix.length">
{{ __('validation.results.fix') }}: <pre x-text='result.fix.join("\r\n")'>
</pre>
@ -71,6 +77,30 @@
@push('scripts')
<script>
function fixerData(name) {
return {
running: false,
fixed: false,
fixer: name,
runFixer() {
event.target.disabled = true;
fetch('{{ route('validate.fix') }}', {
method: 'POST',
body: JSON.stringify({fixer: this.fixer}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
"X-CSRF-Token": document.querySelector('input[name=_token]').value
},
}).then(response => {
if (response.ok) {
this.fixed = true;
} else {
this.running = false;
}
}).catch(response => this.running = false);
}
}
}
</script>
@endpush

View File

@ -98,6 +98,7 @@ Route::group(['middleware' => ['auth'], 'guard' => 'auth'], function () {
Route::resource('port-groups', 'PortGroupController');
Route::get('validate', [\App\Http\Controllers\ValidateController::class, 'index'])->name('validate');
Route::get('validate/results', [\App\Http\Controllers\ValidateController::class, 'runValidation'])->name('validate.results');
Route::post('validate/fix', [\App\Http\Controllers\ValidateController::class, 'runFixer'])->name('validate.fix');
});
Route::get('plugin', 'PluginLegacyController@redirect');

View File

@ -0,0 +1,58 @@
<?php
/*
* ValidationFixTest.php
*
* -Description-
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Tests\Unit;
use LibreNMS\Tests\TestCase;
use LibreNMS\Validations\Rrd\CheckRrdVersion;
use Storage;
class ValidationFixTest extends TestCase
{
public function testRrdVersionFix(): void
{
Storage::fake('base');
Storage::disk('base')->put('config.php', <<<'EOF'
<?php
$config['test'] = 'rrdtool_version';
$config['rrdtool_version'] = '1.0';
$config["rrdtool_version"] = '1.1';
# comment
EOF
);
(new CheckRrdVersion())->fix();
$actual = Storage::disk('base')->get('config.php');
$this->assertSame(<<<'EOF'
<?php
$config['test'] = 'rrdtool_version';
# comment
EOF, $actual);
Storage::disk('base')->delete('config.php');
}
}

View File

@ -172,13 +172,13 @@ EOF;
// output matches that of ValidationResult
function print_fail($msg, $fix = null)
{
c_echo("[%RFAIL%n] $msg");
echo "[\033[31;1mFAIL\033[0m] $msg";
if ($fix && strlen($msg) > 72) {
echo PHP_EOL . ' ';
}
if (! empty($fix)) {
c_echo(" [%BFIX%n] %B$fix%n");
echo " [\033[34;1mFIX\033[0m] \033[34;1m$fix\033[0m";
}
echo PHP_EOL;
}