add possibility to ask for potential update question in CLI updater and also pass them as options to override them; check whether mysql-user exists prior to DROP USER for mysql < 5.7 (as it is missing IF EXISTS options)
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
This commit is contained in:
@@ -34,7 +34,7 @@ $return = [];
|
|||||||
if (Update::versionInUpdate($current_db_version, '202004140')) {
|
if (Update::versionInUpdate($current_db_version, '202004140')) {
|
||||||
$has_preconfig = true;
|
$has_preconfig = true;
|
||||||
$description = 'Froxlor can now optionally validate the dns entries of domains that request Lets Encrypt certificates to reduce dns-related problems (e.g. freshly registered domain or updated a-record).';
|
$description = 'Froxlor can now optionally validate the dns entries of domains that request Lets Encrypt certificates to reduce dns-related problems (e.g. freshly registered domain or updated a-record).';
|
||||||
$question = '<strong>Validate DNS of domains when using Lets Encrypt ';
|
$question = '<strong>Validate DNS of domains when using Lets Encrypt</strong>';
|
||||||
$return['system_le_domain_dnscheck'] = [
|
$return['system_le_domain_dnscheck'] = [
|
||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
'value' => 1,
|
'value' => 1,
|
||||||
|
|||||||
@@ -28,13 +28,17 @@ namespace Froxlor\Cli;
|
|||||||
use Exception;
|
use Exception;
|
||||||
use Froxlor\Froxlor;
|
use Froxlor\Froxlor;
|
||||||
use Froxlor\Install\AutoUpdate;
|
use Froxlor\Install\AutoUpdate;
|
||||||
|
use Froxlor\Install\Preconfig;
|
||||||
use Froxlor\Install\Update;
|
use Froxlor\Install\Update;
|
||||||
use Froxlor\Settings;
|
use Froxlor\Settings;
|
||||||
use Froxlor\System\Mailer;
|
use Froxlor\System\Mailer;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||||
|
use Symfony\Component\Console\Question\Question;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
final class UpdateCommand extends CliCommand
|
final class UpdateCommand extends CliCommand
|
||||||
{
|
{
|
||||||
@@ -44,6 +48,8 @@ final class UpdateCommand extends CliCommand
|
|||||||
$this->setName('froxlor:update');
|
$this->setName('froxlor:update');
|
||||||
$this->setDescription('Check for newer version and update froxlor');
|
$this->setDescription('Check for newer version and update froxlor');
|
||||||
$this->addOption('check-only', 'c', InputOption::VALUE_NONE, 'Only check for newer version and exit')
|
$this->addOption('check-only', 'c', InputOption::VALUE_NONE, 'Only check for newer version and exit')
|
||||||
|
->addOption('show-update-options', 'o', InputOption::VALUE_NONE, 'Show possible update option parameter for the update if any. Only usable in combination with "check-only".')
|
||||||
|
->addOption('update-options', 'O', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Parameter list of update options.')
|
||||||
->addOption('database', 'd', InputOption::VALUE_NONE, 'Only run database updates in case updates are done via apt or manually.')
|
->addOption('database', 'd', InputOption::VALUE_NONE, 'Only run database updates in case updates are done via apt or manually.')
|
||||||
->addOption('mail-notify', 'm', InputOption::VALUE_NONE, 'Additionally inform administrator via email if a newer version was found')
|
->addOption('mail-notify', 'm', InputOption::VALUE_NONE, 'Additionally inform administrator via email if a newer version was found')
|
||||||
->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for download, extract and database-update, just do it (if not --check-only is set)')
|
->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for download, extract and database-update, just do it (if not --check-only is set)')
|
||||||
@@ -63,9 +69,13 @@ final class UpdateCommand extends CliCommand
|
|||||||
$output->writeln('<info>' . lng('update.dbupdate_required') . '</>');
|
$output->writeln('<info>' . lng('update.dbupdate_required') . '</>');
|
||||||
if ($input->getOption('check-only')) {
|
if ($input->getOption('check-only')) {
|
||||||
$output->writeln('<comment>Doing nothing because of "check-only" flag.</>');
|
$output->writeln('<comment>Doing nothing because of "check-only" flag.</>');
|
||||||
|
$this->askUpdateOptions($input, $output, null, false);
|
||||||
} else {
|
} else {
|
||||||
$yestoall = $input->getOption('yes-to-all') !== false;
|
$yestoall = $input->getOption('yes-to-all') !== false;
|
||||||
$helper = $this->getHelper('question');
|
$helper = $this->getHelper('question');
|
||||||
|
|
||||||
|
$this->askUpdateOptions($input, $output, $helper, $yestoall);
|
||||||
|
|
||||||
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
|
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
|
||||||
if ($yestoall || $helper->ask($input, $output, $question)) {
|
if ($yestoall || $helper->ask($input, $output, $question)) {
|
||||||
$result = $this->runUpdate($output, true);
|
$result = $this->runUpdate($output, true);
|
||||||
@@ -101,7 +111,7 @@ final class UpdateCommand extends CliCommand
|
|||||||
}
|
}
|
||||||
// there is a new version
|
// there is a new version
|
||||||
if ($input->getOption('check-only')) {
|
if ($input->getOption('check-only')) {
|
||||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel') . ' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||||
} else {
|
} else {
|
||||||
$text = lng('admin.newerversionavailable') . ' ' . lng('admin.newerversiondetails', [AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
$text = lng('admin.newerversionavailable') . ' ' . lng('admin.newerversiondetails', [AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||||
}
|
}
|
||||||
@@ -152,6 +162,7 @@ final class UpdateCommand extends CliCommand
|
|||||||
// check whether we only wanted to check
|
// check whether we only wanted to check
|
||||||
if ($input->getOption('check-only')) {
|
if ($input->getOption('check-only')) {
|
||||||
//$output->writeln('<comment>Not proceeding as "check-only" is specified</>');
|
//$output->writeln('<comment>Not proceeding as "check-only" is specified</>');
|
||||||
|
$this->askUpdateOptions($input, $output, null, false);
|
||||||
return $result;
|
return $result;
|
||||||
} else {
|
} else {
|
||||||
$yestoall = $input->getOption('yes-to-all') !== false;
|
$yestoall = $input->getOption('yes-to-all') !== false;
|
||||||
@@ -174,6 +185,9 @@ final class UpdateCommand extends CliCommand
|
|||||||
if ($auex == 0) {
|
if ($auex == 0) {
|
||||||
$output->writeln("<info>Froxlor files updated successfully.</>");
|
$output->writeln("<info>Froxlor files updated successfully.</>");
|
||||||
$result = self::SUCCESS;
|
$result = self::SUCCESS;
|
||||||
|
|
||||||
|
$this->askUpdateOptions($input, $output, $helper, $yestoall);
|
||||||
|
|
||||||
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
|
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
|
||||||
if ($yestoall || $helper->ask($input, $output, $question)) {
|
if ($yestoall || $helper->ask($input, $output, $question)) {
|
||||||
$result = $this->runUpdate($output, true);
|
$result = $this->runUpdate($output, true);
|
||||||
@@ -195,12 +209,141 @@ final class UpdateCommand extends CliCommand
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @param $helper
|
||||||
|
* @param bool $yestoall
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function askUpdateOptions(InputInterface $input, OutputInterface $output, $helper, bool $yestoall = false)
|
||||||
|
{
|
||||||
|
// check for preconfigs
|
||||||
|
$preconfig = Preconfig::getPreConfig(true);
|
||||||
|
$show_options_only = $input->getOption('show-update-options') !== false;
|
||||||
|
if (!is_null($helper) && $show_options_only) {
|
||||||
|
$output->writeln('<comment>Unsetting "show-update-options" due to not being called with "check-only".</>');
|
||||||
|
$show_options_only = false;
|
||||||
|
}
|
||||||
|
$update_options = [];
|
||||||
|
// set parameters
|
||||||
|
$uOptions = $input->getOption('update-options');
|
||||||
|
if (!empty($uOptions)) {
|
||||||
|
$options_value = [];
|
||||||
|
foreach ($uOptions as $givenOption) {
|
||||||
|
$optVal = explode("=", $givenOption);
|
||||||
|
if (count($optVal) == 2) {
|
||||||
|
$options_value[$optVal[0]] = $optVal[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($preconfig)) {
|
||||||
|
krsort($preconfig);
|
||||||
|
foreach ($preconfig as $section) {
|
||||||
|
if (!$show_options_only) {
|
||||||
|
$output->writeln("<info>Updater questions for " . $section['title'] . "</>");
|
||||||
|
}
|
||||||
|
foreach ($section['fields'] as $update_field => $metainfo) {
|
||||||
|
if (isset($options_value[$update_field])) {
|
||||||
|
$output->writeln('Setting given parameter "' . $update_field . '" to "' . $options_value[$update_field] . '"');
|
||||||
|
$_POST[$update_field] = $options_value[$update_field];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$default = null;
|
||||||
|
$question_text = html_entity_decode(strip_tags($metainfo['label']), ENT_QUOTES | ENT_IGNORE, "UTF-8");
|
||||||
|
if ($metainfo['type'] == 'checkbox') {
|
||||||
|
$default = (int)$metainfo['checked'];
|
||||||
|
if ($show_options_only) {
|
||||||
|
$update_options[] = [
|
||||||
|
'name' => $update_field,
|
||||||
|
'question' => $question_text,
|
||||||
|
'default' => $default,
|
||||||
|
'choices' => '0: No' . PHP_EOL . '1: Yes' . PHP_EOL
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$question = new ConfirmationQuestion($question_text . ' [' . ($metainfo['checked'] ? 'yes' : 'no') . '] ', (bool)$metainfo['checked'], '/^(y|j)/i');
|
||||||
|
}
|
||||||
|
} elseif ($metainfo['type'] == 'select') {
|
||||||
|
$default = $metainfo['selected'];
|
||||||
|
$choices = "";
|
||||||
|
foreach (array_values($metainfo['select_var'] ?? []) as $index => $choice) {
|
||||||
|
$choices .= $index . ': ' . $choice . PHP_EOL;
|
||||||
|
}
|
||||||
|
if ($show_options_only) {
|
||||||
|
$update_options[] = [
|
||||||
|
'name' => $update_field,
|
||||||
|
'question' => $question_text,
|
||||||
|
'default' => !empty($default) ? $default : '-',
|
||||||
|
'choices' => $choices
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$question = new ChoiceQuestion(
|
||||||
|
$question_text,
|
||||||
|
array_values($metainfo['select_var'] ?? []),
|
||||||
|
$metainfo['selected']
|
||||||
|
);
|
||||||
|
$question->setValidator(function ($answer) use ($metainfo): string {
|
||||||
|
$key = array_keys($metainfo['select_var'])[(int)$answer] ?? false; // Find the key based on the selected value
|
||||||
|
if ($key === false) {
|
||||||
|
throw new \RuntimeException('Invalid selection.');
|
||||||
|
}
|
||||||
|
return $key;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} elseif ($metainfo['type'] == 'text') {
|
||||||
|
$default = $metainfo['value'] ?? '';
|
||||||
|
if ($show_options_only) {
|
||||||
|
$update_options[] = [
|
||||||
|
'name' => $update_field,
|
||||||
|
'question' => $question_text,
|
||||||
|
'default' => $default,
|
||||||
|
'choices' => PHP_EOL
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$question = new Question($question_text . (!empty($metainfo['value']) ? ' [' . $metainfo['value'] . ']' : ''), $default);
|
||||||
|
$question->setValidator(function (string $answer) use ($metainfo): string {
|
||||||
|
if (($metainfo['mandatory'] ?? false) && empty($answer)) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
'Answer cannot be empty'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!empty($metainfo['pattern'] ?? "") && !preg_match("/" . $metainfo['pattern'] . "/", $answer)) {
|
||||||
|
throw new \RuntimeException('Answer does not seem to be in valid format');
|
||||||
|
}
|
||||||
|
return $answer;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$output->writeln("<error>Unknown type " . $metainfo['type'] . "</error>");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$show_options_only) {
|
||||||
|
if ($yestoall) {
|
||||||
|
$_POST[$update_field] = $default;
|
||||||
|
} else {
|
||||||
|
$_POST[$update_field] = $helper->ask($input, $output, $question);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($show_options_only) {
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$io->table(
|
||||||
|
['Parameter', 'Description', 'Default', 'Choices'],
|
||||||
|
$update_options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function mailNotify(InputInterface $input, OutputInterface $output)
|
private function mailNotify(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
if ($input->getOption('mail-notify')) {
|
if ($input->getOption('mail-notify')) {
|
||||||
$last_check_version = Settings::Get('system.update_notify_last');
|
$last_check_version = Settings::Get('system.update_notify_last');
|
||||||
if (Update::versionInUpdate($last_check_version, AutoUpdate::getFromResult('version'))) {
|
if (Update::versionInUpdate($last_check_version, AutoUpdate::getFromResult('version'))) {
|
||||||
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel').' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
$text = lng('update.uc_newinfo', [(Settings::Get('system.update_channel') != 'stable' ? Settings::Get('system.update_channel') . ' ' : ''), AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
|
||||||
$mail = new Mailer(true);
|
$mail = new Mailer(true);
|
||||||
$mail->Body = $text;
|
$mail->Body = $text;
|
||||||
$mail->Subject = "[froxlor] " . lng('update.notify_subject');
|
$mail->Subject = "[froxlor] " . lng('update.notify_subject');
|
||||||
|
|||||||
@@ -187,21 +187,23 @@ class DbManagerMySQL
|
|||||||
*/
|
*/
|
||||||
public function deleteUser(string $username, string $host)
|
public function deleteUser(string $username, string $host)
|
||||||
{
|
{
|
||||||
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.0.2', '<')) {
|
if ($this->userExistsOnHost($username, $host)) {
|
||||||
// Revoke privileges (only required for MySQL 4.1.2 - 5.0.1)
|
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.0.2', '<')) {
|
||||||
$stmt = Database::prepare("REVOKE ALL PRIVILEGES ON * . * FROM `" . $username . "`@`" . $host . "`");
|
// Revoke privileges (only required for MySQL 4.1.2 - 5.0.1)
|
||||||
Database::pexecute($stmt);
|
$stmt = Database::prepare("REVOKE ALL PRIVILEGES ON * . * FROM `" . $username . "`@`" . $host . "`");
|
||||||
|
Database::pexecute($stmt);
|
||||||
|
}
|
||||||
|
// as of MySQL 5.0.2 this also revokes privileges. (requires MySQL 4.1.2+)
|
||||||
|
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.7.0', '<')) {
|
||||||
|
$stmt = Database::prepare("DROP USER :username@:host");
|
||||||
|
} else {
|
||||||
|
$stmt = Database::prepare("DROP USER IF EXISTS :username@:host");
|
||||||
|
}
|
||||||
|
Database::pexecute($stmt, [
|
||||||
|
"username" => $username,
|
||||||
|
"host" => $host
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
// as of MySQL 5.0.2 this also revokes privileges. (requires MySQL 4.1.2+)
|
|
||||||
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.7.0', '<')) {
|
|
||||||
$stmt = Database::prepare("DROP USER :username@:host");
|
|
||||||
} else {
|
|
||||||
$stmt = Database::prepare("DROP USER IF EXISTS :username@:host");
|
|
||||||
}
|
|
||||||
Database::pexecute($stmt, [
|
|
||||||
"username" => $username,
|
|
||||||
"host" => $host
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -85,27 +85,31 @@ class Preconfig
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function getPreConfig
|
* Function getPreConfig
|
||||||
*
|
*
|
||||||
* outputs various form-field-arrays before the update process
|
* outputs various form-field-arrays before the update process
|
||||||
* can be continued (asks for agreement whatever is being asked)
|
* can be continued (asks for agreement whatever is being asked)
|
||||||
*
|
*
|
||||||
|
* @param bool $no_check
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function getPreConfig(): array
|
public static function getPreConfig(bool $no_check = false): array
|
||||||
{
|
{
|
||||||
$preconfig = new self();
|
$preconfig = new self();
|
||||||
|
|
||||||
if ($preconfig->hasPreConfig()) {
|
if ($preconfig->hasPreConfig()) {
|
||||||
$agree = [
|
if (!$no_check) {
|
||||||
'title' => 'Check',
|
$agree = [
|
||||||
'fields' => [
|
'title' => 'Check',
|
||||||
'update_changesagreed' => ['mandatory' => true, 'type' => 'checkrequired', 'value' => 1, 'label' => '<strong>I have read the update notifications above and I am aware of the changes made to my system.</strong>'],
|
'fields' => [
|
||||||
'update_preconfig' => ['type' => 'hidden', 'value' => 1]
|
'update_changesagreed' => ['mandatory' => true, 'type' => 'checkrequired', 'value' => 1, 'label' => '<strong>I have read the update notifications above and I am aware of the changes made to my system.</strong>'],
|
||||||
]
|
'update_preconfig' => ['type' => 'hidden', 'value' => 1]
|
||||||
];
|
]
|
||||||
$preconfig->addToPreConfig($agree);
|
];
|
||||||
|
$preconfig->addToPreConfig($agree);
|
||||||
|
}
|
||||||
return $preconfig->getData();
|
return $preconfig->getData();
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
Reference in New Issue
Block a user