diff --git a/actions/admin/settings/120.system.php b/actions/admin/settings/120.system.php
index ba3dd152..23c60083 100644
--- a/actions/admin/settings/120.system.php
+++ b/actions/admin/settings/120.system.php
@@ -108,6 +108,19 @@ return [
'default' => false,
'save_method' => 'storeSettingField'
],
+ 'update_channel' => [
+ 'label' => lng('serversettings.update_channel'),
+ 'settinggroup' => 'system',
+ 'varname' => 'update_channel',
+ 'type' => 'select',
+ 'default' => 'stable',
+ 'select_var' => [
+ 'stable' => lng('serversettings.uc_stable'),
+ 'testing' => lng('serversettings.uc_testing')
+ ],
+ 'save_method' => 'storeSettingField',
+ 'advanced_mode' => true
+ ],
'system_validatedomain' => [
'label' => lng('serversettings.validate_domain'),
'settinggroup' => 'system',
diff --git a/admin_autoupdate.php b/admin_autoupdate.php
index 341e28b2..827031ae 100644
--- a/admin_autoupdate.php
+++ b/admin_autoupdate.php
@@ -29,32 +29,12 @@ require __DIR__ . '/lib/init.php';
use Froxlor\Froxlor;
use Froxlor\FroxlorLogger;
use Froxlor\Http\HttpClient;
+use Froxlor\Install\AutoUpdate;
use Froxlor\Settings;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Response;
-// define update-uri
-define('UPDATE_URI', "https://version.froxlor.org/Froxlor/api/" . Froxlor::VERSION);
-define('RELEASE_URI', "https://autoupdate.froxlor.org/froxlor-{version}.zip");
-define('CHECKSUM_URI', "https://autoupdate.froxlor.org/froxlor-{version}.zip.sha256");
-
if ($page != 'error') {
- // check for archive-stuff
- if (!extension_loaded('zip')) {
- Response::redirectTo($filename, [
- 'page' => 'error',
- 'errno' => 2
- ]);
- }
-
- // 0.11.x requires 7.4 at least
- if (version_compare("7.4.0", PHP_VERSION, ">=")) {
- Response::redirectTo($filename, [
- 'page' => 'error',
- 'errno' => 10
- ]);
- }
-
// check for webupdate to be enabled
if (Settings::Config('enable_webupdate') != true) {
Response::redirectTo($filename, [
@@ -71,170 +51,98 @@ if ($page == 'overview') {
// check for new version
try {
- $latestversion = HttpClient::urlGet(UPDATE_URI, true, 3);
+ $result = AutoUpdate::checkVersion();
} catch (Exception $e) {
- Response::dynamicError("Version-check currently unavailable, please try again later");
+ Response::dynamicError($e->getMessage());
}
- $latestversion = explode('|', $latestversion);
- if (is_array($latestversion) && count($latestversion) >= 1) {
- $_version = $latestversion[0];
- $_message = isset($latestversion[1]) ? $latestversion[1] : '';
- $_link = isset($latestversion[2]) ? $latestversion[2] : htmlspecialchars($filename . '?page=' . urlencode($page) . '&lookfornewversion=yes');
-
- // add the branding so debian guys are not gettings confused
- // about their version-number
- $version_label = $_version . Froxlor::BRANDING;
- $version_link = $_link;
- $message_addinfo = $_message;
-
- // not numeric -> error-message
- if (!preg_match('/^((\d+\\.)(\d+\\.)(\d+\\.)?(\d+)?(\-(svn|dev|rc)(\d+))?)$/', $_version)) {
- // check for customized version to not output
- // "There is a newer version of froxlor" besides the error-message
- Response::redirectTo($filename, [
- 'page' => 'error',
- 'errno' => 3
- ]);
- } elseif (Froxlor::versionCompare2(Froxlor::VERSION, $_version) == -1) {
- // there is a newer version - yay
- $isnewerversion = 1;
- } else {
- // nothing new
- $isnewerversion = 0;
- }
+ if ($result == 1) {
// anzeige über version-status mit ggfls. formular
// zum update schritt #1 -> download
- if ($isnewerversion == 1) {
- $text = 'There is a newer version available. Update to version ' . $_version . ' now?
(Your current version is: ' . Froxlor::VERSION . ')';
+ $text = lng('admin.newerversionavailable') . ' ' . lng('admin.newerversiondetails', [AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
- $upd_formfield = [
- 'updates' => [
- 'title' => lng('update.update'),
- 'image' => 'fa-solid fa-download',
- 'sections' => [
- 'section_autoupd' => [
- 'fields' => [
- 'newversion' => ['type' => 'hidden', 'value' => $_version]
- ]
- ]
- ],
- 'buttons' => [
- [
- 'class' => 'btn-outline-secondary',
- 'label' => lng('panel.cancel'),
- 'type' => 'reset'
- ],
- [
- 'label' => lng('update.proceed')
+ $upd_formfield = [
+ 'updates' => [
+ 'title' => lng('update.update'),
+ 'image' => 'fa-solid fa-download',
+ 'sections' => [
+ 'section_autoupd' => [
+ 'fields' => [
+ 'newversion' => ['type' => 'hidden', 'value' => AutoUpdate::getFromResult('version')]
]
]
+ ],
+ 'buttons' => [
+ [
+ 'class' => 'btn-outline-secondary',
+ 'label' => lng('panel.cancel'),
+ 'type' => 'reset'
+ ],
+ [
+ 'label' => lng('update.proceed')
+ ]
]
- ];
+ ]
+ ];
- UI::view('user/form-note.html.twig', [
- 'formaction' => $linker->getLink(['section' => 'autoupdate', 'page' => 'getdownload']),
- 'formdata' => $upd_formfield['updates'],
- // alert
- 'type' => 'warning',
- 'alert_msg' => $text
- ]);
- } elseif ($isnewerversion == 0) {
- // all good
- Response::standardSuccess('noupdatesavail');
+ UI::view('user/form-note.html.twig', [
+ 'formaction' => $linker->getLink(['section' => 'autoupdate', 'page' => 'getdownload']),
+ 'formdata' => $upd_formfield['updates'],
+ // alert
+ 'type' => 'warning',
+ 'alert_msg' => $text
+ ]);
+ } else if ($result < 0 || $result > 1) {
+ // remote errors
+ if ($result < 0) {
+ Response::dynamicError(AutoUpdate::getLastError());
} else {
- Response::standardError('customized_version');
+ Response::redirectTo($filename, [
+ 'page' => 'error',
+ 'errno' => $result
+ ]);
}
+ } else {
+ // no new version
+ Response::standardSuccess('update.noupdatesavail');
}
} // download the new archive
elseif ($page == 'getdownload') {
// retrieve the new version from the form
$newversion = isset($_POST['newversion']) ? $_POST['newversion'] : null;
+ $result = 6;
// valid?
if ($newversion !== null) {
- // define files to get
- $toLoad = str_replace('{version}', $newversion, RELEASE_URI);
- $toCheck = str_replace('{version}', $newversion, CHECKSUM_URI);
-
- // check for local destination folder
- if (!is_dir(Froxlor::getInstallDir() . '/updates/')) {
- mkdir(Froxlor::getInstallDir() . '/updates/');
- }
-
- // name archive
- $localArchive = Froxlor::getInstallDir() . '/updates/' . basename($toLoad);
-
- $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "Downloading " . $toLoad . " to " . $localArchive);
-
- // remove old archive
- if (file_exists($localArchive)) {
- @unlink($localArchive);
- }
-
- // get archive data
- try {
- HttpClient::fileGet($toLoad, $localArchive);
- } catch (Exception $e) {
+ $result = AutoUpdate::downloadZip($newversion);
+ if (!is_numeric($result)) {
+ // to the next step
Response::redirectTo($filename, [
- 'page' => 'error',
- 'errno' => 4
+ 'page' => 'extract',
+ 'archive' => $result
]);
}
-
- // validate the integrity of the downloaded file
- $_shouldsum = HttpClient::urlGet($toCheck);
- if (!empty($_shouldsum)) {
- $_t = explode(" ", $_shouldsum);
- $shouldsum = $_t[0];
- } else {
- $shouldsum = null;
- }
- $filesum = hash_file('sha256', $localArchive);
-
- if ($filesum != $shouldsum) {
- Response::redirectTo($filename, [
- 'page' => 'error',
- 'errno' => 9
- ]);
- }
-
- // to the next step
- Response::redirectTo($filename, [
- 'page' => 'extract',
- 'archive' => basename($localArchive)
- ]);
}
Response::redirectTo($filename, [
'page' => 'error',
- 'errno' => 6
+ 'errno' => $result
]);
} // extract and install new version
elseif ($page == 'extract') {
if (isset($_POST['send']) && $_POST['send'] == 'send') {
$toExtract = isset($_POST['archive']) ? $_POST['archive'] : null;
$localArchive = Froxlor::getInstallDir() . '/updates/' . $toExtract;
- // decompress from zip
- $zip = new ZipArchive();
- $res = $zip->open($localArchive);
- if ($res === true) {
- $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "Extracting " . $localArchive . " to " . Froxlor::getInstallDir());
- $zip->extractTo(Froxlor::getInstallDir());
- $zip->close();
- // success - remove unused archive
- @unlink($localArchive);
- // wait a bit before we redirect to be sure
- sleep(2);
- } else {
+ $log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "Extracting " . $localArchive . " to " . Froxlor::getInstallDir());
+ $result = AutoUpdate::extractZip($localArchive);
+ if ($result > 0) {
// error
Response::redirectTo($filename, [
'page' => 'error',
- 'errno' => 8
+ 'errno' => $result
]);
}
-
- // redirect to update-page?
+ // redirect to update-page
Response::redirectTo('admin_updates.php');
} else {
$toExtract = isset($_GET['archive']) ? $_GET['archive'] : null;
@@ -248,7 +156,7 @@ elseif ($page == 'extract') {
]);
}
- $text = 'Extract downloaded archive "' . $toExtract . '"?';
+ $text = lng('admin.extractdownloadedzip', [$toExtract]);
$upd_formfield = [
'updates' => [
diff --git a/bin/froxlor-cli b/bin/froxlor-cli
index 664f6be8..80b62ebc 100755
--- a/bin/froxlor-cli
+++ b/bin/froxlor-cli
@@ -31,6 +31,7 @@ use Froxlor\Cli\RunApiCommand;
use Froxlor\Cli\ConfigServices;
use Froxlor\Cli\PhpSessionclean;
use Froxlor\Cli\SwitchServerIp;
+use Froxlor\Cli\UpdateCommand;
use Froxlor\Froxlor;
// validate correct php version
@@ -51,4 +52,5 @@ $application->add(new RunApiCommand());
$application->add(new ConfigServices());
$application->add(new PhpSessionclean());
$application->add(new SwitchServerIp());
+$application->add(new UpdateCommand());
$application->run();
diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php
index 711e821d..e043f861 100644
--- a/install/froxlor.sql.php
+++ b/install/froxlor.sql.php
@@ -699,6 +699,7 @@ opcache.validate_timestamps'),
('system', 'froxlorusergroup_gid', ''),
('system', 'acmeshpath', '/root/.acme.sh/acme.sh'),
('system', 'distribution', ''),
+ ('system', 'update_channel', 'stable'),
('api', 'enabled', '0'),
('2fa', 'enabled', '1'),
('panel', 'decimal_places', '4'),
diff --git a/install/updates/froxlor/update_0.11.inc.php b/install/updates/froxlor/update_0.11.inc.php
index d968e7d2..a4396f3b 100644
--- a/install/updates/froxlor/update_0.11.inc.php
+++ b/install/updates/froxlor/update_0.11.inc.php
@@ -133,6 +133,7 @@ if (Froxlor::isFroxlorVersion('0.10.99')) {
Settings::AddNew("panel.settings_mode", $panel_settings_mode);
$system_distribution = isset($_POST['system_distribution']) ? $_POST['system_distribution'] : '';
Settings::AddNew("system.distribution", $system_distribution);
+ Settings::AddNew("system.update_channel", 'stable');
Update::lastStepStatus(0);
Update::showUpdateStep("Adjusting existing settings");
diff --git a/lib/Froxlor/Cli/UpdateCommand.php b/lib/Froxlor/Cli/UpdateCommand.php
new file mode 100644
index 00000000..3cf01421
--- /dev/null
+++ b/lib/Froxlor/Cli/UpdateCommand.php
@@ -0,0 +1,176 @@
+
+ * @license https://files.froxlor.org/misc/COPYING.txt GPLv2
+ */
+
+namespace Froxlor\Cli;
+
+use Exception;
+use Froxlor\Froxlor;
+use Froxlor\Install\AutoUpdate;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\ConfirmationQuestion;
+
+final class UpdateCommand extends CliCommand
+{
+
+ protected function configure()
+ {
+ $this->setName('froxlor:update');
+ $this->setDescription('Check for newer version and update froxlor');
+ $this->addOption('check-only', 'c', InputOption::VALUE_NONE, 'Only check for newer version and exit')
+ ->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('integer-return', 'i', InputOption::VALUE_NONE, 'Return integer whether a new version is available or not (implies --check-only). Useful for programmatic use.');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $result = self::SUCCESS;
+
+ $result = $this->validateRequirements($input, $output);
+
+ require Froxlor::getInstallDir() . '/lib/functions.php';
+
+ // version check
+ $newversionavail = false;
+ if ($result == self::SUCCESS) {
+ try {
+ $aucheck = AutoUpdate::checkVersion();
+
+ if ($aucheck == 1) {
+ if ($input->getOption('integer-return')) {
+ $output->write(1);
+ return self::SUCCESS;
+ }
+ // there is a new version
+ $text = lng('admin.newerversionavailable') . ' ' . lng('admin.newerversiondetails', [AutoUpdate::getFromResult('version'), Froxlor::VERSION]);
+ $text = str_replace("
", " ", $text);
+ $text = str_replace("", "", $text);
+ $text = str_replace("", "", $text);
+ $newversionavail = true;
+ $output->writeln('' . $text . '>');
+ $result = self::SUCCESS;
+ } else if ($aucheck < 0 || $aucheck > 1) {
+ if ($input->getOption('integer-return')) {
+ $output->write(-1);
+ return self::INVALID;
+ }
+ // errors
+ if ($aucheck < 0) {
+ $output->writeln('' . AutoUpdate::getLastError() . '>');
+ } else {
+ $errmsg = 'error.autoupdate_' . $aucheck;
+ if ($aucheck == 3) {
+ $errmsg = 'error.customized_version';
+ }
+ $output->writeln('' . lng($errmsg) . '>');
+ }
+ $result = self::INVALID;
+ } else {
+ if ($input->getOption('integer-return')) {
+ $output->write(0);
+ return self::SUCCESS;
+ }
+ // no new version
+ $output->writeln('' . lng('update.noupdatesavail') . '>');
+ $result = self::SUCCESS;
+ }
+ } catch (Exception $e) {
+ if ($input->getOption('integer-return')) {
+ $output->write(-1);
+ return self::FAILURE;
+ }
+ $output->writeln('' . $e->getMessage() . '>');
+ $result = self::FAILURE;
+ }
+ }
+
+ // if there's a newer version, proceed
+ if ($result == self::SUCCESS && $newversionavail) {
+
+ // check whether we only wanted to check
+ if ($input->getOption('check-only')) {
+ //$output->writeln('Not proceeding as "check-only" is specified>');
+ return $result;
+ } else {
+ $yestoall = $input->getOption('yes-to-all') !== false;
+
+ $helper = $this->getHelper('question');
+
+ // ask download
+ $question = new ConfirmationQuestion('Download newer version? [no] ', false, '/^(y|j)/i');
+ if ($yestoall || $helper->ask($input, $output, $question)) {
+ // do download
+ $output->writeln('Downloading...');
+ $audl = AutoUpdate::downloadZip(AutoUpdate::getFromResult('version'));
+ if (!is_numeric($audl)) {
+ // ask extract
+ $question = new ConfirmationQuestion('Extract downloaded archive? [no] ', false, '/^(y|j)/i');
+ if ($yestoall || $helper->ask($input, $output, $question)) {
+ // do extract
+ $output->writeln('Extracting...');
+ $auex = AutoUpdate::extractZip($audl);
+ if ($auex == 0) {
+ $output->writeln("Froxlor files updated successfully.>");
+ $result = self::SUCCCESS;
+ $question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
+ if ($yestoall || $helper->ask($input, $output, $question)) {
+ $result = $this->updateDatabase();
+ }
+ } else {
+ $errmsg = 'error.autoupdate_' . $auex;
+ $output->writeln('' . lng($errmsg) . '>');
+ $result = self::FAILURE;
+ }
+ }
+ } else {
+ $errmsg = 'error.autoupdate_' . $audl;
+ $output->writeln('' . lng($errmsg) . '>');
+ $result = self::FAILURE;
+ }
+ }
+ }
+ }
+ return $result;
+ }
+
+ private function updateDatabase()
+ {
+ include_once Froxlor::getInstallDir() . '/lib/tables.inc.php';
+ define('_CRON_UPDATE', 1);
+ ob_start([
+ 'this',
+ 'cleanUpdateOutput'
+ ]);
+ include_once Froxlor::getInstallDir() . '/install/updatesql.php';
+ ob_end_flush();
+ return self::SUCCCESS;
+ }
+
+ private function cleanUpdateOutput($buffer)
+ {
+ return strip_tags(preg_replace("/
/", "\n", $buffer));
+ }
+}
diff --git a/lib/Froxlor/Install/AutoUpdate.php b/lib/Froxlor/Install/AutoUpdate.php
new file mode 100644
index 00000000..eccca1d3
--- /dev/null
+++ b/lib/Froxlor/Install/AutoUpdate.php
@@ -0,0 +1,173 @@
+
+ * @license https://files.froxlor.org/misc/COPYING.txt GPLv2
+ */
+
+namespace Froxlor\Install;
+
+use Exception;
+use Froxlor\Froxlor;
+use Froxlor\Settings;
+use Froxlor\Http\HttpClient;
+
+class AutoUpdate
+{
+ // define update-uri
+ const UPDATE_URI = "https://version.froxlor.org/froxlor/api2/";
+ const RELEASE_URI = "https://autoupdate.froxlor.org/froxlor-{version}.zip";
+ const CHECKSUM_URI = "https://autoupdate.froxlor.org/froxlor-{version}.zip.sha256";
+
+ const ERR_NOZIPEXT = 2;
+ const ERR_COULDNOTSTORE = 4;
+ const ERR_ZIPNOTFOUND = 7;
+ const ERR_COULDNOTEXTRACT = 8;
+ const ERR_CHKSUM_MISMATCH = 9;
+ const ERR_MINPHP = 10;
+
+ private static $latestversion = "";
+
+ private static $lasterror = "";
+
+ /**
+ * returns status about whether there is a newer version
+ *
+ * 0 = no new version available
+ * 1 = new version available
+ * -1 = remote error message
+ * >1 = local error message
+ *
+ * @return int
+ */
+ public static function checkVersion(): int
+ {
+ $result = self::checkPrerequisites();
+
+ if ($result == 0) {
+ try {
+ $channel = '';
+ if (Settings::Get('system.update_channel') == 'testing') {
+ $channel = '/testing';
+ }
+ $latestversion = HttpClient::urlGet(self::UPDATE_URI . Froxlor::VERSION . $channel, true, 3);
+ } catch (Exception $e) {
+ throw new Exception("Version-check currently unavailable, please try again later", 504);
+ }
+
+ self::$latestversion = json_decode($latestversion, true);
+
+ if (self::$latestversion) {
+ if (!empty(self::$latestversion['error']) && self::$latestversion['error']) {
+ $result = -1;
+ self::$lasterror = self::$latestversion['message'];
+ } else if (isset(self::$latestversion['has_latest']) && self::$latestversion['has_latest'] == false) {
+ $result = 1;
+ }
+ }
+ }
+ return $result;
+ }
+
+ public static function downloadZip(string $newversion)
+ {
+ // define files to get
+ $toLoad = str_replace('{version}', $newversion, self::RELEASE_URI);
+ $toCheck = str_replace('{version}', $newversion, self::CHECKSUM_URI);
+
+ // check for local destination folder
+ if (!is_dir(Froxlor::getInstallDir() . '/updates/')) {
+ mkdir(Froxlor::getInstallDir() . '/updates/');
+ }
+
+ // name archive
+ $localArchive = Froxlor::getInstallDir() . '/updates/' . basename($toLoad);
+
+ // remove old archive
+ if (file_exists($localArchive)) {
+ @unlink($localArchive);
+ }
+
+ // get archive data
+ try {
+ HttpClient::fileGet($toLoad, $localArchive);
+ } catch (Exception $e) {
+ return self::ERR_COULDNOTSTORE;
+ }
+
+ // validate the integrity of the downloaded file
+ $_shouldsum = HttpClient::urlGet($toCheck);
+ if (!empty($_shouldsum)) {
+ $_t = explode(" ", $_shouldsum);
+ $shouldsum = $_t[0];
+ } else {
+ $shouldsum = null;
+ }
+ $filesum = hash_file('sha256', $localArchive);
+
+ if ($filesum != $shouldsum) {
+ return self::ERR_CHKSUM_MISMATCH;
+ }
+
+ return basename($localArchive);
+ }
+
+ public static function extractZip(string $localArchive): int
+ {
+ if (!file_exists($localArchive)) {
+ return self::ERR_ZIPNOTFOUND;
+ }
+ // decompress from zip
+ $zip = new ZipArchive();
+ $res = $zip->open($localArchive);
+ if ($res === true) {
+ $zip->extractTo(Froxlor::getInstallDir());
+ $zip->close();
+ // success - remove unused archive
+ @unlink($localArchive);
+ // wait a bit before we redirect to be sure
+ sleep(3);
+ return 0;
+ }
+ return self::ERR_COULDNOTEXTRACT;
+ }
+
+ private static function checkPrerequisites(): int
+ {
+ if (!extension_loaded('zip')) {
+ return self::ERR_NOZIPEXT;
+ }
+ if (version_compare("7.4.0", PHP_VERSION, ">=")) {
+ return self::ERR_MINPHP;
+ }
+ return 0;
+ }
+
+ public static function getLastError(): string
+ {
+ return self::$lasterror ?? "";
+ }
+
+ public static function getFromResult(string $index)
+ {
+ return self::$latestversion[$index] ?? "";
+ }
+}
diff --git a/lng/de.lng.php b/lng/de.lng.php
index 60c3f736..7e871f2a 100644
--- a/lng/de.lng.php
+++ b/lng/de.lng.php
@@ -329,7 +329,9 @@ return [
'accountdata' => 'Benutzerdaten',
'contactdata' => 'Kontaktdaten',
'servicedata' => 'Dienstleistungsdaten',
- 'newerversionavailable' => 'Eine neuere Version von Froxlor wurde veröffentlicht',
+ 'newerversionavailable' => 'Eine neuere Version von Froxlor wurde veröffentlicht.',
+ 'newerversiondetails' => 'Jetzt auf Version %s aktualisieren?
(Aktuelle Version ist: %s)',
+ 'extractdownloadedzip' => 'Heruntergeladenes Archiv "%s" entpacken?',
'cron' => [
'cronsettings' => 'Cronjob-Einstellungen',
'add' => 'Cronjob hinzufügen',
@@ -2005,6 +2007,10 @@ Vielen Dank, Ihr Administrator',
'title' => 'Pfad zu acme.sh',
'description' => 'Installationspfad zu acme.sh, inklusive acme.sh Script
Standard ist /root/.acme.sh/acme.sh',
],
+ 'update_channel' => [
+ 'title' => 'froxlor Update Kanal',
+ 'description' => 'Wähle den bevorzugten Update Kanal. Standard ist "stable"',
+ ],
],
'spf' => [
'use_spf' => 'Aktiviere SPF für Domains?',
diff --git a/lng/en.lng.php b/lng/en.lng.php
index 3eaba955..a860aebe 100644
--- a/lng/en.lng.php
+++ b/lng/en.lng.php
@@ -331,7 +331,9 @@ return [
'accountdata' => 'Account Data',
'contactdata' => 'Contact Data',
'servicedata' => 'Service Data',
- 'newerversionavailable' => 'There is a newer version of Froxlor available',
+ 'newerversionavailable' => 'There is a newer version of Froxlor available.',
+ 'newerversiondetails' => 'Update to version %s now?
(Your current version is: %s)',
+ 'extractdownloadedzip' => 'Extract downloaded archive "%s"?',
'cron' => [
'cronsettings' => 'Cronjob settings',
'add' => 'Add cronjob',
@@ -2377,6 +2379,12 @@ Yours sincerely, your administrator',
'title' => 'Path to acme.sh',
'description' => 'Set this to where acme.sh is installed to, including the acme.sh script
Default is /root/.acme.sh/acme.sh',
],
+ 'update_channel' => [
+ 'title' => 'froxlor update-channel',
+ 'description' => 'Select the update channel of froxlor. Default is "stable"',
+ ],
+ 'uc_stable' => 'stable',
+ 'uc_testing' => 'testing',
],
'spf' => [
'use_spf' => 'Activate SPF for domains?',