From 05ce813c2363c8cb2167296334c1c806b503e71c Mon Sep 17 00:00:00 2001 From: vkamn Date: Fri, 15 May 2026 21:43:47 +0800 Subject: [PATCH] refactor: move logic from ui to core --- .../selfhosted/installController.cpp | 110 +++++++++++++++ .../selfhosted/installController.h | 11 ++ client/core/controllers/serversController.cpp | 14 -- client/core/controllers/serversController.h | 1 - client/core/installers/mtProxyInstaller.cpp | 48 +++++++ client/core/installers/mtProxyInstaller.h | 14 ++ .../selfhosted/installUiController.cpp | 133 +++--------------- 7 files changed, 203 insertions(+), 128 deletions(-) diff --git a/client/core/controllers/selfhosted/installController.cpp b/client/core/controllers/selfhosted/installController.cpp index 393c7e567..1f163043c 100644 --- a/client/core/controllers/selfhosted/installController.cpp +++ b/client/core/controllers/selfhosted/installController.cpp @@ -1345,3 +1345,113 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia return ErrorCode::NoError; } + +ErrorCode InstallController::setDockerContainerEnabledState(const QString &serverId, DockerContainer container, bool enabled) +{ + auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId); + if (!adminConfig.has_value()) { + return ErrorCode::InternalError; + } + ServerCredentials credentials = adminConfig->credentials(); + if (!credentials.isValid()) { + return ErrorCode::InternalError; + } + const QString containerName = ContainerUtils::containerToString(container); + SshSession sshSession(this); + const QString script = enabled ? QStringLiteral("sudo docker start %1").arg(containerName) + : QStringLiteral("sudo docker stop %1").arg(containerName); + const ErrorCode runError = sshSession.runScript(credentials, script); + if (runError != ErrorCode::NoError) { + return runError; + } + ContainerConfig currentConfig = adminConfig->containerConfig(container); + if (auto *mtConfig = currentConfig.getMtProxyProtocolConfig()) { + mtConfig->isEnabled = enabled; + adminConfig->updateContainerConfig(container, currentConfig); + m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin); + } + return ErrorCode::NoError; +} + +ErrorCode InstallController::queryDockerContainerStatus(const QString &serverId, DockerContainer container, int &statusOut) +{ + statusOut = 3; + auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId); + if (!adminConfig.has_value()) { + return ErrorCode::InternalError; + } + ServerCredentials credentials = adminConfig->credentials(); + if (!credentials.isValid()) { + return ErrorCode::InternalError; + } + const QString containerName = ContainerUtils::containerToString(container); + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data; + return ErrorCode::NoError; + }; + SshSession sshSession(this); + const QString script = QStringLiteral( + "sudo docker inspect --format '{{.State.Status}}' %1 2>/dev/null || echo 'not_found'") + .arg(containerName); + const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + const QString status = stdOut.trimmed(); + if (status == QLatin1String("running")) { + statusOut = 1; + } else if (status == QLatin1String("not_found") || status.isEmpty()) { + statusOut = 0; + } else if (status == QLatin1String("exited") || status == QLatin1String("created") + || status == QLatin1String("paused")) { + statusOut = 2; + } else { + statusOut = 3; + } + return ErrorCode::NoError; +} + +ErrorCode InstallController::queryMtProxyDiagnostics(const QString &serverId, DockerContainer container, int listenPort, + MtProxyContainerDiagnostics &out) +{ + out = {}; + auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId); + if (!adminConfig.has_value()) { + return ErrorCode::InternalError; + } + ServerCredentials credentials = adminConfig->credentials(); + if (!credentials.isValid()) { + return ErrorCode::InternalError; + } + SshSession sshSession(this); + return MtProxyInstaller::queryDiagnostics(sshSession, credentials, container, listenPort, out); +} + +QString InstallController::fetchDockerContainerSecret(const QString &serverId, DockerContainer container) +{ + auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId); + if (!adminConfig.has_value()) { + return {}; + } + ServerCredentials credentials = adminConfig->credentials(); + if (!credentials.isValid()) { + return {}; + } + const QString containerName = ContainerUtils::containerToString(container); + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data; + return ErrorCode::NoError; + }; + SshSession sshSession(this); + const QString path = QStringLiteral("/data/secret"); + const QString cmd = QStringLiteral("sudo docker exec %1 cat %2").arg(containerName, path); + const ErrorCode errorCode = sshSession.runScript(credentials, cmd, cbReadStdOut); + if (errorCode != ErrorCode::NoError) { + return {}; + } + const QString secret = stdOut.trimmed(); + static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$")); + return hex32.match(secret).hasMatch() ? secret : QString(); +} diff --git a/client/core/controllers/selfhosted/installController.h b/client/core/controllers/selfhosted/installController.h index acb61ca41..25c041273 100644 --- a/client/core/controllers/selfhosted/installController.h +++ b/client/core/controllers/selfhosted/installController.h @@ -16,6 +16,7 @@ #include "core/models/containerConfig.h" #include "core/repositories/secureServersRepository.h" #include "core/repositories/secureAppSettingsRepository.h" +#include "core/installers/mtProxyInstaller.h" class SshSession; class InstallerBase; @@ -39,6 +40,16 @@ public: ErrorCode removeAllContainers(const QString &serverId); ErrorCode removeContainer(const QString &serverId, DockerContainer container); + ErrorCode setDockerContainerEnabledState(const QString &serverId, DockerContainer container, bool enabled); + + /// statusOut: 0 = not deployed, 1 = running, 2 = stopped, 3 = error + ErrorCode queryDockerContainerStatus(const QString &serverId, DockerContainer container, int &statusOut); + + ErrorCode queryMtProxyDiagnostics(const QString &serverId, DockerContainer container, int listenPort, + MtProxyContainerDiagnostics &out); + + QString fetchDockerContainerSecret(const QString &serverId, DockerContainer container); + ContainerConfig generateConfig(DockerContainer container, int port, TransportProto transportProto); ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers, SshSession &sshSession); diff --git a/client/core/controllers/serversController.cpp b/client/core/controllers/serversController.cpp index aeedd39f3..7b406ae06 100644 --- a/client/core/controllers/serversController.cpp +++ b/client/core/controllers/serversController.cpp @@ -265,20 +265,6 @@ ContainerConfig ServersController::getContainerConfig(const QString &serverId, D return getServerContainersMap(serverId).value(container); } -void ServersController::updateContainerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &config) -{ - const serverConfigUtils::ConfigType kind = m_serversRepository->serverKind(serverId); - if (kind != serverConfigUtils::ConfigType::SelfHostedAdmin) { - return; - } - auto cfg = m_serversRepository->selfHostedAdminConfig(serverId); - if (!cfg.has_value()) { - return; - } - cfg->updateContainerConfig(container, config); - m_serversRepository->editServer(serverId, cfg->toJson(), kind); -} - int ServersController::getDefaultServerIndex() const { return m_serversRepository->defaultServerIndex(); diff --git a/client/core/controllers/serversController.h b/client/core/controllers/serversController.h index abb0974e9..e8286ed4c 100644 --- a/client/core/controllers/serversController.h +++ b/client/core/controllers/serversController.h @@ -57,7 +57,6 @@ public: QMap getServerContainersMap(const QString &serverId) const; DockerContainer getDefaultContainer(const QString &serverId) const; ContainerConfig getContainerConfig(const QString &serverId, DockerContainer container) const; - void updateContainerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &config); // Validation bool isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType, const QString &serviceProtocol) const; diff --git a/client/core/installers/mtProxyInstaller.cpp b/client/core/installers/mtProxyInstaller.cpp index 4901df022..21ac5bd4a 100644 --- a/client/core/installers/mtProxyInstaller.cpp +++ b/client/core/installers/mtProxyInstaller.cpp @@ -67,6 +67,54 @@ ErrorCode MtProxyInstaller::extractConfigFromContainer(DockerContainer container return ErrorCode::NoError; } +ErrorCode MtProxyInstaller::queryDiagnostics(SshSession &sshSession, const ServerCredentials &credentials, + DockerContainer container, int listenPort, + MtProxyContainerDiagnostics &out) +{ + out = {}; + if (container != DockerContainer::MtProxy) { + return ErrorCode::InternalError; + } + const QString containerName = ContainerUtils::containerToString(container); + const QString script = + QStringLiteral( + "PORT_OK=$(sudo docker exec %1 sh -c 'ss -tlnp 2>/dev/null | grep -q :%2 && echo yes || echo no' 2>/dev/null || echo no); " + "TG_OK=$(curl -s --max-time 5 -o /dev/null -w '%%{http_code}' https://core.telegram.org/getProxySecret 2>/dev/null | grep -q '200' && echo yes || echo no); " + "CLIENTS=$(sudo docker exec amnezia-mtproxy sh -c 'curl -s --max-time 3 http://localhost:2398/stats 2>/dev/null | grep -o \"total_special_connections:[0-9]*\" | cut -d: -f2' 2>/dev/null); " + "CONF_TIME=$(sudo docker exec amnezia-mtproxy sh -c 'stat -c \"%%y\" /data/proxy-multi.conf 2>/dev/null | cut -d. -f1' 2>/dev/null || echo unknown); " + "echo \"PORT_OK=${PORT_OK}\"; " + "echo \"TG_OK=${TG_OK}\"; " + "echo \"CLIENTS=${CLIENTS:-0}\"; " + "echo \"CONF_TIME=${CONF_TIME}\"; " + "echo \"STATS=http://localhost:2398/stats\";") + .arg(containerName) + .arg(listenPort); + + QString stdOut; + auto cbReadStdOut = [&](const QString &data, libssh::Client &) { + stdOut += data; + return ErrorCode::NoError; + }; + const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + for (const QString &line : stdOut.split('\n', Qt::SkipEmptyParts)) { + if (line.startsWith(QLatin1String("PORT_OK="))) { + out.portReachable = line.mid(8).trimmed() == QLatin1String("yes"); + } else if (line.startsWith(QLatin1String("TG_OK="))) { + out.upstreamReachable = line.mid(6).trimmed() == QLatin1String("yes"); + } else if (line.startsWith(QLatin1String("CLIENTS="))) { + out.clientsConnected = line.mid(8).trimmed().toInt(); + } else if (line.startsWith(QLatin1String("CONF_TIME="))) { + out.lastConfigRefresh = line.mid(10).trimmed(); + } else if (line.startsWith(QLatin1String("STATS="))) { + out.statsEndpoint = line.mid(6).trimmed(); + } + } + return ErrorCode::NoError; +} + void MtProxyInstaller::uploadClientSettingsSnapshot(SshSession &sshSession, const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config) { const MtProxyProtocolConfig *mt = config.getMtProxyProtocolConfig(); diff --git a/client/core/installers/mtProxyInstaller.h b/client/core/installers/mtProxyInstaller.h index 88da30bd7..2487c9b56 100644 --- a/client/core/installers/mtProxyInstaller.h +++ b/client/core/installers/mtProxyInstaller.h @@ -3,6 +3,16 @@ #include "installerBase.h" +#include + +struct MtProxyContainerDiagnostics { + bool portReachable = false; + bool upstreamReachable = false; + int clientsConnected = -1; + QString lastConfigRefresh; + QString statsEndpoint; +}; + class MtProxyInstaller : public InstallerBase { Q_OBJECT public: @@ -15,6 +25,10 @@ public: static void uploadClientSettingsSnapshot(SshSession &sshSession, const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &config); + + static amnezia::ErrorCode queryDiagnostics(SshSession &sshSession, const amnezia::ServerCredentials &credentials, + amnezia::DockerContainer container, int listenPort, + MtProxyContainerDiagnostics &out); }; #endif // MTPROXYINSTALLER_H diff --git a/client/ui/controllers/selfhosted/installUiController.cpp b/client/ui/controllers/selfhosted/installUiController.cpp index 739e423ce..6e674f452 100755 --- a/client/ui/controllers/selfhosted/installUiController.cpp +++ b/client/ui/controllers/selfhosted/installUiController.cpp @@ -12,7 +12,6 @@ #include "core/utils/api/apiUtils.h" #include "core/controllers/selfhosted/installController.h" -#include "core/utils/selfhosted/sshSession.h" #include "core/utils/networkUtilities.h" #include "core/utils/protocolEnum.h" #include "core/protocols/protocolUtils.h" @@ -315,26 +314,17 @@ void InstallUiController::updateContainer(const QString &serverId, int container emit installationErrorOccurred(errorCode); } -void InstallUiController::setContainerEnabled(const QString &serverId, int containerIndex, bool enabled) { +void InstallUiController::setContainerEnabled(const QString &serverId, int containerIndex, bool enabled) +{ const DockerContainer container = static_cast(containerIndex); - const ServerCredentials credentials = m_serversController->getServerCredentials(serverId); - const QString containerName = ContainerUtils::containerToString(container); emit serverIsBusy(true); - SshSession sshSession(this); - const QString script = enabled - ? QString("sudo docker start %1").arg(containerName) - : QString("sudo docker stop %1").arg(containerName); - const ErrorCode errorCode = sshSession.runScript(credentials, script); + const ErrorCode errorCode = m_installController->setDockerContainerEnabledState(serverId, container, enabled); emit serverIsBusy(false); if (errorCode == ErrorCode::NoError) { - ContainerConfig currentConfig = m_serversController->getContainerConfig(serverId, container); - if (auto *mtConfig = currentConfig.getMtProxyProtocolConfig()) { - mtConfig->isEnabled = enabled; - m_serversController->updateContainerConfig(serverId, container, currentConfig); - m_protocolModel->updateModel(currentConfig); - } + const ContainerConfig currentConfig = m_serversController->getContainerConfig(serverId, container); + m_protocolModel->updateModel(currentConfig); emit setContainerEnabledFinished(enabled); return; } @@ -342,119 +332,36 @@ void InstallUiController::setContainerEnabled(const QString &serverId, int conta emit installationErrorOccurred(errorCode); } -void InstallUiController::refreshContainerStatus(const QString &serverId, int containerIndex) { +void InstallUiController::refreshContainerStatus(const QString &serverId, int containerIndex) +{ const DockerContainer container = static_cast(containerIndex); - const ServerCredentials credentials = m_serversController->getServerCredentials(serverId); - const QString containerName = ContainerUtils::containerToString(container); - - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data; - return ErrorCode::NoError; - }; - - SshSession sshSession(this); - const QString script = QString( - "sudo docker inspect --format '{{.State.Status}}' %1 2>/dev/null || echo 'not_found'") - .arg(containerName); - const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut); + int status = 3; + const ErrorCode errorCode = m_installController->queryDockerContainerStatus(serverId, container, status); if (errorCode != ErrorCode::NoError) { emit containerStatusRefreshed(3); return; } - - const QString status = stdOut.trimmed(); - if (status == "running") { - emit containerStatusRefreshed(1); - } else if (status == "not_found" || status.isEmpty()) { - emit containerStatusRefreshed(0); - } else if (status == "exited" || status == "created" || status == "paused") { - emit containerStatusRefreshed(2); - } else { - emit containerStatusRefreshed(3); - } + emit containerStatusRefreshed(status); } -void InstallUiController::refreshContainerDiagnostics(const QString &serverId, int containerIndex, int port) { - const ServerCredentials credentials = m_serversController->getServerCredentials(serverId); +void InstallUiController::refreshContainerDiagnostics(const QString &serverId, int containerIndex, int port) +{ const DockerContainer container = static_cast(containerIndex); - const QString containerName = ContainerUtils::containerToString(container); - - const QString script = - QString( - "PORT_OK=$(sudo docker exec %1 sh -c 'ss -tlnp 2>/dev/null | grep -q :%2 && echo yes || echo no' 2>/dev/null || echo no); " - "TG_OK=$(curl -s --max-time 5 -o /dev/null -w '%%{http_code}' https://core.telegram.org/getProxySecret 2>/dev/null | grep -q '200' && echo yes || echo no); " - "CLIENTS=$(sudo docker exec amnezia-mtproxy sh -c 'curl -s --max-time 3 http://localhost:2398/stats 2>/dev/null | grep -o \"total_special_connections:[0-9]*\" | cut -d: -f2' 2>/dev/null); " - "CONF_TIME=$(sudo docker exec amnezia-mtproxy sh -c 'stat -c \"%%y\" /data/proxy-multi.conf 2>/dev/null | cut -d. -f1' 2>/dev/null || echo unknown); " - "echo \"PORT_OK=${PORT_OK}\"; " - "echo \"TG_OK=${TG_OK}\"; " - "echo \"CLIENTS=${CLIENTS:-0}\"; " - "echo \"CONF_TIME=${CONF_TIME}\"; " - "echo \"STATS=http://localhost:2398/stats\";") - .arg(containerName) - .arg(port); - - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data; - return ErrorCode::NoError; - }; - - SshSession sshSession(this); - const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut); + MtProxyContainerDiagnostics diag; + const ErrorCode errorCode = m_installController->queryMtProxyDiagnostics(serverId, container, port, diag); if (errorCode != ErrorCode::NoError) { emit containerDiagnosticsRefreshed(false, false, -1, QString(), QString()); return; } - - bool portReachable = false; - bool upstreamReachable = false; - int clientsConnected = -1; - QString lastConfigRefresh; - QString statsEndpoint; - - for (const QString &line: stdOut.split('\n', Qt::SkipEmptyParts)) { - if (line.startsWith("PORT_OK=")) { - portReachable = line.mid(8).trimmed() == "yes"; - } else if (line.startsWith("TG_OK=")) { - upstreamReachable = line.mid(6).trimmed() == "yes"; - } else if (line.startsWith("CLIENTS=")) { - clientsConnected = line.mid(8).trimmed().toInt(); - } else if (line.startsWith("CONF_TIME=")) { - lastConfigRefresh = line.mid(10).trimmed(); - } else if (line.startsWith("STATS=")) { - statsEndpoint = line.mid(6).trimmed(); - } - } - - emit containerDiagnosticsRefreshed(portReachable, upstreamReachable, clientsConnected, lastConfigRefresh, - statsEndpoint); + emit containerDiagnosticsRefreshed(diag.portReachable, diag.upstreamReachable, diag.clientsConnected, + diag.lastConfigRefresh, diag.statsEndpoint); } -void InstallUiController::fetchContainerSecret(const QString &serverId, int containerIndex) { - const ServerCredentials credentials = m_serversController->getServerCredentials(serverId); +void InstallUiController::fetchContainerSecret(const QString &serverId, int containerIndex) +{ const DockerContainer container = static_cast(containerIndex); - const QString containerName = ContainerUtils::containerToString(container); - - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data; - return ErrorCode::NoError; - }; - - SshSession sshSession(this); - const QString path = QStringLiteral("/data/secret"); - const QString cmd = - QStringLiteral("sudo docker exec %1 cat %2").arg(containerName, path); - const ErrorCode errorCode = sshSession.runScript(credentials, cmd, cbReadStdOut); - if (errorCode != ErrorCode::NoError) { - emit containerSecretFetched(QString()); - return; - } - - const QString secret = stdOut.trimmed(); - static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$")); - emit containerSecretFetched(hex32.match(secret).hasMatch() ? secret : QString()); + const QString secret = m_installController->fetchDockerContainerSecret(serverId, container); + emit containerSecretFetched(secret); } void InstallUiController::rebootServer(const QString &serverId)