Files
cacti/cli/install_cacti.php
Thomas Vincent 2b6e96b23a style(php): [develop] wrap CLI and poller calls without changing execution (#8303)
* style(php): wrap CLI and poller calls without changing execution

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(ci): review security inventories after poller wrapping

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test(boost): compare handoff source contracts by PHP tokens

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* style(php): wrap poller constant strings without changing values

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* style(php): wrap poller concatenations with token-preserving source contracts

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test(cli): assert CDEF repair queries after constant folding

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test: verify PHP formatting preserves parsed behavior

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-10-14 00:01:26 -07:00

559 lines
17 KiB
PHP
Executable File

#!/usr/bin/env php
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
| |
| 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 2 |
| 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. |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
| This code is designed, written, and maintained by the Cacti Group. See |
| about.php and/or the AUTHORS file for specific developer information. |
+-------------------------------------------------------------------------+
| http://www.cacti.net/ |
+-------------------------------------------------------------------------+
*/
require(__DIR__ . '/../include/cli_check.php');
require_once(CACTI_PATH_INSTALL . '/functions.php');
// process calling arguments
$parms = $_SERVER['argv'];
array_shift($parms);
$options = ['Runtime' => 'Cli'];
$should_install = false;
$force_install = false;
$log_options = [];
display_version();
error_reporting(E_ALL);
define('log_install_echo', 'on');
if (cacti_sizeof($parms)) {
foreach ($parms as $parameter) {
if (str_contains($parameter, '=')) {
[$arg, $value] = explode('=', $parameter, 2);
} else {
$arg = $parameter;
$value = '';
}
switch ($arg) {
// Standard parameters
case '-d':
case '--debug':
$logname = 'log_install';
$tmplevel = false;
if (!empty($value)) {
$pos = strpos($value,':');
if ($pos !== false) {
$tmplevel = substr($value,$pos + 1);
$value = substr($value,0,$pos);
}
if (!empty($value)) {
$logname .= '_' . $value;
}
}
if ($tmplevel !== false) {
$level = $tmplevel;
} else {
$level = ($log_options[$logname] ?? log_install_level($logname, POLLER_VERBOSITY_DEBUG)) + 1;
}
$level = log_install_level_sanitize($level);
$log_options[$logname] = $level;
break;
case '--version':
case '-V':
case '-v':
exit(0);
case '--help':
case '-H':
case '-h':
display_help();
exit(0);
// Script specific parameters
case '--accept-eula':
set_install_option($options, 'Eula', 'End User License Agreement', 'Accepted');
break;
case '--automationmode':
case '-am':
set_install_option($options, 'AutomationMode', 'Automation Enabled', $value);
break;
case '--automationrange':
case '-ar':
set_install_option($options, 'AutomationRange', 'Automation Range', $value);
break;
case '--cron':
case '-c':
set_install_option($options, 'CronInterval', 'Cron Interval', $value);
break;
case '--force':
case '-f':
$force_install = true;
break;
case '--ini':
case '-i':
get_install_option($options, $value, false);
break;
case '--json':
case '-j':
get_install_option($options, $value, true);
break;
case '--install':
$should_install = true;
break;
case '--language':
case '--lang':
case '-l':
set_install_option($options, 'Language', 'Language', $value);
break;
case '--mode':
case '-m':
set_install_option($options, 'Mode', 'Mode', $value);
break;
case '--profile':
case '-p':
set_install_option($options, 'Profile', 'Collection Profile', $value);
break;
case '--path':
set_install_multioption($options, 'Paths', 'Path Option', $value, 'path_');
break;
case '--rrdtool':
case '-r':
set_install_option($options, 'RRDVersion', 'RRDTool Version', $value);
break;
case '--snmp':
set_install_multioption($options, 'SnmpOptions', 'Snmp Option', $value, 'Snmp');
break;
case '--table':
set_install_multioption($options, 'Tables', 'Table', $value, 'chk_table_');
break;
case '--template':
set_install_multioption($options, 'Templates', 'Template', $value, 'chk_template_', true);
break;
case '--theme':
case '-t':
set_install_option($options, 'Theme', 'Theme', $value);
break;
case '--notifyadmin':
case '-n':
$value = cacti_strtolower($value) == 'true' ? 'on' : '';
set_install_option($options, 'notify_admin', 'Notify Admin', $value);
break;
case '--adminemailaddress':
case '-e':
if (filter_var($value, FILTER_VALIDATE_EMAIL)) {
set_install_option($options, 'AdminEmailAddress', 'Admin Email Address', $value);
} else {
print 'ERROR: Invalid email address' . PHP_EOL . PHP_EOL;
exit(1);
}
break;
// Bad or unexpected parameter!
default:
print 'ERROR: Invalid Parameter ' . $parameter . PHP_EOL . PHP_EOL;
exit(1);
}
}
}
require_once(CACTI_PATH_LIBRARY . '/poller.php');
if (!register_process_start('install', 'master', 0, 86400)) {
print 'An Installation was already in progress' . PHP_EOL;
exit(1);
}
try {
db_execute("DELETE FROM settings WHERE name like 'log_install%' or name = 'install_eula'");
foreach ($log_options as $logname => $level) {
set_config_option($logname, $level);
}
include_once(CACTI_PATH_LIBRARY . '/api_automation.php');
include_once(CACTI_PATH_LIBRARY . '/api_automation_tools.php');
include_once(CACTI_PATH_LIBRARY . '/api_data_source.php');
include_once(CACTI_PATH_LIBRARY . '/api_device.php');
include_once(CACTI_PATH_LIBRARY . '/data_query.php');
include_once(CACTI_PATH_LIBRARY . '/import.php');
include_once(CACTI_PATH_LIBRARY . '/installer.php');
include_once(CACTI_PATH_LIBRARY . '/utility.php');
$options['Step'] = Installer::STEP_INSTALL_CONFIRM;
$results = ['Step' => $options['Step']];
$update_char = 'o';
debug_install_array('Options', $options);
$installer = new Installer($options);
$results = $installer->jsonSerialize();
debug_install_array('Result', $results);
process_install_errors($results);
$install_mode = 'no';
switch ($installer->getMode()) {
case Installer::MODE_INSTALL:
$install_mode = 'INSTALL CORE';
break;
case Installer::MODE_POLLER:
$install_mode = 'INSTALL POLLER';
break;
case Installer::MODE_UPGRADE:
$install_mode = 'UPGRADE';
break;
case Installer::MODE_DOWNGRADE:
$install_mode = 'DOWNGRADE';
break;
}
log_install_always('cli', 'Installer prepared for ' . $install_mode . ' action');
$message = '';
if ($installer->getStep() == Installer::STEP_INSTALL_CONFIRM && $should_install) {
$time = '';
if ($force_install) {
$time = '-b';
}
log_install_always('cli', 'Starting installation...');
Installer::beginInstall($time, $installer);
log_install_always('cli', 'Finished installation...');
}
$step = $installer->getStep();
$exitCode = 0;
log_install_high('cli','getStep(): ' . $step);
switch ($installer->getStep()) {
case Installer::STEP_INSTALL:
log_install_always('cli', 'An Installation was already in progress');
$exitCode = 1;
break;
case Installer::STEP_INSTALL_CONFIRM:
log_install_always('cli', 'No errors were detected. Install not performed as --install not specified');
break;
case Installer::STEP_ERROR:
log_install_always('cli', 'One or more errors occurred during install, please refer to log files');
process_install_errors(['Errors'=>$installer->getErrors()]);
$exitCode = 1;
break;
case Installer::STEP_COMPLETE:
log_install_always('cli', 'Installation has now completed, you may launch the web console');
break;
default:
log_install_always('cli', 'Unexpected step (' . $installer->getStep() . ')');
$exitCode = 1;
break;
}
print PHP_EOL;
} finally {
unregister_process('install', 'master', 0);
}
exit($exitCode);
/**
* get_install_option - gets the install options from a json file
*/
function get_install_option(array &$options, string $file, bool $json = true) : void {
if (empty($file)) {
printf('ERROR: You specified an invalid options file. Unable to import options' . PHP_EOL);
exit(1);
}
if ($json) {
if (!file_exists($file)) {
printf('ERROR: File %s does not exist ' . PHP_EOL, $file);
exit(1);
}
if (is_readable($file)) {
$contents = file_get_contents($file);
if (empty($contents)) {
printf('ERROR: Unable to import options from file \'%s\'' . PHP_EOL, $file);
exit(1);
}
if (function_exists('json_validate')) {
if (!json_validate($contents)) {
printf('ERROR: Failed to decode json options in file \'%s\'' . PHP_EOL, $file);
exit(1);
}
}
$options = json_decode($contents, true);
if (!cacti_sizeof($options)) {
printf('ERROR: Failed to decode options in file \'%s\'' . PHP_EOL, $file);
exit(1);
}
} else {
printf('ERROR: Unable to read file \'%s\'' . PHP_EOL, $file);
exit(1);
}
} else {
if (!file_exists($file)) {
printf('ERROR: File \'%s\' does not exist ' . PHP_EOL, $file);
exit(1);
}
if (is_readable($file)) {
$temp_options = parse_ini_file($file);
if (is_array($temp_options) && sizeof($temp_options) > 0) {
$options = $temp_options;
} else {
printf('ERROR: Unable to import options from file \'%s\'' . PHP_EOL, $file);
exit(1);
}
} else {
printf('ERROR: Unable to read file \'%s\'' . PHP_EOL, $file);
exit(1);
}
}
}
/**
* set_install_option - sets and optional displays debug line of action
*/
function set_install_option(array &$options, string $key, string $display_name, mixed $value) : void {
$options[$key] = $value;
log_install_high('cli',sprintf('Setting %s to \'%s\'', $display_name, $value));
}
/**
* set_install_multioption - sets sub-options that have multiple key/value combinations with optional prefix
*/
function set_install_multioption(array &$options,
string $key,
string $display_name,
mixed $value,
string $prefix,
bool $replace_dots = false) : void {
$option_pos = strpos($value, ':');
if ($option_pos !== false) {
if (!isset($options[$key])) {
$options[$key] = [];
} elseif (!is_array($options[$key])) {
printf('ERROR: Invalid %s option group%s', $display_name, PHP_EOL);
exit(1);
}
$option_name = trim(substr($value, 0, $option_pos));
if ($replace_dots) {
$option_name = str_replace('.', '_', $option_name);
}
$prefix_len = strlen($prefix);
if ($prefix_len > 0 && substr($option_name, 0, $prefix_len) == $prefix) {
$option_key = $option_name;
$option_name = substr($option_key, $prefix_len);
} else {
$option_key = $prefix . $option_name;
}
$option_value = trim(substr($value, $option_pos + 1));
set_install_option($options[$key], $option_key, $display_name . ' \'' . $option_name . '\'', $option_value);
} else {
print 'ERROR: Invalid ' . $display_name . ' value ' . $value . PHP_EOL . PHP_EOL;
exit(1);
}
}
function debug_install_array(string $parent, array $contents, int $indent = 0) : void {
$hasContents = false;
foreach ($contents as $key => $value) {
if (is_array($value) || is_object($value)) {
debug_install_array($parent . '.' . $key, $value, $indent + 1);
} else {
$hasContents = true;
log_install_debug('cli', $parent . '.' . $key . ': ' . $value);
}
}
if (!$hasContents) {
log_install_debug('cli',$parent . ' (no items)');
}
}
function process_install_errors(array $results) : void {
if (isset($results['Errors']) && cacti_sizeof($results['Errors']) > 0) {
$errors = $results['Errors'];
$count = 0;
$sections = 0;
foreach ($errors as $error_section => $error_array) {
$sections++;
print $error_section . PHP_EOL;
foreach ($error_array as $error_key => $error) {
$count++;
print $error_key . ' Error #' . $count . ' - ' . $error . PHP_EOL;
}
}
print PHP_EOL .
'Unable to continue as ' .
$count .
' issue' .
($count == 1 ? '' : 's') .
' in ' .
$sections .
' section' .
($sections == 1 ? '' : 's') .
' were found.' .
PHP_EOL;
// exit does not execute the caller's finally block.
unregister_process('install', 'master', 0);
exit(1);
}
}
/**
* display_version - displays version information
*
* @return void
*/
function display_version() : void {
$version = get_cacti_cli_version();
print "Cacti Install Utility, Version $version, " . COPYRIGHT_YEARS . PHP_EOL;
}
/**
* display_help - displays the usage of the function
*
* @return void
*/
function display_help() : void {
print PHP_EOL . 'usage: install_cacti.php [--debug] --accept-eula ' . PHP_EOL;
print ' [--automationmode=] [--automationrange=] [--cron=]' . PHP_EOL;
print ' [--language=] [--mode=] [--profile=] [--path=]' . PHP_EOL;
print ' [--rrdtool=] [--snmp=] [--table=] [--template=]' . PHP_EOL;
print ' [--theme=] [--adminemailaddress=] [--notifyadmin=]' . PHP_EOL;
print PHP_EOL . 'A utility to install/upgrade Cacti to the currently sourced version' . PHP_EOL;
print PHP_EOL . 'Flags:' . PHP_EOL;
print ' -d | --debug - Display verbose output during execution' . PHP_EOL;
print ' -h | --help - Display this help' . PHP_EOL;
print ' -v | --version - Display version' . PHP_EOL;
print ' -f | --force - Override certain safety checks' . PHP_EOL;
print PHP_EOL . 'Required:' . PHP_EOL;
print ' --accept-eula - Accept the End User License Agreement' . PHP_EOL;
print ' --install - Perform the installation' . PHP_EOL;
print PHP_EOL . 'Optional:' . PHP_EOL;
print ' -am | --automationmode - Enable/Disable automatic network discovery' . PHP_EOL;
print ' -ar | --automationrange - Set automatic network discovery subnet' . PHP_EOL;
print ' -c | --cron - Set the cron interval' . PHP_EOL;
print ' -l | --lang[uage] - Set system language' . PHP_EOL;
print ' -m | --mode - Set the installation mode' . PHP_EOL;
print ' -p | --profile - Set the default Data Collector profile' . PHP_EOL;
print ' -r | --rrdtool - Set the RRD Tool version' . PHP_EOL;
print ' -t | --theme - Set system theme' . PHP_EOL;
print ' -i | --ini - Load settings from ini file' . PHP_EOL;
print ' -j | --json - Load settings from json file' . PHP_EOL;
print ' -e | --adminemailaddress - Set admin email address' . PHP_EOL;
print ' -n | --notifyadmin - Enable/Disable email notify primary admin of issues (true/false)' . PHP_EOL;
print PHP_EOL . 'Multi-value optional:' . PHP_EOL;
print ' These options may be used more than once to apply multiple values. All' . PHP_EOL;
print ' values should be in "option_key:option_value" format (see below). If an' . PHP_EOL;
print ' option has a prefix, this is optional and is automatically added to the' . PHP_EOL;
print ' the option_key specified if it does not start with that prefix' . PHP_EOL . PHP_EOL;
print ' Note: reusing an option_key will replace its value with the last one' . PHP_EOL;
print ' specified.' . PHP_EOL . PHP_EOL;
print ' --path - Sets path locations. Example: ' . PHP_EOL;
print ' --path=cactilog:/usr/share/cacti/log/cacti.log' . PHP_EOL;
print ' --path=cactilog:c:\cacti\log\cacti.log' . PHP_EOL;
print ' Prefix: path_' . PHP_EOL;
print PHP_EOL;
print ' --snmp - Sets default snmp options. Example:' . PHP_EOL;
print ' --snmp=SnmpCommunity:public' . PHP_EOL;
print ' --snmp=Community:public' . PHP_EOL;
print ' Prefix: Snmp' . PHP_EOL;
print PHP_EOL;
print ' Note: the following two options expect a value of either 1 (Action) or' . PHP_EOL;
print ' 0 (Skip)' . PHP_EOL;
print PHP_EOL;
print ' --template - Sets templates to be installed. Example:' . PHP_EOL;
print ' --template=Cisco_Router.xml.gz:1' . PHP_EOL;
print PHP_EOL;
print ' --table - Selects a table to be converted to UTF8. Example:' . PHP_EOL;
print ' --table=plugin_config:1' . PHP_EOL;
print PHP_EOL;
}