Files
amnezia-client/client/core/utils/selfhosted/scriptsRegistry.cpp
Nethius c28452a5da feat: desktop updater (#825)
* added changelog drawer

* Created a scaffold for Linux installation

* implement Linux updating

* Add debug logs about installer in service

* Add client side of installation logic for Windows and MacOS

* Add service side of installation logic for Windows

* ru readme

* Update README_RU.md

* Add files via upload

* chore: added clang-format config files (#1293)

* Update README_RU.md

* Update README.md

* feature: added subscription expiration date for premium v2 (#1261)

* feature: added subscription expiration date for premium v2

* feature: added a check for the presence of the “services” field in the response body of the getServicesList() function

* feature: added prohibition to change location when connection is active

* bugfix: renamed public_key->end_date to public_key->expires_at according to the changes on the backend

* feature/xray user management (#972)

* feature: implement client management functionality for Xray

---------

Co-authored-by: aiamnezia <ai@amnezia.org>
Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>

* Fix formatting

* Add some logs

* Add logs from installattion shell on Windows

* Fix installation for Windows and MacOS

* Optimized code

* Move installer running to client side for Ubuntu

* Move installer launch logic to client side for Windows

* Clean service code

* Add linux_install script to resources

* Add logs for UpdateController

* Add draft for MacOS installation

* Disable updates checking for Android and iOS

* chore: fixed macos update script

* chore: remove duplicate lines

* chore: post merge fixes

* chore: add missing ifdef

* decrease version for testing

* chore: added changelog text processing depend on OS

* add .vscode to .gitignore

* Change updater downloading method to retrieving link from the gateway

* add Release date file creation to s3 deploy script

* Add release date downloading from endpoint

* update check refactoring

* feat: switch macOS auto-update from DMG to ZIP+PKG installer

- Update macOS artifact URL from .dmg to .zip
- Rewrite mac_installer.sh to extract ZIP and install PKG via osascript
- Increase download timeout to 30s for larger ZIP files

* fix: fix Android build

* feat: Change get request for updater link to post

* refactor: preparing NewsModel for update notifications

- Changed `updateModel` to `setNewsList` for better semantic meaning.
- Delegate model container updating to private method updateModel
- Updated the logic for marking news as read to use item IDs instead of a boolean flag.

* feat: Move update notification in news list

- Updated `UpdateController` to handle empty release dates in header text.
- Added `getVersion` method to `UpdateController` for version retrieval.
- Enhanced `NewsModel` to support update notifications with new methods for marking updates as skipped and setting update notifications.
- Updated QML pages to display update information and provide actions for updates and skipping them.
- Introduced `isUpdate` property in `NewsItem` to differentiate between regular news and updates.

* feat: Implement rate limit workaround for gateway requests

- Added a delay before contacting the gateway in both `UpdateController` and `ApiNewsController` to prevent rate limit issues caused by simultaneous requests.

* refactor: Convert synchronous network requests to asynchronous in UpdateController

- Updated `UpdateController` to use asynchronous network requests for fetching gateway URL, version info, changelog, and release date.
- Introduced `doGetAsync` method to handle asynchronous GET requests with error handling.
- Removed synchronous methods to improve responsiveness and prevent blocking the UI during network operations.
- Added a mechanism to prevent multiple concurrent update checks.

* chore: Decrease AmneziaVPN version to 4.8.10.0 in CMakeLists.txt for testing

* refactor: Improve update check handling to avoid rate limit issues

- Updated `CoreController` to initiate update checks after news fetching is complete.
- Removed synchronous waiting in `ApiNewsController` to streamline the fetching process.

* fix: fixed typo in IsReadRole

* fix: fix updater filenames

* chore: move updateController to core

* refactor: update to mvvm

* chore: tiny fix

---------

Co-authored-by: aiamnezia <ai@amnezia.org>
Co-authored-by: aiamnezia <ai@amnezia.com>
Co-authored-by: Pokamest Nikak <pokamest@gmail.com>
Co-authored-by: KsZnak <ksu@amnezia.org>
Co-authored-by: Cyril Anisimov <cyan84@gmail.com>
Co-authored-by: vkamn <vk@amnezia.org>
2026-05-04 12:37:19 +08:00

318 lines
14 KiB
C++

#include "scriptsRegistry.h"
#include <QDebug>
#include <QFile>
#include <QJsonObject>
#include <QObject>
#include <QLoggingCategory>
#include "core/utils/networkUtilities.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.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/containerConfig.h"
#include "core/models/protocols/openVpnProtocolConfig.h"
#include "core/models/protocols/wireGuardProtocolConfig.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include "core/models/protocols/xrayProtocolConfig.h"
#include "core/models/protocols/sftpProtocolConfig.h"
#include "core/models/protocols/socks5ProxyProtocolConfig.h"
using namespace amnezia;
using namespace ProtocolUtils;
QString amnezia::scriptFolder(amnezia::DockerContainer container)
{
switch (container) {
case DockerContainer::OpenVpn: return QLatin1String("openvpn");
case DockerContainer::WireGuard: return QLatin1String("wireguard");
case DockerContainer::Awg2: return QLatin1String("awg");
case DockerContainer::Awg: return QLatin1String("awg_legacy");
case DockerContainer::Ipsec: return QLatin1String("ipsec");
case DockerContainer::Xray: return QLatin1String("xray");
case DockerContainer::TorWebSite: return QLatin1String("website_tor");
case DockerContainer::Dns: return QLatin1String("dns");
case DockerContainer::Sftp: return QLatin1String("sftp");
case DockerContainer::Socks5Proxy: return QLatin1String("socks5_proxy");
default: return QString();
}
}
QString amnezia::scriptName(SharedScriptType type)
{
switch (type) {
case SharedScriptType::prepare_host: return QLatin1String("prepare_host.sh");
case SharedScriptType::install_docker: return QLatin1String("install_docker.sh");
case SharedScriptType::build_container: return QLatin1String("build_container.sh");
case SharedScriptType::remove_container: return QLatin1String("remove_container.sh");
case SharedScriptType::remove_all_containers: return QLatin1String("remove_all_containers.sh");
case SharedScriptType::setup_host_firewall: return QLatin1String("setup_host_firewall.sh");
case SharedScriptType::check_connection: return QLatin1String("check_connection.sh");
case SharedScriptType::check_server_is_busy: return QLatin1String("check_server_is_busy.sh");
case SharedScriptType::check_user_in_sudo: return QLatin1String("check_user_in_sudo.sh");
default: return QString();
}
}
QString amnezia::scriptName(ProtocolScriptType type)
{
switch (type) {
case ProtocolScriptType::dockerfile: return QLatin1String("Dockerfile");
case ProtocolScriptType::run_container: return QLatin1String("run_container.sh");
case ProtocolScriptType::configure_container: return QLatin1String("configure_container.sh");
case ProtocolScriptType::container_startup: return QLatin1String("start.sh");
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf");
case ProtocolScriptType::awg_template: return QLatin1String("template.conf");
case ProtocolScriptType::xray_template: return QLatin1String("template.json");
default: return QString();
}
}
QString amnezia::scriptName(ClientScriptType type)
{
switch (type) {
case ClientScriptType::linux_installer: return QLatin1String("linux_installer.sh");
case ClientScriptType::mac_installer: return QLatin1String("mac_installer.sh");
default: return QString();
}
}
QString amnezia::scriptData(amnezia::SharedScriptType type)
{
QString fileName = QString(":/server_scripts/%1").arg(amnezia::scriptName(type));
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "Warning: script missing" << fileName;
return "";
}
QByteArray ba = file.readAll();
if (ba.isEmpty()) {
qDebug() << "Warning: script is empty" << fileName;
}
return ba;
}
QString amnezia::scriptData(amnezia::ProtocolScriptType type, DockerContainer container)
{
QString fileName = QString(":/server_scripts/%1/%2").arg(amnezia::scriptFolder(container), amnezia::scriptName(type));
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "Warning: script missing" << fileName;
return "";
}
QByteArray data = file.readAll();
data.replace("\r", "");
return data;
}
QString amnezia::scriptData(ClientScriptType type)
{
QString fileName = QString(":/client_scripts/%1").arg(amnezia::scriptName(type));
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "Warning: script missing" << fileName;
return "";
}
QByteArray data = file.readAll();
if (data.isEmpty()) {
qDebug() << "Warning: script is empty" << fileName;
}
data.replace("\r", "");
return data;
}
amnezia::ScriptVars amnezia::genBaseVars(const ServerCredentials &credentials,
DockerContainer container,
const QString &primaryDns,
const QString &secondaryDns)
{
ScriptVars vars;
vars.append({ { "$REMOTE_HOST", credentials.hostName } });
vars.append({ { "$CONTAINER_NAME", ContainerUtils::containerToString(container) } });
vars.append({ { "$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerUtils::containerToString(container) } });
QString serverIp = (!ContainerUtils::isAwgContainer(container) && container != DockerContainer::WireGuard && container != DockerContainer::Xray)
? NetworkUtilities::getIPAddress(credentials.hostName)
: credentials.hostName;
if (!serverIp.isEmpty()) {
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
} else {
qWarning() << "amnezia::genBaseVars unable to resolve address for credentials.hostName";
}
QString dns1 = primaryDns.isEmpty() ? QString("8.8.8.8") : primaryDns;
QString dns2 = secondaryDns.isEmpty() ? QString("8.8.4.4") : secondaryDns;
vars.append({ { "$PRIMARY_SERVER_DNS", dns1 } });
vars.append({ { "$SECONDARY_SERVER_DNS", dns2 } });
// IPsec vars (constants)
vars.append({ { "$IPSEC_VPN_L2TP_NET", "192.168.42.0/24" } });
vars.append({ { "$IPSEC_VPN_L2TP_POOL", "192.168.42.10-192.168.42.250" } });
vars.append({ { "$IPSEC_VPN_L2TP_LOCAL", "192.168.42.1" } });
vars.append({ { "$IPSEC_VPN_XAUTH_NET", "192.168.43.0/24" } });
vars.append({ { "$IPSEC_VPN_XAUTH_POOL", "192.168.43.10-192.168.43.250" } });
vars.append({ { "$IPSEC_VPN_SHA2_TRUNCBUG", "yes" } });
vars.append({ { "$IPSEC_VPN_VPN_ANDROID_MTU_FIX", "yes" } });
vars.append({ { "$IPSEC_VPN_DISABLE_IKEV2", "no" } });
vars.append({ { "$IPSEC_VPN_DISABLE_L2TP", "no" } });
vars.append({ { "$IPSEC_VPN_DISABLE_XAUTH", "no" } });
vars.append({ { "$IPSEC_VPN_C2C_TRAFFIC", "no" } });
return vars;
}
amnezia::ScriptVars amnezia::genOpenVpnVars(const ContainerConfig &containerConfig)
{
ScriptVars vars;
if (auto* openVpnProtocolConfig = containerConfig.getOpenVpnProtocolConfig()) {
const OpenVpnServerConfig& config = openVpnProtocolConfig->serverConfig;
vars.append({ { "$OPENVPN_SUBNET_IP", config.subnetAddress.isEmpty() ? protocols::openvpn::defaultSubnetAddress : config.subnetAddress } });
vars.append({ { "$OPENVPN_SUBNET_CIDR", config.subnetCidr.isEmpty() ? protocols::openvpn::defaultSubnetCidr : config.subnetCidr } });
vars.append({ { "$OPENVPN_SUBNET_MASK", config.subnetMask.isEmpty() ? protocols::openvpn::defaultSubnetMask : config.subnetMask } });
vars.append({ { "$OPENVPN_PORT", config.port.isEmpty() ? protocols::openvpn::defaultPort : config.port } });
vars.append({ { "$OPENVPN_TRANSPORT_PROTO", config.transportProto.isEmpty() ? protocols::openvpn::defaultTransportProto : config.transportProto } });
vars.append({ { "$OPENVPN_NCP_DISABLE", config.ncpDisable ? protocols::openvpn::ncpDisableString : "" } });
vars.append({ { "$OPENVPN_CIPHER", config.cipher.isEmpty() ? protocols::openvpn::defaultCipher : config.cipher } });
vars.append({ { "$OPENVPN_HASH", config.hash.isEmpty() ? protocols::openvpn::defaultHash : config.hash } });
vars.append({ { "$OPENVPN_TLS_AUTH", config.tlsAuth ? protocols::openvpn::tlsAuthString : "" } });
if (!config.tlsAuth) {
vars.append({ { "$OPENVPN_TA_KEY", "" } });
}
vars.append({ { "$OPENVPN_ADDITIONAL_CLIENT_CONFIG", config.additionalClientConfig.isEmpty() ? protocols::openvpn::defaultAdditionalClientConfig : config.additionalClientConfig } });
vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG", config.additionalServerConfig.isEmpty() ? protocols::openvpn::defaultAdditionalServerConfig : config.additionalServerConfig } });
}
return vars;
}
amnezia::ScriptVars amnezia::genXrayVars(const ContainerConfig &containerConfig)
{
ScriptVars vars;
if (auto* xrayProtocolConfig = containerConfig.getXrayProtocolConfig()) {
const XrayServerConfig& config = xrayProtocolConfig->serverConfig;
vars.append({ { "$XRAY_SITE_NAME", config.site.isEmpty() ? protocols::xray::defaultSite : config.site } });
vars.append({ { "$XRAY_SERVER_PORT", config.port.isEmpty() ? protocols::xray::defaultPort : config.port } });
}
return vars;
}
amnezia::ScriptVars amnezia::genWireGuardVars(const ContainerConfig &containerConfig)
{
ScriptVars vars;
if (auto* wireGuardProtocolConfig = containerConfig.getWireGuardProtocolConfig()) {
const WireGuardServerConfig& config = wireGuardProtocolConfig->serverConfig;
vars.append({ { "$WIREGUARD_SUBNET_IP", config.subnetAddress.isEmpty() ? protocols::wireguard::defaultSubnetAddress : config.subnetAddress } });
vars.append({ { "$WIREGUARD_SUBNET_CIDR", config.subnetCidr.isEmpty() ? protocols::wireguard::defaultSubnetCidr : config.subnetCidr } });
vars.append({ { "$WIREGUARD_SERVER_PORT", config.port.isEmpty() ? protocols::wireguard::defaultPort : config.port } });
}
return vars;
}
amnezia::ScriptVars amnezia::genAwgVars(const ContainerConfig &containerConfig)
{
ScriptVars vars;
if (auto* awgProtocolConfig = containerConfig.getAwgProtocolConfig()) {
const AwgServerConfig& config = awgProtocolConfig->serverConfig;
vars.append({ { "$AWG_SUBNET_IP", config.subnetAddress.isEmpty() ? protocols::wireguard::defaultSubnetAddress : config.subnetAddress } });
vars.append({ { "$WIREGUARD_SUBNET_CIDR", config.subnetCidr.isEmpty() ? protocols::wireguard::defaultSubnetCidr : config.subnetCidr } });
vars.append({ { "$AWG_SERVER_PORT", config.port.isEmpty() ? protocols::awg::defaultPort : config.port } });
vars.append({ { "$JUNK_PACKET_COUNT", config.junkPacketCount } });
vars.append({ { "$JUNK_PACKET_MIN_SIZE", config.junkPacketMinSize } });
vars.append({ { "$JUNK_PACKET_MAX_SIZE", config.junkPacketMaxSize } });
vars.append({ { "$INIT_PACKET_JUNK_SIZE", config.initPacketJunkSize } });
vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", config.responsePacketJunkSize } });
vars.append({ { "$INIT_PACKET_MAGIC_HEADER", config.initPacketMagicHeader } });
vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", config.responsePacketMagicHeader } });
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", config.underloadPacketMagicHeader } });
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", config.transportPacketMagicHeader } });
vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", config.cookieReplyPacketJunkSize } });
vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", config.transportPacketJunkSize } });
vars.append({ { "$SPECIAL_JUNK_1", config.specialJunk1 } });
vars.append({ { "$SPECIAL_JUNK_2", config.specialJunk2 } });
vars.append({ { "$SPECIAL_JUNK_3", config.specialJunk3 } });
vars.append({ { "$SPECIAL_JUNK_4", config.specialJunk4 } });
vars.append({ { "$SPECIAL_JUNK_5", config.specialJunk5 } });
}
return vars;
}
amnezia::ScriptVars amnezia::genSftpVars(const ContainerConfig &containerConfig)
{
ScriptVars vars;
if (auto* sftpProtocolConfig = containerConfig.getSftpProtocolConfig()) {
vars.append({ { "$SFTP_PORT", sftpProtocolConfig->port.isEmpty() ? QString::number(ProtocolUtils::defaultPort(Proto::Sftp)) : sftpProtocolConfig->port } });
vars.append({ { "$SFTP_USER", sftpProtocolConfig->userName } });
vars.append({ { "$SFTP_PASSWORD", sftpProtocolConfig->password } });
}
return vars;
}
amnezia::ScriptVars amnezia::genSocks5ProxyVars(const ContainerConfig &containerConfig)
{
ScriptVars vars;
if (auto* socks5ProxyProtocolConfig = containerConfig.getSocks5ProxyProtocolConfig()) {
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyProtocolConfig->port.isEmpty() ? protocols::socks5Proxy::defaultPort : socks5ProxyProtocolConfig->port } });
QString socks5user = (!socks5ProxyProtocolConfig->userName.isEmpty() && !socks5ProxyProtocolConfig->password.isEmpty())
? QString("users %1:CL:%2").arg(socks5ProxyProtocolConfig->userName, socks5ProxyProtocolConfig->password)
: "";
vars.append({ { "$SOCKS5_USER", socks5user } });
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
}
return vars;
}
amnezia::ScriptVars amnezia::genProtocolVarsForContainer(DockerContainer container, const ContainerConfig &containerConfig)
{
ScriptVars vars;
Proto protocol = ContainerUtils::defaultProtocol(container);
switch (protocol) {
case Proto::OpenVpn:
vars.append(genOpenVpnVars(containerConfig));
break;
case Proto::Xray:
vars.append(genXrayVars(containerConfig));
break;
case Proto::WireGuard:
vars.append(genWireGuardVars(containerConfig));
break;
case Proto::Awg:
vars.append(genAwgVars(containerConfig));
break;
case Proto::Sftp:
vars.append(genSftpVars(containerConfig));
break;
case Proto::Socks5Proxy:
vars.append(genSocks5ProxyVars(containerConfig));
break;
default:
break;
}
return vars;
}