Compare commits

...
Author SHA1 Message Date
vkamn be4f39ee9a chore: bump xcode-version 2026-11-14 00:56:56 +09:00
vkamn 0514a5d5b1 chore: remove double build for macos ne and ios (#3464) 2026-11-14 00:11:49 +09:00
vkamn c6af4cc266 chore: upload iOS and macOS NE builds to TestFlight on version tags (#3463) 2026-11-14 00:06:56 +09:00
vkamn 62a8a79931 feat: ci release ios (#3462)
* feat: upload iOS and macOS NE builds to TestFlight on version tags

* chore: bump version
2026-11-13 23:40:12 +09:00
vkamn f15932bf22 feat: ci release (#3461)
* fix: restore universal APK and speed up Android CI

* chore: bump version

* feat: create draft pre-release with build artifacts on version tags
2026-11-13 21:52:42 +09:00
vkamn 8165462865 feat: selfhosted improvements (#3458)
* chore: set firefox as default fingerprint for xray selfhosted

* feat: add cascade endpoint and port to WG/AWG configs

* fix: build server info tabs from available pages

* feat: pick the default Reality site and SNI at random
2026-11-13 21:20:16 +09:00
NickVs2015 864c1faab8 chore: update awg-apple to 3.1.5 (#3454) 2026-11-12 12:26:59 +09:00
yp 3a6bf7376c fix: ios stop hev before restarting xray tunnel (#3428) 2026-11-12 01:19:30 +09:00
yp ba402f898a fix: ui block hover through pinned navigation bar (#3439) 2026-11-12 01:12:15 +09:00
46 changed files with 782 additions and 134 deletions

No files matched your search

@@ -14,7 +14,6 @@ runs:
- name: Setup provisioning profile
shell: bash
run: |
PROFILES_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
TEMP_FILE=$(mktemp)
echo "${{ inputs.provisioning_profile_base64 }}" | base64 --decode > "$TEMP_FILE"
@@ -26,6 +25,12 @@ runs:
exit 1
fi
mkdir -p "$PROFILES_DIR"
mv "$TEMP_FILE" "$PROFILES_DIR/$PROFILE_UUID.mobileprovision"
echo "Installed profile: $PROFILE_UUID"
PROFILE_EXTENSION=mobileprovision
grep -aq "<string>OSX</string>" "$TEMP_FILE" && PROFILE_EXTENSION=provisionprofile
for PROFILES_DIR in "$HOME/Library/MobileDevice/Provisioning Profiles" "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"; do
mkdir -p "$PROFILES_DIR"
cp "$TEMP_FILE" "$PROFILES_DIR/$PROFILE_UUID.$PROFILE_EXTENSION"
done
rm -f "$TEMP_FILE"
echo "Installed profile: $PROFILE_UUID.$PROFILE_EXTENSION"
+23
View File
@@ -0,0 +1,23 @@
## Version differences
### Android
* For Android version 11 and higher, you need to use .apk that start with - AmneziaVPN_${VERSION}_android11+
* For Android version 9-10, you need to use .apk that start with - AmneziaVPN_${VERSION}_android9-10
* ***The app version for Android 7 and 8 is temporarily unavailable***
### macOS
!!! The previous version (4.8.8.2 and lower) of the application (installed via the .dmg installer) must be uninstalled via “move to bin” in the applications folder
* For macOS version 13 and higher, you need to use .pkg - AmneziaVPN_${VERSION}_macos.pkg
* ***The app version for macOS 10.15 - 12, is temporarily unavailable***
### Linux
For Ubuntu you need to install xcb plugin:
apt install libxcb-cursor0 libxcb-xinerama0 libxcb-icccm4 libxcb-keysyms1 libopengl0 libxkbcommon-x11-0
## Changelog
### General
* Improve stability
**Full Changelog**: https://github.com/amnezia-vpn/amnezia-client/compare/${PREVIOUS_TAG}...${VERSION}
+171 -36
View File
@@ -4,6 +4,8 @@ on:
push:
branches:
- '**'
tags:
- '[0-9]+.[0-9]+.[0-9]+.[0-9]+'
env:
QT_MIRROR: https://mirrors.ocf.berkeley.edu/qt/ # https://download.qt.io/static/mirrorlist/
@@ -375,7 +377,7 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.0'
xcode-version: '26.4'
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
@@ -415,12 +417,24 @@ jobs:
APPSTORE_CONNECT_KEY_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ID }}
APPSTORE_CONNECT_ISSUER_ID: ${{ secrets.APPSTORE_CONNECT_ISSUER_ID }}
APPSTORE_CONNECT_PRIVATE_KEY: ${{ secrets.APPSTORE_CONNECT_PRIVATE_KEY }}
CONFIGURE_ONLY: ${{ startsWith(github.ref, 'refs/tags/') && 'true' || '' }}
shell: bash
run: |
sh deploy/build.sh -t ios | \
sed -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/d' | \
sed -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/d' | \
sed -e '/-DPROD_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DPROD_AGW_PUBLIC_KEY/d' | \
sed -e '/-DDEV_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DDEV_AGW_PUBLIC_KEY/d'
sh deploy/build.sh -t ios | sed -f deploy/hide_secrets.sed
- name: 'Archive for App Store'
if: startsWith(github.ref, 'refs/tags/')
working-directory: deploy
shell: bash
run: fastlane ios archive 2>&1 | sed -f hide_secrets.sed
- name: 'Upload App Store package'
if: startsWith(github.ref, 'refs/tags/')
uses: actions/upload-artifact@v7
with:
path: deploy/build/ios/AmneziaVPN.ipa
archive: false
retention-days: 7
# ------------------------------------------------------
@@ -629,12 +643,7 @@ jobs:
env:
QT_VERSION: 6.10.1
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
KEYCHAIN_PASSWORD: ""
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -652,13 +661,37 @@ jobs:
submodules: 'true'
fetch-depth: 10
- uses: ./.github/actions/apple-setup-provisioning-profile
- uses: ./.github/actions/apple-setup-keychain
id: setup-keychain
with:
provisioning_profile_base64: ${{ secrets.MAC_APP_PROVISIONING_PROFILE }}
keychain-password: ${{ env.KEYCHAIN_PASSWORD }}
- uses: ./.github/actions/apple-setup-provisioning-profile
- name: 'Install signing certificate'
uses: ./.github/actions/apple-install-cert
with:
provisioning_profile_base64: ${{ secrets.MAC_NE_PROVISIONING_PROFILE }}
keychain-path: ${{ steps.setup-keychain.outputs.keychain-path }}
keychain-password: ${{ env.KEYCHAIN_PASSWORD }}
cert-base64: ${{ secrets.IOS_SIGNING_CERT_BASE64 }}
cert-password: ${{ secrets.IOS_SIGNING_CERT_PASSWORD }}
- name: 'Install installer certificate'
if: startsWith(github.ref, 'refs/tags/')
uses: ./.github/actions/apple-install-cert
with:
keychain-path: ${{ steps.setup-keychain.outputs.keychain-path }}
keychain-password: ${{ env.KEYCHAIN_PASSWORD }}
cert-base64: ${{ secrets.MAC_APPSTORE_INSTALLER_CERT_BASE64 }}
cert-password: ${{ secrets.MAC_APPSTORE_INSTALLER_CERT_PASSWORD }}
- name: 'Install app provisioning profile'
uses: ./.github/actions/apple-setup-provisioning-profile
with:
provisioning_profile_base64: ${{ secrets.APPSTORE_CONNECT_MAC_PROVISIONING }}
- name: 'Install NE provisioning profile'
uses: ./.github/actions/apple-setup-provisioning-profile
with:
provisioning_profile_base64: ${{ secrets.APPSTORE_CONNECT_MAC_NE_PROVISIONING }}
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
@@ -677,18 +710,6 @@ jobs:
set-env: 'true'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install go'
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache: false
- name: 'Setup gomobile'
run: |
export PATH=$PATH:~/go/bin
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
- name: 'Setup python'
uses: actions/setup-python@v6
with:
@@ -698,15 +719,32 @@ jobs:
run: pip install "conan==2.28.0"
- name: 'Build project'
run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
bash deploy/build_macos_ne.sh
env:
QT_INSTALL_DIR: ${{ runner.temp }}
CONFIGURE_ONLY: ${{ startsWith(github.ref, 'refs/tags/') && 'true' || '' }}
shell: bash
run: deploy/build.sh -t macos-ne | sed -f deploy/hide_secrets.sed
- name: 'Upload unpacked artifact'
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_unpacked
path: deploy/build/client/AmneziaVPN.app
path: deploy/build/client/Release/AmneziaVPN.app
retention-days: 7
- name: 'Archive for App Store'
if: startsWith(github.ref, 'refs/tags/')
working-directory: deploy
shell: bash
run: fastlane mac archive 2>&1 | sed -f hide_secrets.sed
- name: 'Upload App Store package'
if: startsWith(github.ref, 'refs/tags/')
uses: actions/upload-artifact@v7
with:
path: deploy/build/osx/AmneziaVPN.pkg
archive: false
retention-days: 7
# ------------------------------------------------------
@@ -802,6 +840,8 @@ jobs:
APP_ANDROID_MIN_SDK: ${{ matrix.min_sdk }}
APP_ANDROID_MAX_SDK: ${{ matrix.max_sdk }}
APP_ANDROID_VERSION_CODE_OFFSET: ${{ matrix.version_code_offset }}
CMAKE_C_COMPILER_LAUNCHER: ccache
CMAKE_CXX_COMPILER_LAUNCHER: ccache
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
@@ -896,6 +936,12 @@ jobs:
- name: 'Install conan'
run: pip install "conan==2.28.0"
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
with:
key: android-${{ matrix.label }}
max-size: 1G
- name: 'Decode keystore secret to file'
env:
KEYSTORE_BASE64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_BASE64 }}
@@ -933,8 +979,7 @@ jobs:
fi
for abi in arm64-v8a armeabi-v7a x86 x86_64; do
deploy/build.sh -t android --sign --abi ${abi} --build ./deploy/build/${abi}
mv deploy/build/${abi}/client/android-build/AmneziaVPN.apk \
mv deploy/build/client/android-build/build/outputs/apk/oss/release/*-${abi}-release.apk \
$OUT/AmneziaVPN_${VERSION}_${LABEL}_${abi}.apk
done
@@ -953,9 +998,9 @@ jobs:
OUT=deploy/artifacts
VERSION=$(grep CMAKE_PROJECT_VERSION:STATIC deploy/build/CMakeCache.txt | cut -d= -f2)
deploy/build.sh -t android --sign --aab --play --build ./deploy/build/play
deploy/build.sh -t android --sign --aab --play
mv deploy/build/play/client/android-build/build/outputs/bundle/playRelease/android-build-play-release.aab \
mv deploy/build/client/android-build/build/outputs/bundle/playRelease/android-build-play-release.aab \
$OUT/AmneziaVPN_${VERSION}_${LABEL}_play.aab
- name: 'Upload universal APK'
@@ -1647,3 +1692,93 @@ jobs:
disable-animations: true
script: bash ci_run_android_tests.sh
# ------------------------------------------------------
Pre-Release:
if: startsWith(github.ref, 'refs/tags/')
needs: [Build-Linux-Ubuntu, Build-Windows, Build-iOS, Build-MacOS, Build-MacOS-NE, Build-Android]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: 'Get release notes template'
uses: actions/checkout@v4
with:
sparse-checkout: .github/release-notes.md
sparse-checkout-cone-mode: false
- name: 'Download Android APKs'
uses: actions/download-artifact@v8
with:
pattern: 'AmneziaVPN_*_{android11+,android9-10}_{arm64-v8a,armeabi-v7a,x86,x86_64}.apk'
path: dist
merge-multiple: true
- name: 'Download desktop installers'
uses: actions/download-artifact@v8
with:
pattern: 'AmneziaVPN_*_{linux_x64.run,macos_x64.pkg}'
path: dist
merge-multiple: true
- name: 'Publish pre-release'
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
run: |
ls -l dist
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release upload "$TAG" dist/* --repo "$GITHUB_REPOSITORY" --clobber
else
export VERSION="$TAG"
export PREVIOUS_TAG=$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName -q .tagName)
envsubst '$VERSION $PREVIOUS_TAG' < .github/release-notes.md > release-notes.md
gh release create "$TAG" dist/* --repo "$GITHUB_REPOSITORY" \
--title "$TAG" --notes-file release-notes.md --draft --prerelease --verify-tag
fi
# ------------------------------------------------------
TestFlight:
if: startsWith(github.ref, 'refs/tags/')
needs: [Build-Linux-Ubuntu, Build-Windows, Build-iOS, Build-MacOS, Build-MacOS-NE, Build-Android]
runs-on: macos-26
env:
APPSTORE_CONNECT_KEY_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ID }}
APPSTORE_CONNECT_ISSUER_ID: ${{ secrets.APPSTORE_CONNECT_ISSUER_ID }}
APPSTORE_CONNECT_PRIVATE_KEY: ${{ secrets.APPSTORE_CONNECT_PRIVATE_KEY }}
defaults:
run:
working-directory: deploy
shell: bash
steps:
- name: 'Get fastlane config'
uses: actions/checkout@v4
with:
sparse-checkout: |
deploy/fastlane
deploy/hide_secrets.sed
sparse-checkout-cone-mode: false
- name: 'Download iOS package'
uses: actions/download-artifact@v8
with:
name: AmneziaVPN.ipa
path: deploy/build/ios
- name: 'Download macOS package'
uses: actions/download-artifact@v8
with:
name: AmneziaVPN.pkg
path: deploy/build/osx
- name: 'Upload iOS build to TestFlight'
run: fastlane ios testflight_upload 2>&1 | sed -f hide_secrets.sed
- name: 'Upload macOS build to TestFlight'
run: fastlane mac testflight_upload 2>&1 | sed -f hide_secrets.sed
+2 -2
View File
@@ -5,7 +5,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PROJECT AmneziaVPN)
if(NOT AMNEZIAVPN_VERSION)
set(AMNEZIAVPN_VERSION 5.1.0.6 CACHE STRING "Client app version")
set(AMNEZIAVPN_VERSION 5.1.1.3 CACHE STRING "Client app version")
endif()
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
@@ -34,7 +34,7 @@ set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
# bump by 2 on every release, because we're releasing two versions on the play store
set(APP_ANDROID_VERSION_CODE 2190)
set(APP_ANDROID_VERSION_CODE 2192)
if(DEFINED APP_ANDROID_VERSION_CODE_OFFSET)
math(EXPR APP_ANDROID_VERSION_CODE "${APP_ANDROID_VERSION_CODE} + ${APP_ANDROID_VERSION_CODE_OFFSET}")
endif()
+8 -5
View File
@@ -54,6 +54,7 @@ android {
storePassword = providers.environmentVariable("QT_ANDROID_KEYSTORE_STORE_PASS").orNull
keyAlias = providers.environmentVariable("QT_ANDROID_KEYSTORE_ALIAS").orNull
keyPassword = providers.environmentVariable("QT_ANDROID_KEYSTORE_STORE_PASS").orNull
enableV3Signing = true
}
}
@@ -111,7 +112,7 @@ android {
isEnable = abiList.size > 1
reset()
include(*abiList.toTypedArray())
isUniversalApk = false
isUniversalApk = true
}
}
@@ -122,7 +123,8 @@ android {
outputs.map { it as BaseVariantOutputImpl }
.forEach { output ->
if (output.outputFileName.endsWith(".apk")) {
output.outputFileName = "$outputBaseName-${buildType.name}.apk"
output.outputFileName = listOfNotNull(outputBaseName, output.getFilter("ABI"), buildType.name)
.joinToString("-", postfix = ".apk")
}
}
}
@@ -143,14 +145,15 @@ android {
val base = outputBaseName.ifEmpty { "android-build" }
val unsignedSuffix = if (buildTypeName == "release") "-unsigned" else ""
val universalOutput = outputs.map { it as BaseVariantOutputImpl }.first { it.getFilter("ABI") == null }
packageApplicationProvider.configure {
doLast {
val srcDir = layout.buildDirectory.dir("outputs/apk/oss/$buildTypeName").get().asFile
val dstDir = layout.buildDirectory.dir("outputs/apk").get().asFile
dstDir.mkdirs()
srcDir.listFiles()?.filter { it.name.endsWith(".apk") }?.forEach { apk ->
apk.copyTo(File(dstDir, "$base-$buildTypeName$unsignedSuffix.apk"), overwrite = true)
}
File(srcDir, universalOutput.outputFileName)
.copyTo(File(dstDir, "$base-$buildTypeName$unsignedSuffix.apk"), overwrite = true)
}
}
+4 -4
View File
@@ -32,14 +32,14 @@ endif()
# -- signing
if(NOT CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER)
set(CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN" CACHE STRING "iOS app provisioning profile specifier for release")
set(CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.amneziaVPN" CACHE STRING "iOS app provisioning profile specifier for release")
endif()
if(NOT CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER_DEBUG)
set(CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER_DEBUG "dev ios.org.amnezia.AmneziaVPN" CACHE STRING "iOS app provisioning profile specifier for debug")
set(CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER_DEBUG "dev ios.org.amnezia.amneziaVPN" CACHE STRING "iOS app provisioning profile specifier for debug")
endif()
if(NOT CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER)
set(CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN" CACHE STRING "iOS network extension provisioning profile specifier for release")
set(CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.amneziaVPN.NE" CACHE STRING "iOS network extension provisioning profile specifier for release")
endif()
if(NOT CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER_DEBUG)
set(CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER_DEBUG "dev ios.org.amnezia.AmneziaVPN" CACHE STRING "iOS network extension provisioning profile specifier for debug")
set(CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER_DEBUG "dev ios.org.amnezia.amneziaVPN.NE" CACHE STRING "iOS network extension provisioning profile specifier for debug")
endif()
+1 -1
View File
@@ -24,7 +24,7 @@ endif()
# -- signing
if(NOT CLIENT_MACOS_PROVISIONING_PROFILE_SPECIFIER)
set(CLIENT_MACOS_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.AmneziaVPN" CACHE STRING "macOS app provisioning profile specifier for release")
set(CLIENT_MACOS_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.amneziaVPN" CACHE STRING "macOS app provisioning profile specifier for release")
endif()
if(NOT CLIENT_MACOS_PROVISIONING_PROFILE_SPECIFIER_DEBUG)
set(CLIENT_MACOS_PROVISIONING_PROFILE_SPECIFIER_DEBUG "dev macos.org.amnezia.AmneziaVPN" CACHE STRING "macOS app provisioning profile specifier for debug")
+7 -1
View File
@@ -156,6 +156,12 @@ message(${QtCore_location})
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
set(MACDEPLOYQT_ARGS -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR})
if(Qt6_VERSION VERSION_GREATER_EQUAL 6.11)
list(APPEND MACDEPLOYQT_ARGS -no-codesign)
endif()
# $<TARGET_BUNDLE_DIR> points to the build tree; xcodebuild archive builds the bundle elsewhere
add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:${PROJECT}> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR} -no-codesign
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt \${TARGET_BUILD_DIR}/\${WRAPPER_NAME} ${MACDEPLOYQT_ARGS}
)
@@ -66,6 +66,8 @@ ProtocolConfig AwgConfigurator::createConfig(const ServerCredentials &credential
newClientConfig.nativeConfig = awgConfig;
newClientConfig.hostName = wgConfig->clientConfig->hostName;
newClientConfig.port = wgConfig->clientConfig->port;
newClientConfig.cascadeEndpoint = wgConfig->clientConfig->cascadeEndpoint;
newClientConfig.cascadePort = wgConfig->clientConfig->cascadePort;
newClientConfig.clientIp = wgConfig->clientConfig->clientIp;
newClientConfig.clientPrivateKey = wgConfig->clientConfig->clientPrivateKey;
newClientConfig.clientPublicKey = wgConfig->clientConfig->clientPublicKey;
@@ -266,6 +266,11 @@ ProtocolConfig WireguardConfigurator::createConfig(const ServerCredentials &cred
clientConfig.nativeConfig = config;
clientConfig.hostName = connData.host;
clientConfig.port = connData.port.toInt();
if (wireguardServerConfig) {
clientConfig.setCascade(wireguardServerConfig->cascadeEndpoint, wireguardServerConfig->cascadePort);
} else if (awgServerConfig) {
clientConfig.setCascade(awgServerConfig->cascadeEndpoint, awgServerConfig->cascadePort);
}
clientConfig.clientIp = connData.clientIP;
clientConfig.clientPrivateKey = connData.clientPrivKey;
clientConfig.clientPublicKey = connData.clientPubKey;
@@ -289,6 +289,15 @@ QJsonObject ConnectionController::createConnectionConfiguration(const QPair<QStr
ContainerUtils::isAwgContainer(container) ? protocols::awg::defaultMtu :
protocols::wireguard::defaultMtu;
}
const QString cascadeEndpoint = vpnConfigData.value(configKey::cascadeEndpoint).toString();
if (!cascadeEndpoint.isEmpty()) {
vpnConfigData[configKey::hostName] = cascadeEndpoint;
}
const QString cascadePort = vpnConfigData.value(configKey::cascadePort).toString();
if (!cascadePort.isEmpty()) {
vpnConfigData[configKey::port] = cascadePort.toInt();
}
}
vpnConfiguration.insert(ProtocolUtils::key_proto_config_data(proto), vpnConfigData);
+7 -2
View File
@@ -1,5 +1,8 @@
#include "installerBase.h"
#include <QRandomGenerator>
#include <iterator>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
@@ -85,8 +88,10 @@ ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int p
srv.transport = protocols::xray::defaultTransport;
srv.security = protocols::xray::defaultSecurity;
srv.flow = protocols::xray::defaultFlow;
srv.site = protocols::xray::defaultSite;
srv.sni = protocols::xray::defaultSni;
const int siteIndex =
QRandomGenerator::global()->bounded(static_cast<int>(std::size(protocols::xray::defaultSites)));
srv.site = protocols::xray::defaultSites[siteIndex];
srv.sni = srv.site;
srv.alpn = protocols::xray::defaultAlpn;
srv.fingerprint = protocols::xray::defaultFingerprint;
config.protocolConfig = xrayConfig;
@@ -72,6 +72,12 @@ QJsonObject AwgServerConfig::toJson() const
if (!port.isEmpty()) {
obj[configKey::port] = this->port;
}
if (!cascadeEndpoint.isEmpty()) {
obj[configKey::cascadeEndpoint] = cascadeEndpoint;
}
if (!cascadePort.isEmpty()) {
obj[configKey::cascadePort] = cascadePort;
}
if (!transportProto.isEmpty()) {
obj[configKey::transportProto] = transportProto;
}
@@ -166,6 +172,8 @@ AwgServerConfig AwgServerConfig::fromJson(const QJsonObject& json)
AwgServerConfig config;
config.port = json.value(configKey::port).toString();
config.cascadeEndpoint = json.value(configKey::cascadeEndpoint).toString();
config.cascadePort = json.value(configKey::cascadePort).toString();
config.transportProto = json.value(configKey::transportProto).toString();
config.protocolVersion = json.value(configKey::protocolVersion).toString();
config.subnetAddress = json.value(configKey::subnetAddress).toString();
@@ -218,6 +226,12 @@ QJsonObject AwgClientConfig::toJson() const
if (port > 0) {
obj[configKey::port] = port;
}
if (!cascadeEndpoint.isEmpty()) {
obj[configKey::cascadeEndpoint] = cascadeEndpoint;
}
if (!cascadePort.isEmpty()) {
obj[configKey::cascadePort] = cascadePort;
}
if (!clientIp.isEmpty()) {
obj[configKey::clientIp] = clientIp;
}
@@ -333,6 +347,14 @@ QJsonObject AwgClientConfig::toJson() const
return obj;
}
void AwgClientConfig::setCascade(const QString &newEndpoint, const QString &newPort)
{
cascadeEndpoint = newEndpoint;
cascadePort = newPort;
nativeConfig = replaceWireGuardEndpoint(nativeConfig, newEndpoint.isEmpty() ? hostName : newEndpoint,
newPort.isEmpty() ? port : newPort.toInt());
}
AwgClientConfig AwgClientConfig::fromJson(const QJsonObject& json)
{
AwgClientConfig config;
@@ -340,6 +362,8 @@ AwgClientConfig AwgClientConfig::fromJson(const QJsonObject& json)
config.nativeConfig = json.value(configKey::config).toString();
config.hostName = json.value(configKey::hostName).toString();
config.port = json.value(configKey::port).toInt(0);
config.cascadeEndpoint = json.value(configKey::cascadeEndpoint).toString();
config.cascadePort = json.value(configKey::cascadePort).toString();
config.clientIp = json.value(configKey::clientIp).toString();
config.clientPrivateKey = json.value(configKey::clientPrivKey).toString();
config.clientPublicKey = json.value(configKey::clientPubKey).toString();
@@ -19,6 +19,8 @@ namespace AwgConstant
struct AwgServerConfig {
QString port;
QString cascadeEndpoint;
QString cascadePort;
QString transportProto;
QString protocolVersion;
QString subnetAddress;
@@ -61,6 +63,8 @@ struct AwgClientConfig {
QString nativeConfig;
QString hostName;
int port;
QString cascadeEndpoint;
QString cascadePort;
QString clientIp;
QString clientPrivateKey;
QString clientPublicKey;
@@ -95,6 +99,9 @@ struct AwgClientConfig {
QString maxHandshakeAttempts;
QString randomTrailers;
QString disableCookies;
void setCascade(const QString &newEndpoint, const QString &newPort);
QJsonObject toJson() const;
static AwgClientConfig fromJson(const QJsonObject& json);
};
@@ -20,6 +20,12 @@ QJsonObject WireGuardServerConfig::toJson() const
if (!port.isEmpty()) {
obj[configKey::port] = port;
}
if (!cascadeEndpoint.isEmpty()) {
obj[configKey::cascadeEndpoint] = cascadeEndpoint;
}
if (!cascadePort.isEmpty()) {
obj[configKey::cascadePort] = cascadePort;
}
if (!transportProto.isEmpty()) {
obj[configKey::transportProto] = transportProto;
}
@@ -45,6 +51,8 @@ WireGuardServerConfig WireGuardServerConfig::fromJson(const QJsonObject& json)
WireGuardServerConfig config;
config.port = json.value(configKey::port).toString();
config.cascadeEndpoint = json.value(configKey::cascadeEndpoint).toString();
config.cascadePort = json.value(configKey::cascadePort).toString();
config.transportProto = json.value(configKey::transportProto).toString();
config.subnetAddress = json.value(configKey::subnetAddress).toString();
config.subnetMask = json.value(configKey::subnetMask).toString();
@@ -73,6 +81,12 @@ QJsonObject WireGuardClientConfig::toJson() const
if (port > 0) {
obj[configKey::port] = port;
}
if (!cascadeEndpoint.isEmpty()) {
obj[configKey::cascadeEndpoint] = cascadeEndpoint;
}
if (!cascadePort.isEmpty()) {
obj[configKey::cascadePort] = cascadePort;
}
if (!clientIp.isEmpty()) {
obj[configKey::clientIp] = clientIp;
}
@@ -119,6 +133,14 @@ QJsonObject WireGuardClientConfig::toJson() const
return obj;
}
void WireGuardClientConfig::setCascade(const QString &newEndpoint, const QString &newPort)
{
cascadeEndpoint = newEndpoint;
cascadePort = newPort;
nativeConfig = replaceWireGuardEndpoint(nativeConfig, newEndpoint.isEmpty() ? hostName : newEndpoint,
newPort.isEmpty() ? port : newPort.toInt());
}
WireGuardClientConfig WireGuardClientConfig::fromJson(const QJsonObject& json)
{
WireGuardClientConfig config;
@@ -126,6 +148,8 @@ WireGuardClientConfig WireGuardClientConfig::fromJson(const QJsonObject& json)
config.nativeConfig = json.value(configKey::config).toString();
config.hostName = json.value(configKey::hostName).toString();
config.port = json.value(configKey::port).toInt(0);
config.cascadeEndpoint = json.value(configKey::cascadeEndpoint).toString();
config.cascadePort = json.value(configKey::cascadePort).toString();
config.clientIp = json.value(configKey::clientIp).toString();
config.clientPrivateKey = json.value(configKey::clientPrivKey).toString();
config.clientPublicKey = json.value(configKey::clientPubKey).toString();
@@ -12,6 +12,8 @@ namespace amnezia
struct WireGuardServerConfig {
QString port;
QString cascadeEndpoint;
QString cascadePort;
QString transportProto;
QString subnetAddress;
QString subnetMask;
@@ -28,6 +30,8 @@ struct WireGuardClientConfig {
QString nativeConfig;
QString hostName;
int port;
QString cascadeEndpoint;
QString cascadePort;
QString clientIp;
QString clientPrivateKey;
QString clientPublicKey;
@@ -41,6 +45,8 @@ struct WireGuardClientConfig {
QMap<QString, QString> awgParams;
bool isObfuscationEnabled = false;
void setCascade(const QString &newEndpoint, const QString &newPort);
QJsonObject toJson() const;
static WireGuardClientConfig fromJson(const QJsonObject& json);
};
@@ -354,9 +354,6 @@ void XrayServerConfig::applyDefaults(bool fillFlowDefault)
transportProto = ProtocolUtils::transportProtoToString(ProtocolUtils::defaultTransportProto(Proto::Xray),
Proto::Xray);
}
if (site.isEmpty()) {
site = protocols::xray::defaultSite;
}
if (transport.isEmpty()) {
transport = protocols::xray::defaultTransport;
}
@@ -367,7 +364,7 @@ void XrayServerConfig::applyDefaults(bool fillFlowDefault)
flow = protocols::xray::defaultFlow;
}
if (sni.isEmpty()) {
sni = site.isEmpty() ? QString::fromLatin1(protocols::xray::defaultSni) : site;
sni = site;
}
if (alpn.isEmpty()) {
alpn = protocols::xray::defaultAlpn;
@@ -569,7 +566,7 @@ QJsonObject XrayServerConfig::streamSettingsJson(XrayStreamSide side) const
if (securityEff == QLatin1String(px::securityTls)) {
QJsonObject tlsSettings;
tlsSettings[px::serverName] = sni.isEmpty() ? QString::fromLatin1(px::defaultSni) : sni;
tlsSettings[px::serverName] = sni;
const QString alpnEff = alpn.isEmpty() ? QString::fromLatin1(px::defaultAlpn) : alpn;
QJsonArray alpnArray;
for (const QString &a : alpnEff.split(QLatin1Char(','))) {
@@ -598,7 +595,7 @@ QJsonObject XrayServerConfig::streamSettingsJson(XrayStreamSide side) const
if (clientSide && securityEff == QLatin1String(px::securityReality)) {
QJsonObject realitySettings;
realitySettings[px::fingerprint] = fingerprintEff;
realitySettings[px::serverName] = sni.isEmpty() ? QString::fromLatin1(px::defaultSni) : sni;
realitySettings[px::serverName] = sni;
streamSettings[px::realitySettings] = realitySettings;
}
@@ -1139,11 +1136,10 @@ QJsonObject XrayServerConfig::toServerInboundJson(const XrayServerInboundInputs
QJsonObject streamSettings = serverStreamSettings();
if (xrayEffective::security(*this) == QLatin1String(px::securityReality)) {
const QString siteEff = site.isEmpty() ? QString::fromLatin1(px::defaultSite) : site;
QJsonObject realitySettings;
realitySettings[px::dest] = siteEff + QStringLiteral(":443");
realitySettings[px::dest] = site + QStringLiteral(":443");
realitySettings[px::privateKey] = inputs.realityPrivateKey;
realitySettings[px::serverNames] = QJsonArray { sni.isEmpty() ? siteEff : sni };
realitySettings[px::serverNames] = QJsonArray { sni.isEmpty() ? site : sni };
realitySettings[px::shortIds] = QJsonArray { inputs.realityShortId };
streamSettings[px::realitySettings] = realitySettings;
}
@@ -1323,10 +1319,9 @@ QJsonObject XrayServerConfig::serverView() const
view[px::streamSettings] = serverStreamSettings();
if (xrayEffective::security(*this) == QLatin1String(px::securityReality)) {
const QString siteEff = site.isEmpty() ? QString::fromLatin1(px::defaultSite) : site;
QJsonObject reality;
reality[px::dest] = siteEff;
reality[px::serverName] = sni.isEmpty() ? siteEff : sni;
reality[px::dest] = site;
reality[px::serverName] = sni.isEmpty() ? site : sni;
view[QStringLiteral("reality")] = reality;
}
+8
View File
@@ -1,6 +1,7 @@
#include "protocolUtils.h"
#include <QRandomGenerator>
#include <QRegularExpression>
#include <QJsonObject>
#include <QObject>
@@ -216,3 +217,10 @@ QString ProtocolUtils::key_proto_config_path(Proto p)
return protoToString(p) + "_config_path";
}
QString ProtocolUtils::replaceWireGuardEndpoint(QString nativeConfig, const QString &host, int port)
{
static const QRegularExpression endpointLine(QString(R"(^(\s*%1\s*=\s*).*$)").arg(protocols::wireguard::Endpoint),
QRegularExpression::MultilineOption);
const QString endpointHost = host.contains(':') ? QString("[%1]").arg(host) : host;
return nativeConfig.replace(endpointLine, QString("\\1%1:%2").arg(endpointHost).arg(port));
}
+2
View File
@@ -39,6 +39,8 @@ namespace amnezia
QString key_proto_config_data(Proto p);
QString key_proto_config_path(Proto p);
QString replaceWireGuardEndpoint(QString nativeConfig, const QString &host, int port);
}
}
+2
View File
@@ -13,6 +13,8 @@ namespace amnezia
constexpr QLatin1String password("password");
constexpr QLatin1String port("port");
constexpr QLatin1String localPort("local_port");
constexpr QLatin1String cascadeEndpoint("cascade_endpoint");
constexpr QLatin1String cascadePort("cascade_port");
constexpr QLatin1String dns1("dns1");
constexpr QLatin1String dns2("dns2");
@@ -57,7 +57,22 @@ namespace amnezia
constexpr char shortidPath[] = "/opt/amnezia/xray/xray_short_id.key";
constexpr char tlsCertPath[] = "/opt/amnezia/xray/tls_cert.pem";
constexpr char tlsKeyPath[] = "/opt/amnezia/xray/tls_key.pem";
constexpr char defaultSite[] = "www.googletagmanager.com";
constexpr const char *defaultSites[] = {
"www.microsoft.com",
"www.apple.com",
"www.icloud.com",
"dl.google.com",
"www.bing.com",
"www.samsung.com",
"www.nvidia.com",
"www.cisco.com",
"www.asus.com",
"www.logitech.com",
"www.dropbox.com",
"www.mozilla.org",
"addons.mozilla.org",
"www.speedtest.net",
};
constexpr char defaultPort[] = "443";
constexpr char defaultLocalProxyPort[] = "10808";
@@ -69,8 +84,7 @@ namespace amnezia
constexpr char defaultSecurity[] = "reality";
constexpr char defaultFlow[] = "xtls-rprx-vision";
constexpr char defaultTransport[] = "raw";
constexpr char defaultFingerprint[] = "hellochrome_120";
constexpr char defaultSni[] = "www.googletagmanager.com";
constexpr char defaultFingerprint[] = "firefox";
constexpr char defaultAlpn[] = "h2";
constexpr char defaultXhttpMode[] = "Stream-one";
@@ -208,7 +208,7 @@ amnezia::ScriptVars amnezia::genXrayVars(const ContainerConfig &containerConfig)
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_SITE_NAME", config.site } });
vars.append({ { "$XRAY_SERVER_PORT", config.port.isEmpty() ? protocols::xray::defaultPort : config.port } });
}
@@ -205,10 +205,10 @@ extension PacketTunnelProvider {
}
}
func stopXray(completionHandler: () -> Void) {
func stopXray(completionHandler: @escaping () -> Void) {
Socks5Tunnel.quit()
LibXrayStopXray()
completionHandler()
hevSocksTunnelGroup.notify(queue: .main, execute: completionHandler)
}
func sockCallback(fd: uintptr_t) {
@@ -345,10 +345,13 @@ extension PacketTunnelProvider {
return
}
DispatchQueue.global().async {
hevSocksTunnelGroup.enter()
DispatchQueue.global().async { [hevSocksTunnelGroup] in
xrayLog(.info, message: "Hev socks started")
completionHandler(nil)
Socks5Tunnel.run(withConfig: configurationFilePath)
let result = Socks5Tunnel.run(withConfig: configurationFilePath)
xrayLog(.info, message: "Hev socks stopped: \(result)")
hevSocksTunnelGroup.leave()
}
}
}
@@ -69,6 +69,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
var protoType: TunnelProtoType?
var activeIfaceIdx: UInt32 = 0
let hevSocksTunnelGroup = DispatchGroup()
func openVPNPacketFlow() -> OpenVPNAdapterPacketFlow {
openVPNPacketFlowAdapter
@@ -316,10 +317,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
updateActiveInterfaceIndex(for: changePath)
reasserting = true
xrayLog(.info, message: "Applying network change to xray tunnel")
stopXray { }
startXray { [weak self] error in
self?.reasserting = false
completion(error)
stopXray { [weak self] in
self?.startXray { [weak self] error in
self?.reasserting = false
completion(error)
}
}
}
+1 -1
View File
@@ -75,7 +75,7 @@ void TestXrayConfigModel::isValidSni_data()
{
QTest::addColumn<QString>("sni");
QTest::addColumn<bool>("valid");
QTest::newRow("empty") << "" << true;
QTest::newRow("empty") << "" << false;
QTest::newRow("domain") << "www.example.com" << true;
QTest::newRow("ipv4") << "203.0.113.10" << true;
QTest::newRow("star") << "*" << false;
@@ -58,6 +58,73 @@ void AwgConfigModel::setPort(const QString &value)
emit changed();
}
QString AwgConfigModel::cascadeEndpoint() const
{
return m_protocolConfig.serverConfig.cascadeEndpoint;
}
void AwgConfigModel::setCascadeEndpoint(const QString &value)
{
if (m_protocolConfig.serverConfig.cascadeEndpoint == value) {
return;
}
m_protocolConfig.serverConfig.cascadeEndpoint = value;
emit changed();
}
QString AwgConfigModel::cascadePort() const
{
return m_protocolConfig.serverConfig.cascadePort;
}
void AwgConfigModel::setCascadePort(const QString &value)
{
if (m_protocolConfig.serverConfig.cascadePort == value) {
return;
}
m_protocolConfig.serverConfig.cascadePort = value;
emit changed();
}
QString AwgConfigModel::clientHostName() const
{
return m_protocolConfig.clientConfig->hostName;
}
QString AwgConfigModel::clientPort() const
{
const int port = m_protocolConfig.clientConfig->port;
return port > 0 ? QString::number(port) : QString();
}
QString AwgConfigModel::clientCascadeEndpoint() const
{
return m_protocolConfig.clientConfig->cascadeEndpoint;
}
void AwgConfigModel::setClientCascadeEndpoint(const QString &value)
{
if (m_protocolConfig.clientConfig->cascadeEndpoint == value) {
return;
}
m_protocolConfig.clientConfig->setCascade(value, m_protocolConfig.clientConfig->cascadePort);
emit changed();
}
QString AwgConfigModel::clientCascadePort() const
{
return m_protocolConfig.clientConfig->cascadePort;
}
void AwgConfigModel::setClientCascadePort(const QString &value)
{
if (m_protocolConfig.clientConfig->cascadePort == value) {
return;
}
m_protocolConfig.clientConfig->setCascade(m_protocolConfig.clientConfig->cascadeEndpoint, value);
emit changed();
}
QString AwgConfigModel::clientMtu() const
{
return m_protocolConfig.clientConfig->mtu;
@@ -709,8 +776,15 @@ amnezia::AwgProtocolConfig AwgConfigModel::getProtocolConfig()
{
bool serverSettingsChanged = !m_protocolConfig.serverConfig.hasEqualServerSettings(m_originalProtocolConfig.serverConfig);
const auto &serverConfig = m_protocolConfig.serverConfig;
const auto &originalServerConfig = m_originalProtocolConfig.serverConfig;
const bool cascadeChanged = serverConfig.cascadeEndpoint != originalServerConfig.cascadeEndpoint
|| serverConfig.cascadePort != originalServerConfig.cascadePort;
if (serverSettingsChanged) {
m_protocolConfig.clearClientConfig();
} else if (cascadeChanged) {
m_protocolConfig.clientConfig->setCascade(serverConfig.cascadeEndpoint, serverConfig.cascadePort);
}
return m_protocolConfig;
@@ -14,7 +14,13 @@ class AwgConfigModel : public QObject
Q_PROPERTY(QString subnetAddress READ subnetAddress WRITE setSubnetAddress NOTIFY changed)
Q_PROPERTY(QString port READ port WRITE setPort NOTIFY changed)
Q_PROPERTY(QString cascadeEndpoint READ cascadeEndpoint WRITE setCascadeEndpoint NOTIFY changed)
Q_PROPERTY(QString cascadePort READ cascadePort WRITE setCascadePort NOTIFY changed)
Q_PROPERTY(QString clientMtu READ clientMtu WRITE setClientMtu NOTIFY changed)
Q_PROPERTY(QString clientHostName READ clientHostName NOTIFY changed)
Q_PROPERTY(QString clientPort READ clientPort NOTIFY changed)
Q_PROPERTY(QString clientCascadeEndpoint READ clientCascadeEndpoint WRITE setClientCascadeEndpoint NOTIFY changed)
Q_PROPERTY(QString clientCascadePort READ clientCascadePort WRITE setClientCascadePort NOTIFY changed)
Q_PROPERTY(QString clientJunkPacketCount READ clientJunkPacketCount WRITE setClientJunkPacketCount NOTIFY changed)
Q_PROPERTY(QString clientJunkPacketMinSize READ clientJunkPacketMinSize WRITE setClientJunkPacketMinSize NOTIFY changed)
Q_PROPERTY(QString clientJunkPacketMaxSize READ clientJunkPacketMaxSize WRITE setClientJunkPacketMaxSize NOTIFY changed)
@@ -69,6 +75,21 @@ public:
QString port() const;
void setPort(const QString &value);
QString cascadeEndpoint() const;
void setCascadeEndpoint(const QString &value);
QString cascadePort() const;
void setCascadePort(const QString &value);
QString clientHostName() const;
QString clientPort() const;
QString clientCascadeEndpoint() const;
void setClientCascadeEndpoint(const QString &value);
QString clientCascadePort() const;
void setClientCascadePort(const QString &value);
QString clientMtu() const;
void setClientMtu(const QString &value);
@@ -40,6 +40,73 @@ void WireGuardConfigModel::setPort(const QString &value)
emit changed();
}
QString WireGuardConfigModel::cascadeEndpoint() const
{
return m_protocolConfig.serverConfig.cascadeEndpoint;
}
void WireGuardConfigModel::setCascadeEndpoint(const QString &value)
{
if (m_protocolConfig.serverConfig.cascadeEndpoint == value) {
return;
}
m_protocolConfig.serverConfig.cascadeEndpoint = value;
emit changed();
}
QString WireGuardConfigModel::cascadePort() const
{
return m_protocolConfig.serverConfig.cascadePort;
}
void WireGuardConfigModel::setCascadePort(const QString &value)
{
if (m_protocolConfig.serverConfig.cascadePort == value) {
return;
}
m_protocolConfig.serverConfig.cascadePort = value;
emit changed();
}
QString WireGuardConfigModel::clientHostName() const
{
return m_protocolConfig.clientConfig->hostName;
}
QString WireGuardConfigModel::clientPort() const
{
const int port = m_protocolConfig.clientConfig->port;
return port > 0 ? QString::number(port) : QString();
}
QString WireGuardConfigModel::clientCascadeEndpoint() const
{
return m_protocolConfig.clientConfig->cascadeEndpoint;
}
void WireGuardConfigModel::setClientCascadeEndpoint(const QString &value)
{
if (m_protocolConfig.clientConfig->cascadeEndpoint == value) {
return;
}
m_protocolConfig.clientConfig->setCascade(value, m_protocolConfig.clientConfig->cascadePort);
emit changed();
}
QString WireGuardConfigModel::clientCascadePort() const
{
return m_protocolConfig.clientConfig->cascadePort;
}
void WireGuardConfigModel::setClientCascadePort(const QString &value)
{
if (m_protocolConfig.clientConfig->cascadePort == value) {
return;
}
m_protocolConfig.clientConfig->setCascade(m_protocolConfig.clientConfig->cascadeEndpoint, value);
emit changed();
}
QString WireGuardConfigModel::clientMtu() const
{
if (m_protocolConfig.clientConfig.has_value()) {
@@ -96,8 +163,15 @@ amnezia::WireGuardProtocolConfig WireGuardConfigModel::getProtocolConfig()
{
bool serverSettingsChanged = !m_protocolConfig.serverConfig.hasEqualServerSettings(m_originalProtocolConfig.serverConfig);
const auto &serverConfig = m_protocolConfig.serverConfig;
const auto &originalServerConfig = m_originalProtocolConfig.serverConfig;
const bool cascadeChanged = serverConfig.cascadeEndpoint != originalServerConfig.cascadeEndpoint
|| serverConfig.cascadePort != originalServerConfig.cascadePort;
if (serverSettingsChanged) {
m_protocolConfig.clearClientConfig();
} else if (cascadeChanged) {
m_protocolConfig.clientConfig->setCascade(serverConfig.cascadeEndpoint, serverConfig.cascadePort);
}
return m_protocolConfig;
@@ -14,7 +14,13 @@ class WireGuardConfigModel : public QObject
Q_PROPERTY(QString subnetAddress READ subnetAddress WRITE setSubnetAddress NOTIFY changed)
Q_PROPERTY(QString port READ port WRITE setPort NOTIFY changed)
Q_PROPERTY(QString cascadeEndpoint READ cascadeEndpoint WRITE setCascadeEndpoint NOTIFY changed)
Q_PROPERTY(QString cascadePort READ cascadePort WRITE setCascadePort NOTIFY changed)
Q_PROPERTY(QString clientMtu READ clientMtu WRITE setClientMtu NOTIFY changed)
Q_PROPERTY(QString clientHostName READ clientHostName NOTIFY changed)
Q_PROPERTY(QString clientPort READ clientPort NOTIFY changed)
Q_PROPERTY(QString clientCascadeEndpoint READ clientCascadeEndpoint WRITE setClientCascadeEndpoint NOTIFY changed)
Q_PROPERTY(QString clientCascadePort READ clientCascadePort WRITE setClientCascadePort NOTIFY changed)
public:
explicit WireGuardConfigModel(QObject *parent = nullptr);
@@ -25,6 +31,21 @@ public:
QString port() const;
void setPort(const QString &value);
QString cascadeEndpoint() const;
void setCascadeEndpoint(const QString &value);
QString cascadePort() const;
void setCascadePort(const QString &value);
QString clientHostName() const;
QString clientPort() const;
QString clientCascadeEndpoint() const;
void setClientCascadeEndpoint(const QString &value);
QString clientCascadePort() const;
void setClientCascadePort(const QString &value);
QString clientMtu() const;
void setClientMtu(const QString &value);
+2 -10
View File
@@ -759,7 +759,7 @@ QStringList XrayConfigModel::transportOptions()
QStringList XrayConfigModel::fingerprintOptions()
{
return { "hellochrome_120", "chrome", "firefox", "safari", "ios", "android", "edge", "360", "qq", "random" };
return { "chrome", "firefox", "safari", "ios", "android", "edge", "360", "qq", "random" };
}
QStringList XrayConfigModel::alpnOptions()
@@ -833,11 +833,6 @@ QString XrayConfigModel::portDefault()
return QString::fromLatin1(protocols::xray::defaultPort);
}
QString XrayConfigModel::sniDefault()
{
return QString::fromLatin1(protocols::xray::defaultSni);
}
QString XrayConfigModel::xhttpHostDefault()
{
return QString::fromLatin1(protocols::xray::defaultXhttpHost);
@@ -933,10 +928,7 @@ bool XrayConfigModel::isValidHost(const QString &host)
bool XrayConfigModel::isValidSni(const QString &sni)
{
const QString t = sni.trimmed();
if (t.isEmpty()) {
return true;
}
if (t.contains(QLatin1Char('*'))) {
if (t.isEmpty() || t.contains(QLatin1Char('*'))) {
return false;
}
return isValidSingleHost(t);
@@ -251,7 +251,6 @@ public:
Q_INVOKABLE static QString mkcpDefaultCwndMultiplier();
Q_INVOKABLE static QString portDefault();
Q_INVOKABLE static QString sniDefault();
Q_INVOKABLE static QString xhttpHostDefault();
Q_INVOKABLE static QString xhttpUplinkChunkSizeDefault();
Q_INVOKABLE static QString scMaxEachPostBytesMinDefault();
@@ -0,0 +1,87 @@
import QtQuick
import QtQuick.Layouts
import MtProxyConfig 1.0
import "../Controls2"
import "../Controls2/TextTypes"
ColumnLayout {
id: root
property string endpoint
property string port
property string endpointPlaceholder
property string portPlaceholder
property bool warningVisible: false
property var scroller: null
signal endpointEdited(string text)
signal portEdited(string text)
spacing: 12
H2TextType {
Layout.fillWidth: true
Layout.topMargin: 28
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Cascade connection")
}
NotificationType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: root.warningVisible
variant: NotificationType.Warning
text: qsTr("Cascade endpoint and port are the address and port of the entry server that forwards traffic to this server. New client configs will connect to them instead of this server's address and port. Existing configs are not changed")
}
TextFieldType {
id: endpointTextField
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Cascade endpoint")
textField.placeholderText: root.endpointPlaceholder
textField.text: root.endpoint
textField.maximumLength: 253
textField.validator: PublicHostInputValidator {}
textField.onTextEdited: root.endpointEdited(textField.text.trim())
textField.onActiveFocusChanged: {
if (textField.activeFocus && root.scroller) {
root.scroller.scrollToItem(endpointTextField)
}
}
}
TextFieldType {
id: portTextField
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Cascade port")
textField.placeholderText: root.portPlaceholder
textField.text: root.port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onTextEdited: root.portEdited(textField.text)
textField.onActiveFocusChanged: {
if (textField.activeFocus && root.scroller) {
root.scroller.scrollToItem(portTextField)
}
}
}
}
@@ -118,6 +118,7 @@ Item {
enabled: d.scrolled > 0
acceptedButtons: Qt.AllButtons
hoverEnabled: true
}
IconButtonType {
-10
View File
@@ -15,16 +15,6 @@ TabButton {
readonly property int maximumHorizontalPadding: root.size === TabButtonType.Large ? 24 : 10
implicitWidth: implicitContentWidth + 2 * root.maximumHorizontalPadding
width: {
if (!root.visible) {
return 0
}
const tabBar = root.TabBar.tabBar
if (!tabBar || tabBar.visibleTabCount === undefined) {
return implicitWidth
}
return tabBar.availableWidth / Math.max(1, tabBar.visibleTabCount)
}
implicitHeight: root.size === TabButtonType.Large ? 48 : 36
horizontalPadding: Math.max(4, Math.min(root.maximumHorizontalPadding, (root.width - implicitContentWidth) / 2))
-11
View File
@@ -4,17 +4,6 @@ import QtQuick.Controls
TabBar {
id: root
readonly property int visibleTabCount: {
let visibleCount = 0
for (let i = 0; i < root.count; ++i) {
const tab = root.itemAt(i)
if (tab && tab.visible) {
++visibleCount
}
}
return visibleCount
}
spacing: 0
background: null
}
@@ -271,6 +271,20 @@ PageType {
}
}
CascadeSettings {
Layout.fillWidth: true
scroller: smartScroll
endpoint: AwgConfigModel.clientCascadeEndpoint
port: AwgConfigModel.clientCascadePort
endpointPlaceholder: AwgConfigModel.clientHostName
portPlaceholder: AwgConfigModel.clientPort
onEndpointEdited: (text) => { AwgConfigModel.clientCascadeEndpoint = text }
onPortEdited: (text) => { AwgConfigModel.clientCascadePort = text }
}
H2TextType {
Layout.fillWidth: true
Layout.topMargin: 28
@@ -464,6 +464,23 @@ PageType {
}
}
}
CascadeSettings {
Layout.fillWidth: true
enabled: root.isEnabled
warningVisible: true
scroller: smartScroll
endpoint: AwgConfigModel.cascadeEndpoint
port: AwgConfigModel.cascadePort
endpointPlaceholder: ServersUiController.serverHostName(ServersUiController.processedServerId)
portPlaceholder: AwgConfigModel.port
onEndpointEdited: (text) => { AwgConfigModel.cascadeEndpoint = text }
onPortEdited: (text) => { AwgConfigModel.cascadePort = text }
}
}
}
@@ -60,6 +60,18 @@ PageType {
checkEmptyText: true
}
CascadeSettings {
Layout.fillWidth: true
endpoint: WireGuardConfigModel.clientCascadeEndpoint
port: WireGuardConfigModel.clientCascadePort
endpointPlaceholder: WireGuardConfigModel.clientHostName
portPlaceholder: WireGuardConfigModel.clientPort
onEndpointEdited: (text) => { WireGuardConfigModel.clientCascadeEndpoint = text }
onPortEdited: (text) => { WireGuardConfigModel.clientCascadePort = text }
}
H2TextType {
Layout.fillWidth: true
Layout.topMargin: 40
@@ -77,6 +77,20 @@ PageType {
checkEmptyText: true
}
CascadeSettings {
Layout.fillWidth: true
warningVisible: true
endpoint: WireGuardConfigModel.cascadeEndpoint
port: WireGuardConfigModel.cascadePort
endpointPlaceholder: ServersUiController.serverHostName(ServersUiController.processedServerId)
portPlaceholder: WireGuardConfigModel.port
onEndpointEdited: (text) => { WireGuardConfigModel.cascadeEndpoint = text }
onPortEdited: (text) => { WireGuardConfigModel.cascadePort = text }
}
}
}
@@ -159,7 +159,6 @@ PageType {
id: sniFieldTls
Layout.fillWidth: true
headerText: qsTr("Server Name (SNI)")
placeholderText: XrayConfigModel.sniDefault()
textField.text: XrayConfigModel.sni
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9._-]*$/ }
textField.onTextEdited: XrayConfigModel.sni = textField.text
@@ -216,7 +215,6 @@ PageType {
id: sniFieldReality
Layout.fillWidth: true
headerText: qsTr("Server Name (SNI)")
placeholderText: XrayConfigModel.sniDefault()
textField.text: XrayConfigModel.sni
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9._-]*$/ }
textField.onTextEdited: XrayConfigModel.sni = textField.text
+28 -18
View File
@@ -21,13 +21,30 @@ PageType {
readonly property int pageSettingsServerServices: 1
readonly property int pageSettingsServerData: 2
readonly property var tabTitles: [qsTr("Protocols"), qsTr("Services"), qsTr("Management")]
readonly property var availableTabs: {
const tabs = []
if (protocolsPage.installedProtocolsCount > 0) {
tabs.push(root.pageSettingsServerProtocols)
}
if (servicesPage.installedServicesCount > 0) {
tabs.push(root.pageSettingsServerServices)
}
tabs.push(root.pageSettingsServerData)
return tabs
}
property int currentTab: (ServersUiController.isServerFromApi(ServersUiController.processedServerId)
&& !ServersUiController.serverHasInstalledContainers(ServersUiController.processedServerId)) ?
root.pageSettingsServerData : root.pageSettingsServerProtocols
property var processedServer
Connections {
target: PageController
function onGoToPageSettingsServerServices() {
tabBar.setCurrentIndex(root.pageSettingsServerServices)
root.currentTab = root.pageSettingsServerServices
}
}
@@ -111,25 +128,18 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
currentIndex: (ServersUiController.isServerFromApi(ServersUiController.processedServerId)
&& !ServersUiController.serverHasInstalledContainers(ServersUiController.processedServerId)) ?
root.pageSettingsServerData : root.pageSettingsServerProtocols
currentIndex: root.availableTabs.indexOf(root.currentTab)
TabButtonType {
id: protocolsTab
visible: protocolsPage.installedProtocolsCount > 0
text: qsTr("Protocols")
}
Repeater {
model: root.availableTabs
TabButtonType {
id: servicesTab
visible: servicesPage.installedServicesCount > 0
text: qsTr("Services")
}
TabButtonType {
required property int modelData
TabButtonType {
id: dataTab
text: qsTr("Management")
text: root.tabTitles[modelData]
onClicked: root.currentTab = modelData
}
}
}
@@ -140,7 +150,7 @@ PageType {
Layout.fillHeight: true
Layout.topMargin: 24
currentIndex: tabBar.currentIndex
currentIndex: root.currentTab
PageSettingsServerProtocols {
id: protocolsPage
+1
View File
@@ -1,6 +1,7 @@
<RCC>
<qresource prefix="/ui/qml">
<file>Components/CopyableValueRow.qml</file>
<file>Components/CascadeSettings.qml</file>
<file>Components/ConfigContentSheet.qml</file>
<file>Components/QrCodeCard.qml</file>
<file>Components/ShareClientInfoSheet.qml</file>
+3
View File
@@ -35,6 +35,7 @@ while [[ $# -gt 0 ]]; do
--aab) : ${BUILD_AAB=true}; shift ;;
--apk) : ${BUILD_APK=true}; shift ;;
--play) : ${BUILD_PLAY=true}; shift ;;
--configure-only) : ${CONFIGURE_ONLY=true}; shift ;;
--help|-h|?)
echo "Usage: $0 [options]"
echo " Options:"
@@ -49,6 +50,7 @@ while [[ $# -gt 0 ]]; do
echo " --aab - whether to build AAB. only applicable to Android"
echo " --apk - whether to build APK. use with --play. only applicable to Android"
echo " --play - build Play flavor (Google Play Billing). use with --aab or --apk. only applicable to Android"
echo " --configure-only - run CMake configure and skip the build"
exit 0
;;
*) echo "Unknown arg \"$1\". Use $0 -h to get help"; exit 1 ;;
@@ -217,6 +219,7 @@ if [[ -n "$FORCE" ]]; then
fi
run_traced cmake -S "$SOURCE_PATH" -B "$BUILD_PATH" "${args[@]}"
[[ -n "$CONFIGURE_ONLY" ]] && exit 0
run_traced cmake --build "$BUILD_PATH" --config "$CMAKE_BUILD_TYPE" --parallel "$JOBS"
if [[ -n "$BUILD_AAB" ]]; then
+42
View File
@@ -0,0 +1,42 @@
def archive_build(app_platform)
build_app(
project: "build/AmneziaVPN.xcodeproj",
scheme: "AmneziaVPN",
configuration: "Release",
export_method: "app-store",
output_directory: "build/#{app_platform}",
output_name: "AmneziaVPN"
)
end
def authorize_app_store_connect
key = ENV.fetch("APPSTORE_CONNECT_PRIVATE_KEY")
app_store_connect_api_key(
key_id: ENV.fetch("APPSTORE_CONNECT_KEY_ID"),
issuer_id: ENV.fetch("APPSTORE_CONNECT_ISSUER_ID"),
key_content: key,
is_key_content_base64: !key.include?("PRIVATE KEY")
)
end
platform :ios do
lane :archive do
archive_build("ios")
end
lane :testflight_upload do
authorize_app_store_connect
upload_to_testflight(ipa: "build/ios/AmneziaVPN.ipa", skip_waiting_for_build_processing: true)
end
end
platform :mac do
lane :archive do
archive_build("osx")
end
lane :testflight_upload do
authorize_app_store_connect
upload_to_testflight(app_platform: "osx", pkg: "build/osx/AmneziaVPN.pkg", skip_waiting_for_build_processing: true)
end
end
+9
View File
@@ -0,0 +1,9 @@
s/\(_ENDPOINT\\\{0,1\}=\\\{0,1\}"\)[^"]*/\1***/g
/-Xcc -DPROD_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }
/-Xcc -DPROD_AGW_PUBLIC_KEY/d
/-Xcc -DDEV_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }
/-Xcc -DDEV_AGW_PUBLIC_KEY/d
/-DPROD_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }
/-DPROD_AGW_PUBLIC_KEY/d
/-DDEV_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }
/-DDEV_AGW_PUBLIC_KEY/d
+2 -2
View File
@@ -9,7 +9,7 @@ import os
class AwgApple(ConanFile):
name = "awg-apple"
version = "3.1.4"
version = "3.1.5"
settings = "os", "arch", "compiler"
@property
@@ -45,7 +45,7 @@ class AwgApple(ConanFile):
def source(self):
get(self, f"https://github.com/amnezia-vpn/amneziawg-apple/archive/refs/tags/v{self.version}.zip",
sha256="09d7b760d18232fdf121ed2286b2f171b501dc31137e5e7d557c1ee3a99ef772", strip_root=True
sha256="b3bd3680fa966dae67162c261e1b0a42235a0bd706ec6ea67cc04e707ef3b7a8", strip_root=True
)
def generate(self):