mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
feat: desktop updater (#825)
* added changelog drawer * Created a scaffold for Linux installation * implement Linux updating * Add debug logs about installer in service * Add client side of installation logic for Windows and MacOS * Add service side of installation logic for Windows * ru readme * Update README_RU.md * Add files via upload * chore: added clang-format config files (#1293) * Update README_RU.md * Update README.md * feature: added subscription expiration date for premium v2 (#1261) * feature: added subscription expiration date for premium v2 * feature: added a check for the presence of the “services” field in the response body of the getServicesList() function * feature: added prohibition to change location when connection is active * bugfix: renamed public_key->end_date to public_key->expires_at according to the changes on the backend * feature/xray user management (#972) * feature: implement client management functionality for Xray --------- Co-authored-by: aiamnezia <ai@amnezia.org> Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com> * Fix formatting * Add some logs * Add logs from installattion shell on Windows * Fix installation for Windows and MacOS * Optimized code * Move installer running to client side for Ubuntu * Move installer launch logic to client side for Windows * Clean service code * Add linux_install script to resources * Add logs for UpdateController * Add draft for MacOS installation * Disable updates checking for Android and iOS * chore: fixed macos update script * chore: remove duplicate lines * chore: post merge fixes * chore: add missing ifdef * decrease version for testing * chore: added changelog text processing depend on OS * add .vscode to .gitignore * Change updater downloading method to retrieving link from the gateway * add Release date file creation to s3 deploy script * Add release date downloading from endpoint * update check refactoring * feat: switch macOS auto-update from DMG to ZIP+PKG installer - Update macOS artifact URL from .dmg to .zip - Rewrite mac_installer.sh to extract ZIP and install PKG via osascript - Increase download timeout to 30s for larger ZIP files * fix: fix Android build * feat: Change get request for updater link to post * refactor: preparing NewsModel for update notifications - Changed `updateModel` to `setNewsList` for better semantic meaning. - Delegate model container updating to private method updateModel - Updated the logic for marking news as read to use item IDs instead of a boolean flag. * feat: Move update notification in news list - Updated `UpdateController` to handle empty release dates in header text. - Added `getVersion` method to `UpdateController` for version retrieval. - Enhanced `NewsModel` to support update notifications with new methods for marking updates as skipped and setting update notifications. - Updated QML pages to display update information and provide actions for updates and skipping them. - Introduced `isUpdate` property in `NewsItem` to differentiate between regular news and updates. * feat: Implement rate limit workaround for gateway requests - Added a delay before contacting the gateway in both `UpdateController` and `ApiNewsController` to prevent rate limit issues caused by simultaneous requests. * refactor: Convert synchronous network requests to asynchronous in UpdateController - Updated `UpdateController` to use asynchronous network requests for fetching gateway URL, version info, changelog, and release date. - Introduced `doGetAsync` method to handle asynchronous GET requests with error handling. - Removed synchronous methods to improve responsiveness and prevent blocking the UI during network operations. - Added a mechanism to prevent multiple concurrent update checks. * chore: Decrease AmneziaVPN version to 4.8.10.0 in CMakeLists.txt for testing * refactor: Improve update check handling to avoid rate limit issues - Updated `CoreController` to initiate update checks after news fetching is complete. - Removed synchronous waiting in `ApiNewsController` to streamline the fetching process. * fix: fixed typo in IsReadRole * fix: fix updater filenames * chore: move updateController to core * refactor: update to mvvm * chore: tiny fix --------- Co-authored-by: aiamnezia <ai@amnezia.org> Co-authored-by: aiamnezia <ai@amnezia.com> Co-authored-by: Pokamest Nikak <pokamest@gmail.com> Co-authored-by: KsZnak <ksu@amnezia.org> Co-authored-by: Cyril Anisimov <cyan84@gmail.com> Co-authored-by: vkamn <vk@amnezia.org>
This commit is contained in:
@@ -146,6 +146,7 @@ void CoreController::initCoreControllers()
|
||||
m_servicesCatalogController = new ServicesCatalogController(m_appSettingsRepository);
|
||||
m_subscriptionController = new SubscriptionController(m_serversRepository, m_appSettingsRepository);
|
||||
m_newsController = new NewsController(m_appSettingsRepository, m_serversController);
|
||||
m_updateController = new UpdateController(m_appSettingsRepository, this);
|
||||
|
||||
m_installController = new InstallController(m_serversRepository, m_appSettingsRepository, this);
|
||||
m_exportController = new ExportController(m_serversRepository, m_appSettingsRepository, this);
|
||||
@@ -212,6 +213,9 @@ void CoreController::initControllers()
|
||||
|
||||
m_apiNewsUiController = new ApiNewsUiController(m_newsModel, m_newsController, this);
|
||||
setQmlContextProperty("ApiNewsController", m_apiNewsUiController);
|
||||
|
||||
m_updateUiController = new UpdateUiController(m_updateController, this);
|
||||
setQmlContextProperty("UpdateController", m_updateUiController);
|
||||
}
|
||||
|
||||
void CoreController::initAndroidController()
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "ui/controllers/ipSplitTunnelingUiController.h"
|
||||
#include "ui/controllers/systemController.h"
|
||||
#include "ui/controllers/languageUiController.h"
|
||||
#include "ui/controllers/updateUiController.h"
|
||||
#include "ui/controllers/api/servicesCatalogUiController.h"
|
||||
|
||||
#include "core/controllers/serversController.h"
|
||||
@@ -39,6 +40,7 @@
|
||||
#include "core/controllers/selfhosted/installController.h"
|
||||
#include "core/controllers/settingsController.h"
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "core/controllers/updateController.h"
|
||||
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
@@ -159,6 +161,7 @@ private:
|
||||
AppSplitTunnelingUiController* m_appSplitTunnelingUiController;
|
||||
AllowedDnsUiController* m_allowedDnsUiController;
|
||||
LanguageUiController* m_languageUiController;
|
||||
UpdateUiController* m_updateUiController;
|
||||
|
||||
SubscriptionUiController* m_subscriptionUiController;
|
||||
ApiNewsUiController* m_apiNewsUiController;
|
||||
@@ -173,6 +176,7 @@ private:
|
||||
ServicesCatalogController* m_servicesCatalogController;
|
||||
SubscriptionController* m_subscriptionController;
|
||||
NewsController* m_newsController;
|
||||
UpdateController* m_updateController;
|
||||
InstallController* m_installController;
|
||||
ExportController* m_exportController;
|
||||
ConnectionController* m_connectionController;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "ui/controllers/selfhosted/installUiController.h"
|
||||
#include "ui/controllers/importUiController.h"
|
||||
#include "ui/controllers/api/subscriptionUiController.h"
|
||||
#include "ui/controllers/updateUiController.h"
|
||||
#include "ui/models/serversModel.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "core/controllers/ipSplitTunnelingController.h"
|
||||
@@ -83,6 +84,7 @@ void CoreSignalHandlers::initAllHandlers()
|
||||
initIosImportHandler();
|
||||
initIosSettingsHandler();
|
||||
initNotificationHandler();
|
||||
initUpdateFoundHandler();
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initErrorMessagesHandler()
|
||||
@@ -410,3 +412,19 @@ void CoreSignalHandlers::initNotificationHandler()
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initUpdateFoundHandler()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
connect(m_coreController->m_apiNewsUiController, &ApiNewsUiController::fetchNewsFinished, m_coreController->m_updateUiController,
|
||||
&UpdateUiController::checkForUpdates);
|
||||
|
||||
connect(m_coreController->m_updateUiController, &UpdateUiController::updateFound, this, [this]() {
|
||||
const QString version = m_coreController->m_updateUiController->getVersion();
|
||||
const QString updateId = version.isEmpty() ? QStringLiteral("update") : QStringLiteral("update-%1").arg(version);
|
||||
m_coreController->m_newsModel->setUpdateNotification(
|
||||
updateId, m_coreController->m_updateUiController->getHeaderText(), m_coreController->m_updateUiController->getChangelogText());
|
||||
emit m_coreController->m_pageController->showChangelogDrawer();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ private:
|
||||
void initIosImportHandler();
|
||||
void initIosSettingsHandler();
|
||||
void initNotificationHandler();
|
||||
void initUpdateFoundHandler();
|
||||
|
||||
CoreController* m_coreController;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
#include "updateController.h"
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QVersionNumber>
|
||||
#include <QUrl>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSysInfo>
|
||||
#include <QTimer>
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "logger.h"
|
||||
#include "version.h"
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/errorStrings.h"
|
||||
#include "core/utils/selfhosted/scriptsRegistry.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
Logger logger("UpdateController");
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_x64.exe");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN_installer.exe";
|
||||
#elif defined(Q_OS_MACOS)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_macos.pkg");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.pkg";
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_linux_x64.tar");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.tar";
|
||||
#endif
|
||||
}
|
||||
|
||||
UpdateController::UpdateController(SecureAppSettingsRepository* appSettingsRepository, QObject *parent)
|
||||
: QObject(parent), m_appSettingsRepository(appSettingsRepository)
|
||||
{
|
||||
}
|
||||
|
||||
QString UpdateController::getRawChangelogText() const
|
||||
{
|
||||
return m_changelogText;
|
||||
}
|
||||
|
||||
QString UpdateController::getReleaseDate() const
|
||||
{
|
||||
return m_releaseDate;
|
||||
}
|
||||
|
||||
QString UpdateController::getVersion() const
|
||||
{
|
||||
return m_version;
|
||||
}
|
||||
|
||||
void UpdateController::checkForUpdates()
|
||||
{
|
||||
if (m_updateCheckRunning || !m_appSettingsRepository) {
|
||||
return;
|
||||
}
|
||||
m_updateCheckRunning = true;
|
||||
|
||||
fetchGatewayUrl();
|
||||
}
|
||||
|
||||
void UpdateController::finishUpdateCheck()
|
||||
{
|
||||
m_updateCheckRunning = false;
|
||||
}
|
||||
|
||||
void UpdateController::doGetAsync(const QString &endpoint, std::function<void(bool, QByteArray)> onDone)
|
||||
{
|
||||
QString fullUrl = m_baseUrl + endpoint;
|
||||
|
||||
QNetworkRequest req;
|
||||
req.setTransferTimeout(7000);
|
||||
req.setUrl(QUrl(fullUrl));
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->get(req);
|
||||
setupNetworkErrorHandling(reply, endpoint);
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, endpoint, onDone]() {
|
||||
const bool ok = (reply->error() == QNetworkReply::NoError);
|
||||
QByteArray data;
|
||||
if (ok) {
|
||||
data = reply->readAll();
|
||||
} else {
|
||||
handleNetworkError(reply, endpoint);
|
||||
}
|
||||
reply->deleteLater();
|
||||
onDone(ok, data);
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateController::fetchGatewayUrl()
|
||||
{
|
||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_appSettingsRepository->getGatewayEndpoint(),
|
||||
m_appSettingsRepository->isDevGatewayEnv(),
|
||||
7000,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
|
||||
QJsonObject apiPayload;
|
||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
||||
apiPayload[apiDefs::key::osVersion] = QSysInfo::productType();
|
||||
apiPayload[apiDefs::key::installationUuid] = m_appSettingsRepository->getInstallationUuid(true);
|
||||
|
||||
// Workaround: wait before contacting gateway to avoid rate limit triggered by other requests (news etc.)
|
||||
QTimer::singleShot(1000, this, [this, gatewayController, apiPayload]() {
|
||||
gatewayController->postAsync(QStringLiteral("%1v1/updater_endpoint"), apiPayload)
|
||||
.then(this, [this](QPair<ErrorCode, QByteArray> result) {
|
||||
auto [err, gatewayResponse] = result;
|
||||
if (err != ErrorCode::NoError) {
|
||||
logger.error() << errorString(err);
|
||||
finishUpdateCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject gatewayData = QJsonDocument::fromJson(gatewayResponse).object();
|
||||
|
||||
QString baseUrl = gatewayData.value("url").toString();
|
||||
if (baseUrl.endsWith('/')) {
|
||||
baseUrl.chop(1);
|
||||
}
|
||||
m_baseUrl = baseUrl;
|
||||
|
||||
fetchVersionInfo();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateController::fetchVersionInfo()
|
||||
{
|
||||
doGetAsync("/VERSION", [this](bool ok, QByteArray data) {
|
||||
if (!ok) {
|
||||
finishUpdateCheck();
|
||||
return;
|
||||
}
|
||||
m_version = QString::fromUtf8(data).trimmed();
|
||||
|
||||
if (!isNewVersionAvailable()) {
|
||||
finishUpdateCheck();
|
||||
return;
|
||||
}
|
||||
fetchChangelog();
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateController::fetchChangelog()
|
||||
{
|
||||
doGetAsync("/CHANGELOG", [this](bool ok, QByteArray data) {
|
||||
if (!ok) {
|
||||
m_changelogText.clear();
|
||||
} else {
|
||||
m_changelogText = QString::fromUtf8(data);
|
||||
}
|
||||
fetchReleaseDate();
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateController::fetchReleaseDate()
|
||||
{
|
||||
doGetAsync("/RELEASE_DATE", [this](bool ok, QByteArray data) {
|
||||
if (ok) {
|
||||
m_releaseDate = QString::fromUtf8(data).trimmed();
|
||||
} else {
|
||||
m_releaseDate = QString();
|
||||
}
|
||||
|
||||
m_downloadUrl = composeDownloadUrl();
|
||||
emit updateFound();
|
||||
finishUpdateCheck();
|
||||
});
|
||||
}
|
||||
|
||||
bool UpdateController::isNewVersionAvailable() const
|
||||
{
|
||||
auto currentVersion = QVersionNumber::fromString(QString(APP_VERSION));
|
||||
auto newVersion = QVersionNumber::fromString(m_version);
|
||||
return newVersion > currentVersion;
|
||||
}
|
||||
|
||||
void UpdateController::setupNetworkErrorHandling(QNetworkReply* reply, const QString& operation)
|
||||
{
|
||||
QObject::connect(reply, &QNetworkReply::errorOccurred, [reply, operation](QNetworkReply::NetworkError error) {
|
||||
logger.error() << QString("Network error occurred while fetching %1: %2 %3")
|
||||
.arg(operation, reply->errorString(), QString::number(error));
|
||||
});
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::sslErrors, [operation](const QList<QSslError> &errors) {
|
||||
QStringList errorStrings;
|
||||
for (const QSslError &err : errors) {
|
||||
errorStrings << err.errorString();
|
||||
}
|
||||
logger.error() << QString("SSL errors while fetching %1: %2").arg(operation, errorStrings.join("; "));
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateController::handleNetworkError(QNetworkReply* reply, const QString& operation)
|
||||
{
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
logger.error() << errorString(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "Error message:" << err;
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
logger.error() << errorString(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
}
|
||||
|
||||
QString UpdateController::composeDownloadUrl() const
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
const QString fileName = QString(kInstallerRemoteFileNamePattern).arg(m_version);
|
||||
return m_baseUrl + "/" + fileName;
|
||||
#else
|
||||
return QString();
|
||||
#endif
|
||||
}
|
||||
|
||||
void UpdateController::runInstaller()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (m_downloadUrl.isEmpty()) {
|
||||
logger.error() << "Download URL is empty";
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(30000);
|
||||
request.setUrl(m_downloadUrl);
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->get(request);
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, [this, reply]() {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QFile file(kInstallerLocalPath);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
logger.error() << "Failed to open installer file for writing:" << kInstallerLocalPath << "Error:" << file.errorString();
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.write(reply->readAll()) == -1) {
|
||||
logger.error() << "Failed to write installer data to file:" << kInstallerLocalPath << "Error:" << file.errorString();
|
||||
file.close();
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
runWindowsInstaller(kInstallerLocalPath);
|
||||
#elif defined(Q_OS_MACOS)
|
||||
runMacInstaller(kInstallerLocalPath);
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
runLinuxInstaller(kInstallerLocalPath);
|
||||
#endif
|
||||
} else {
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
logger.error() << errorString(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
logger.error() << QString::fromUtf8(reply->readAll());
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "Error message:" << err;
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
logger.error() << errorString(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
int UpdateController::runWindowsInstaller(const QString &installerPath)
|
||||
{
|
||||
qint64 pid;
|
||||
bool success = QProcess::startDetached(installerPath, QStringList(), QString(), &pid);
|
||||
|
||||
if (success) {
|
||||
logger.info() << "Installation process started with PID:" << pid;
|
||||
} else {
|
||||
logger.error() << "Failed to start installation process";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
int UpdateController::runMacInstaller(const QString &installerPath)
|
||||
{
|
||||
// Create temporary directory for extraction
|
||||
QTemporaryDir extractDir;
|
||||
extractDir.setAutoRemove(false);
|
||||
if (!extractDir.isValid()) {
|
||||
logger.error() << "Failed to create temporary directory";
|
||||
return -1;
|
||||
}
|
||||
logger.info() << "Temporary directory created:" << extractDir.path();
|
||||
|
||||
// Create script file in the temporary directory
|
||||
QString scriptPath = extractDir.path() + "/mac_installer.sh";
|
||||
QFile scriptFile(scriptPath);
|
||||
if (!scriptFile.open(QIODevice::WriteOnly)) {
|
||||
logger.error() << "Failed to create script file";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get script content from registry
|
||||
QString scriptContent = amnezia::scriptData(amnezia::ClientScriptType::mac_installer);
|
||||
if (scriptContent.isEmpty()) {
|
||||
logger.error() << "macOS installer script content is empty";
|
||||
scriptFile.close();
|
||||
return -1;
|
||||
}
|
||||
|
||||
scriptFile.write(scriptContent.toUtf8());
|
||||
scriptFile.close();
|
||||
logger.info() << "Script file created:" << scriptPath;
|
||||
|
||||
// Make script executable
|
||||
QFile::setPermissions(scriptPath, QFile::permissions(scriptPath) | QFile::ExeUser);
|
||||
|
||||
// Start detached process
|
||||
qint64 pid;
|
||||
bool success =
|
||||
QProcess::startDetached("/bin/bash", QStringList() << scriptPath << extractDir.path() << installerPath, extractDir.path(), &pid);
|
||||
|
||||
if (success) {
|
||||
logger.info() << "Installation process started with PID:" << pid;
|
||||
} else {
|
||||
logger.error() << "Failed to start installation process";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
int UpdateController::runLinuxInstaller(const QString &installerPath)
|
||||
{
|
||||
// Create temporary directory for extraction
|
||||
QTemporaryDir extractDir;
|
||||
extractDir.setAutoRemove(false);
|
||||
if (!extractDir.isValid()) {
|
||||
logger.error() << "Failed to create temporary directory";
|
||||
return -1;
|
||||
}
|
||||
logger.info() << "Temporary directory created:" << extractDir.path();
|
||||
|
||||
// Create script file in the temporary directory
|
||||
QString scriptPath = extractDir.path() + "/installer.sh";
|
||||
QFile scriptFile(scriptPath);
|
||||
if (!scriptFile.open(QIODevice::WriteOnly)) {
|
||||
logger.error() << "Failed to create script file";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get script content from registry
|
||||
QString scriptContent = amnezia::scriptData(amnezia::ClientScriptType::linux_installer);
|
||||
scriptFile.write(scriptContent.toUtf8());
|
||||
scriptFile.close();
|
||||
logger.info() << "Script file created:" << scriptPath;
|
||||
|
||||
// Make script executable
|
||||
QFile::setPermissions(scriptPath, QFile::permissions(scriptPath) | QFile::ExeUser);
|
||||
|
||||
// Start detached process
|
||||
qint64 pid;
|
||||
bool success =
|
||||
QProcess::startDetached("/bin/bash", QStringList() << scriptPath << extractDir.path() << installerPath, extractDir.path(), &pid);
|
||||
|
||||
if (success) {
|
||||
logger.info() << "Installation process started with PID:" << pid;
|
||||
} else {
|
||||
logger.error() << "Failed to start installation process";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef UPDATECONTROLLER_H
|
||||
#define UPDATECONTROLLER_H
|
||||
|
||||
#include <functional>
|
||||
#include <QObject>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
|
||||
class UpdateController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UpdateController(SecureAppSettingsRepository* appSettingsRepository, QObject *parent = nullptr);
|
||||
|
||||
QString getRawChangelogText() const;
|
||||
QString getReleaseDate() const;
|
||||
QString getVersion() const;
|
||||
|
||||
public slots:
|
||||
void checkForUpdates();
|
||||
void runInstaller();
|
||||
|
||||
signals:
|
||||
void updateFound();
|
||||
|
||||
private:
|
||||
void finishUpdateCheck();
|
||||
void fetchGatewayUrl();
|
||||
void fetchVersionInfo();
|
||||
void fetchChangelog();
|
||||
void fetchReleaseDate();
|
||||
void doGetAsync(const QString &endpoint, std::function<void(bool, QByteArray)> onDone);
|
||||
bool isNewVersionAvailable() const;
|
||||
void setupNetworkErrorHandling(QNetworkReply* reply, const QString& operation);
|
||||
void handleNetworkError(QNetworkReply* reply, const QString& operation);
|
||||
QString composeDownloadUrl() const;
|
||||
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
|
||||
QString m_baseUrl;
|
||||
QString m_changelogText;
|
||||
QString m_version;
|
||||
QString m_releaseDate;
|
||||
QString m_downloadUrl;
|
||||
bool m_updateCheckRunning = false;
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
int runWindowsInstaller(const QString &installerPath);
|
||||
#elif defined(Q_OS_MACOS)
|
||||
int runMacInstaller(const QString &installerPath);
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
int runLinuxInstaller(const QString &installerPath);
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // UPDATECONTROLLER_H
|
||||
Reference in New Issue
Block a user