From f7668245cbab1120cff39de29599e82562683a81 Mon Sep 17 00:00:00 2001 From: Andrew Carr Date: Sat, 24 Apr 2021 17:19:05 +0000 Subject: [PATCH 1/3] Added a panel to show the status of the apt packages This allows a user to include a status panel of what package updates are pending on an Apt Package managed system such as Ubuntu. Also included is the configuration file change to include a 'package_management' section that must be configured for proper operation. (I was unable to get the panel to not display if the section was missing) It is currently configured to be not enabled. Change the flag to 'true' to enable the panel. While setup for Apt, this could easily be extended for yum, pacman, etc and I hope they would include their configuration inside of 'package_management' as well. Tested on: Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-72-generic x86_64) --- conf/esm.config.json | 5 ++- index.php | 17 ++++++++++- js/esm.js | 42 +++++++++++++++++++++++-- libs/apt.php | 73 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 libs/apt.php diff --git a/conf/esm.config.json b/conf/esm.config.json index 2947c82..655feff 100644 --- a/conf/esm.config.json +++ b/conf/esm.config.json @@ -67,5 +67,8 @@ "protocol": "tcp" } ] - } + }, + "package_management": { + "apt": false + } } diff --git a/index.php b/index.php index a869d0a..d4598a2 100644 --- a/index.php +++ b/index.php @@ -374,7 +374,22 @@ - + get('package_management:apt') == true): ?> +
+
+

Package Update Status

+
    +
  • +
+
+ +
+ + +
+
+
+
diff --git a/js/esm.js b/js/esm.js index b61eb57..329a44c 100644 --- a/js/esm.js +++ b/js/esm.js @@ -294,6 +294,42 @@ esm.getServices = function() { } +esm.getAptStatus = function() { + var module = 'apt'; + + esm.reloadBlock_spin(module); + + $.get('libs/'+module+'.php', function(data) { + var $box = $('.box#esm-'+module+' .box-content tbody'); + $box.empty(); + + var html = ''; + + if( data.status === 0 ) { + var package_color = data.standard > 0 ? 'label success' : ''; + var security_color = data.security > 0 ? 'label error' : ''; + + html += ''; + html += 'Available Package Updates'; + html += ''+data.standard+''; + html += ''; + html += ''; + html += 'Available Security Updates'; + html += ''+data.security+''; + html += ''; + } else { + // If the module isn't disabled, something else went wrong + if( data.status !== 1 ) { + console.error("Unable to retrieve package updates", data); + } + } + + $box.append(html); + + esm.reloadBlock_spin(module); + }, 'json'); +} + esm.getAll = function() { esm.getSystem(); @@ -306,6 +342,7 @@ esm.getAll = function() { esm.getNetwork(); esm.getPing(); esm.getServices(); + esm.getAptStatus(); } esm.reloadBlock = function(block) { @@ -367,5 +404,6 @@ esm.mapping = { last_login: esm.getLast_login, network: esm.getNetwork, ping: esm.getPing, - services: esm.getServices -}; \ No newline at end of file + services: esm.getServices, + apt: esm.getAptStatus +}; diff --git a/libs/apt.php b/libs/apt.php new file mode 100644 index 0000000..00580cf --- /dev/null +++ b/libs/apt.php @@ -0,0 +1,73 @@ +; + - 'standard' is an int representing the upgrade packages available + - 'security' is an int representing the security upgrade packages available + * At least on Ubuntu 20.x, the path of the 'apt-check' utility is + '/usr/lib/update-notifier/apt-check'. The utility and it's path will need to be validated + on other Linux distributions and Ubuntu versions. + + Configuration / Usage + * The property 'blahblah' must be in the 'esm.config.json' file with the property of 'true' + for this library to execute correctly. + * Status Values & Messages: + - '0' - 'Success' will be set if everything was successful. + - '1' - 'Disabled' will be set if this library is disabled. + - '2' - 'Failure' will be set if apt-check failed to execute. Can have additional + information appended to the message. + - '3' - 'Not Available' will be set if apt-check could not be located. + - '-1' - 'Unknown' or other messages will be set for any other reason. + +*/ +$configKey = 'package_management:apt'; + +$command_path = '/usr/lib/update-notifier/apt-check'; + +// apt-check outputs to stderr, 2>&1 concat's stderr to stdout +$options = '2>&1'; + +$datas = array(); + +if (count($Config->get($configKey)) != 1) { + $datas['status'] = -1; + $datas['message'] = 'Not Configured'; +} elseif ($Config->get($configKey) == false ) { + $datas['status'] = 1; + $datas['message'] = 'Disabled'; +} elseif ($Config->get($configKey) == true ) { + // If the command is configured & enabled + if( file_exists($command_path) ) { + $command = $command_path . " " . $options; + $execresult = exec($command, $output, $retval); + if( $execresult ) { + $items = explode(';', $output[0]); + $datas['status'] = 0; + $datas['message'] = 'Success'; + $datas['standard'] = $items[0]; + $datas['security'] = $items[1]; + } else { + $datas['status'] = 2; + $datas['message'] = 'Failure ' . $retval; + } + } else { + $datas['status'] = 3; + $datas['message'] = 'Not Available'; + } +} else { + // Not sure what's going on here.... + $datas['status'] = -1; + $datas['message'] = 'Unknown'; +} + +echo json_encode($datas); From 567c4ebd5b5b92b6316f9c51089dbadedf2eb553 Mon Sep 17 00:00:00 2001 From: Andrew Carr Date: Fri, 20 Aug 2021 17:32:19 +0000 Subject: [PATCH 2/3] Adjusted to account for lack of `apt-check` * Introduced a block to parse the result of apt-get * Corrected issue with checking for the presence of the configuration * Fixed issue with exec() and parsing of the results --- README.md | 1 + conf/esm.config.json | 2 +- js/esm.js | 2 + libs/apt.php | 101 ++++++++++++++++++++++++++++++++++--------- 4 files changed, 84 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 53ac9c5..1e3fb76 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ In its [Web](http://www.ezservermonitor.com/esm-web/features) version eSM is a P - **Last login** : display last 5 user connections - **Ping** : ping the hosts defined in the configuration file - **Services** : displays the status (up or down) services defined in the configuration file +- **Package Update Status** : displays the status of pending package updates for `apt` based systems Several themes are available ! diff --git a/conf/esm.config.json b/conf/esm.config.json index 655feff..a86a0c0 100644 --- a/conf/esm.config.json +++ b/conf/esm.config.json @@ -69,6 +69,6 @@ ] }, "package_management": { - "apt": false + "apt": true } } diff --git a/js/esm.js b/js/esm.js index 329a44c..cf6bd68 100644 --- a/js/esm.js +++ b/js/esm.js @@ -306,6 +306,8 @@ esm.getAptStatus = function() { var html = ''; if( data.status === 0 ) { + console.log("apt-status", data); + var package_color = data.standard > 0 ? 'label success' : ''; var security_color = data.security > 0 ? 'label error' : ''; diff --git a/libs/apt.php b/libs/apt.php index 00580cf..6add4be 100644 --- a/libs/apt.php +++ b/libs/apt.php @@ -10,47 +10,85 @@ Debian and Ubuntu. 'apt-check' notes: - * apt-check is the utility used by apt to determine if there are packages available. + * apt-check is the utility used by apt to determine if there are packages available in Ubuntu + distributions. * If called with no parameters, it returns with a tuple of numbers in the format: ; - 'standard' is an int representing the upgrade packages available - 'security' is an int representing the security upgrade packages available * At least on Ubuntu 20.x, the path of the 'apt-check' utility is '/usr/lib/update-notifier/apt-check'. The utility and it's path will need to be validated - on other Linux distributions and Ubuntu versions. + on other Ubuntu versions. + * The results of apt-check are cached on the OS side of things and thus, if we can, we should + prefer the use of this command. + + 'apt-get' notes: + * The command `apt-get update` **must** be run before this will report the correct number of + packages. As this will need to be run with super user privaleges, it is recommended that a + simple cron job or timer script be configured for this job. + * The `apt-get` approach is used if the `apt-check` command cannot be found. Most likely, it + means that this script is not running in an Ubuntu environment. + * Basically, this calls and filters 'apt-get --simulate dist-upgrade'. If this call is + successful, the results are filtered with php commands to get the number of standard and + security updates. + * This call is not cached and can take a bit of time to complete. + * In the grand scheme of things, this is basically running these two CLI commands: + - 'apt-get --simulate dist-upgrade |grep "^Inst" |grep --ignore-case securi |wc --lines' + - 'apt-get --simulate dist-upgrade |grep "^Inst" |wc --lines' Configuration / Usage - * The property 'blahblah' must be in the 'esm.config.json' file with the property of 'true' - for this library to execute correctly. + * The property 'package_management:apt' must be in the 'esm.config.json' file with the property + of 'true' for this library to execute correctly. See the example 'esm.config.json' for the + syntax. * Status Values & Messages: - - '0' - 'Success' will be set if everything was successful. - - '1' - 'Disabled' will be set if this library is disabled. - - '2' - 'Failure' will be set if apt-check failed to execute. Can have additional + - `0` - 'Success' will be set if everything was successful. + - `1` - 'Disabled' will be set if this library is disabled. + - `2` - 'Failure' will be set if apt-check failed to execute. Can have additional information appended to the message. - - '3' - 'Not Available' will be set if apt-check could not be located. - - '-1' - 'Unknown' or other messages will be set for any other reason. - + - `3` - 'Not Available' will be set if apt-check could not be located. + - `-1` - 'Unknown' or other messages will be set for any other reason. + * The JSON return structure schema: + - (int) `status` - the status of the call, can be negative. + - (string) `message` - the result of the call in freetext form. + - (int) `standard` - the number of packages awaiting upgrade. Optional + - (int) `security` - on success, the number of security packages awaiting upgrade. Optional */ + $configKey = 'package_management:apt'; -$command_path = '/usr/lib/update-notifier/apt-check'; +// The command paths. Intentionally not configurable to prevent remote execution bugs. +$apt_get_root_path = '/bin/apt-get'; +$apt_get_usr_path = '/usr/bin/apt-get'; +$apt_get_path = ''; +$apt_check_path = '/usr/lib/update-notifier/apt-check'; -// apt-check outputs to stderr, 2>&1 concat's stderr to stdout -$options = '2>&1'; +// Quickly find the apt-get path, just in case. +if(file_exists($apt_get_root_path)) { + $apt_get_path = $apt_get_root_path; +} else if( file_exists($apt_get_usr_path)) { + $apt_get_path = $apt_get_usr_path; +} $datas = array(); -if (count($Config->get($configKey)) != 1) { - $datas['status'] = -1; - $datas['message'] = 'Not Configured'; -} elseif ($Config->get($configKey) == false ) { +// TODO Determine how to test for the existance of a key before trying to access it. If you load a +// non-existant key with $Config->get($configKey), you'll get the entire configuration back. If you +// try to load a partially existing key (e.g. the 'package_management' header exists but not the +// 'apt' key) you'll get nothing back. Return a status of -1, message of 'not configured'. +if ($Config->get($configKey) == false ) { $datas['status'] = 1; $datas['message'] = 'Disabled'; } elseif ($Config->get($configKey) == true ) { - // If the command is configured & enabled - if( file_exists($command_path) ) { + // Check each command path for existance & if it's executable. + if( file_exists($apt_check_path) && is_executable($apt_check_path) ) { + $command_path = $apt_check_path; + // apt-check outputs to stderr, 2>&1 concat's stderr to stdout + $options = '2>&1'; + $command = $command_path . " " . $options; + $execresult = exec($command, $output, $retval); - if( $execresult ) { + + if( $retval == 0 ) { $items = explode(';', $output[0]); $datas['status'] = 0; $datas['message'] = 'Success'; @@ -58,7 +96,28 @@ $datas['security'] = $items[1]; } else { $datas['status'] = 2; - $datas['message'] = 'Failure ' . $retval; + $datas['message'] = 'apt-check failure - error code ' . $retval; + } + } else if ( $apt_get_path != '' && file_exists($apt_get_path) && is_executable($apt_get_path) ) { + $command_path = $apt_get_path; + + $options = "--simulate dist-upgrade"; + $command = $command_path . " " . $options; + + $execresult = exec($command, $output, $retval); + + if( $retval == 0 ) { + // Success - now filter the results + $standard = preg_grep('/^Inst/', $output); + $security = preg_grep('/securi/i', $standard); + + $datas['status'] = 0; + $datas['message'] = 'Success'; + $datas['standard'] = sizeof($standard); + $datas['security'] = sizeof($security); + } else { + $datas['status'] = 2; + $datas['message'] = 'apt-get failure - error code ' . $retval; } } else { $datas['status'] = 3; From 1c72e063870dd18bc8e9ca2973dae23fb67dfe11 Mon Sep 17 00:00:00 2001 From: Andrew Carr Date: Sat, 21 Aug 2021 23:08:07 +0000 Subject: [PATCH 3/3] Added apt-get update call --- conf/esm.config.json | 3 ++- libs/apt.php | 23 ++++++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/conf/esm.config.json b/conf/esm.config.json index a86a0c0..0cae5a9 100644 --- a/conf/esm.config.json +++ b/conf/esm.config.json @@ -69,6 +69,7 @@ ] }, "package_management": { - "apt": true + "apt": false, + "apt_update_before_check": false } } diff --git a/libs/apt.php b/libs/apt.php index 6add4be..7e0679c 100644 --- a/libs/apt.php +++ b/libs/apt.php @@ -23,8 +23,14 @@ 'apt-get' notes: * The command `apt-get update` **must** be run before this will report the correct number of - packages. As this will need to be run with super user privaleges, it is recommended that a + packages. As this will need to be run with super user privileges, it is recommended that a simple cron job or timer script be configured for this job. + * It is possible to enable it directly by putting 'apt_update_before_check' parameter to true. + Note that you'll then need to grant sudo apt-get rights to www-data: + `sudo visudo` + `www-data ALL=(ALL) NOPASSWD: /bin/apt-get` + ACTIVATING IT MAY HAVE IMPACT ON SECURITY OF YOUR COMPUTER SO USE IT WITH CAUTION + THIS APPROACH WILL ALSO SLOW DOWN PAGE LOAD * The `apt-get` approach is used if the `apt-check` command cannot be found. Most likely, it means that this script is not running in an Ubuntu environment. * Basically, this calls and filters 'apt-get --simulate dist-upgrade'. If this call is @@ -54,6 +60,7 @@ */ $configKey = 'package_management:apt'; +$optionalUpdateKey = 'package_management:apt_update_before_check'; // The command paths. Intentionally not configurable to prevent remote execution bugs. $apt_get_root_path = '/bin/apt-get'; @@ -70,8 +77,8 @@ $datas = array(); -// TODO Determine how to test for the existance of a key before trying to access it. If you load a -// non-existant key with $Config->get($configKey), you'll get the entire configuration back. If you +// TODO Determine how to test for the existence of a key before trying to access it. If you load a +// non-existent key with $Config->get($configKey), you'll get the entire configuration back. If you // try to load a partially existing key (e.g. the 'package_management' header exists but not the // 'apt' key) you'll get nothing back. Return a status of -1, message of 'not configured'. if ($Config->get($configKey) == false ) { @@ -99,6 +106,16 @@ $datas['message'] = 'apt-check failure - error code ' . $retval; } } else if ( $apt_get_path != '' && file_exists($apt_get_path) && is_executable($apt_get_path) ) { + // If requested in config, update apt (getting latest infos from apt server) before doing an apt. + // WARNING (security potential issue): sudo apt-get will then need to be allowed for www-data user + if ($Config->get($optionalUpdateKey) == true) { + $updateCommand = 'sudo --non-interactive ' . $apt_get_path . ' --quiet --yes update'; + $execresult = exec($updateCommand, $output, $retval); + if ( $retval != 0 ) { + error_log("Failed to execute '$updateCommand' from php script"); + } + } + $command_path = $apt_get_path; $options = "--simulate dist-upgrade";