mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
* refactor: move business logic from servers model * refactor: move containersModel initialization * refactor: added protocol ui controller and removed settings class from protocols model * refactor: moved cli management to separate controller * refactor: moved app split to separate controller * refactor: moved site split to separate controller * refactor: moved allowed dns to separate controller * refactor: moved language logic to separate ui controller * refactor: removed Settings from devices model * refactor: moved configs and services api logit to separate core controller * refactor: added a layer with a repository between the storage and controllers * refactor: use child parent system instead of smart pointers for controllers and models initialization * refactor: moved install functions from server controller to install controller * refactor: install controller refactoring * chore: renamed exportController to exportUiController * refactor: separate export controller * refactor: removed VpnConfigurationsController * chore: renamed ServerController to SshSession * refactor: replaced ServerController to SshSession * chore: moved qml controllers to separate folder * chore: include fixes * chore: moved utils from core root to core/utils * chore: include fixes * chore: rename core/utils files to camelCase foramt * chore: include fixes * chore: moved some utils to api and selfhosted folders * chore: include fixes * chore: remove unused file * chore: moved serialization folder to core/utils * chore: include fixes * chore: moved some files from client root to core/utils * chore: include fixes * chore: moved ui utils to ui/utils folder * chore: include fixes * chore: move utils from root to ui/utils * chore: include fixes * chore: moved configurators to core/configurators * chore: include fixes * refactor: moved iap logic from ui controller to core * refactor: moved remaining core logic from ApiConfigsController to SubscriptionController * chore: rename apiNewsController to apiNewsUiController * refactor: moved core logic from news ui controller to core * chore: renamed apiConfigsController to subscriptionUiController * chore: include fixes * refactor: merge ApiSettingsController with SubscriptionUiController * chore: moved ui selfhosted controllers to separate folder * chore: include fixes * chore: rename connectionController to connectiomUiController * refactor: moved core logic from connectionUiController * chore: rename settingsController to settingsUiController * refactor: move core logic from settingsUiController * refactor: moved core controller signal/slot connections to separate class * fix: newsController fixes after refactoring * chore: rename model to camelCase * chore: include fixes * chore: remove unused code * chore: move selfhosted core to separate folder * chore: include fixes * chore: rename importController to importUiController * refactor: move core logic from importUiController * chore: minor fixes * chore: remove prem v1 migration * refactor: remove openvpn over cloak and openvpn over shadowsocks * refactor: removed protocolsForContainer function * refactor: add core models * refactor: replace json with c++ structs for server config * refactor: move getDnsPair to ServerConfigUtils * feat: add admin selfhosted config export test * feat: add multi import test * refactor: use coreController for tests * feat: add few simple tests * chore: qrepos in all core controllers * feat: add test for settings * refactor: remove repo dependency from configurators * chore: moved protocols to core folder * chore: include fixes * refactor: moved containersDefs, defs, apiDefs, protocolsDefs to different places * chore: include fixes * chore: build fixes * chore: build fixes * refactor: remove q repo and interface repo * feat: add test for ui servers model and controller * chore: renamed to camelCase * chore: include fixes * refactor: moved core logic from sites ui controller * fix: fixed api config processing * fix: fixed processed server index processing * refactor: protocol models now use c++ structs instead of json configs * refactor: servers model now use c++ struct instead of json config * fix: fixed default server index processing * fix: fix logs init * fix: fix secure settings load keys * chore: build fixes * fix: fixed clear settings * fix: fixed restore backup * fix: sshSession usage * fix: fixed export functions signatures * fix: return missing part from buildContainerWorker * fix: fixed server description on page home * refactor: add container config helpers functions * refactor: c++ structs instead of json * chore: add dns protocol config struct * refactor: move config utils functions to config structs * feat: add test for selfhosted server setup * refactor: separate resources.qrc * fix: fixed server rename * chore: return nameOverriddenByUser * fix: build fixes * fix: fixed models init * refactor: cleanup models usage * fix: fixed models init * chore: cleanup connections and functions signatures * chore: cleanup updateModel calls * feat: added cache to servers repo * chore: cleanup unused functions * chore: ssxray processing * chore: remove transportProtoWithDefault and portWithDefault functions * chore: removed proto types any and l2tp * refactor: moved some constants * fix: fixed native configs export * refactor: remove json from processConfigWith functions * fix: fixed processed server index usage * fix: qml warning fixes * chore: merge fixes * chore: update tests * fix: fixed xray config processing * fix: fixed split tunneling processing * chore: rename sites controllers and model * chore: rename fixes * chore: minor fixes * chore: remove ability to load backup from "file with connection settings" button * fix: fixed api device revoke * fix: remove full model update when renaming a user * fix: fixed premium/free server rename * fix: fixed selfhosted new server install * fix: fixed updateContainer function * fix: fixed revoke for external premium configs * feat: add native configs qr processing * chore: codestyle fixes * fix: fixed admin config create * chore: again remove ability to load backup from "file with connection settings" button * chore: minor fixes * fix: fixed variables initialization * fix: fixed qml imports * fix: minor fixes * fix: fix vpnConnection function calls * feat: add buckup error handling * fix: fixed admin config revok * fix: fixed selfhosted awg installation * fix: ad visability * feat: add empty check for primary dns * chore: minor fixes
1180 lines
46 KiB
C++
1180 lines
46 KiB
C++
#include "installController.h"
|
|
|
|
#include "core/models/protocolConfig.h"
|
|
|
|
#include <QDebug>
|
|
#include <QEventLoop>
|
|
#include <QFutureWatcher>
|
|
#include <QRegularExpression>
|
|
#include <QThread>
|
|
#include <QtConcurrent>
|
|
|
|
#include "core/configurators/configuratorBase.h"
|
|
#include "core/utils/containerEnum.h"
|
|
#include "core/utils/containers/containerUtils.h"
|
|
#include "core/utils/protocolEnum.h"
|
|
#include "core/utils/selfhosted/sshSession.h"
|
|
#include "core/installers/awgInstaller.h"
|
|
#include "core/installers/installerBase.h"
|
|
#include "core/installers/openvpnInstaller.h"
|
|
#include "core/installers/sftpInstaller.h"
|
|
#include "core/installers/socks5Installer.h"
|
|
#include "core/installers/torInstaller.h"
|
|
#include "core/installers/wireguardInstaller.h"
|
|
#include "core/installers/xrayInstaller.h"
|
|
#include "core/utils/networkUtilities.h"
|
|
#include "core/utils/api/apiUtils.h"
|
|
#include "core/repositories/secureServersRepository.h"
|
|
#include "core/repositories/secureAppSettingsRepository.h"
|
|
#include "core/utils/selfhosted/scriptsRegistry.h"
|
|
#include "core/utils/selfhosted/sshClient.h"
|
|
#include "logger.h"
|
|
#include "core/utils/protocolEnum.h"
|
|
#include "core/protocols/protocolUtils.h"
|
|
#include "core/utils/constants/configKeys.h"
|
|
#include "core/utils/constants/protocolConstants.h"
|
|
#include "core/models/serverConfig.h"
|
|
#include "core/models/containerConfig.h"
|
|
#include "core/models/protocols/awgProtocolConfig.h"
|
|
#include "ui/models/protocols/wireguardConfigModel.h"
|
|
#include "core/utils/utilities.h"
|
|
#include <QDesktopServices>
|
|
#include <QDir>
|
|
#include <QProcess>
|
|
#include <QStandardPaths>
|
|
#include <QUrl>
|
|
#include <QSysInfo>
|
|
#ifdef Q_OS_WINDOWS
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
using namespace amnezia;
|
|
using namespace ProtocolUtils;
|
|
|
|
namespace
|
|
{
|
|
Logger logger("InstallController");
|
|
}
|
|
|
|
InstallController::InstallController(SecureServersRepository *serversRepository,
|
|
SecureAppSettingsRepository* appSettingsRepository,
|
|
QObject *parent)
|
|
: QObject(parent),
|
|
m_serversRepository(serversRepository),
|
|
m_appSettingsRepository(appSettingsRepository),
|
|
m_cancelInstallation(false)
|
|
{
|
|
}
|
|
|
|
InstallController::~InstallController()
|
|
{
|
|
stopAllSftpMounts();
|
|
}
|
|
|
|
ErrorCode InstallController::setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config,
|
|
bool isUpdate)
|
|
{
|
|
qDebug().noquote() << "InstallController::setupContainer" << ContainerUtils::containerToString(container);
|
|
SshSession sshSession(this);
|
|
ErrorCode e = ErrorCode::NoError;
|
|
|
|
e = isUserInSudo(credentials, sshSession);
|
|
if (e)
|
|
return e;
|
|
|
|
e = isServerDpkgBusy(credentials, sshSession);
|
|
if (e)
|
|
return e;
|
|
|
|
e = installDockerWorker(credentials, container, sshSession);
|
|
if (e)
|
|
return e;
|
|
qDebug().noquote() << "InstallController::setupContainer installDockerWorker finished";
|
|
|
|
if (!isUpdate) {
|
|
e = isServerPortBusy(credentials, container, config, sshSession);
|
|
if (e)
|
|
return e;
|
|
}
|
|
|
|
e = prepareHostWorker(credentials, container, sshSession);
|
|
if (e)
|
|
return e;
|
|
qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished";
|
|
|
|
sshSession.runScript(credentials,
|
|
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
|
|
amnezia::genBaseVars(credentials, container, QString(), QString())));
|
|
qDebug().noquote() << "InstallController::setupContainer removeContainer finished";
|
|
|
|
qDebug().noquote() << "buildContainerWorker start";
|
|
e = buildContainerWorker(credentials, container, config, sshSession);
|
|
if (e)
|
|
return e;
|
|
qDebug().noquote() << "InstallController::setupContainer buildContainerWorker finished";
|
|
|
|
e = runContainerWorker(credentials, container, config, sshSession);
|
|
if (e)
|
|
return e;
|
|
qDebug().noquote() << "InstallController::setupContainer runContainerWorker finished";
|
|
|
|
e = configureContainerWorker(credentials, container, config, sshSession);
|
|
if (e)
|
|
return e;
|
|
qDebug().noquote() << "InstallController::setupContainer configureContainerWorker finished";
|
|
|
|
setupServerFirewall(credentials, sshSession);
|
|
qDebug().noquote() << "InstallController::setupContainer setupServerFirewall finished";
|
|
|
|
return startupContainerWorker(credentials, container, config, sshSession);
|
|
}
|
|
|
|
ErrorCode InstallController::updateContainer(int serverIndex, DockerContainer container, const ContainerConfig &oldConfig,
|
|
ContainerConfig &newConfig)
|
|
{
|
|
if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) {
|
|
m_serversRepository->setContainerConfig(serverIndex, container, newConfig);
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
|
SshSession sshSession(this);
|
|
|
|
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
|
qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
|
|
|
|
ErrorCode errorCode = ErrorCode::NoError;
|
|
if (reinstallRequired) {
|
|
errorCode = setupContainer(credentials, container, newConfig, true);
|
|
} else {
|
|
errorCode = configureContainerWorker(credentials, container, newConfig, sshSession);
|
|
if (errorCode == ErrorCode::NoError) {
|
|
errorCode = startupContainerWorker(credentials, container, newConfig, sshSession);
|
|
}
|
|
}
|
|
|
|
if (errorCode == ErrorCode::NoError) {
|
|
clearCachedProfile(serverIndex, container);
|
|
m_serversRepository->setContainerConfig(serverIndex, container, newConfig);
|
|
}
|
|
|
|
return errorCode;
|
|
}
|
|
|
|
void InstallController::clearCachedProfile(int serverIndex, DockerContainer container)
|
|
{
|
|
if (ContainerUtils::containerService(container) == ServiceType::Other) {
|
|
return;
|
|
}
|
|
|
|
ContainerConfig containerConfigModel = m_serversRepository->containerConfig(serverIndex, container);
|
|
|
|
m_serversRepository->clearLastConnectionConfig(serverIndex, container);
|
|
|
|
emit clientRevocationRequested(serverIndex, containerConfigModel, container);
|
|
}
|
|
|
|
ErrorCode InstallController::validateAndPrepareConfig(int serverIndex)
|
|
{
|
|
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
|
|
|
if (serverConfigModel.isApiConfig()) {
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
DockerContainer container = serverConfigModel.defaultContainer();
|
|
|
|
if (container == DockerContainer::None) {
|
|
return ErrorCode::NoInstalledContainersError;
|
|
}
|
|
|
|
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container);
|
|
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
|
SshSession sshSession;
|
|
|
|
auto isProtocolConfigExists = [](const ContainerConfig &cfg) {
|
|
return cfg.protocolConfig.hasClientConfig();
|
|
};
|
|
|
|
if (!isProtocolConfigExists(containerConfig)) {
|
|
QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
|
|
ErrorCode errorCode = processContainerForAdmin(container, containerConfig, credentials, sshSession, serverIndex, clientName);
|
|
if (errorCode != ErrorCode::NoError) {
|
|
return errorCode;
|
|
}
|
|
m_serversRepository->setContainerConfig(serverIndex, container, containerConfig);
|
|
}
|
|
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
void InstallController::validateConfig(int serverIndex)
|
|
{
|
|
QFuture<ErrorCode> future = QtConcurrent::run([this, serverIndex]() {
|
|
return validateAndPrepareConfig(serverIndex);
|
|
});
|
|
|
|
auto *watcher = new QFutureWatcher<ErrorCode>(this);
|
|
connect(watcher, &QFutureWatcher<ErrorCode>::finished, this, [this, watcher]() {
|
|
ErrorCode errorCode = watcher->result();
|
|
watcher->deleteLater();
|
|
|
|
if (errorCode == ErrorCode::NoError) {
|
|
emit configValidated(true);
|
|
return;
|
|
}
|
|
|
|
emit validationErrorOccurred(errorCode);
|
|
emit configValidated(false);
|
|
});
|
|
watcher->setFuture(future);
|
|
}
|
|
|
|
ErrorCode InstallController::prepareContainerConfig(DockerContainer container, const ServerCredentials &credentials, ContainerConfig &containerConfig, SshSession &sshSession)
|
|
{
|
|
if (!ContainerUtils::isSupportedByCurrentPlatform(container)) {
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
if (ContainerUtils::containerService(container) != ServiceType::Other) {
|
|
Proto protocol = ContainerUtils::defaultProtocol(container);
|
|
|
|
DnsSettings dnsSettings = {
|
|
m_appSettingsRepository->primaryDns(),
|
|
m_appSettingsRepository->secondaryDns()
|
|
};
|
|
|
|
auto configurator = ConfiguratorBase::create(protocol, &sshSession);
|
|
ErrorCode errorCode = ErrorCode::NoError;
|
|
ProtocolConfig newProtocolConfig = configurator->createConfig(credentials, container, containerConfig, dnsSettings, errorCode);
|
|
if (errorCode != ErrorCode::NoError) {
|
|
return errorCode;
|
|
}
|
|
|
|
containerConfig.protocolConfig = newProtocolConfig;
|
|
}
|
|
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
void InstallController::adminAppendRequested(int serverIndex, DockerContainer container,
|
|
const ContainerConfig &containerConfig, const QString &clientName)
|
|
{
|
|
if (ContainerUtils::containerService(container) == ServiceType::Other
|
|
|| !containerConfig.protocolConfig.hasClientConfig()) {
|
|
return;
|
|
}
|
|
QString clientId = containerConfig.protocolConfig.clientId();
|
|
if (!clientId.isEmpty()) {
|
|
emit clientAppendRequested(serverIndex, clientId, clientName, container);
|
|
}
|
|
}
|
|
|
|
ErrorCode InstallController::processContainerForAdmin(DockerContainer container, ContainerConfig &containerConfig,
|
|
const ServerCredentials &credentials, SshSession &sshSession,
|
|
int serverIndex, const QString &clientName)
|
|
{
|
|
if (ContainerUtils::isSupportedByCurrentPlatform(container)) {
|
|
ErrorCode errorCode = prepareContainerConfig(container, credentials, containerConfig, sshSession);
|
|
if (errorCode != ErrorCode::NoError) {
|
|
return errorCode;
|
|
}
|
|
}
|
|
adminAppendRequested(serverIndex, container, containerConfig, clientName);
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
ErrorCode InstallController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession)
|
|
{
|
|
amnezia::ScriptVars baseVars = amnezia::genBaseVars(credentials, container, QString(), QString());
|
|
|
|
QString dockerfilePath = "/opt/amnezia/" + ContainerUtils::containerToString(container) + "/Dockerfile";
|
|
QString removeScript = QString("sudo rm %1").arg(dockerfilePath);
|
|
|
|
ErrorCode errorCode = sshSession.runScript(credentials, sshSession.replaceVars(removeScript, baseVars));
|
|
if (errorCode != ErrorCode::NoError) {
|
|
return errorCode;
|
|
}
|
|
|
|
errorCode = sshSession.uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(), dockerfilePath);
|
|
if (errorCode != ErrorCode::NoError) {
|
|
return errorCode;
|
|
}
|
|
|
|
QString stdOut;
|
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
|
|
amnezia::ScriptVars protocolVars = amnezia::genProtocolVarsForContainer(container, config);
|
|
baseVars.append(protocolVars);
|
|
ErrorCode error = sshSession.runScript(
|
|
credentials, sshSession.replaceVars(amnezia::scriptData(SharedScriptType::build_container), baseVars), cbReadStdOut,
|
|
cbReadStdErr);
|
|
|
|
if (stdOut.contains("doesn't work on cgroups v2"))
|
|
return ErrorCode::ServerDockerOnCgroupsV2;
|
|
if (stdOut.contains("cgroup mountpoint does not exist"))
|
|
return ErrorCode::ServerCgroupMountpoint;
|
|
if (stdOut.contains("have reached") && stdOut.contains("pull rate limit"))
|
|
return ErrorCode::DockerPullRateLimit;
|
|
|
|
return error;
|
|
}
|
|
|
|
ErrorCode InstallController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, SshSession &sshSession)
|
|
{
|
|
QString stdOut;
|
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
|
|
amnezia::ScriptVars baseVars = amnezia::genBaseVars(credentials, container, QString(), QString());
|
|
amnezia::ScriptVars protocolVars = amnezia::genProtocolVarsForContainer(container, config);
|
|
baseVars.append(protocolVars);
|
|
ErrorCode e = sshSession.runScript(
|
|
credentials, sshSession.replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container), baseVars),
|
|
cbReadStdOut);
|
|
|
|
if (stdOut.contains("address already in use"))
|
|
return ErrorCode::ServerPortAlreadyAllocatedError;
|
|
if (stdOut.contains("is already in use by container"))
|
|
return ErrorCode::ServerPortAlreadyAllocatedError;
|
|
if (stdOut.contains("invalid publish"))
|
|
return ErrorCode::ServerDockerFailedError;
|
|
|
|
return e;
|
|
}
|
|
|
|
ErrorCode InstallController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, SshSession &sshSession)
|
|
{
|
|
QString stdOut;
|
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
|
|
amnezia::ScriptVars baseVars = amnezia::genBaseVars(credentials, container, QString(), QString());
|
|
amnezia::ScriptVars protocolVars = amnezia::genProtocolVarsForContainer(container, config);
|
|
baseVars.append(protocolVars);
|
|
ErrorCode e = sshSession.runContainerScript(
|
|
credentials, container,
|
|
sshSession.replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container), baseVars),
|
|
cbReadStdOut, cbReadStdErr);
|
|
|
|
updateContainerConfigAfterInstallation(container, config, stdOut);
|
|
|
|
return e;
|
|
}
|
|
|
|
ErrorCode InstallController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession)
|
|
{
|
|
QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container);
|
|
|
|
if (script.isEmpty()) {
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
amnezia::ScriptVars baseVars = amnezia::genBaseVars(credentials, container, QString(), QString());
|
|
amnezia::ScriptVars protocolVars = amnezia::genProtocolVarsForContainer(container, config);
|
|
baseVars.append(protocolVars);
|
|
ErrorCode e = sshSession.uploadTextFileToContainer(container, credentials, sshSession.replaceVars(script, baseVars),
|
|
"/opt/amnezia/start.sh");
|
|
if (e)
|
|
return e;
|
|
|
|
return sshSession.runScript(
|
|
credentials,
|
|
sshSession.replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && "
|
|
"/opt/amnezia/start.sh\"",
|
|
baseVars));
|
|
}
|
|
|
|
ErrorCode InstallController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession)
|
|
{
|
|
if (container == DockerContainer::Dns) {
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
QString stdOut;
|
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
|
|
const Proto protocol = ContainerUtils::defaultProtocol(container);
|
|
QStringList fixedPorts = ContainerUtils::fixedPortsForContainer(container);
|
|
|
|
QString port = config.protocolConfig.port();
|
|
if (port.isEmpty()) {
|
|
port = QString::number(ProtocolUtils::defaultPort(protocol));
|
|
}
|
|
QString transportProto = config.protocolConfig.transportProto();
|
|
if (transportProto.isEmpty()) {
|
|
transportProto = ProtocolUtils::transportProtoToString(ProtocolUtils::defaultTransportProto(protocol), protocol);
|
|
}
|
|
|
|
// TODO reimplement with netstat
|
|
QString script = QString("which lsof > /dev/null 2>&1 || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
|
|
for (auto &port : fixedPorts) {
|
|
script = script.append("|:%1").arg(port);
|
|
}
|
|
|
|
if (transportProto == "tcpandudp") {
|
|
QString tcpProtoScript = script;
|
|
QString udpProtoScript = script;
|
|
tcpProtoScript.append("' | grep -i tcp");
|
|
udpProtoScript.append("' | grep -i udp");
|
|
tcpProtoScript.append(" | grep LISTEN");
|
|
|
|
ErrorCode errorCode = sshSession.runScript(
|
|
credentials,
|
|
sshSession.replaceVars(tcpProtoScript, amnezia::genBaseVars(credentials, container, QString(), QString())),
|
|
cbReadStdOut, cbReadStdErr);
|
|
if (errorCode != ErrorCode::NoError) {
|
|
return errorCode;
|
|
}
|
|
|
|
errorCode = sshSession.runScript(
|
|
credentials,
|
|
sshSession.replaceVars(udpProtoScript, amnezia::genBaseVars(credentials, container, QString(), QString())),
|
|
cbReadStdOut, cbReadStdErr);
|
|
if (errorCode != ErrorCode::NoError) {
|
|
return errorCode;
|
|
}
|
|
|
|
if (!stdOut.isEmpty()) {
|
|
return ErrorCode::ServerPortAlreadyAllocatedError;
|
|
}
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
script = script.append("' | grep -i %1").arg(transportProto);
|
|
|
|
if (transportProto == "tcp") {
|
|
script = script.append(" | grep LISTEN");
|
|
}
|
|
|
|
ErrorCode errorCode = sshSession.runScript(
|
|
credentials, sshSession.replaceVars(script, amnezia::genBaseVars(credentials, container, QString(), QString())),
|
|
cbReadStdOut, cbReadStdErr);
|
|
if (errorCode != ErrorCode::NoError) {
|
|
return errorCode;
|
|
}
|
|
|
|
if (!stdOut.isEmpty()) {
|
|
return ErrorCode::ServerPortAlreadyAllocatedError;
|
|
}
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
bool InstallController::isReinstallContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig)
|
|
{
|
|
if (container == DockerContainer::OpenVpn) {
|
|
const auto* oldOvpnConfig = oldConfig.getOpenVpnProtocolConfig();
|
|
const auto* newOvpnConfig = newConfig.getOpenVpnProtocolConfig();
|
|
|
|
if (oldOvpnConfig && newOvpnConfig) {
|
|
if (!oldOvpnConfig->serverConfig.hasEqualServerSettings(newOvpnConfig->serverConfig)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ContainerUtils::isAwgContainer(container)) {
|
|
const auto* oldAwgConfig = oldConfig.getAwgProtocolConfig();
|
|
const auto* newAwgConfig = newConfig.getAwgProtocolConfig();
|
|
|
|
if (oldAwgConfig && newAwgConfig) {
|
|
if (!oldAwgConfig->serverConfig.hasEqualServerSettings(newAwgConfig->serverConfig)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (container == DockerContainer::WireGuard) {
|
|
const auto* oldWgConfig = oldConfig.getWireGuardProtocolConfig();
|
|
const auto* newWgConfig = newConfig.getWireGuardProtocolConfig();
|
|
|
|
if (oldWgConfig && newWgConfig) {
|
|
if (!oldWgConfig->serverConfig.hasEqualServerSettings(newWgConfig->serverConfig)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
|
|
const auto* oldXrayConfig = oldConfig.getXrayProtocolConfig();
|
|
const auto* newXrayConfig = newConfig.getXrayProtocolConfig();
|
|
|
|
if (oldXrayConfig && newXrayConfig) {
|
|
if (oldXrayConfig->serverConfig.port != newXrayConfig->serverConfig.port)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (container == DockerContainer::Socks5Proxy) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void InstallController::cancelInstallation()
|
|
{
|
|
m_cancelInstallation = true;
|
|
}
|
|
|
|
ErrorCode InstallController::installDockerWorker(const ServerCredentials &credentials, DockerContainer container, SshSession &sshSession)
|
|
{
|
|
QString stdOut;
|
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &client) {
|
|
stdOut += data + "\n";
|
|
|
|
if (data.contains("Automatically restart Docker daemon?")) {
|
|
return client.writeResponse("yes");
|
|
}
|
|
return ErrorCode::NoError;
|
|
};
|
|
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
|
|
ErrorCode error = sshSession.runScript(
|
|
credentials,
|
|
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::install_docker),
|
|
amnezia::genBaseVars(credentials, DockerContainer::None, QString(), QString())),
|
|
cbReadStdOut, cbReadStdErr);
|
|
|
|
qDebug().noquote() << "InstallController::installDockerWorker" << stdOut;
|
|
|
|
if (container == DockerContainer::Awg2) {
|
|
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
|
|
QRegularExpressionMatch match = regex.match(stdOut);
|
|
if (match.hasMatch()) {
|
|
int majorVersion = match.captured(1).toInt();
|
|
int minorVersion = match.captured(2).toInt();
|
|
|
|
if (majorVersion < 4 || (majorVersion == 4 && minorVersion < 14)) {
|
|
return ErrorCode::ServerLinuxKernelTooOld;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (stdOut.contains("lock"))
|
|
return ErrorCode::ServerPacketManagerError;
|
|
if (stdOut.contains("command not found"))
|
|
return ErrorCode::ServerDockerFailedError;
|
|
|
|
return error;
|
|
}
|
|
|
|
ErrorCode InstallController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, SshSession &sshSession)
|
|
{
|
|
// create folder on host
|
|
return sshSession.runScript(credentials,
|
|
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::prepare_host),
|
|
amnezia::genBaseVars(credentials, container, QString(), QString())));
|
|
}
|
|
|
|
ErrorCode InstallController::isUserInSudo(const ServerCredentials &credentials, SshSession &sshSession)
|
|
{
|
|
QString stdOut;
|
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
|
|
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
|
ErrorCode error = sshSession.runScript(
|
|
credentials,
|
|
sshSession.replaceVars(scriptData, amnezia::genBaseVars(credentials, DockerContainer::None, QString(), QString())),
|
|
cbReadStdOut, cbReadStdErr);
|
|
|
|
if (credentials.userName != "root" && stdOut.contains("sudo:") && !stdOut.contains("uname:") && stdOut.contains("not found"))
|
|
return ErrorCode::ServerSudoPackageIsNotPreinstalled;
|
|
if (credentials.userName != "root" && !stdOut.contains("sudo") && !stdOut.contains("wheel"))
|
|
return ErrorCode::ServerUserNotInSudo;
|
|
if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory"))
|
|
return ErrorCode::ServerUserDirectoryNotAccessible;
|
|
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
|
|
return ErrorCode::ServerUserNotAllowedInSudoers;
|
|
if (stdOut.contains("password is required"))
|
|
return ErrorCode::ServerUserPasswordRequired;
|
|
|
|
return error;
|
|
}
|
|
|
|
ErrorCode InstallController::isServerDpkgBusy(const ServerCredentials &credentials, SshSession &sshSession)
|
|
{
|
|
m_cancelInstallation = false;
|
|
QString stdOut;
|
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
|
|
QFutureWatcher<ErrorCode> watcher;
|
|
|
|
QFuture<ErrorCode> future = QtConcurrent::run([this, &stdOut, &cbReadStdOut, &cbReadStdErr, &credentials, &sshSession]() {
|
|
// max 100 attempts
|
|
for (int i = 0; i < 30; ++i) {
|
|
if (m_cancelInstallation) {
|
|
return ErrorCode::ServerCancelInstallation;
|
|
}
|
|
stdOut.clear();
|
|
sshSession.runScript(
|
|
credentials,
|
|
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy),
|
|
amnezia::genBaseVars(credentials, DockerContainer::None, QString(), QString())),
|
|
cbReadStdOut, cbReadStdErr);
|
|
|
|
if (stdOut.contains("Packet manager not found"))
|
|
return ErrorCode::ServerPacketManagerError;
|
|
if (stdOut.contains("fuser not installed") || stdOut.contains("cat not installed"))
|
|
return ErrorCode::NoError;
|
|
|
|
if (stdOut.isEmpty()) {
|
|
return ErrorCode::NoError;
|
|
} else {
|
|
#ifdef MZ_DEBUG
|
|
qDebug().noquote() << stdOut;
|
|
#endif
|
|
emit serverIsBusy(true);
|
|
QThread::msleep(10000);
|
|
}
|
|
}
|
|
return ErrorCode::ServerPacketManagerError;
|
|
});
|
|
|
|
QEventLoop wait;
|
|
QObject::connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
|
|
watcher.setFuture(future);
|
|
wait.exec();
|
|
|
|
emit serverIsBusy(false);
|
|
|
|
return future.result();
|
|
}
|
|
|
|
ErrorCode InstallController::setupServerFirewall(const ServerCredentials &credentials, SshSession &sshSession)
|
|
{
|
|
return sshSession.runScript(
|
|
credentials,
|
|
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall),
|
|
amnezia::genBaseVars(credentials, DockerContainer::None, QString(), QString())));
|
|
}
|
|
|
|
ErrorCode InstallController::rebootServer(int serverIndex)
|
|
{
|
|
auto credentials = m_serversRepository->serverCredentials(serverIndex);
|
|
SshSession sshSession(this);
|
|
|
|
QString script = QString("sudo reboot");
|
|
|
|
QString stdOut;
|
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data;
|
|
return ErrorCode::NoError;
|
|
};
|
|
|
|
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
|
|
return sshSession.runScript(credentials, script, cbReadStdOut, cbReadStdErr);
|
|
}
|
|
|
|
ErrorCode InstallController::removeAllContainers(int serverIndex)
|
|
{
|
|
auto credentials = m_serversRepository->serverCredentials(serverIndex);
|
|
SshSession sshSession(this);
|
|
ErrorCode errorCode = sshSession.runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers));
|
|
|
|
if (errorCode == ErrorCode::NoError) {
|
|
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
|
serverConfigModel.visit([](auto& arg) {
|
|
arg.containers.clear();
|
|
arg.defaultContainer = DockerContainer::None;
|
|
});
|
|
m_serversRepository->editServer(serverIndex, serverConfigModel);
|
|
}
|
|
|
|
return errorCode;
|
|
}
|
|
|
|
ErrorCode InstallController::removeContainer(int serverIndex, DockerContainer container)
|
|
{
|
|
auto credentials = m_serversRepository->serverCredentials(serverIndex);
|
|
SshSession sshSession(this);
|
|
ErrorCode errorCode = sshSession.runScript(
|
|
credentials,
|
|
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
|
|
amnezia::genBaseVars(credentials, container, QString(), QString())));
|
|
|
|
if (errorCode == ErrorCode::NoError) {
|
|
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
|
QMap<DockerContainer, ContainerConfig> containers = serverConfigModel.containers();
|
|
containers.remove(container);
|
|
|
|
DockerContainer defaultContainer = serverConfigModel.defaultContainer();
|
|
if (defaultContainer == container) {
|
|
if (containers.isEmpty()) {
|
|
defaultContainer = DockerContainer::None;
|
|
} else {
|
|
defaultContainer = containers.begin().key();
|
|
}
|
|
}
|
|
|
|
serverConfigModel.visit([&containers, defaultContainer](auto& arg) {
|
|
arg.containers = containers;
|
|
arg.defaultContainer = defaultContainer;
|
|
});
|
|
m_serversRepository->editServer(serverIndex, serverConfigModel);
|
|
}
|
|
|
|
return errorCode;
|
|
}
|
|
|
|
QScopedPointer<InstallerBase> InstallController::createInstaller(DockerContainer container)
|
|
{
|
|
switch (container) {
|
|
case DockerContainer::Awg: return QScopedPointer<InstallerBase>(new AwgInstaller(this));
|
|
case DockerContainer::Awg2: return QScopedPointer<InstallerBase>(new AwgInstaller(this));
|
|
case DockerContainer::WireGuard: return QScopedPointer<InstallerBase>(new WireguardInstaller(this));
|
|
case DockerContainer::OpenVpn: return QScopedPointer<InstallerBase>(new OpenVpnInstaller(this));
|
|
case DockerContainer::Xray:
|
|
case DockerContainer::SSXray: return QScopedPointer<InstallerBase>(new XrayInstaller(this));
|
|
case DockerContainer::TorWebSite: return QScopedPointer<InstallerBase>(new TorInstaller(this));
|
|
case DockerContainer::Sftp: return QScopedPointer<InstallerBase>(new SftpInstaller(this));
|
|
case DockerContainer::Socks5Proxy: return QScopedPointer<InstallerBase>(new Socks5Installer(this));
|
|
default: return QScopedPointer<InstallerBase>(new InstallerBase(this));
|
|
}
|
|
}
|
|
|
|
ContainerConfig InstallController::generateConfig(DockerContainer container, int port, TransportProto transportProto)
|
|
{
|
|
auto installer = createInstaller(container);
|
|
return installer->generateConfig(container, port, transportProto);
|
|
}
|
|
|
|
ErrorCode InstallController::installContainer(const ServerCredentials &credentials, DockerContainer container, int port,
|
|
TransportProto transportProto, ContainerConfig &config)
|
|
{
|
|
config = generateConfig(container, port, transportProto);
|
|
return setupContainer(credentials, container, config, false);
|
|
}
|
|
|
|
|
|
bool InstallController::isUpdateDockerContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig)
|
|
{
|
|
if (ContainerUtils::isAwgContainer(container)) {
|
|
const auto* oldAwgConfig = oldConfig.getAwgProtocolConfig();
|
|
const auto* newAwgConfig = newConfig.getAwgProtocolConfig();
|
|
|
|
if (oldAwgConfig && newAwgConfig) {
|
|
if (oldAwgConfig->serverConfig.hasEqualServerSettings(newAwgConfig->serverConfig)) {
|
|
return false;
|
|
}
|
|
}
|
|
} else if (container == DockerContainer::WireGuard) {
|
|
const auto* oldWgConfig = oldConfig.getWireGuardProtocolConfig();
|
|
const auto* newWgConfig = newConfig.getWireGuardProtocolConfig();
|
|
|
|
if (oldWgConfig && newWgConfig) {
|
|
if (oldWgConfig->serverConfig.hasEqualServerSettings(newWgConfig->serverConfig)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ErrorCode InstallController::scanServerForInstalledContainers(int serverIndex)
|
|
{
|
|
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
|
SshSession sshSession(this);
|
|
|
|
QMap<DockerContainer, ContainerConfig> installedContainers;
|
|
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
|
if (errorCode != ErrorCode::NoError) {
|
|
return errorCode;
|
|
}
|
|
|
|
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
|
QMap<DockerContainer, ContainerConfig> containers = serverConfigModel.containers();
|
|
bool hasNewContainers = false;
|
|
|
|
QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
|
|
for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) {
|
|
if (!containers.contains(iterator.key())) {
|
|
ContainerConfig containerConfig = iterator.value();
|
|
errorCode = processContainerForAdmin(iterator.key(), containerConfig, credentials, sshSession,
|
|
serverIndex, clientName);
|
|
if (errorCode != ErrorCode::NoError) {
|
|
return errorCode;
|
|
}
|
|
containers.insert(iterator.key(), containerConfig);
|
|
hasNewContainers = true;
|
|
|
|
DockerContainer defaultContainer = serverConfigModel.defaultContainer();
|
|
if (defaultContainer == DockerContainer::None
|
|
&& ContainerUtils::containerService(iterator.key()) != ServiceType::Other
|
|
&& ContainerUtils::isSupportedByCurrentPlatform(iterator.key())) {
|
|
serverConfigModel.visit([iterator](auto& arg) {
|
|
arg.defaultContainer = iterator.key();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasNewContainers) {
|
|
serverConfigModel.visit([&containers](auto& arg) {
|
|
arg.containers = containers;
|
|
});
|
|
m_serversRepository->editServer(serverIndex, serverConfigModel);
|
|
}
|
|
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
ErrorCode InstallController::installServer(const ServerCredentials &credentials, DockerContainer container, int port,
|
|
TransportProto transportProto, bool &wasContainerInstalled)
|
|
{
|
|
SshSession sshSession(this);
|
|
QMap<DockerContainer, ContainerConfig> installedContainers;
|
|
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
|
if (errorCode) {
|
|
return errorCode;
|
|
}
|
|
|
|
wasContainerInstalled = false;
|
|
if (!installedContainers.contains(container)) {
|
|
ContainerConfig config;
|
|
errorCode = installContainer(credentials, container, port, transportProto, config);
|
|
if (errorCode) {
|
|
return errorCode;
|
|
}
|
|
|
|
installedContainers.insert(container, config);
|
|
wasContainerInstalled = true;
|
|
}
|
|
|
|
QMap<DockerContainer, ContainerConfig> preparedContainers;
|
|
for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) {
|
|
DockerContainer container = iterator.key();
|
|
ContainerConfig containerConfig = iterator.value();
|
|
|
|
if (ContainerUtils::isSupportedByCurrentPlatform(container)) {
|
|
errorCode = prepareContainerConfig(container, credentials, containerConfig, sshSession);
|
|
if (errorCode != ErrorCode::NoError) {
|
|
return errorCode;
|
|
}
|
|
}
|
|
preparedContainers.insert(container, containerConfig);
|
|
}
|
|
|
|
SelfHostedServerConfig serverConfig;
|
|
serverConfig.hostName = credentials.hostName;
|
|
serverConfig.userName = credentials.userName;
|
|
serverConfig.password = credentials.secretData;
|
|
serverConfig.port = credentials.port;
|
|
serverConfig.description = m_appSettingsRepository->nextAvailableServerName();
|
|
|
|
for (auto iterator = preparedContainers.begin(); iterator != preparedContainers.end(); iterator++) {
|
|
serverConfig.containers.insert(iterator.key(), iterator.value());
|
|
}
|
|
|
|
serverConfig.defaultContainer = container;
|
|
|
|
m_serversRepository->addServer(ServerConfig(serverConfig));
|
|
|
|
int serverIndex = m_serversRepository->serversCount() - 1;
|
|
QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
|
|
for (auto iterator = preparedContainers.begin(); iterator != preparedContainers.end(); iterator++) {
|
|
adminAppendRequested(serverIndex, iterator.key(), iterator.value(), clientName);
|
|
}
|
|
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
ErrorCode InstallController::installContainer(int serverIndex, DockerContainer container, int port,
|
|
TransportProto transportProto, bool &wasContainerInstalled)
|
|
{
|
|
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
|
SshSession sshSession(this);
|
|
|
|
QMap<DockerContainer, ContainerConfig> installedContainers;
|
|
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
|
if (errorCode) {
|
|
return errorCode;
|
|
}
|
|
|
|
wasContainerInstalled = false;
|
|
if (!installedContainers.contains(container)) {
|
|
ContainerConfig config;
|
|
errorCode = installContainer(credentials, container, port, transportProto, config);
|
|
if (errorCode) {
|
|
return errorCode;
|
|
}
|
|
|
|
installedContainers.insert(container, config);
|
|
wasContainerInstalled = true;
|
|
}
|
|
|
|
QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
|
|
for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) {
|
|
ContainerConfig existingConfigModel = m_serversRepository->containerConfig(serverIndex, iterator.key());
|
|
if (existingConfigModel.container == DockerContainer::None) {
|
|
ContainerConfig containerConfig = iterator.value();
|
|
errorCode = processContainerForAdmin(iterator.key(), containerConfig, credentials, sshSession,
|
|
serverIndex, clientName);
|
|
if (errorCode != ErrorCode::NoError) {
|
|
return errorCode;
|
|
}
|
|
m_serversRepository->setContainerConfig(serverIndex, iterator.key(), containerConfig);
|
|
}
|
|
}
|
|
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
ErrorCode InstallController::checkSshConnection(const ServerCredentials &credentials, QString &output,
|
|
std::function<QString()> passphraseCallback)
|
|
{
|
|
SshSession sshSession(this);
|
|
ErrorCode errorCode = ErrorCode::NoError;
|
|
|
|
ServerCredentials processedCredentials = credentials;
|
|
|
|
if (processedCredentials.secretData.contains("BEGIN") && processedCredentials.secretData.contains("PRIVATE KEY")) {
|
|
if (!passphraseCallback) {
|
|
return ErrorCode::SshPrivateKeyError;
|
|
}
|
|
|
|
QString decryptedPrivateKey;
|
|
errorCode = sshSession.getDecryptedPrivateKey(processedCredentials, decryptedPrivateKey, passphraseCallback);
|
|
if (errorCode != ErrorCode::NoError) {
|
|
return errorCode;
|
|
}
|
|
processedCredentials.secretData = decryptedPrivateKey;
|
|
}
|
|
|
|
output = sshSession.checkSshConnection(processedCredentials, errorCode);
|
|
return errorCode;
|
|
}
|
|
|
|
bool InstallController::isServerAlreadyExists(const ServerCredentials &credentials, int &existingServerIndex)
|
|
{
|
|
int serversCount = m_serversRepository->serversCount();
|
|
for (int i = 0; i < serversCount; i++) {
|
|
const ServerCredentials existingCredentials = m_serversRepository->serverCredentials(i);
|
|
if (credentials.hostName == existingCredentials.hostName && credentials.port == existingCredentials.port) {
|
|
existingServerIndex = i;
|
|
return true;
|
|
}
|
|
}
|
|
existingServerIndex = -1;
|
|
return false;
|
|
}
|
|
|
|
ErrorCode InstallController::mountSftpDrive(const ServerCredentials &credentials, const QString &port, const QString &password,
|
|
const QString &username)
|
|
{
|
|
QString mountPath;
|
|
QString cmd;
|
|
QString hostname = credentials.hostName;
|
|
|
|
#ifdef Q_OS_WINDOWS
|
|
mountPath = Utils::getNextDriverLetter() + ":";
|
|
cmd = "C:\\Program Files\\SSHFS-Win\\bin\\sshfs.exe";
|
|
#elif defined AMNEZIA_DESKTOP
|
|
mountPath = QString("%1/sftp:%2:%3").arg(QStandardPaths::writableLocation(QStandardPaths::HomeLocation), hostname, port);
|
|
QDir dir(mountPath);
|
|
if (!dir.exists()) {
|
|
dir.mkpath(mountPath);
|
|
}
|
|
|
|
cmd = "/usr/local/bin/sshfs";
|
|
|
|
QSharedPointer<QProcess> process(new QProcess(this));
|
|
process->setProcessChannelMode(QProcess::MergedChannels);
|
|
|
|
connect(process.get(), &QProcess::readyRead, this, [process, mountPath]() {
|
|
QString s = process->readAll();
|
|
if (s.contains("The service sshfs has been started")) {
|
|
QDesktopServices::openUrl(QUrl("file:///" + mountPath));
|
|
}
|
|
qDebug() << s;
|
|
});
|
|
|
|
process->setProgram(cmd);
|
|
|
|
QString args = QString("%1@%2:/ %3 "
|
|
"-o port=%4 "
|
|
"-f "
|
|
"-o reconnect "
|
|
"-o rellinks "
|
|
"-o fstypename=SSHFS "
|
|
"-o ssh_command=/usr/bin/ssh.exe "
|
|
"-o UserKnownHostsFile=/dev/null "
|
|
"-o StrictHostKeyChecking=no "
|
|
"-o password_stdin")
|
|
.arg(username, hostname, mountPath, port);
|
|
|
|
process->setArguments(args.split(" ", Qt::SkipEmptyParts));
|
|
process->start();
|
|
process->waitForStarted(50);
|
|
if (process->state() != QProcess::Running) {
|
|
qDebug() << "mountSftpDrive process not started";
|
|
qDebug() << args;
|
|
return ErrorCode::ServerContainerMissingError;
|
|
} else {
|
|
process->write((password + "\n").toUtf8());
|
|
}
|
|
|
|
m_sftpMountProcesses.append(process);
|
|
#else
|
|
Q_UNUSED(mountPath);
|
|
Q_UNUSED(cmd);
|
|
Q_UNUSED(password);
|
|
return ErrorCode::NoError;
|
|
#endif
|
|
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
void InstallController::stopAllSftpMounts()
|
|
{
|
|
#ifdef Q_OS_WINDOWS
|
|
for (QSharedPointer<QProcess> process : m_sftpMountProcesses) {
|
|
Utils::signalCtrl(process->processId(), CTRL_C_EVENT);
|
|
process->kill();
|
|
process->waitForFinished();
|
|
}
|
|
m_sftpMountProcesses.clear();
|
|
#endif
|
|
}
|
|
|
|
void InstallController::updateContainerConfigAfterInstallation(DockerContainer container, ContainerConfig &containerConfig, const QString &stdOut)
|
|
{
|
|
Proto mainProto = ContainerUtils::defaultProtocol(container);
|
|
|
|
if (container == DockerContainer::TorWebSite) {
|
|
if (auto* torProtocolConfig = containerConfig.getTorProtocolConfig()) {
|
|
qDebug() << "amnezia-tor onions" << stdOut;
|
|
|
|
QString onion = stdOut;
|
|
onion.replace("\n", "");
|
|
torProtocolConfig->serverConfig.site = onion;
|
|
}
|
|
}
|
|
}
|
|
|
|
ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentials &credentials,
|
|
QMap<DockerContainer, ContainerConfig> &installedContainers, SshSession &sshSession)
|
|
{
|
|
QString stdOut;
|
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
|
stdOut += data + "\n";
|
|
return ErrorCode::NoError;
|
|
};
|
|
|
|
QString script = QString("sudo docker ps --format '{{.Names}} {{.Ports}}'");
|
|
ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut, cbReadStdErr);
|
|
if (errorCode != ErrorCode::NoError) {
|
|
return errorCode;
|
|
}
|
|
|
|
const static QRegularExpression containerAndPortRegExp("(amnezia[-a-z0-9]*).*?:([0-9]*)->[0-9]*/(udp|tcp).*");
|
|
const static QRegularExpression torOrDnsRegExp("(amnezia-(?:torwebsite|dns)).*?([0-9]*)/(udp|tcp).*");
|
|
|
|
QStringList containerInfos = stdOut.split("\n");
|
|
for (const QString &containerInfo : containerInfos) {
|
|
if (containerInfo.isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
QRegularExpressionMatch containerAndPortMatch = containerAndPortRegExp.match(containerInfo);
|
|
if (containerAndPortMatch.hasMatch()) {
|
|
QString name = containerAndPortMatch.captured(1);
|
|
QString portStr = containerAndPortMatch.captured(2);
|
|
QString transportProtoStr = containerAndPortMatch.captured(3);
|
|
DockerContainer container = ContainerUtils::containerFromString(name);
|
|
|
|
if (container == DockerContainer::None) {
|
|
continue;
|
|
}
|
|
|
|
int port = portStr.toInt();
|
|
TransportProto transportProto = ProtocolUtils::transportProtoFromString(transportProtoStr);
|
|
|
|
auto installer = createInstaller(container);
|
|
ContainerConfig config = installer->createBaseConfig(container, port, transportProto);
|
|
ErrorCode extractError = installer->extractConfigFromContainer(container, credentials, &sshSession, config);
|
|
|
|
if (extractError != ErrorCode::NoError && extractError != ErrorCode::ServerContainerMissingError) {
|
|
return extractError;
|
|
}
|
|
|
|
installedContainers.insert(container, config);
|
|
}
|
|
|
|
QRegularExpressionMatch torOrDnsRegMatch = torOrDnsRegExp.match(containerInfo);
|
|
if (torOrDnsRegMatch.hasMatch()) {
|
|
QString name = torOrDnsRegMatch.captured(1);
|
|
QString portStr = torOrDnsRegMatch.captured(2);
|
|
QString transportProtoStr = torOrDnsRegMatch.captured(3);
|
|
DockerContainer container = ContainerUtils::containerFromString(name);
|
|
|
|
if (container == DockerContainer::None) {
|
|
continue;
|
|
}
|
|
|
|
int port = portStr.toInt();
|
|
TransportProto transportProto = ProtocolUtils::transportProtoFromString(transportProtoStr);
|
|
|
|
auto installer = createInstaller(container);
|
|
ContainerConfig config = installer->createBaseConfig(container, port, transportProto);
|
|
ErrorCode extractError = installer->extractConfigFromContainer(container, credentials, &sshSession, config);
|
|
|
|
if (extractError != ErrorCode::NoError && extractError != ErrorCode::ServerContainerMissingError) {
|
|
return extractError;
|
|
}
|
|
|
|
installedContainers.insert(container, config);
|
|
}
|
|
}
|
|
|
|
return ErrorCode::NoError;
|
|
}
|