Compare commits

..

7 Commits

Author SHA1 Message Date
MrMirDan
ace60af9c8 update: ui 2026-06-09 17:12:34 +03:00
MrMirDan
3c49622683 update: method to check if container is installed 2026-06-09 17:12:09 +03:00
MrMirDan
583f058dd9 update: separated self-hosted dns settings 2026-06-09 17:10:22 +03:00
yp
a9861d18b7 fix: wrong index on xray pages (#2669)
* test crash xray

* fixed save config xray

* reset file

* fixed text port & reset file

* fixed textFieldWithHeaderType.textField
2026-06-01 12:22:54 +08:00
lunardunno
c14138f031 fix: deleting volumes when cleaning the server (#2673)
* Deleting volumes when cleaning the server

* force the remove volumes
2026-06-01 11:54:34 +08:00
yyy-amnezia
60686fde24 fix: link OpenVPNAdapter statically (#2645)
* fix(ios): link OpenVPNAdapter statically

* chore(conan): simplify openvpnadapter merge process and make everything via XCrun

---------

Co-authored-by: Yaroslav Gurov <ygurov@proton.me>
2026-05-30 13:59:35 +08:00
Yaroslav Gurov
bd0747296e fix: networkextension proper framework linking (#2668) 2026-05-28 23:09:49 +08:00
33 changed files with 560 additions and 98 deletions

View File

@@ -54,7 +54,6 @@ target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Info.plist.in
MACOSX_BUNDLE_ICON_FILE "AppIcon"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"

View File

@@ -73,7 +73,7 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
if (!cfg.has_value()) return ErrorCode::InternalError;
container = cfg->defaultContainer;
containerConfigModel = cfg->containerConfig(container);
dns = cfg->getDnsPair(m_appSettingsRepository->useAmneziaDns(), primaryDns, secondaryDns);
dns = cfg->getDnsPair(primaryDns, secondaryDns);
hostName = cfg->hostName;
description = cfg->description;
break;

View File

@@ -65,13 +65,15 @@ ExportController::ExportResult ExportController::generateConnectionConfig(const
}
ContainerConfig containerConfig = adminConfig->containerConfig(container);
const QPair<QString, QString> dns = adminConfig->getDnsPair(m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns());
if (ContainerUtils::containerService(container) != ServiceType::Other) {
SshSession sshSession;
Proto protocol = ContainerUtils::defaultProtocol(container);
DnsSettings dnsSettings = {
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns()
dns.first,
dns.second
};
auto configurator = ConfiguratorBase::create(protocol, &sshSession);
@@ -88,10 +90,6 @@ ExportController::ExportResult ExportController::generateConnectionConfig(const
}
}
const QPair<QString, QString> dns = adminConfig->getDnsPair(m_appSettingsRepository->useAmneziaDns(),
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns());
adminConfig->containers.clear();
adminConfig->containers[container] = containerConfig;
adminConfig->defaultContainer = container;
@@ -133,16 +131,14 @@ ExportController::NativeConfigResult ExportController::generateNativeConfig(cons
result.errorCode = ErrorCode::InternalError;
return result;
}
const QPair<QString, QString> dns = adminConfig->getDnsPair(m_appSettingsRepository->useAmneziaDns(),
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns());
const QPair<QString, QString> dns = adminConfig->getDnsPair(m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns());
ContainerConfig modifiedContainerConfig = containerConfig;
modifiedContainerConfig.container = container;
DnsSettings dnsSettings = {
m_appSettingsRepository->primaryDns(),
m_appSettingsRepository->secondaryDns()
dns.first,
dns.second
};
SshSession sshSession;

View File

@@ -211,7 +211,8 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
newConfig.getXrayProtocolConfig() && newConfig.getXrayProtocolConfig()->serverConfig.isThirdPartyConfig;
if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged && !skipXrayInboundSync) {
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
const QPair<QString, QString> dns = adminConfig->getDnsPair(m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns());
DnsSettings dnsSettings = { dns.first, dns.second };
XrayConfigurator xrayConfigurator(&sshSession);
qDebug() << "InstallController::updateContainer applying Xray server inbound sync, reinstall="
<< reinstallRequired;
@@ -795,8 +796,8 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
qDebug().noquote() << "InstallController::installDockerWorker" << stdOut;
if (container == DockerContainer::Awg2) {
QRegularExpression kernelVersionRegex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
QRegularExpressionMatch match = kernelVersionRegex.match(stdOut);
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();
@@ -809,19 +810,8 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
if (stdOut.contains("lock"))
return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("Container runtime is not supported"))
return ErrorCode::ServerContainerRuntimeNotSupported;
QRegularExpression notFoundRegex(
R"(^.*(?:sudo:|docker:).*not found.*$)",
QRegularExpression::MultilineOption);
if (notFoundRegex.match(stdOut).hasMatch()) {
if (stdOut.contains("command not found"))
return ErrorCode::ServerDockerFailedError;
}
if (stdOut.contains("Container runtime service not running"))
return ErrorCode::ContainerRuntimeServiceNotRunning;
return error;
}
@@ -858,7 +848,7 @@ ErrorCode InstallController::isUserInSudo(const ServerCredentials &credentials,
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(QRegularExpression(R"(\bsudoers\b)")) || stdOut.contains("is not allowed to") || stdOut.contains("can't do that"))
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
return ErrorCode::ServerUserNotAllowedInSudoers;
if (stdOut.contains("password is required") || stdOut.contains("authentication is required"))
return ErrorCode::ServerUserPasswordRequired;

View File

@@ -83,6 +83,46 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
}
}
bool ServersController::changeServerDns(const QString &serverId, const QString &primaryDns, const QString &secondaryDns, const int &dnsMode)
{
const serverConfigUtils::ConfigType kind = m_serversRepository->serverKind(serverId);
if(kind == serverConfigUtils::ConfigType::SelfHostedAdmin) {
auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
if (!cfg.has_value()) return false;
cfg->dns1 = primaryDns;
cfg->dns2 = secondaryDns;
cfg->dnsMode = dnsMode;
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
return true;
}else{
return false;
}
}
QPair<QString, QString> ServersController::getDnsPair(const QString &serverId) const
{
const serverConfigUtils::ConfigType kind = m_serversRepository->serverKind(serverId);
if (kind == serverConfigUtils::ConfigType::SelfHostedAdmin) {
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
return cfg.has_value()
? cfg->getDnsPair(m_appSettingsRepository->primaryDns(), m_appSettingsRepository->primaryDns())
: QPair<QString, QString>();
} else {
return QPair<QString, QString>();
}
}
int ServersController::getDnsMode(const QString &serverId) const
{
const serverConfigUtils::ConfigType kind = m_serversRepository->serverKind(serverId);
if (kind == serverConfigUtils::ConfigType::SelfHostedAdmin) {
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
return cfg.has_value() ? cfg->dnsMode : int();
} else {
return int();
}
}
void ServersController::removeServer(const QString &serverId)
{
m_serversRepository->removeServer(serverId);

View File

@@ -37,6 +37,7 @@ public:
// Server management
bool renameServer(const QString &serverId, const QString &name);
bool changeServerDns(const QString &serverId, const QString &primaryDns, const QString &secondaryDns, const int &dnsMode);
void removeServer(const QString &serverId);
void setDefaultServer(const QString &serverId);
@@ -57,6 +58,8 @@ public:
QMap<DockerContainer, ContainerConfig> getServerContainersMap(const QString &serverId) const;
DockerContainer getDefaultContainer(const QString &serverId) const;
ContainerConfig getContainerConfig(const QString &serverId, DockerContainer container) const;
QPair<QString, QString> getDnsPair(const QString &serverId) const;
int getDnsMode(const QString &serverId) const;
// Validation
bool isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType, const QString &serviceProtocol) const;

View File

@@ -64,15 +64,14 @@ void SelfHostedAdminServerConfig::clearCachedClientProfile(DockerContainer conta
containers[container] = cleared;
}
QPair<QString, QString> SelfHostedAdminServerConfig::getDnsPair(bool isAmneziaDnsEnabled, const QString &primaryDns,
const QString &secondaryDns) const
QPair<QString, QString> SelfHostedAdminServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
{
QString d1 = dns1;
QString d2 = dns2;
const bool dnsOnServer = containers.contains(DockerContainer::Dns);
if (d1.isEmpty() || !NetworkUtilities::checkIPv4Format(d1)) {
d1 = (isAmneziaDnsEnabled && dnsOnServer) ? protocols::dns::amneziaDnsIp : primaryDns;
d1 = ((dnsMode == 1) && dnsOnServer) ? protocols::dns::amneziaDnsIp : primaryDns;
}
if (d2.isEmpty() || !NetworkUtilities::checkIPv4Format(d2)) {
d2 = secondaryDns;
@@ -110,6 +109,9 @@ QJsonObject SelfHostedAdminServerConfig::toJson() const
if (!dns2.isEmpty()) {
obj[configKey::dns2] = dns2;
}
if (dnsMode > -1) {
obj[configKey::dnsMode] = dnsMode;
}
if (!userName.isEmpty()) {
obj[configKey::userName] = userName;
@@ -147,6 +149,11 @@ SelfHostedAdminServerConfig SelfHostedAdminServerConfig::fromJson(const QJsonObj
config.dns1 = json.value(configKey::dns1).toString();
config.dns2 = json.value(configKey::dns2).toString();
if (json.contains(configKey::dnsMode)) {
config.dnsMode = json.value(configKey::dnsMode).toInt();
} else {
config.dnsMode = 0;
}
config.userName = json.value(configKey::userName).toString();
config.password = json.value(configKey::password).toString();

View File

@@ -26,6 +26,7 @@ struct SelfHostedAdminServerConfig {
DockerContainer defaultContainer;
QString dns1;
QString dns2;
int dnsMode = 0;
QString userName;
QString password;
@@ -41,8 +42,7 @@ struct SelfHostedAdminServerConfig {
void clearCachedClientProfile(DockerContainer container);
QPair<QString, QString> getDnsPair(bool isAmneziaDnsEnabled, const QString &primaryDns,
const QString &secondaryDns) const;
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
QJsonObject toJson() const;
static SelfHostedAdminServerConfig fromJson(const QJsonObject &json);

View File

@@ -80,8 +80,8 @@ QString getProtocolName(DockerContainer defaultContainer, const QMap<DockerConta
namespace amnezia
{
ServerDescription buildServerDescription(const SelfHostedAdminServerConfig &server, bool isAmneziaDnsEnabled)
{
ServerDescription buildServerDescription(const SelfHostedAdminServerConfig &server, bool /*isAmneziaDnsEnabled*/)
{
ServerDescription row = buildBaseDescription(server);
row.selfHostedSshCredentials.hostName = server.hostName;
row.selfHostedSshCredentials.userName = server.userName;
@@ -92,7 +92,7 @@ ServerDescription buildServerDescription(const SelfHostedAdminServerConfig &serv
&& !row.selfHostedSshCredentials.secretData.isEmpty();
row.serverName = server.displayName;
row.baseDescription = getBaseDescription(server.containers, isAmneziaDnsEnabled, row.hasWriteAccess, row.primaryDnsIsAmnezia);
row.baseDescription = getBaseDescription(server.containers, server.dnsMode == 1, row.hasWriteAccess, row.primaryDnsIsAmnezia);
const QString protocolName = getProtocolName(server.defaultContainer, server.containers);
row.expandedServerDescription = row.baseDescription + row.hostName;

View File

@@ -15,6 +15,7 @@ namespace amnezia
constexpr QLatin1String dns1("dns1");
constexpr QLatin1String dns2("dns2");
constexpr QLatin1String dnsMode("dnsMode");
constexpr QLatin1String serverIndex("serverIndex");
constexpr QLatin1String description("description");

View File

@@ -38,8 +38,6 @@ namespace amnezia
XrayServerConfigInvalid = 215,
XrayServerNoVlessClients = 216,
XrayRealityKeysReadFailed = 217,
ServerContainerRuntimeNotSupported = 218,
ContainerRuntimeServiceNotRunning = 219,
// Ssh connection errors
SshRequestDeniedError = 300,
@@ -125,3 +123,5 @@ namespace amnezia
Q_DECLARE_METATYPE(amnezia::ErrorCode)
#endif // ERRORCODES_H

View File

@@ -39,8 +39,6 @@ QString errorString(ErrorCode code) {
case(ErrorCode::XrayRealityKeysReadFailed):
errorMessage = QObject::tr("Server error: failed to read XRay Reality keys from the server");
break;
case(ErrorCode::ServerContainerRuntimeNotSupported): errorMessage = QObject::tr("Server error: The default container runtime available for installation on this server is not supported.\n Install Docker Engine on the server manually and try again."); break;
case(ErrorCode::ContainerRuntimeServiceNotRunning): errorMessage = QObject::tr("Container runtime error: The container runtime service is not running.\n Check the container runtime service on the server, or wait about a minute and try again."); break;
// Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;

View File

@@ -26,6 +26,8 @@ set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
)
if(DEPLOY)
@@ -114,10 +116,20 @@ target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR})
target_include_directories(networkextension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
find_package(openvpnadapter REQUIRED)
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
target_link_libraries(networkextension PRIVATE amnezia::openvpnadapter)
find_package(awg-apple REQUIRED)
target_link_libraries(networkextension PRIVATE amnezia::awg-apple)
find_package(hev-socks5-tunnel REQUIRED)
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
target_link_libraries(networkextension PRIVATE heiher::hev-socks5-tunnel)

View File

@@ -1,8 +1,7 @@
if which apt-get > /dev/null 2>&1 || command -v apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
elif which dnf > /dev/null 2>&1 || command -v dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
elif which yum > /dev/null 2>&1 || command -v yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
elif which zypper > /dev/null 2>&1 || command -v zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
elif which pacman > /dev/null 2>&1 || command -v pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
else echo "Packet manager not found"; echo "Internal error"; exit 1;\
fi;\
if sudo -n which $LOCK_CMD > /dev/null 2>&1 || command -v $LOCK_CMD > /dev/null 2>&1; then sudo -n $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi
if which apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
elif which dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
elif which yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
elif which zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
elif which pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\
if command -v $LOCK_CMD > /dev/null 2>&1; then sudo $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi

View File

@@ -1,8 +1,8 @@
if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then opt="--version";\
elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then opt="--version";\
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then opt="--version";\
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then opt="--version";\
elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then opt="--version";\
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); opt="--version";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); opt="--version";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\
else pm="uname"; opt="-a";\
fi;\
CUR_USER=$(whoami 2>/dev/null || echo $HOME | sed 's/.*\///');\

View File

@@ -1,34 +1,25 @@
if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then silent_inst="-yq install --install-recommends"; what_pkg="-s install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then silent_inst="-yq install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then silent_inst="-y -q install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then silent_inst="-nq install"; what_pkg="--dry-run install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="suse";\
elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then silent_inst="-S --noconfirm --noprogressbar --quiet"; what_pkg="-Sp"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
fi;\
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, What pkg command: $what_pkg, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg, Language: $LANG";\
echo $LANG | grep -qE '^(en_US.UTF-8|C.UTF-8|C)$' || export LC_ALL=C;\
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install --install-recommends"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="opensuse";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
else echo "Packet manager not found"; exit 1; fi;\
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
if ! command -v sudo > /dev/null 2>&1; then $pm $check_pkgs; $pm $silent_inst sudo; fi;\
if ! sudo -n sh -c 'command -v which > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst which; fi;\
if ! sudo -n sh -c 'command -v fuser > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst psmisc; fi;\
if ! sudo -n sh -c 'command -v lsof > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst lsof; fi;\
if ! sudo -n sh -c 'command -v docker > /dev/null 2>&1'; then \
sudo -n $pm $check_pkgs;\
if ! sudo -n $pm $what_pkg $docker_pkg 2>/dev/null | grep -qi podman; then \
sudo -n $pm $silent_inst $docker_pkg;\
sleep 5; sudo -n systemctl enable --now docker; sleep 5;\
else \
echo "Container runtime is not supported";\
exit 1;\
fi;\
if ! command -v fuser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst psmisc; fi;\
if ! command -v lsof > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst lsof; fi;\
if ! command -v docker > /dev/null 2>&1; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
sleep 5; sudo systemctl enable --now docker; sleep 5;\
fi;\
if [ "$(sudo -n cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
if ! sudo -n sh -c 'command -v apparmor_parser > /dev/null 2>&1'; then \
sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst apparmor;\
fi;\
if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
if ! command -v apparmor_parser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst apparmor; fi;\
fi;\
if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then \
sleep 5; sudo -n systemctl start docker; sleep 5;\
if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then echo "Container runtime service not running"; fi;\
if [ "$(systemctl is-active docker)" != "active" ]; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
sleep 5; sudo systemctl start docker; sleep 5;\
fi;\
sudo -n docker --version || docker --version;\
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; fi;\
docker --version;\
uname -sr

View File

@@ -1,5 +1,6 @@
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
sudo docker images -a --format table | grep amnezia | awk '{print $3, $1 ":" $2}' | xargs sudo docker rmi;\
sudo docker volume ls | grep amnezia | awk '{print $2}' | xargs sudo docker volume rm -f;\
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
sudo rm -frd /opt/amnezia

View File

@@ -22,6 +22,7 @@ namespace PageLoader
PageSettingsServersList,
PageSettings,
PageSettingsServerData,
PageSettingsServerDns,
PageSettingsServerInfo,
PageSettingsServerProtocols,
PageSettingsServerServices,

View File

@@ -100,6 +100,12 @@ void ServersUiController::editServerName(const QString &serverId, const QString
updateModel();
}
void ServersUiController::editServerDns(const QString &primaryDns, const QString &secondaryDns, const int &dnsMode)
{
m_serversController->changeServerDns(m_processedServerId, primaryDns, secondaryDns, dnsMode);
emit processedServerDnsModeChanged(m_processedServerId);
}
void ServersUiController::setDefaultServer(const QString &serverId)
{
if (serverId.isEmpty()) {
@@ -276,6 +282,24 @@ QString ServersUiController::serverHostName(const QString &serverId) const
return serverDescriptionById(serverId).hostName;
}
QVariantMap ServersUiController::getDnsPair(const QString &serverId) const
{
QVariantMap result;
result["first"] = m_serversController->getDnsPair(serverId).first;
result["second"] = m_serversController->getDnsPair(serverId).second;
return result;
}
int ServersUiController::processedServerDnsMode() const
{
return serverDnsMode(m_processedServerId);
}
int ServersUiController::serverDnsMode(const QString &serverId) const
{
return m_serversController->getDnsMode(serverId);
}
int ServersUiController::serverDefaultContainer(const QString &serverId) const
{
const auto &description = serverDescriptionById(serverId);

View File

@@ -30,6 +30,7 @@ class ServersUiController : public QObject
Q_PROPERTY(QString processedServerId READ getProcessedServerId WRITE setProcessedServerId NOTIFY processedServerIdChanged)
Q_PROPERTY(int processedContainerIndex READ getProcessedContainerIndex WRITE setProcessedContainerIndex NOTIFY processedContainerIndexChanged)
Q_PROPERTY(int processedServerDnsMode READ processedServerDnsMode NOTIFY processedServerDnsModeChanged)
Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerIdChanged)
Q_PROPERTY(bool hasServersFromGatewayApi READ hasServersFromGatewayApi NOTIFY hasServersFromGatewayApiChanged)
@@ -51,6 +52,7 @@ public slots:
void removeServerAtIndex(int index);
void editServerName(const QString &serverId, const QString &name);
void editServerDns(const QString &primaryDns, const QString &secondaryDns, const int &dnsMode);
void setDefaultServer(const QString &serverId);
void setDefaultServerAtIndex(int index);
@@ -74,6 +76,9 @@ public slots:
QString serverName(const QString &serverId) const;
QString serverHostName(const QString &serverId) const;
QVariantMap getDnsPair(const QString &serverId) const;
int processedServerDnsMode() const;
int serverDnsMode(const QString &serverId) const;
int serverDefaultContainer(const QString &serverId) const;
bool isServerFromApi(const QString &serverId) const;
bool isServerCountrySelectionAvailable(const QString &serverId) const;
@@ -111,6 +116,7 @@ signals:
void defaultServerIdChanged(const QString &serverId);
void processedServerIdChanged(const QString &serverId);
void processedContainerIndexChanged(int index);
void processedServerDnsModeChanged(const QString &serverId);
void hasServersFromGatewayApiChanged();
void updateApiCountryModel();
void updateApiServicesModel();

View File

@@ -120,6 +120,11 @@ bool ContainersModel::isServiceContainer(const int containerIndex)
return qvariant_cast<ServiceType>(data(index(containerIndex), ServiceTypeRole) == ServiceType::Other);
}
bool ContainersModel::isContainerInstalled(const int containerIndex)
{
return qvariant_cast<bool>(data(index(containerIndex), IsInstalledRole));
}
bool ContainersModel::hasInstalledServices()
{
for (const auto &container : m_containers.keys()) {

View File

@@ -71,6 +71,7 @@ public slots:
bool isSupportedByCurrentPlatform(const int containerIndex);
bool isServiceContainer(const int containerIndex);
bool isContainerInstalled(const int containerIndex);
bool hasInstalledServices();
bool hasInstalledProtocols();

View File

@@ -112,7 +112,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {

View File

@@ -279,7 +279,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {

View File

@@ -17,6 +17,10 @@ import "../Components"
PageType {
id: root
enableTimer: false
property bool portDirty: false
function formatTransport(value) {
if (value === "raw") return "RAW (TCP)"
if (value === "xhttp") return "XHTTP"
@@ -39,8 +43,8 @@ PageType {
anchors.right: parent.right
anchors.topMargin: 20 + PageController.safeAreaTopMargin
onFocusChanged: {
if (this.activeFocus) {
onActiveFocusChanged: {
if (backButton.enabled && backButton.activeFocus) {
listView.positionViewAtBeginning()
}
}
@@ -60,8 +64,6 @@ PageType {
delegate: ColumnLayout {
width: listView.width
property alias focusItemId: textFieldWithHeaderType.textField
spacing: 0
Text {
@@ -107,13 +109,32 @@ PageType {
Layout.rightMargin: 16
enabled: listView.enabled
headerText: qsTr("Port")
textField.text: port
Binding {
target: textFieldWithHeaderType.textField
property: "text"
value: port
when: !textFieldWithHeaderType.textField.activeFocus
restoreMode: Binding.RestoreNone
}
textField.maximumLength: 5
textField.validator: IntValidator {
bottom: 1; top: 65535
}
textField.onActiveFocusChanged: {
if (textField.activeFocus && textField.text === "" && port !== "") {
textField.text = port
}
}
textField.onTextChanged: {
root.portDirty = (textField.text !== port)
}
textField.onEditingFinished: {
if (textField.text !== port) port = textField.text
if (textField.text !== port) {
port = textField.text
}
root.portDirty = false
}
checkEmptyText: true
}
@@ -172,9 +193,8 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: listView.enabled
&& (XrayConfigModel.hasUnsavedChanges
|| textFieldWithHeaderType.textField.text !== port)
enabled: visible && textFieldWithHeaderType.errorText === ""
&& (XrayConfigModel.hasUnsavedChanges || root.portDirty)
enabled: visible && textFieldWithHeaderType.textField.text !== ""
text: qsTr("Save")
onClicked: function() {
forceActiveFocus()

View File

@@ -742,7 +742,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {

View File

@@ -95,7 +95,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {

View File

@@ -211,7 +211,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {

View File

@@ -208,7 +208,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {

View File

@@ -96,6 +96,7 @@ PageType {
property list<QtObject> serverActions: [
check,
dns,
reboot,
remove,
clear,
@@ -116,6 +117,33 @@ PageType {
}
}
QtObject {
id: dns
property bool isVisible: root.isServerWithWriteAccess
readonly property string title: qsTr("DNS server")
function getDescription() {
var dnsMode = ServersUiController.processedServerDnsMode
if (dnsMode === 0) return qsTr("DNS by default (1.1.1.1 1.0.0.1)")
if (dnsMode === 1) return qsTr("AmneziaDNS")
if (dnsMode === 2) {
var dnsPair = ServersUiController.getDnsPair(ServersUiController.processedServerId)
return qsTr("Specified DNS (%1 %2)")
.arg(dnsPair.first)
.arg(dnsPair.second)
}
return ""
}
readonly property string description: getDescription()
readonly property var tColor: AmneziaStyle.color.paleGray
readonly property var clickedHandler: function() {
PageController.goToPage(PageEnum.PageSettingsServerDns)
}
}
QtObject {
id: reboot

View File

@@ -0,0 +1,324 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import Style 1.0
import ContainerProps 1.0
import "./"
import "../Controls2"
import "../Config"
import "../Controls2/TextTypes"
import "../Components"
PageType {
id: root
property int amneziaDnsIndex: ContainerProps.containerFromString("amnezia-dns")
property int dnsMode: ServersUiController.serverDnsMode(ServersUiController.processedServerId)
property int selectedIndex: dnsMode
property bool dnsFieldsVisible: false
property bool saveButtonVisible: false
property string primaryDnsText: ServersUiController.getDnsPair(ServersUiController.processedServerId).first
property string secondaryDnsText: ServersUiController.getDnsPair(ServersUiController.processedServerId).second
BackButtonType {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20 + PageController.safeAreaTopMargin
onFocusChanged: {
if (this.activeFocus) {
listView.positionViewAtBeginning()
}
}
}
ListViewType {
id: listView
anchors.top: backButton.bottom
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
header: ColumnLayout {
width: listView.width
spacing: 16
BaseHeaderType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("DNS server")
}
ParagraphTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16
BasicButtonType {
id: privacyPolicyButton
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 16
Layout.topMargin: -15
implicitHeight: 25
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.goldenApricot
text: qsTr("Learn more")
clickedFunc: function() {
Qt.openUrlExternally(LanguageUiController.getCurrentSiteUrl("dns"))
}
}
}
}
model: dnsList
ButtonGroup {
id: dnsChoicesRadioButtonGroup
}
delegate: ColumnLayout {
id: content
width: listView.width
height: content.implicitHeight
RowLayout {
VerticalRadioButton {
id: dnsChoiceRadioButton
Layout.fillWidth: true
Layout.leftMargin: 16
text: title
descriptionText: description
imageSource: "qrc:/images/controls/download.svg"
ButtonGroup.group: dnsChoicesRadioButtonGroup
checked: index === selectedIndex
checkable: !ConnectionController.isConnected
onClicked: {
if (ConnectionController.isConnectionInProgress) {
PageController.showNotificationMessage(qsTr("Unable change dns settings while trying to make an active connection"))
return
}
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Unable change dns settings while there is an active connection"))
return
}
if (index !== selectedIndex) {
selectedIndex = index
clickedHandler()
}
}
Keys.onEnterPressed: {
if (checkable) {
checked = true
}
dnsChoiceRadioButton.clicked()
}
Keys.onReturnPressed: {
if (checkable) {
checked = true
}
dnsChoiceRadioButton.clicked()
}
}
BasicButtonType {
id: removeAmneziaDnsButton
visible: ContainersModel.isContainerInstalled(amneziaDnsIndex) && title === "AmneziaDNS"
Layout.rightMargin: 32
Layout.alignment: Qt.AlignRight
defaultColor: AmneziaStyle.color.midnightBlack
hoveredColor: AmneziaStyle.color.slateGray
pressedColor: AmneziaStyle.color.mutedGray
leftImageSource: image
leftImageColor: AmneziaStyle.color.paleGray
clickedFunc: function() {
ContainersModel.setProcessedContainerIndex(amneziaDnsIndex)
var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName())
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected && (root.dnsMode === 1)) {
PageController.showNotificationMessage(qsTr("Cannot remove AmneziaDNS from running server"))
} else {
PageController.goToPage(PageEnum.PageDeinstalling)
InstallController.removeContainer(ServersUiController.processedServerId, amneziaDnsIndex)
}
}
var noButtonFunction = function() {}
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
}
}
DividerType {
}
}
footer: ColumnLayout {
width: listView.width
TextFieldWithHeaderType {
id: primaryDns
visible: root.dnsFieldsVisible
Layout.fillWidth: true
Layout.topMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Primary DNS")
textField.text: root.primaryDnsText
textField.validator: RegularExpressionValidator {
regularExpression: InstallController.ipAddressRegExp()
}
}
TextFieldWithHeaderType {
id: secondaryDns
visible: root.dnsFieldsVisible
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Secondary DNS")
textField.text: root.secondaryDnsText
textField.validator: RegularExpressionValidator {
regularExpression: InstallController.ipAddressRegExp()
}
}
BasicButtonType {
id: saveButton
visible: root.saveButtonVisible || (root.dnsMode === 1 && !ContainersModel.isContainerInstalled(amneziaDnsIndex))
Layout.fillWidth: true
Layout.topMargin: root.dnsFieldsVisible ? 8 : 32
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 8
text: qsTr("Save")
clickedFunc: function() {
if (!ContainersModel.isContainerInstalled(amneziaDnsIndex)) {
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.install(amneziaDnsIndex, InstallController.getPortForInstall(ContainerProps.defaultProtocol(amneziaDnsIndex)),
InstallController.defaultTransportProto(ContainerProps.defaultProtocol(amneziaDnsIndex)), ServersUiController.processedServerId)
}
if (primaryDns.textField.text === "") {
primaryDns.errorText = qsTr("Primary DNS cannot be empty")
return
}
primaryDns.errorText = ""
secondaryDns.errorText = ""
root.saveButtonVisible = false
root.dnsMode = root.selectedIndex
ServersUiController.editServerDns(primaryDns.textField.text, secondaryDns.textField.text, root.dnsMode)
PageController.showNotificationMessage(qsTr("Settings saved"))
}
}
CaptionTextType {
visible: !ContainersModel.isContainerInstalled(amneziaDnsIndex) && root.selectedIndex === 1
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
text: qsTr("The AmneziaDNS service will be installed on the server")
color: AmneziaStyle.color.mutedGray
}
}
}
property list<QtObject> dnsList: [
defaultDns,
amneziaDns,
specifyDns,
]
QtObject {
id: defaultDns
readonly property string title: qsTr("DNS by default")
readonly property string description: qsTr("1.1.1.1 1.0.0.1")
readonly property var clickedHandler: function() {
root.dnsFieldsVisible = false
root.saveButtonVisible = true
root.primaryDnsText = "1.1.1.1"
root.secondaryDnsText = "1.0.0.1"
}
}
QtObject {
id: amneziaDns
readonly property string title: qsTr("AmneziaDNS")
readonly property string description: qsTr("AmneziaDNS on your server")
readonly property string image: qsTr("qrc:/images/controls/trash.svg")
readonly property var clickedHandler: function() {
root.dnsFieldsVisible = false
root.saveButtonVisible = true
}
}
QtObject {
id: specifyDns
readonly property string title: qsTr("Specify DNS server address")
readonly property var clickedHandler: function() {
root.dnsFieldsVisible = true
root.saveButtonVisible = true
var dnsPair = ServersUiController.getDnsPair(ServersUiController.processedServerId)
if (!dnsPair) return
root.primaryDnsText = dnsPair.first || ""
root.secondaryDnsText = dnsPair.second || ""
}
}
}

View File

@@ -107,6 +107,7 @@
<file>Pages2/PageSettingsKillSwitchExceptions.qml</file>
<file>Pages2/PageSettingsLogging.qml</file>
<file>Pages2/PageSettingsServerData.qml</file>
<file>Pages2/PageSettingsServerDns.qml</file>
<file>Pages2/PageSettingsServerInfo.qml</file>
<file>Pages2/PageSettingsServerProtocol.qml</file>
<file>Pages2/PageSettingsServerProtocols.qml</file>

View File

@@ -5,6 +5,7 @@ from conan.errors import ConanInvalidConfiguration
from conan.tools.scm import Git
from conan.internal.model.pkg_type import PackageType
from conan.tools.files import chdir
from conan.tools.apple import XCRun
import os
import shutil
@@ -49,7 +50,10 @@ class OpenVPNAdapter(ConanFile):
def build(self):
with chdir(self, self.source_folder):
self.run("xcrun xcodebuild"
xcrun = XCRun(self)
xcodebuild = xcrun.find("xcodebuild")
self.run(f"{xcodebuild}"
" -project OpenVPNAdapter.xcodeproj"
" -scheme OpenVPNAdapter"
" -configuration Release"
@@ -57,10 +61,20 @@ class OpenVPNAdapter(ConanFile):
f" -sdk {self._sdk}"
f' "CONFIGURATION_BUILD_DIR={self.build_folder}"'
f' "BUILT_PRODUCTS_DIR={self.build_folder}"'
" MACH_O_TYPE=staticlib"
" BUILD_LIBRARY_FOR_DISTRIBUTION=YES"
" CODE_SIGNING_ALLOWED=NO"
)
openvpnadapter = os.path.join(self.build_folder, "OpenVPNAdapter.framework", "OpenVPNAdapter")
self.run(f"{xcrun.libtool} -static -o"
f" {openvpnadapter}"
f" {openvpnadapter}"
f' {os.path.join(self.build_folder, "OpenVPNClient.framework", "OpenVPNClient")}'
f' {os.path.join(self.build_folder, "LZ4.framework", "LZ4")}'
f' {os.path.join(self.build_folder, "mbedTLS.framework", "mbedTLS")}'
)
def package(self):
shutil.copytree(os.path.join(self.build_folder, "OpenVPNAdapter.framework"),
os.path.join(self.package_folder, "OpenVPNAdapter.framework"))
@@ -70,3 +84,4 @@ class OpenVPNAdapter(ConanFile):
self.cpp_info.type = PackageType.STATIC
self.cpp_info.package_framework = True
self.cpp_info.location = os.path.join(self.package_folder, "OpenVPNAdapter.framework")
self.cpp_info.frameworks = ["SystemConfiguration"]