Merge branch 'dev' into update_server_scripts
This commit is contained in:
commit
26218b22ee
42 changed files with 2065 additions and 1782 deletions
9
.github/workflows/deploy.yml
vendored
9
.github/workflows/deploy.yml
vendored
|
|
@ -227,7 +227,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
|
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
|
||||||
QT_VERSION: 6.4.3
|
QT_VERSION: 6.4.3
|
||||||
QIF_VERSION: 4.7
|
QIF_VERSION: 4.6
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
|
|
@ -245,10 +245,15 @@ jobs:
|
||||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
setup-python: 'true'
|
setup-python: 'true'
|
||||||
tools: 'tools_ifw'
|
|
||||||
set-env: 'true'
|
set-env: 'true'
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
|
- name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}'
|
||||||
|
run: |
|
||||||
|
mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework
|
||||||
|
wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip
|
||||||
|
unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/
|
||||||
|
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
74
README.md
74
README.md
|
|
@ -7,13 +7,15 @@
|
||||||
Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Very easy to use - enter your ip address, ssh login and password, and Amnezia will automatically install VPN docker containers to your server and connect to VPN.
|
|
||||||
- OpenVPN, ShadowSocks, WireGuard, IKEv2 protocols support.
|
- Very easy to use - enter your IP address, SSH login, and password, and Amnezia will automatically install VPN docker containers to your server and connect to the VPN.
|
||||||
|
- OpenVPN, ShadowSocks, WireGuard, and IKEv2 protocols support.
|
||||||
- Masking VPN with OpenVPN over Cloak plugin
|
- Masking VPN with OpenVPN over Cloak plugin
|
||||||
- Split tunneling support - add any sites to client to enable VPN only for them (only for desktops)
|
- Split tunneling support - add any sites to the client to enable VPN only for them (only for desktops)
|
||||||
- Windows, MacOS, Linux, Android, iOS releases.
|
- Windows, MacOS, Linux, Android, iOS releases.
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
[https://amnezia.org](https://amnezia.org) - project website
|
[https://amnezia.org](https://amnezia.org) - project website
|
||||||
[https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
[https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||||
[https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
[https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
||||||
|
|
@ -21,13 +23,13 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
|
||||||
|
|
||||||
## Tech
|
## Tech
|
||||||
|
|
||||||
AmneziaVPN uses a number of open source projects to work:
|
AmneziaVPN uses several open-source projects to work:
|
||||||
|
|
||||||
- [OpenSSL](https://www.openssl.org/)
|
- [OpenSSL](https://www.openssl.org/)
|
||||||
- [OpenVPN](https://openvpn.net/)
|
- [OpenVPN](https://openvpn.net/)
|
||||||
- [ShadowSocks](https://shadowsocks.org/)
|
- [ShadowSocks](https://shadowsocks.org/)
|
||||||
- [Qt](https://www.qt.io/)
|
- [Qt](https://www.qt.io/)
|
||||||
- [LibSsh](https://libssh.org) - forked form Qt Creator
|
- [LibSsh](https://libssh.org) - forked from Qt Creator
|
||||||
- and more...
|
- and more...
|
||||||
|
|
||||||
## Checking out the source code
|
## Checking out the source code
|
||||||
|
|
@ -43,14 +45,15 @@ git submodule update --init --recursive
|
||||||
Want to contribute? Welcome!
|
Want to contribute? Welcome!
|
||||||
|
|
||||||
### Building sources and deployment
|
### Building sources and deployment
|
||||||
Look deploy folder for build scripts.
|
|
||||||
|
|
||||||
### How to build iOS app from source code on MacOS
|
Check deploy folder for build scripts.
|
||||||
|
|
||||||
|
### How to build an iOS app from source code on MacOS
|
||||||
|
|
||||||
1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher.
|
1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher.
|
||||||
|
|
||||||
2. We use QT to generate the XCode project. we need QT version 6.6.1. Install QT for macos in [here](https://doc.qt.io/qt-6/macos.html) or [QT Online Installer](https://www.qt.io/download-open-source). Required modules:
|
2. We use QT to generate the XCode project. We need QT version 6.6.1. Install QT for MacOS [here](https://doc.qt.io/qt-6/macos.html) or [QT Online Installer](https://www.qt.io/download-open-source). Required modules:
|
||||||
- macOS
|
- MacOS
|
||||||
- iOS
|
- iOS
|
||||||
- Qt 5 Compatibility Module
|
- Qt 5 Compatibility Module
|
||||||
- Qt Shader Tools
|
- Qt Shader Tools
|
||||||
|
|
@ -59,18 +62,18 @@ Look deploy folder for build scripts.
|
||||||
- Qt Multimedia
|
- Qt Multimedia
|
||||||
- Qt Remote Objects
|
- Qt Remote Objects
|
||||||
|
|
||||||
3. Install cmake is require. We recommend cmake version 3.25. You can install cmake in [here](https://cmake.org/download/)
|
3. Install CMake if required. We recommend CMake version 3.25. You can install CMake [here](https://cmake.org/download/)
|
||||||
|
|
||||||
4. You also need to install go >= v1.16. If you don't have it done already,
|
4. You also need to install go >= v1.16. If you don't have it installed already,
|
||||||
download go from the [official website](https://golang.org/dl/) or use Homebrew.
|
download go from the [official website](https://golang.org/dl/) or use Homebrew.
|
||||||
Latest version is recommended. Install gomobile
|
The latest version is recommended. Install gomobile
|
||||||
```bash
|
```bash
|
||||||
export PATH=$PATH:~/go/bin
|
export PATH=$PATH:~/go/bin
|
||||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
gomobile init
|
gomobile init
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Build project
|
5. Build the project
|
||||||
```bash
|
```bash
|
||||||
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
|
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
|
||||||
export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
|
export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
|
||||||
|
|
@ -88,62 +91,63 @@ of the bin folder where gomobile was installed. Usually, it's in `GOPATH`.
|
||||||
export PATH=$(PATH):/path/to/GOPATH/bin
|
export PATH=$(PATH):/path/to/GOPATH/bin
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Open XCode project. You can then run/test/archive/ship the app.
|
6. Open the XCode project. You can then run /test/archive/ship the app.
|
||||||
|
|
||||||
If build fails with the following error
|
If the build fails with the following error
|
||||||
```
|
```
|
||||||
make: ***
|
make: ***
|
||||||
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
|
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
|
||||||
Error 1
|
Error 1
|
||||||
```
|
```
|
||||||
Add a user defined variable to both AmneziaVPN and WireGuardNetworkExtension targets' build settings with
|
Add a user-defined variable to both AmneziaVPN and WireGuardNetworkExtension targets' build settings with
|
||||||
key `PATH` and value `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
|
key `PATH` and value `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
|
||||||
|
|
||||||
if above error still persists on you M1 Mac, then most probably you need to install arch based cmake
|
if the above error persists on your M1 Mac, then most probably you need to install arch based CMake
|
||||||
```
|
```
|
||||||
arch -arm64 brew install cmake
|
arch -arm64 brew install cmake
|
||||||
```
|
```
|
||||||
|
|
||||||
Build might fail with "source files not found" error the first time you try it, because modern XCode build system compiles
|
Build might fail with the "source files not found" error the first time you try it, because the modern XCode build system compiles dependencies in parallel, and some dependencies end up being built after the ones that
|
||||||
dependencies in parallel, and some dependencies end up being built after the ones that
|
require them. In this case, simply restart the build.
|
||||||
require them. In this case simply restart the build.
|
|
||||||
|
|
||||||
## How to build the Android app
|
## How to build the Android app
|
||||||
_tested on Mac OS_
|
|
||||||
|
_Tested on Mac OS_
|
||||||
|
|
||||||
The Android app has the following requirements:
|
The Android app has the following requirements:
|
||||||
* JDK 11
|
* JDK 11
|
||||||
* Android platform SDK 33
|
* Android platform SDK 33
|
||||||
* cmake 3.25.0
|
* CMake 3.25.0
|
||||||
|
|
||||||
After you have installed QT, QT Creator and Android Studio installed, you need to configure QT Creator correctly. Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`.
|
After you have installed QT, QT Creator, and Android Studio, you need to configure QT Creator correctly. Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`.
|
||||||
* set path to jdk 11
|
* set path to JDK 11
|
||||||
* set path to Android SDK ($ANDROID_HOME)
|
* set path to Android SDK ($ANDROID_HOME)
|
||||||
|
|
||||||
In case you get errors regarding missing SDK or 'sdkmanager not running', you cannot fix them by correcting the paths and you have some spare GBs on your disk, you can let QT Creator install all requirements by choosing an empty folder for `Android SDK location` and click on `Set Up SDK`. Be aware: This will install a second Android SDK and NDK on your machine!
|
In case you get errors regarding missing SDK or 'SDK manager not running', you cannot fix them by correcting the paths. If you have some spare GBs on your disk, you can let QT Creator install all requirements by choosing an empty folder for `Android SDK location` and clicking on `Set Up SDK`. Be aware: This will install a second Android SDK and NDK on your machine!
|
||||||
|
Double-check that the right CMake version is configured: Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab, you'll find an entry for `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at <path>` from the drop-down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case, click in the preferences window on the side menu item `CMake`, then on the tab `Tools` in the center content view, and finally on the button `Add` to set the path to your installed CMake.
|
||||||
|
Please make sure that you have selected Android Platform SDK 33 for your project: click in the main view's side menu on `Projects`, and on the left, you'll see a section `Build & Run` showing different Android build targets. You can select any of them, Amnezia VPN's project setup is designed in a way that all Android targets will be built. Click on the targets submenu item `Build` and scroll in the center content view to `Build Steps`. Click on `Details` at the end of the headline `Build Android APK` (the `Details` button might be hidden in case the QT Creator Window is not running in full screen!). Here we are: Choose `android-33` as `Android Build Platform SDK`.
|
||||||
|
|
||||||
Double check that the right cmake version is configured: Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab you'll find an entry `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at <path>` from the drop down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case click in the preferences window on the side menu item `CMake`, then on the tab `Tools`in the center content view and finally on the Button `Add` to set the path to your installed CMake.
|
That's it! You should be ready to compile the project from QT Creator!
|
||||||
|
|
||||||
Please make sure that you have selected Android Platform SDK 33 for your project: click in the main view's side menu on on `Projects`, on the left you'll see a section `Build & Run` showing different Android build targets. You can select any of them, Amnezia VPN's project setup is designed in a way that always all Android targets will be build. Click on the targets submenu item `Build` and scroll in the center content view to `Build Steps`. Click on `Details` at the end of the headline `Build Android APK` (The `Details` button might be hidden in case QT Creator Window is not running in full screen!). Here we are: choose `android-33` as `Android Build platform SDK`.
|
|
||||||
|
|
||||||
That's it you should be ready to compile the project from QT Creator!
|
|
||||||
|
|
||||||
### Development flow
|
### Development flow
|
||||||
After you've hit the build button, QT-Creator copies the whole project to a folder in the repositories parent directory. The folder should look something like `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`.
|
|
||||||
If you want to develop Amnezia VPNs Android components written in Kotlin, such as components using system APIs, you need to import the generated project in Android Studio with `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` as the projects root directory. While you should be able to compile the generated project from Android Studio, you cannot work directly in the repository's Android project. So whenever you are confident with your work in the generated project, you'll need to copy and paste the affected files to the corresponding path in the repositories Android project so that you can add and commit your changes!
|
|
||||||
|
|
||||||
You may face compiling issues in QT Creator after you've worked in Android Studio on the generated project. Just do a `./gradlew clean` in the generated project's root directory (`<path>/client/android-build/.`) and you should be good to continue.
|
After you've hit the build button, QT-Creator copies the whole project to a folder in the repository parent directory. The folder should look something like `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`.
|
||||||
|
If you want to develop Amnezia VPNs Android components written in Kotlin, such as components using system APIs, you need to import the generated project in Android Studio with `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` as the projects root directory. While you should be able to compile the generated project from Android Studio, you cannot work directly in the repository's Android project. So whenever you are confident with your work in the generated project, you'll need to copy and paste the affected files to the corresponding path in the repository's Android project so that you can add and commit your changes!
|
||||||
|
|
||||||
|
You may face compiling issues in QT Creator after you've worked in Android Studio on the generated project. Just do a `./gradlew clean` in the generated project's root directory (`<path>/client/android-build/.`) and you should be good to go.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
GPL v.3
|
|
||||||
|
GPL v3.0
|
||||||
|
|
||||||
## Donate
|
## Donate
|
||||||
|
|
||||||
Bitcoin: bc1qn9rhsffuxwnhcuuu4qzrwp4upkrq94xnh8r26u
|
Bitcoin: bc1qn9rhsffuxwnhcuuu4qzrwp4upkrq94xnh8r26u
|
||||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
|
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
|
||||||
payeer.com: P2561305
|
payeer.com: P2561305
|
||||||
ko-fi.com: [https://ko-fi.com/amnezia_vpn](https://ko-fi.com/amnezia_vpn)
|
ko-fi.com: [https://ko-fi.com/amnezia_vpn](https://ko-fi.com/amnezia_vpn)
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
## etc
|
|
||||||
This project is tested with BrowserStack.
|
This project is tested with BrowserStack.
|
||||||
We express our gratitude to [BrowserStack](https://www.browserstack.com) for supporting our project.
|
We express our gratitude to [BrowserStack](https://www.browserstack.com) for supporting our project.
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,6 @@ int main(int argc, char *argv[])
|
||||||
AllowSetForegroundWindow(0);
|
AllowSetForegroundWindow(0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_IOS)
|
|
||||||
QtAppDelegateInitialize();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
app.registerTypes();
|
app.registerTypes();
|
||||||
|
|
||||||
app.setApplicationName(APPLICATION_NAME);
|
app.setApplicationName(APPLICATION_NAME);
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||||
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
|
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
|
||||||
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
|
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
|
||||||
// todo review wg ipv6
|
// todo review wg ipv6
|
||||||
#ifndef Q_OS_WINDOWS
|
#ifdef Q_OS_MACOS
|
||||||
json.insert("deviceIpv6Address", "dead::1");
|
json.insert("deviceIpv6Address", "dead::1");
|
||||||
#endif
|
#endif
|
||||||
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
|
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
|
||||||
|
|
|
||||||
|
|
@ -18,40 +18,32 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||||
// send empty string to NEDNSSettings.matchDomains
|
// send empty string to NEDNSSettings.matchDomains
|
||||||
networkSettings?.dnsSettings?.matchDomains = [""]
|
networkSettings?.dnsSettings?.matchDomains = [""]
|
||||||
|
|
||||||
if splitTunnelType == "1" {
|
if splitTunnelType == 1 {
|
||||||
var ipv4IncludedRoutes = [NEIPv4Route]()
|
var ipv4IncludedRoutes = [NEIPv4Route]()
|
||||||
let STSdata = Data(splitTunnelSites!.utf8)
|
|
||||||
do {
|
for allowedIPString in splitTunnelSites {
|
||||||
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
|
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
||||||
for allowedIPString in STSArray {
|
ipv4IncludedRoutes.append(NEIPv4Route(
|
||||||
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
destinationAddress: "\(allowedIP.address)",
|
||||||
ipv4IncludedRoutes.append(NEIPv4Route(
|
subnetMask: "\(allowedIP.subnetMask())"))
|
||||||
destinationAddress: "\(allowedIP.address)",
|
|
||||||
subnetMask: "\(allowedIP.subnetMask())"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
wg_log(.error, message: "Parse JSONSerialization Error")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
|
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
|
||||||
} else {
|
} else {
|
||||||
if splitTunnelType == "2" {
|
if splitTunnelType == 2 {
|
||||||
var ipv4ExcludedRoutes = [NEIPv4Route]()
|
var ipv4ExcludedRoutes = [NEIPv4Route]()
|
||||||
var ipv4IncludedRoutes = [NEIPv4Route]()
|
var ipv4IncludedRoutes = [NEIPv4Route]()
|
||||||
var ipv6IncludedRoutes = [NEIPv6Route]()
|
var ipv6IncludedRoutes = [NEIPv6Route]()
|
||||||
let STSdata = Data(splitTunnelSites!.utf8)
|
|
||||||
do {
|
for excludeIPString in splitTunnelSites {
|
||||||
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
|
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
||||||
for excludeIPString in STSArray {
|
ipv4ExcludedRoutes.append(NEIPv4Route(
|
||||||
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
destinationAddress: "\(excludeIP.address)",
|
||||||
ipv4ExcludedRoutes.append(NEIPv4Route(
|
subnetMask: "\(excludeIP.subnetMask())"))
|
||||||
destinationAddress: "\(excludeIP.address)",
|
|
||||||
subnetMask: "\(excludeIP.subnetMask())"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
wg_log(.error, message: "Parse JSONSerialization Error")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let allIPv4 = IPAddressRange(from: "0.0.0.0/0") {
|
if let allIPv4 = IPAddressRange(from: "0.0.0.0/0") {
|
||||||
ipv4IncludedRoutes.append(NEIPv4Route(
|
ipv4IncludedRoutes.append(NEIPv4Route(
|
||||||
destinationAddress: "\(allIPv4.address)",
|
destinationAddress: "\(allIPv4.address)",
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
|
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
|
||||||
|
|
||||||
private var openVPNConfig: Data?
|
private var openVPNConfig: Data?
|
||||||
var splitTunnelType: String?
|
var splitTunnelType: Int!
|
||||||
var splitTunnelSites: String?
|
var splitTunnelSites: [String]!
|
||||||
|
|
||||||
let vpnReachability = OpenVPNReachability()
|
let vpnReachability = OpenVPNReachability()
|
||||||
|
|
||||||
|
|
@ -81,22 +81,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
if action == Constants.kActionStatus {
|
if action == Constants.kActionStatus {
|
||||||
handleStatusAppMessage(messageData, completionHandler: completionHandler)
|
handleStatusAppMessage(messageData, completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == Constants.kActionStart {
|
|
||||||
splitTunnelType = message[Constants.kMessageKeySplitTunnelType] as? String
|
|
||||||
splitTunnelSites = message[Constants.kMessageKeySplitTunnelSites] as? String
|
|
||||||
}
|
|
||||||
|
|
||||||
let callbackWrapper: (NSNumber?) -> Void = { errorCode in
|
|
||||||
// let tunnelId = self.tunnelConfig?.id ?? ""
|
|
||||||
let response: [String: Any] = [
|
|
||||||
Constants.kMessageKeyAction: action,
|
|
||||||
Constants.kMessageKeyErrorCode: errorCode ?? NSNull(),
|
|
||||||
Constants.kMessageKeyTunnelId: 0
|
|
||||||
]
|
|
||||||
|
|
||||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
||||||
|
|
@ -169,110 +153,118 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
completionHandler: @escaping (Error?) -> Void) {
|
completionHandler: @escaping (Error?) -> Void) {
|
||||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||||
let wgConfig: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
let wgConfigData: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
||||||
wg_log(.error, message: "Can't start WireGuard config missing")
|
wg_log(.error, message: "Can't start WireGuard config missing")
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let wgConfigStr = try? JSONDecoder().decode(WGConfig.self, from: wgConfig).str,
|
do {
|
||||||
let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
|
let wgConfig = try JSONDecoder().decode(WGConfig.self, from: wgConfigData)
|
||||||
else {
|
let wgConfigStr = wgConfig.str
|
||||||
wg_log(.error, message: "Can't parse WireGuard config")
|
log(.info, message: "wgConfig: \(wgConfig.redux.replacingOccurrences(of: "\n", with: " "))")
|
||||||
completionHandler(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log(.info, message: "wgConfig: \(wgConfigStr.replacingOccurrences(of: "\n", with: " "))")
|
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
|
||||||
|
|
||||||
if tunnelConfiguration.peers.first!.allowedIPs
|
if tunnelConfiguration.peers.first!.allowedIPs
|
||||||
.map({ $0.stringRepresentation })
|
.map({ $0.stringRepresentation })
|
||||||
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
|
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
|
||||||
if splitTunnelType == "1" {
|
if wgConfig.splitTunnelType == 1 {
|
||||||
for index in tunnelConfiguration.peers.indices {
|
for index in tunnelConfiguration.peers.indices {
|
||||||
tunnelConfiguration.peers[index].allowedIPs.removeAll()
|
tunnelConfiguration.peers[index].allowedIPs.removeAll()
|
||||||
var allowedIPs = [IPAddressRange]()
|
var allowedIPs = [IPAddressRange]()
|
||||||
let STSdata = Data(splitTunnelSites!.utf8)
|
|
||||||
do {
|
for allowedIPString in wgConfig.splitTunnelSites {
|
||||||
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
|
|
||||||
for allowedIPString in STSArray {
|
|
||||||
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
||||||
allowedIPs.append(allowedIP)
|
allowedIPs.append(allowedIP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
wg_log(.error, message: "Parse JSONSerialization Error")
|
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
|
||||||
}
|
}
|
||||||
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
|
} else if wgConfig.splitTunnelType == 2 {
|
||||||
}
|
for index in tunnelConfiguration.peers.indices {
|
||||||
} else if splitTunnelType == "2" {
|
var excludeIPs = [IPAddressRange]()
|
||||||
for index in tunnelConfiguration.peers.indices {
|
|
||||||
var excludeIPs = [IPAddressRange]()
|
for excludeIPString in wgConfig.splitTunnelSites {
|
||||||
let STSdata = Data(splitTunnelSites!.utf8)
|
|
||||||
do {
|
|
||||||
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
|
|
||||||
for excludeIPString in STSArray {
|
|
||||||
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
||||||
excludeIPs.append(excludeIP)
|
excludeIPs.append(excludeIP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
wg_log(.error, message: "Parse JSONSerialization Error")
|
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
|
||||||
}
|
}
|
||||||
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
wg_log(.info, message: "Starting wireguard tunnel from the " +
|
wg_log(.info, message: "Starting wireguard tunnel from the " +
|
||||||
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
||||||
|
|
||||||
// Start the tunnel
|
// Start the tunnel
|
||||||
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
||||||
guard let adapterError else {
|
guard let adapterError else {
|
||||||
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
|
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
|
||||||
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch adapterError {
|
switch adapterError {
|
||||||
case .cannotLocateTunnelFileDescriptor:
|
case .cannotLocateTunnelFileDescriptor:
|
||||||
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
|
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
|
||||||
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||||
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||||
case .dnsResolution(let dnsErrors):
|
case .dnsResolution(let dnsErrors):
|
||||||
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
|
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
|
||||||
.joined(separator: ", ")
|
.joined(separator: ", ")
|
||||||
wg_log(.error, message:
|
wg_log(.error, message:
|
||||||
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
|
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
|
||||||
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
|
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
|
||||||
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
|
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
|
||||||
case .setNetworkSettings(let error):
|
case .setNetworkSettings(let error):
|
||||||
wg_log(.error, message:
|
wg_log(.error, message:
|
||||||
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
|
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
|
||||||
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||||
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||||
case .startWireGuardBackend(let errorCode):
|
case .startWireGuardBackend(let errorCode):
|
||||||
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
|
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
|
||||||
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
|
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
|
||||||
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
|
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
|
||||||
case .invalidState:
|
case .invalidState:
|
||||||
fatalError()
|
fatalError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
|
||||||
|
completionHandler(nil)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
|
private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
|
||||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||||
let ovpnConfiguration: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
|
let openVPNConfigData = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
|
||||||
|
|
||||||
wg_log(.error, message: "Can't start startOpenVPN()")
|
wg_log(.error, message: "Can't start startOpenVPN()")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
do {
|
||||||
|
log(.info, message: "providerConfiguration: \(String(decoding: openVPNConfigData, as: UTF8.self).replacingOccurrences(of: "\n", with: " "))")
|
||||||
|
|
||||||
|
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
|
||||||
|
log(.info, message: "openVPNConfig: \(openVPNConfig.str.replacingOccurrences(of: "\n", with: " "))")
|
||||||
|
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
||||||
|
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||||
|
} catch {
|
||||||
|
log(.error, message: "Can't parse OpenVPN config: \(error.localizedDescription)")
|
||||||
|
|
||||||
|
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
|
||||||
|
log(.error, message: "Can't parse OpenVPN config: \(underlyingError.localizedDescription)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
@interface QtAppDelegate : UIResponder <UIApplicationDelegate>
|
@interface QIOSApplicationDelegate
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface QIOSApplicationDelegate (AmneziaVPNDelegate)
|
||||||
@end
|
@end
|
||||||
|
|
|
||||||
|
|
@ -3,25 +3,15 @@
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
||||||
@implementation QtAppDelegate {
|
UIView *_screen;
|
||||||
UIView *_screen;
|
|
||||||
}
|
|
||||||
|
|
||||||
+(QtAppDelegate *)sharedQtAppDelegate {
|
|
||||||
static dispatch_once_t pred;
|
|
||||||
static QtAppDelegate *shared = nil;
|
|
||||||
dispatch_once(&pred, ^{
|
|
||||||
shared = [[super alloc] init];
|
|
||||||
});
|
|
||||||
return shared;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
|
||||||
|
|
||||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||||
{
|
{
|
||||||
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
|
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
|
||||||
// Override point for customization after application launch.
|
// Override point for customization after application launch.
|
||||||
NSLog(@"Did this launch option happen");
|
NSLog(@"Application didFinishLaunchingWithOptions");
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,31 +60,27 @@
|
||||||
- (BOOL)application:(UIApplication *)app
|
- (BOOL)application:(UIApplication *)app
|
||||||
openURL:(NSURL *)url
|
openURL:(NSURL *)url
|
||||||
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
|
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
|
||||||
|
|
||||||
NSLog(@"Application openURL: %@", url);
|
|
||||||
if (url.fileURL) {
|
if (url.fileURL) {
|
||||||
QString filePath(url.path.UTF8String);
|
QString filePath(url.path.UTF8String);
|
||||||
if (filePath.isEmpty()) return NO;
|
if (filePath.isEmpty()) return NO;
|
||||||
|
|
||||||
if (filePath.contains("backup")) {
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||||
IosController::Instance()->importBackupFromOutside(filePath);
|
NSLog(@"Application openURL: %@", url);
|
||||||
} else {
|
|
||||||
QFile file(filePath);
|
if (filePath.contains("backup")) {
|
||||||
bool isOpenFile = file.open(QIODevice::ReadOnly);
|
IosController::Instance()->importBackupFromOutside(filePath);
|
||||||
QByteArray data = file.readAll();
|
} else {
|
||||||
|
QFile file(filePath);
|
||||||
IosController::Instance()->importConfigFromOutside(QString(data));
|
bool isOpenFile = file.open(QIODevice::ReadOnly);
|
||||||
}
|
QByteArray data = file.readAll();
|
||||||
|
|
||||||
|
IosController::Instance()->importConfigFromOutside(QString(data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void QtAppDelegateInitialize()
|
|
||||||
{
|
|
||||||
[[UIApplication sharedApplication] setDelegate: [QtAppDelegate sharedQtAppDelegate]];
|
|
||||||
NSLog(@"Created a new AppDelegate");
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,41 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct WGConfigData: Decodable {
|
struct WGConfig: Decodable {
|
||||||
let initPacketMagicHeader, responsePacketMagicHeader: String?
|
let initPacketMagicHeader, responsePacketMagicHeader: String?
|
||||||
let underloadPacketMagicHeader, transportPacketMagicHeader: String?
|
let underloadPacketMagicHeader, transportPacketMagicHeader: String?
|
||||||
let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String?
|
let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String?
|
||||||
let initPacketJunkSize, responsePacketJunkSize: String?
|
let initPacketJunkSize, responsePacketJunkSize: String?
|
||||||
|
let dns1: String
|
||||||
|
let dns2: String
|
||||||
|
let hostName: String
|
||||||
|
let port: Int
|
||||||
|
let clientIP: String
|
||||||
|
let clientPrivateKey: String
|
||||||
|
let serverPublicKey: String
|
||||||
|
let presharedKey: String
|
||||||
|
var allowedIPs: [String]
|
||||||
|
var persistentKeepAlive: String
|
||||||
|
let splitTunnelType: Int
|
||||||
|
let splitTunnelSites: [String]
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case initPacketMagicHeader = "H1", responsePacketMagicHeader = "H2"
|
||||||
|
case underloadPacketMagicHeader = "H3", transportPacketMagicHeader = "H4"
|
||||||
|
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
|
||||||
|
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2"
|
||||||
|
case dns1
|
||||||
|
case dns2
|
||||||
|
case hostName
|
||||||
|
case port
|
||||||
|
case clientIP = "client_ip"
|
||||||
|
case clientPrivateKey = "client_priv_key"
|
||||||
|
case serverPublicKey = "server_pub_key"
|
||||||
|
case presharedKey = "psk_key"
|
||||||
|
case allowedIPs = "allowed_ips"
|
||||||
|
case persistentKeepAlive = "persistent_keep_alive"
|
||||||
|
case splitTunnelType
|
||||||
|
case splitTunnelSites
|
||||||
|
}
|
||||||
|
|
||||||
var settings: String {
|
var settings: String {
|
||||||
junkPacketCount == nil ? "" :
|
junkPacketCount == nil ? "" :
|
||||||
|
|
@ -22,114 +53,45 @@ struct WGConfigData: Decodable {
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
let clientIP: String
|
|
||||||
let clientPrivateKey: String
|
|
||||||
let clientPublicKey: String
|
|
||||||
let serverPublicKey: String
|
|
||||||
let presharedKey: String
|
|
||||||
let hostName: String
|
|
||||||
let port: Int
|
|
||||||
|
|
||||||
var allowedIPs: [String]
|
|
||||||
var persistentKeepAlive: String
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case initPacketMagicHeader = "H1", responsePacketMagicHeader = "H2"
|
|
||||||
case underloadPacketMagicHeader = "H3", transportPacketMagicHeader = "H4"
|
|
||||||
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
|
|
||||||
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2"
|
|
||||||
|
|
||||||
case clientIP = "client_ip" // "10.8.1.16"
|
|
||||||
case clientPrivateKey = "client_priv_key"
|
|
||||||
case clientPublicKey = "client_pub_key"
|
|
||||||
case serverPublicKey = "server_pub_key"
|
|
||||||
case presharedKey = "psk_key"
|
|
||||||
|
|
||||||
case allowedIPs = "allowed_ips"
|
|
||||||
case persistentKeepAlive = "persistent_keep_alive"
|
|
||||||
case hostName
|
|
||||||
case port
|
|
||||||
}
|
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
||||||
self.initPacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .initPacketMagicHeader)
|
|
||||||
self.responsePacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .responsePacketMagicHeader)
|
|
||||||
self.underloadPacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .underloadPacketMagicHeader)
|
|
||||||
self.transportPacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .transportPacketMagicHeader)
|
|
||||||
self.junkPacketCount = try container.decodeIfPresent(String.self, forKey: .junkPacketCount)
|
|
||||||
self.junkPacketMinSize = try container.decodeIfPresent(String.self, forKey: .junkPacketMinSize)
|
|
||||||
self.junkPacketMaxSize = try container.decodeIfPresent(String.self, forKey: .junkPacketMaxSize)
|
|
||||||
self.initPacketJunkSize = try container.decodeIfPresent(String.self, forKey: .initPacketJunkSize)
|
|
||||||
self.responsePacketJunkSize = try container.decodeIfPresent(String.self, forKey: .responsePacketJunkSize)
|
|
||||||
self.clientIP = try container.decode(String.self, forKey: .clientIP)
|
|
||||||
self.clientPrivateKey = try container.decode(String.self, forKey: .clientPrivateKey)
|
|
||||||
self.clientPublicKey = try container.decode(String.self, forKey: .clientPublicKey)
|
|
||||||
self.serverPublicKey = try container.decode(String.self, forKey: .serverPublicKey)
|
|
||||||
self.presharedKey = try container.decode(String.self, forKey: .presharedKey)
|
|
||||||
self.allowedIPs = try container.decodeIfPresent([String].self, forKey: .allowedIPs) ?? ["0.0.0.0/0", "::/0"]
|
|
||||||
self.persistentKeepAlive = try container.decodeIfPresent(String.self, forKey: .persistentKeepAlive) ?? "25"
|
|
||||||
self.hostName = try container.decode(String.self, forKey: .hostName)
|
|
||||||
self.port = try container.decode(Int.self, forKey: .port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WGConfig: Decodable {
|
|
||||||
let data: WGConfigData
|
|
||||||
let configVersion: Int
|
|
||||||
let description: String
|
|
||||||
let dns1: String
|
|
||||||
let dns2: String
|
|
||||||
let hostName: String
|
|
||||||
let `protocol`: String
|
|
||||||
let splitTunnelSites: [String]
|
|
||||||
let splitTunnelType: Int
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case awgConfigData = "awg_config_data", wgConfigData = "wireguard_config_data"
|
|
||||||
case configData
|
|
||||||
case configVersion = "config_version"
|
|
||||||
case description
|
|
||||||
case dns1
|
|
||||||
case dns2
|
|
||||||
case hostName
|
|
||||||
case `protocol`
|
|
||||||
case splitTunnelSites
|
|
||||||
case splitTunnelType
|
|
||||||
}
|
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
||||||
|
|
||||||
if container.contains(.awgConfigData) {
|
|
||||||
self.data = try container.decode(WGConfigData.self, forKey: .awgConfigData)
|
|
||||||
} else {
|
|
||||||
self.data = try container.decode(WGConfigData.self, forKey: .wgConfigData)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.configVersion = try container.decode(Int.self, forKey: .configVersion)
|
|
||||||
self.description = try container.decode(String.self, forKey: .description)
|
|
||||||
self.dns1 = try container.decode(String.self, forKey: .dns1)
|
|
||||||
self.dns2 = try container.decode(String.self, forKey: .dns2)
|
|
||||||
self.hostName = try container.decode(String.self, forKey: .hostName)
|
|
||||||
self.protocol = try container.decode(String.self, forKey: .protocol)
|
|
||||||
self.splitTunnelSites = try container.decode([String].self, forKey: .splitTunnelSites)
|
|
||||||
self.splitTunnelType = try container.decode(Int.self, forKey: .splitTunnelType)
|
|
||||||
}
|
|
||||||
|
|
||||||
var str: String {
|
var str: String {
|
||||||
"""
|
"""
|
||||||
[Interface]
|
[Interface]
|
||||||
Address = \(data.clientIP)/32
|
Address = \(clientIP)
|
||||||
DNS = \(dns1), \(dns2)
|
DNS = \(dns1), \(dns2)
|
||||||
PrivateKey = \(data.clientPrivateKey)
|
PrivateKey = \(clientPrivateKey)
|
||||||
\(data.settings)
|
\(settings)
|
||||||
[Peer]
|
[Peer]
|
||||||
PublicKey = \(data.serverPublicKey)
|
PublicKey = \(serverPublicKey)
|
||||||
PresharedKey = \(data.presharedKey)
|
PresharedKey = \(presharedKey)
|
||||||
AllowedIPs = \(data.allowedIPs.joined(separator: ", "))
|
AllowedIPs = \(allowedIPs.joined(separator: ", "))
|
||||||
Endpoint = \(data.hostName):\(data.port)
|
Endpoint = \(hostName):\(port)
|
||||||
PersistentKeepalive = \(data.persistentKeepAlive)
|
PersistentKeepalive = \(persistentKeepAlive)
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
var redux: String {
|
||||||
|
"""
|
||||||
|
[Interface]
|
||||||
|
Address = \(clientIP)
|
||||||
|
DNS = \(dns1), \(dns2)
|
||||||
|
PrivateKey = ***
|
||||||
|
\(settings)
|
||||||
|
[Peer]
|
||||||
|
PublicKey = ***
|
||||||
|
PresharedKey = ***
|
||||||
|
AllowedIPs = \(allowedIPs.joined(separator: ", "))
|
||||||
|
Endpoint = \(hostName):\(port)
|
||||||
|
PersistentKeepalive = \(persistentKeepAlive)
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct OpenVPNConfig: Decodable {
|
||||||
|
let config: String
|
||||||
|
let splitTunnelType: Int
|
||||||
|
let splitTunnelSites: [String]
|
||||||
|
|
||||||
|
var str: String {
|
||||||
|
"splitTunnelType: \(splitTunnelType) splitTunnelSites: \(splitTunnelSites) config: \(config)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,6 @@ void IosController::checkStatus()
|
||||||
m_rxBytes = rxBytes;
|
m_rxBytes = rxBytes;
|
||||||
m_txBytes = txBytes;
|
m_txBytes = txBytes;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IosController::vpnStatusDidChange(void *pNotification)
|
void IosController::vpnStatusDidChange(void *pNotification)
|
||||||
|
|
@ -244,13 +243,13 @@ void IosController::vpnStatusDidChange(void *pNotification)
|
||||||
|
|
||||||
if (session /* && session == TunnelManager.session */ ) {
|
if (session /* && session == TunnelManager.session */ ) {
|
||||||
qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session;
|
qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session;
|
||||||
|
|
||||||
if (session.status == NEVPNStatusDisconnected) {
|
if (session.status == NEVPNStatusDisconnected) {
|
||||||
if (@available(iOS 16.0, *)) {
|
if (@available(iOS 16.0, *)) {
|
||||||
[session fetchLastDisconnectErrorWithCompletionHandler:^(NSError * _Nullable error) {
|
[session fetchLastDisconnectErrorWithCompletionHandler:^(NSError * _Nullable error) {
|
||||||
if (error != nil) {
|
if (error != nil) {
|
||||||
qDebug() << "Disconnect error" << error.domain << error.code << error.localizedDescription;
|
qDebug() << "Disconnect error" << error.domain << error.code << error.localizedDescription;
|
||||||
|
|
||||||
if ([error.domain isEqualToString:NEVPNConnectionErrorDomain]) {
|
if ([error.domain isEqualToString:NEVPNConnectionErrorDomain]) {
|
||||||
switch (error.code) {
|
switch (error.code) {
|
||||||
case NEVPNConnectionErrorOverslept:
|
case NEVPNConnectionErrorOverslept:
|
||||||
|
|
@ -315,11 +314,11 @@ void IosController::vpnStatusDidChange(void *pNotification)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NSError *underlyingError = error.userInfo[@"NSUnderlyingError"];
|
NSError *underlyingError = error.userInfo[@"NSUnderlyingError"];
|
||||||
if (underlyingError != nil) {
|
if (underlyingError != nil) {
|
||||||
qDebug() << "Disconnect underlying error" << underlyingError.domain << underlyingError.code << underlyingError.localizedDescription;
|
qDebug() << "Disconnect underlying error" << underlyingError.domain << underlyingError.code << underlyingError.localizedDescription;
|
||||||
|
|
||||||
if ([underlyingError.domain isEqualToString:@"NEAgentErrorDomain"]) {
|
if ([underlyingError.domain isEqualToString:@"NEAgentErrorDomain"]) {
|
||||||
switch (underlyingError.code) {
|
switch (underlyingError.code) {
|
||||||
case 1:
|
case 1:
|
||||||
|
|
@ -342,7 +341,7 @@ void IosController::vpnStatusDidChange(void *pNotification)
|
||||||
qDebug() << "Disconnect error is unavailable on iOS < 16.0";
|
qDebug() << "Disconnect error is unavailable on iOS < 16.0";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit connectionStateChanged(iosStatusToState(session.status));
|
emit connectionStateChanged(iosStatusToState(session.status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -357,7 +356,22 @@ bool IosController::setupOpenVPN()
|
||||||
QJsonObject ovpn = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::OpenVpn)].toObject();
|
QJsonObject ovpn = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::OpenVpn)].toObject();
|
||||||
QString ovpnConfig = ovpn[config_key::config].toString();
|
QString ovpnConfig = ovpn[config_key::config].toString();
|
||||||
|
|
||||||
return startOpenVPN(ovpnConfig);
|
QJsonObject openVPNConfig {};
|
||||||
|
openVPNConfig.insert(config_key::config, ovpnConfig);
|
||||||
|
openVPNConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
|
||||||
|
|
||||||
|
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
|
||||||
|
|
||||||
|
for(int index = 0; index < splitTunnelSites.count(); index++) {
|
||||||
|
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
openVPNConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
|
||||||
|
|
||||||
|
QJsonDocument openVPNConfigDoc(openVPNConfig);
|
||||||
|
QString openVPNConfigStr(openVPNConfigDoc.toJson(QJsonDocument::Compact));
|
||||||
|
|
||||||
|
return startOpenVPN(openVPNConfigStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IosController::setupCloak()
|
bool IosController::setupCloak()
|
||||||
|
|
@ -394,27 +408,123 @@ bool IosController::setupCloak()
|
||||||
ovpnConfig.append(cloakBase64);
|
ovpnConfig.append(cloakBase64);
|
||||||
ovpnConfig.append("\n</cloak>\n");
|
ovpnConfig.append("\n</cloak>\n");
|
||||||
|
|
||||||
return startOpenVPN(ovpnConfig);
|
QJsonObject openVPNConfig {};
|
||||||
|
openVPNConfig.insert(config_key::config, ovpnConfig);
|
||||||
|
openVPNConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
|
||||||
|
|
||||||
|
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
|
||||||
|
|
||||||
|
for(int index = 0; index < splitTunnelSites.count(); index++) {
|
||||||
|
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
openVPNConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
|
||||||
|
|
||||||
|
QJsonDocument openVPNConfigDoc(openVPNConfig);
|
||||||
|
QString openVPNConfigStr(openVPNConfigDoc.toJson(QJsonDocument::Compact));
|
||||||
|
|
||||||
|
return startOpenVPN(openVPNConfigStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IosController::setupWireGuard()
|
bool IosController::setupWireGuard()
|
||||||
{
|
{
|
||||||
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::WireGuard)].toObject();
|
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::WireGuard)].toObject();
|
||||||
|
|
||||||
QJsonDocument doc(m_rawConfig);
|
|
||||||
QString wgConfig(doc.toJson(QJsonDocument::Compact));
|
|
||||||
|
|
||||||
return startWireGuard(wgConfig);
|
QJsonObject wgConfig {};
|
||||||
|
wgConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]);
|
||||||
|
wgConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]);
|
||||||
|
wgConfig.insert(config_key::hostName, config[config_key::hostName]);
|
||||||
|
wgConfig.insert(config_key::port, config[config_key::port]);
|
||||||
|
wgConfig.insert(config_key::client_ip, config[config_key::client_ip]);
|
||||||
|
wgConfig.insert(config_key::client_priv_key, config[config_key::client_priv_key]);
|
||||||
|
wgConfig.insert(config_key::server_pub_key, config[config_key::server_pub_key]);
|
||||||
|
wgConfig.insert(config_key::psk_key, config[config_key::psk_key]);
|
||||||
|
wgConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
|
||||||
|
|
||||||
|
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
|
||||||
|
|
||||||
|
for(int index = 0; index < splitTunnelSites.count(); index++) {
|
||||||
|
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
wgConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
|
||||||
|
|
||||||
|
if (config.contains(config_key::allowed_ips)) {
|
||||||
|
QJsonArray allowed_ips;
|
||||||
|
QStringList allowed_ips_list = config[config_key::allowed_ips].toString().split(", ");
|
||||||
|
|
||||||
|
for(int index = 0; index < allowed_ips_list.length(); index++) {
|
||||||
|
allowed_ips.append(allowed_ips_list[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgConfig.insert(config_key::allowed_ips, allowed_ips);
|
||||||
|
} else {
|
||||||
|
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
|
||||||
|
wgConfig.insert(config_key::allowed_ips, allowed_ips);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgConfig.insert("persistent_keep_alive", "25");
|
||||||
|
|
||||||
|
QJsonDocument wgConfigDoc(wgConfig);
|
||||||
|
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
|
||||||
|
|
||||||
|
return startWireGuard(wgConfigDocStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IosController::setupAwg()
|
bool IosController::setupAwg()
|
||||||
{
|
{
|
||||||
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Awg)].toObject();
|
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Awg)].toObject();
|
||||||
|
|
||||||
QJsonDocument doc(m_rawConfig);
|
QJsonObject wgConfig {};
|
||||||
QString wgConfig(doc.toJson(QJsonDocument::Compact));
|
wgConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]);
|
||||||
|
wgConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]);
|
||||||
|
wgConfig.insert(config_key::hostName, config[config_key::hostName]);
|
||||||
|
wgConfig.insert(config_key::port, config[config_key::port]);
|
||||||
|
wgConfig.insert(config_key::client_ip, config[config_key::client_ip]);
|
||||||
|
wgConfig.insert(config_key::client_priv_key, config[config_key::client_priv_key]);
|
||||||
|
wgConfig.insert(config_key::server_pub_key, config[config_key::server_pub_key]);
|
||||||
|
wgConfig.insert(config_key::psk_key, config[config_key::psk_key]);
|
||||||
|
wgConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
|
||||||
|
|
||||||
return startWireGuard(wgConfig);
|
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
|
||||||
|
|
||||||
|
for(int index = 0; index < splitTunnelSites.count(); index++) {
|
||||||
|
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
wgConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
|
||||||
|
|
||||||
|
if (config.contains(config_key::allowed_ips)) {
|
||||||
|
QJsonArray allowed_ips;
|
||||||
|
QStringList allowed_ips_list = config[config_key::allowed_ips].toString().split(", ");
|
||||||
|
|
||||||
|
for(int index = 0; index < allowed_ips_list.length(); index++) {
|
||||||
|
allowed_ips.append(allowed_ips_list[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgConfig.insert(config_key::allowed_ips, allowed_ips);
|
||||||
|
} else {
|
||||||
|
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
|
||||||
|
wgConfig.insert(config_key::allowed_ips, allowed_ips);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgConfig.insert("persistent_keep_alive", "25");
|
||||||
|
wgConfig.insert(config_key::initPacketMagicHeader, config[config_key::initPacketMagicHeader]);
|
||||||
|
wgConfig.insert(config_key::responsePacketMagicHeader, config[config_key::responsePacketMagicHeader]);
|
||||||
|
wgConfig.insert(config_key::underloadPacketMagicHeader, config[config_key::underloadPacketMagicHeader]);
|
||||||
|
wgConfig.insert(config_key::transportPacketMagicHeader, config[config_key::transportPacketMagicHeader]);
|
||||||
|
|
||||||
|
wgConfig.insert(config_key::initPacketJunkSize, config[config_key::initPacketJunkSize]);
|
||||||
|
wgConfig.insert(config_key::responsePacketJunkSize, config[config_key::responsePacketJunkSize]);
|
||||||
|
|
||||||
|
wgConfig.insert(config_key::junkPacketCount, config[config_key::junkPacketCount]);
|
||||||
|
wgConfig.insert(config_key::junkPacketMinSize, config[config_key::junkPacketMinSize]);
|
||||||
|
wgConfig.insert(config_key::junkPacketMaxSize, config[config_key::junkPacketMaxSize]);
|
||||||
|
|
||||||
|
QJsonDocument wgConfigDoc(wgConfig);
|
||||||
|
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
|
||||||
|
|
||||||
|
return startWireGuard(wgConfigDocStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IosController::startOpenVPN(const QString &config)
|
bool IosController::startOpenVPN(const QString &config)
|
||||||
|
|
@ -448,23 +558,17 @@ bool IosController::startWireGuard(const QString &config)
|
||||||
void IosController::startTunnel()
|
void IosController::startTunnel()
|
||||||
{
|
{
|
||||||
NSString *protocolName = @"Unknown";
|
NSString *protocolName = @"Unknown";
|
||||||
|
|
||||||
NETunnelProviderProtocol *tunnelProtocol = (NETunnelProviderProtocol *)m_currentTunnel.protocolConfiguration;
|
NETunnelProviderProtocol *tunnelProtocol = (NETunnelProviderProtocol *)m_currentTunnel.protocolConfiguration;
|
||||||
if (tunnelProtocol.providerConfiguration[@"wireguard"] != nil) {
|
if (tunnelProtocol.providerConfiguration[@"wireguard"] != nil) {
|
||||||
protocolName = @"WireGuard";
|
protocolName = @"WireGuard";
|
||||||
} else if (tunnelProtocol.providerConfiguration[@"ovpn"] != nil) {
|
} else if (tunnelProtocol.providerConfiguration[@"ovpn"] != nil) {
|
||||||
protocolName = @"OpenVPN";
|
protocolName = @"OpenVPN";
|
||||||
}
|
}
|
||||||
|
|
||||||
m_rxBytes = 0;
|
m_rxBytes = 0;
|
||||||
m_txBytes = 0;
|
m_txBytes = 0;
|
||||||
|
|
||||||
int STT = m_rawConfig["splitTunnelType"].toInt();
|
|
||||||
QJsonArray splitTunnelSites = m_rawConfig["splitTunnelSites"].toArray();
|
|
||||||
QJsonDocument doc;
|
|
||||||
doc.setArray(splitTunnelSites);
|
|
||||||
QString STS(doc.toJson());
|
|
||||||
|
|
||||||
[m_currentTunnel setEnabled:YES];
|
[m_currentTunnel setEnabled:YES];
|
||||||
|
|
||||||
[m_currentTunnel saveToPreferencesWithCompletionHandler:^(NSError *saveError) {
|
[m_currentTunnel saveToPreferencesWithCompletionHandler:^(NSError *saveError) {
|
||||||
|
|
@ -485,23 +589,6 @@ void IosController::startTunnel()
|
||||||
NSError *startError = nil;
|
NSError *startError = nil;
|
||||||
qDebug() << iosStatusToState(m_currentTunnel.connection.status);
|
qDebug() << iosStatusToState(m_currentTunnel.connection.status);
|
||||||
|
|
||||||
|
|
||||||
NSString *actionKey = [NSString stringWithUTF8String:MessageKey::action];
|
|
||||||
NSString *actionValue = [NSString stringWithUTF8String:Action::start];
|
|
||||||
NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId];
|
|
||||||
NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @"";
|
|
||||||
NSString *SplitTunnelTypeKey = [NSString stringWithUTF8String:MessageKey::SplitTunnelType];
|
|
||||||
NSString *SplitTunnelTypeValue = [NSString stringWithFormat:@"%d",STT];
|
|
||||||
NSString *SplitTunnelSitesKey = [NSString stringWithUTF8String:MessageKey::SplitTunnelSites];
|
|
||||||
NSString *SplitTunnelSitesValue = STS.toNSString();
|
|
||||||
|
|
||||||
|
|
||||||
NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue,
|
|
||||||
SplitTunnelTypeKey: SplitTunnelTypeValue, SplitTunnelSitesKey: SplitTunnelSitesValue};
|
|
||||||
|
|
||||||
sendVpnExtensionMessage(message);
|
|
||||||
|
|
||||||
|
|
||||||
BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError];
|
BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError];
|
||||||
|
|
||||||
if (!started || startError) {
|
if (!started || startError) {
|
||||||
|
|
@ -516,7 +603,6 @@ void IosController::startTunnel()
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool IosController::isOurManager(NETunnelProviderManager* manager) {
|
bool IosController::isOurManager(NETunnelProviderManager* manager) {
|
||||||
NETunnelProviderProtocol* tunnelProto = (NETunnelProviderProtocol*)manager.protocolConfiguration;
|
NETunnelProviderProtocol* tunnelProto = (NETunnelProviderProtocol*)manager.protocolConfiguration;
|
||||||
|
|
||||||
|
|
@ -578,7 +664,7 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
|
||||||
NETunnelProviderSession *session = (NETunnelProviderSession *)m_currentTunnel.connection;
|
NETunnelProviderSession *session = (NETunnelProviderSession *)m_currentTunnel.connection;
|
||||||
|
|
||||||
NSError *sendError = nil;
|
NSError *sendError = nil;
|
||||||
|
|
||||||
if ([session respondsToSelector:@selector(sendProviderMessage:returnError:responseHandler:)]) {
|
if ([session respondsToSelector:@selector(sendProviderMessage:returnError:responseHandler:)]) {
|
||||||
[session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler];
|
[session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler];
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
sudo docker build --no-cache --pull -t $CONTAINER_NAME $DOCKERFILE_FOLDER --build-arg SERVER_ARCH=$(uname -m)
|
sudo docker build --no-cache --pull -t $CONTAINER_NAME $DOCKERFILE_FOLDER
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
FROM alpine:3.15
|
FROM alpine:3.15
|
||||||
LABEL maintainer="AmneziaVPN"
|
LABEL maintainer="AmneziaVPN"
|
||||||
|
|
||||||
ARG SS_RELEASE="v1.13.1"
|
ARG SS_RELEASE="v1.18.1"
|
||||||
ARG CLOAK_RELEASE="v2.5.5"
|
ARG CLOAK_RELEASE="v2.8.0"
|
||||||
ARG SERVER_ARCH
|
|
||||||
|
|
||||||
#Install required packages
|
#Install required packages
|
||||||
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools
|
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools
|
||||||
|
|
@ -16,20 +15,19 @@ RUN mkdir -p /opt/amnezia
|
||||||
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
|
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
|
||||||
RUN chmod a+x /opt/amnezia/start.sh
|
RUN chmod a+x /opt/amnezia/start.sh
|
||||||
|
|
||||||
RUN if [ $SERVER_ARCH="x86_64" ]; then CK_ARCH="amd64"; \
|
RUN SERVER_ARCH=$(uname -m) && \
|
||||||
elif [ $SERVER_ARCH="i686" ]; then CK_ARCH="386"; \
|
if [ $SERVER_ARCH="x86_64" ]; then CK_ARCH="amd64"; \
|
||||||
elif [ $SERVER_ARCH="aarch64" ]; then CK_ARCH="arm64"; \
|
elif [ $SERVER_ARCH="i686" ]; then CK_ARCH="386"; \
|
||||||
elif [ $SERVER_ARCH="arm" ]; then CK_ARCH="arm"; \
|
elif [ $SERVER_ARCH="aarch64" ]; then CK_ARCH="arm64"; \
|
||||||
else exit -1; fi && \
|
elif [ $SERVER_ARCH="arm" ]; then CK_ARCH="arm"; \
|
||||||
curl -L https://github.com/cbeuw/Cloak/releases/download/${CLOAK_RELEASE}/ck-server-linux-${CK_ARCH}-${CLOAK_RELEASE} > /usr/bin/ck-server
|
else exit -1; fi && \
|
||||||
RUN chmod a+x /usr/bin/ck-server
|
curl -L https://github.com/cbeuw/Cloak/releases/download/${CLOAK_RELEASE}/ck-server-linux-${CK_ARCH}-${CLOAK_RELEASE} > /usr/bin/ck-server && \
|
||||||
|
chmod a+x /usr/bin/ck-server && \
|
||||||
|
curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/${SS_RELEASE}/shadowsocks-${SS_RELEASE}.${SERVER_ARCH}-unknown-linux-musl.tar.xz > /usr/bin/ss.tar.xz && \
|
||||||
|
tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/ && \
|
||||||
|
chmod a+x /usr/bin/ssserver
|
||||||
|
|
||||||
RUN curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/${SS_RELEASE}/shadowsocks-${SS_RELEASE}.${SERVER_ARCH}-unknown-linux-musl.tar.xz > /usr/bin/ss.tar.xz
|
# Tune network
|
||||||
|
|
||||||
RUN tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/
|
|
||||||
RUN chmod a+x /usr/bin/ssserver
|
|
||||||
|
|
||||||
# Tune network
|
|
||||||
RUN echo -e " \n\
|
RUN echo -e " \n\
|
||||||
fs.file-max = 51200 \n\
|
fs.file-max = 51200 \n\
|
||||||
\n\
|
\n\
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
FROM alpine:3.15
|
FROM alpine:3.15
|
||||||
LABEL maintainer="AmneziaVPN"
|
LABEL maintainer="AmneziaVPN"
|
||||||
|
|
||||||
ARG SS_RELEASE="v1.13.1"
|
ARG SS_RELEASE="v1.18.1"
|
||||||
ARG SERVER_ARCH
|
|
||||||
|
|
||||||
#Install required packages
|
#Install required packages
|
||||||
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools xz
|
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools xz
|
||||||
|
|
@ -15,7 +14,16 @@ RUN mkdir -p /opt/amnezia
|
||||||
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
|
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
|
||||||
RUN chmod a+x /opt/amnezia/start.sh
|
RUN chmod a+x /opt/amnezia/start.sh
|
||||||
|
|
||||||
RUN curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/${SS_RELEASE}/shadowsocks-${SS_RELEASE}.${SERVER_ARCH}-unknown-linux-musl.tar.xz > /usr/bin/ss.tar.xz;\
|
RUN SERVER_ARCH=$(uname -m); \
|
||||||
|
SUFFIX=""; \
|
||||||
|
if [ ! -z "$(echo ${SERVER_ARCH} | grep -i arm)" ]; then \
|
||||||
|
if [ ! -z "$(cat /proc/cpuinfo | grep -i vfp)" ]; then \
|
||||||
|
SUFFIX="eabihf"; \
|
||||||
|
else \
|
||||||
|
SUFFIX="eabi"; \
|
||||||
|
fi; \
|
||||||
|
fi; \
|
||||||
|
curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/${SS_RELEASE}/shadowsocks-${SS_RELEASE}.${SERVER_ARCH}-unknown-linux-musl${SUFFIX}.tar.xz > /usr/bin/ss.tar.xz;\
|
||||||
tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/;\
|
tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/;\
|
||||||
chmod a+x /usr/bin/ssserver;
|
chmod a+x /usr/bin/ssserver;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
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 stop;\
|
||||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv
|
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
|
||||||
sudo docker images -a | grep amnezia | awk '{print $3}' | xargs sudo docker rmi
|
sudo docker images -a | grep amnezia | awk '{print $3}' | xargs sudo docker rmi;\
|
||||||
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm
|
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
|
||||||
|
sudo rm -frd /opt/amnezia
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
sudo docker stop $CONTAINER_NAME
|
sudo docker stop $CONTAINER_NAME;\
|
||||||
sudo docker rm -fv $CONTAINER_NAME
|
sudo docker rm -fv $CONTAINER_NAME;\
|
||||||
sudo docker rmi $CONTAINER_NAME
|
sudo docker rmi $CONTAINER_NAME
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -25,12 +25,13 @@ ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &s
|
||||||
|
|
||||||
void ConnectionController::openConnection()
|
void ConnectionController::openConnection()
|
||||||
{
|
{
|
||||||
if (!m_containersModel->isAnyContainerInstalled()) {
|
int serverIndex = m_serversModel->getDefaultServerIndex();
|
||||||
|
|
||||||
|
if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
||||||
emit noInstalledContainers();
|
emit noInstalledContainers();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int serverIndex = m_serversModel->getDefaultServerIndex();
|
|
||||||
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
|
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
|
||||||
|
|
||||||
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
|
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
|
||||||
|
|
|
||||||
|
|
@ -418,4 +418,6 @@ void ExportController::clearPreviousConfig()
|
||||||
m_config.clear();
|
m_config.clear();
|
||||||
m_nativeConfigString.clear();
|
m_nativeConfigString.clear();
|
||||||
m_qrCodes.clear();
|
m_qrCodes.clear();
|
||||||
|
|
||||||
|
emit exportConfigChanged();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,16 @@ void PageController::closeWindow()
|
||||||
void PageController::keyPressEvent(Qt::Key key)
|
void PageController::keyPressEvent(Qt::Key key)
|
||||||
{
|
{
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case Qt::Key_Back: emit closePage();
|
case Qt::Key_Back:
|
||||||
|
case Qt::Key_Escape: {
|
||||||
|
if (m_drawerDepth) {
|
||||||
|
emit closeTopDrawer();
|
||||||
|
setDrawerDepth(getDrawerDepth() - 1);
|
||||||
|
} else {
|
||||||
|
emit escapePressed();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: return;
|
default: return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -123,7 +132,7 @@ bool PageController::isTriggeredByConnectButton()
|
||||||
return m_isTriggeredByConnectButton;
|
return m_isTriggeredByConnectButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PageController::setTriggeredBtConnectButton(bool trigger)
|
void PageController::setTriggeredByConnectButton(bool trigger)
|
||||||
{
|
{
|
||||||
m_isTriggeredByConnectButton = trigger;
|
m_isTriggeredByConnectButton = trigger;
|
||||||
}
|
}
|
||||||
|
|
@ -132,3 +141,15 @@ void PageController::closeApplication()
|
||||||
{
|
{
|
||||||
qApp->quit();
|
qApp->quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PageController::setDrawerDepth(const int depth)
|
||||||
|
{
|
||||||
|
if (depth >= 0) {
|
||||||
|
m_drawerDepth = depth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int PageController::getDrawerDepth()
|
||||||
|
{
|
||||||
|
return m_drawerDepth;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,10 +83,13 @@ public slots:
|
||||||
void showOnStartup();
|
void showOnStartup();
|
||||||
|
|
||||||
bool isTriggeredByConnectButton();
|
bool isTriggeredByConnectButton();
|
||||||
void setTriggeredBtConnectButton(bool trigger);
|
void setTriggeredByConnectButton(bool trigger);
|
||||||
|
|
||||||
void closeApplication();
|
void closeApplication();
|
||||||
|
|
||||||
|
void setDrawerDepth(const int depth);
|
||||||
|
int getDrawerDepth();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void goToPage(PageLoader::PageEnum page, bool slide = true);
|
void goToPage(PageLoader::PageEnum page, bool slide = true);
|
||||||
void goToStartPage();
|
void goToStartPage();
|
||||||
|
|
@ -105,7 +108,7 @@ signals:
|
||||||
void showNotificationMessage(const QString &message);
|
void showNotificationMessage(const QString &message);
|
||||||
|
|
||||||
void showBusyIndicator(bool visible);
|
void showBusyIndicator(bool visible);
|
||||||
void enableTabBar(bool enabled);
|
void disableControls(bool disabled);
|
||||||
|
|
||||||
void hideMainWindow();
|
void hideMainWindow();
|
||||||
void raiseMainWindow();
|
void raiseMainWindow();
|
||||||
|
|
@ -113,12 +116,17 @@ signals:
|
||||||
void showPassphraseRequestDrawer();
|
void showPassphraseRequestDrawer();
|
||||||
void passphraseRequestDrawerClosed(QString passphrase);
|
void passphraseRequestDrawerClosed(QString passphrase);
|
||||||
|
|
||||||
|
void escapePressed();
|
||||||
|
void closeTopDrawer();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSharedPointer<ServersModel> m_serversModel;
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
|
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
|
||||||
bool m_isTriggeredByConnectButton;
|
bool m_isTriggeredByConnectButton;
|
||||||
|
|
||||||
|
int m_drawerDepth = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PAGECONTROLLER_H
|
#endif // PAGECONTROLLER_H
|
||||||
|
|
|
||||||
|
|
@ -92,11 +92,11 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString
|
||||||
|
|
||||||
mainFileDialog->setProperty("acceptLabel", QVariant::fromValue(acceptLabel));
|
mainFileDialog->setProperty("acceptLabel", QVariant::fromValue(acceptLabel));
|
||||||
mainFileDialog->setProperty("nameFilters", QVariant::fromValue(QStringList(nameFilter)));
|
mainFileDialog->setProperty("nameFilters", QVariant::fromValue(QStringList(nameFilter)));
|
||||||
if (!selectedFile.isEmpty()) {
|
|
||||||
mainFileDialog->setProperty("selectedFile", QVariant::fromValue(selectedFile));
|
|
||||||
}
|
|
||||||
mainFileDialog->setProperty("isSaveMode", QVariant::fromValue(isSaveMode));
|
|
||||||
mainFileDialog->setProperty("defaultSuffix", QVariant::fromValue(defaultSuffix));
|
mainFileDialog->setProperty("defaultSuffix", QVariant::fromValue(defaultSuffix));
|
||||||
|
mainFileDialog->setProperty("isSaveMode", QVariant::fromValue(isSaveMode));
|
||||||
|
if (!selectedFile.isEmpty()) {
|
||||||
|
mainFileDialog->setProperty("selectedFile", QVariant::fromValue(QUrl(selectedFile)));
|
||||||
|
}
|
||||||
QMetaObject::invokeMethod(mainFileDialog, "open");
|
QMetaObject::invokeMethod(mainFileDialog, "open");
|
||||||
|
|
||||||
bool isFileDialogAccepted = false;
|
bool isFileDialogAccepted = false;
|
||||||
|
|
|
||||||
|
|
@ -83,20 +83,6 @@ QJsonObject ContainersModel::getContainerConfig(const int containerIndex)
|
||||||
return qvariant_cast<QJsonObject>(data(index(containerIndex), ConfigRole));
|
return qvariant_cast<QJsonObject>(data(index(containerIndex), ConfigRole));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ContainersModel::isAnyContainerInstalled()
|
|
||||||
{
|
|
||||||
for (int row=0; row < rowCount(); row++) {
|
|
||||||
QModelIndex idx = this->index(row, 0);
|
|
||||||
|
|
||||||
if (this->data(idx, IsInstalledRole).toBool() &&
|
|
||||||
this->data(idx, ServiceTypeRole).toInt() == ServiceType::Vpn) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> ContainersModel::roleNames() const
|
QHash<int, QByteArray> ContainersModel::roleNames() const
|
||||||
{
|
{
|
||||||
QHash<int, QByteArray> roles;
|
QHash<int, QByteArray> roles;
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,6 @@ public slots:
|
||||||
|
|
||||||
QJsonObject getContainerConfig(const int containerIndex);
|
QJsonObject getContainerConfig(const int containerIndex);
|
||||||
|
|
||||||
bool isAnyContainerInstalled();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,9 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
|
||||||
case DefaultContainerRole: {
|
case DefaultContainerRole: {
|
||||||
return ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString());
|
return ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString());
|
||||||
}
|
}
|
||||||
|
case HasInstalledContainers: {
|
||||||
|
return serverHasInstalledContainers(index.row());
|
||||||
|
}
|
||||||
case IsServerFromApiRole: {
|
case IsServerFromApiRole: {
|
||||||
return server.value(config_key::configVersion).toInt();
|
return server.value(config_key::configVersion).toInt();
|
||||||
}
|
}
|
||||||
|
|
@ -302,6 +305,7 @@ QHash<int, QByteArray> ServersModel::roleNames() const
|
||||||
roles[ContainsAmneziaDnsRole] = "containsAmneziaDns";
|
roles[ContainsAmneziaDnsRole] = "containsAmneziaDns";
|
||||||
|
|
||||||
roles[DefaultContainerRole] = "defaultContainer";
|
roles[DefaultContainerRole] = "defaultContainer";
|
||||||
|
roles[HasInstalledContainers] = "hasInstalledContainers";
|
||||||
|
|
||||||
roles[IsServerFromApiRole] = "isServerFromApi";
|
roles[IsServerFromApiRole] = "isServerFromApi";
|
||||||
return roles;
|
return roles;
|
||||||
|
|
@ -548,6 +552,19 @@ bool ServersModel::isServerFromApiAlreadyExists(const quint16 crc)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ServersModel::serverHasInstalledContainers(const int serverIndex) const
|
||||||
|
{
|
||||||
|
QJsonObject server = m_servers.at(serverIndex).toObject();
|
||||||
|
const auto containers = server.value(config_key::containers).toArray();
|
||||||
|
for (auto it = containers.begin(); it != containers.end(); it++) {
|
||||||
|
auto container = ContainerProps::containerFromString(it->toObject().value(config_key::container).toString());
|
||||||
|
if (ContainerProps::containerService(container) == ServiceType::Vpn) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
QVariant ServersModel::getDefaultServerData(const QString roleString)
|
QVariant ServersModel::getDefaultServerData(const QString roleString)
|
||||||
{
|
{
|
||||||
auto roles = roleNames();
|
auto roles = roleNames();
|
||||||
|
|
@ -560,11 +577,6 @@ QVariant ServersModel::getDefaultServerData(const QString roleString)
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServersModel::setDefaultServerData(const QString roleString, const QVariant &value)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant ServersModel::getProcessedServerData(const QString roleString)
|
QVariant ServersModel::getProcessedServerData(const QString roleString)
|
||||||
{
|
{
|
||||||
auto roles = roleNames();
|
auto roles = roleNames();
|
||||||
|
|
@ -577,11 +589,6 @@ QVariant ServersModel::getProcessedServerData(const QString roleString)
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServersModel::setProcessedServerData(const QString roleString, const QVariant &value)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling()
|
bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling()
|
||||||
{
|
{
|
||||||
auto server = m_servers.at(m_defaultServerIndex).toObject();
|
auto server = m_servers.at(m_defaultServerIndex).toObject();
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ public:
|
||||||
|
|
||||||
DefaultContainerRole,
|
DefaultContainerRole,
|
||||||
|
|
||||||
|
HasInstalledContainers,
|
||||||
IsServerFromApiRole,
|
IsServerFromApiRole,
|
||||||
|
|
||||||
HasAmneziaDns
|
HasAmneziaDns
|
||||||
|
|
@ -101,10 +102,8 @@ public slots:
|
||||||
bool isServerFromApiAlreadyExists(const quint16 crc);
|
bool isServerFromApiAlreadyExists(const quint16 crc);
|
||||||
|
|
||||||
QVariant getDefaultServerData(const QString roleString);
|
QVariant getDefaultServerData(const QString roleString);
|
||||||
void setDefaultServerData(const QString roleString, const QVariant &value);
|
|
||||||
|
|
||||||
QVariant getProcessedServerData(const QString roleString);
|
QVariant getProcessedServerData(const QString roleString);
|
||||||
void setProcessedServerData(const QString roleString, const QVariant &value);
|
|
||||||
|
|
||||||
bool isDefaultServerDefaultContainerHasSplitTunneling();
|
bool isDefaultServerDefaultContainerHasSplitTunneling();
|
||||||
|
|
||||||
|
|
@ -123,6 +122,7 @@ signals:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ServerCredentials serverCredentials(int index) const;
|
ServerCredentials serverCredentials(int index) const;
|
||||||
|
|
||||||
void updateContainersModel();
|
void updateContainersModel();
|
||||||
void updateDefaultServerContainersModel();
|
void updateDefaultServerContainersModel();
|
||||||
|
|
||||||
|
|
@ -130,6 +130,8 @@ private:
|
||||||
|
|
||||||
bool isAmneziaDnsContainerInstalled(const int serverIndex) const;
|
bool isAmneziaDnsContainerInstalled(const int serverIndex) const;
|
||||||
|
|
||||||
|
bool serverHasInstalledContainers(const int serverIndex) const;
|
||||||
|
|
||||||
QJsonArray m_servers;
|
QJsonArray m_servers;
|
||||||
|
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,14 @@ DrawerType2 {
|
||||||
expandedContent: Item {
|
expandedContent: Item {
|
||||||
implicitHeight: root.expandedHeight
|
implicitHeight: root.expandedHeight
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
|
||||||
|
function onOpened() {
|
||||||
|
header.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Header2Type {
|
Header2Type {
|
||||||
id: header
|
id: header
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
|
@ -48,6 +56,8 @@ DrawerType2 {
|
||||||
anchors.rightMargin: 16
|
anchors.rightMargin: 16
|
||||||
|
|
||||||
headerText: root.headerText
|
headerText: root.headerText
|
||||||
|
|
||||||
|
KeyNavigation.tab: shareButton
|
||||||
}
|
}
|
||||||
|
|
||||||
FlickableType {
|
FlickableType {
|
||||||
|
|
@ -68,12 +78,15 @@ DrawerType2 {
|
||||||
visible: root.contentVisible
|
visible: root.contentVisible
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
|
id: shareButton
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
text: qsTr("Share")
|
text: qsTr("Share")
|
||||||
imageSource: "qrc:/images/controls/share-2.svg"
|
imageSource: "qrc:/images/controls/share-2.svg"
|
||||||
|
|
||||||
|
KeyNavigation.tab: copyConfigTextButton
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
var fileName = ""
|
var fileName = ""
|
||||||
if (GC.isMobile()) {
|
if (GC.isMobile()) {
|
||||||
|
|
@ -107,6 +120,8 @@ DrawerType2 {
|
||||||
|
|
||||||
text: qsTr("Copy")
|
text: qsTr("Copy")
|
||||||
imageSource: "qrc:/images/controls/copy.svg"
|
imageSource: "qrc:/images/controls/copy.svg"
|
||||||
|
|
||||||
|
KeyNavigation.tab: copyNativeConfigStringButton.visible ? copyNativeConfigStringButton : showSettingsButton
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
|
|
@ -125,9 +140,13 @@ DrawerType2 {
|
||||||
|
|
||||||
text: qsTr("Copy config string")
|
text: qsTr("Copy config string")
|
||||||
imageSource: "qrc:/images/controls/copy.svg"
|
imageSource: "qrc:/images/controls/copy.svg"
|
||||||
|
|
||||||
|
KeyNavigation.tab: showSettingsButton
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
|
id: showSettingsButton
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 24
|
Layout.topMargin: 24
|
||||||
|
|
||||||
|
|
@ -143,6 +162,8 @@ DrawerType2 {
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
configContentDrawer.open()
|
configContentDrawer.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KeyNavigation.tab: header
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawerType2 {
|
DrawerType2 {
|
||||||
|
|
@ -258,6 +279,8 @@ DrawerType2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
id: qrCodeContainer
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: width
|
Layout.preferredHeight: width
|
||||||
Layout.topMargin: 20
|
Layout.topMargin: 20
|
||||||
|
|
|
||||||
|
|
@ -33,22 +33,23 @@ Button {
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
id: background_border
|
id: focusBorder
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
border.color: root.activeFocus ? root.borderFocusedColor : "transparent"
|
border.color: root.activeFocus ? root.borderFocusedColor : "transparent"
|
||||||
border.width: root.activeFocus ? root.borderFocusedWidth : "transparent"
|
border.width: root.activeFocus ? root.borderFocusedWidth : "transparent"
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
radius: 16
|
radius: 16
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: background
|
id: background
|
||||||
|
|
||||||
anchors.fill: background_border
|
anchors.fill: focusBorder
|
||||||
anchors.margins: root.activeFocus ? 2: 0
|
anchors.margins: root.activeFocus ? 2 : 0
|
||||||
|
|
||||||
radius: 16
|
radius: root.activeFocus ? 14 : 16
|
||||||
color: {
|
color: {
|
||||||
if (root.enabled) {
|
if (root.enabled) {
|
||||||
if (root.pressed) {
|
if (root.pressed) {
|
||||||
|
|
@ -59,8 +60,8 @@ Button {
|
||||||
return disabledColor
|
return disabledColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
border.color: root.activeFocus ? "transparent" : borderColor
|
border.color: borderColor
|
||||||
border.width: root.activeFocus ? 0 : borderWidth
|
border.width: borderWidth
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
PropertyAnimation { duration: 200 }
|
PropertyAnimation { duration: 200 }
|
||||||
|
|
@ -95,13 +96,13 @@ Button {
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: background_border
|
anchors.fill: focusBorder
|
||||||
enabled: false
|
enabled: false
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
anchors.fill: background_border
|
anchors.fill: focusBorder
|
||||||
|
|
||||||
implicitWidth: content.implicitWidth
|
implicitWidth: content.implicitWidth
|
||||||
implicitHeight: content.implicitHeight
|
implicitHeight: content.implicitHeight
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ Item {
|
||||||
property real expandedHeight
|
property real expandedHeight
|
||||||
property real collapsedHeight: 0
|
property real collapsedHeight: 0
|
||||||
|
|
||||||
|
property int depthIndex: 0
|
||||||
|
|
||||||
signal entered
|
signal entered
|
||||||
signal exited
|
signal exited
|
||||||
signal pressed(bool pressed, bool entered)
|
signal pressed(bool pressed, bool entered)
|
||||||
|
|
@ -36,6 +38,24 @@ Item {
|
||||||
signal closed
|
signal closed
|
||||||
signal opened
|
signal opened
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: PageController
|
||||||
|
|
||||||
|
function onCloseTopDrawer() {
|
||||||
|
if (depthIndex === PageController.getDrawerDepth()) {
|
||||||
|
if (isCollapsed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
aboutToHide()
|
||||||
|
|
||||||
|
drawerContent.state = root.drawerCollapsed
|
||||||
|
depthIndex = 0
|
||||||
|
closed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root
|
target: root
|
||||||
|
|
||||||
|
|
@ -47,6 +67,8 @@ Item {
|
||||||
aboutToHide()
|
aboutToHide()
|
||||||
|
|
||||||
drawerContent.state = root.drawerCollapsed
|
drawerContent.state = root.drawerCollapsed
|
||||||
|
depthIndex = 0
|
||||||
|
PageController.setDrawerDepth(PageController.getDrawerDepth() - 1)
|
||||||
closed()
|
closed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,6 +80,8 @@ Item {
|
||||||
aboutToShow()
|
aboutToShow()
|
||||||
|
|
||||||
drawerContent.state = root.drawerExpanded
|
drawerContent.state = root.drawerExpanded
|
||||||
|
depthIndex = PageController.getDrawerDepth() + 1
|
||||||
|
PageController.setDrawerDepth(depthIndex)
|
||||||
opened()
|
opened()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ import "../Config"
|
||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
Component.onCompleted: PageController.enableTabBar(false)
|
Component.onCompleted: PageController.disableControls(true)
|
||||||
Component.onDestruction: PageController.enableTabBar(true)
|
Component.onDestruction: PageController.disableControls(false)
|
||||||
|
|
||||||
SortFilterProxyModel {
|
SortFilterProxyModel {
|
||||||
id: proxyServersModel
|
id: proxyServersModel
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ PageType {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
expandedContent: Item {
|
expandedContent: Item {
|
||||||
id: serverMenuContainer
|
id: serverMenuContainer
|
||||||
|
|
||||||
implicitHeight: root.height * 0.9
|
implicitHeight: root.height * 0.9
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ PageType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Junk packet count")
|
headerText: "Jc - Junk packet count"
|
||||||
textFieldText: junkPacketCount
|
textFieldText: junkPacketCount
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
|
|
@ -136,7 +136,7 @@ PageType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Junk packet minimum size")
|
headerText: "Jmin - Junk packet minimum size"
|
||||||
textFieldText: junkPacketMinSize
|
textFieldText: junkPacketMinSize
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
|
|
@ -156,7 +156,7 @@ PageType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Junk packet maximum size")
|
headerText: "Jmax - Junk packet maximum size"
|
||||||
textFieldText: junkPacketMaxSize
|
textFieldText: junkPacketMaxSize
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
|
|
@ -176,7 +176,7 @@ PageType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Init packet junk size")
|
headerText: "S1 - Init packet junk size"
|
||||||
textFieldText: initPacketJunkSize
|
textFieldText: initPacketJunkSize
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
|
|
@ -196,7 +196,7 @@ PageType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Response packet junk size")
|
headerText: "S2 - Response packet junk size"
|
||||||
textFieldText: responsePacketJunkSize
|
textFieldText: responsePacketJunkSize
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
|
|
@ -216,7 +216,7 @@ PageType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Init packet magic header")
|
headerText: "H1 - Init packet magic header"
|
||||||
textFieldText: initPacketMagicHeader
|
textFieldText: initPacketMagicHeader
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
|
|
@ -236,7 +236,7 @@ PageType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Response packet magic header")
|
headerText: "H2 - Response packet magic header"
|
||||||
textFieldText: responsePacketMagicHeader
|
textFieldText: responsePacketMagicHeader
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
|
|
@ -256,7 +256,7 @@ PageType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Transport packet magic header")
|
headerText: "H4 - Transport packet magic header"
|
||||||
textFieldText: transportPacketMagicHeader
|
textFieldText: transportPacketMagicHeader
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
|
|
@ -276,7 +276,7 @@ PageType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Underload packet magic header")
|
headerText: "H3 - Underload packet magic header"
|
||||||
textFieldText: underloadPacketMagicHeader
|
textFieldText: underloadPacketMagicHeader
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -187,9 +187,8 @@ PageType {
|
||||||
|
|
||||||
visible: {
|
visible: {
|
||||||
if (PageController.isTriggeredByConnectButton()) {
|
if (PageController.isTriggeredByConnectButton()) {
|
||||||
PageController.setTriggeredBtConnectButton(false)
|
PageController.setTriggeredByConnectButton(false)
|
||||||
|
return false
|
||||||
return ContainersModel.isAnyContainerInstalled()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ import "../Config"
|
||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
Component.onCompleted: PageController.enableTabBar(false)
|
Component.onCompleted: PageController.disableControls(true)
|
||||||
Component.onDestruction: PageController.enableTabBar(true)
|
Component.onDestruction: PageController.disableControls(false)
|
||||||
|
|
||||||
property bool isTimerRunning: true
|
property bool isTimerRunning: true
|
||||||
property string progressBarText: qsTr("Usually it takes no more than 5 minutes")
|
property string progressBarText: qsTr("Usually it takes no more than 5 minutes")
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ import "../Components"
|
||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property bool isControlsDisabled: false
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: PageController
|
target: PageController
|
||||||
|
|
||||||
|
|
@ -45,6 +47,18 @@ PageType {
|
||||||
stackView.pop()
|
stackView.pop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onDisableControls(disabled) {
|
||||||
|
isControlsDisabled = disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEscapePressed() {
|
||||||
|
if (isControlsDisabled || busyIndicator.visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
PageController.closePage()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
|
|
||||||
|
|
@ -308,6 +308,10 @@ PageType {
|
||||||
ValueFilter {
|
ValueFilter {
|
||||||
roleName: "hasWriteAccess"
|
roleName: "hasWriteAccess"
|
||||||
value: true
|
value: true
|
||||||
|
},
|
||||||
|
ValueFilter {
|
||||||
|
roleName: "hasInstalledContainers"
|
||||||
|
value: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -324,8 +328,12 @@ PageType {
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
serverSelectorListView.currentIndex = ServersModel.isDefaultServerHasWriteAccess() ?
|
if (ServersModel.isDefaultServerHasWriteAccess() && ServersModel.getDefaultServerData("hasInstalledContainers")) {
|
||||||
proxyServersModel.mapFromSource(ServersModel.defaultIndex) : 0
|
serverSelectorListView.currentIndex = proxyServersModel.mapFromSource(ServersModel.defaultIndex)
|
||||||
|
} else {
|
||||||
|
serverSelectorListView.currentIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
serverSelectorListView.triggerCurrentItem()
|
serverSelectorListView.triggerCurrentItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -480,6 +488,7 @@ PageType {
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 40
|
Layout.topMargin: 40
|
||||||
|
Layout.bottomMargin: 32
|
||||||
|
|
||||||
enabled: shareButtonEnabled
|
enabled: shareButtonEnabled
|
||||||
visible: accessTypeSelector.currentIndex === 0
|
visible: accessTypeSelector.currentIndex === 0
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@ PageType {
|
||||||
tabBar.enabled = !visible
|
tabBar.enabled = !visible
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEnableTabBar(enabled) {
|
function onDisableControls(disabled) {
|
||||||
tabBar.enabled = enabled
|
tabBar.enabled = !disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClosePage() {
|
function onClosePage() {
|
||||||
|
|
@ -60,8 +60,8 @@ PageType {
|
||||||
} else {
|
} else {
|
||||||
tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate)
|
tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
tabBar.isServerInfoShow = page === PageEnum.PageSettingsServerInfo || PageEnum.PageSettingsSplitTunneling || tabBar.isServerInfoShow
|
tabBar.isServerInfoShow = (page === PageEnum.PageSettingsServerInfo) || (page === PageEnum.PageSettingsSplitTunneling) || tabBar.isServerInfoShow
|
||||||
}
|
}
|
||||||
|
|
||||||
function onGoToStartPage() {
|
function onGoToStartPage() {
|
||||||
|
|
@ -70,6 +70,21 @@ PageType {
|
||||||
tabBarStackView.pop()
|
tabBarStackView.pop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onEscapePressed() {
|
||||||
|
if (!tabBar.enabled || busyIndicator.visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var pageName = tabBarStackView.currentItem.objectName
|
||||||
|
if ((pageName === PageController.getPagePath(PageEnum.PageShare)) ||
|
||||||
|
(pageName === PageController.getPagePath(PageEnum.PageSettings))) {
|
||||||
|
PageController.goToPageHome()
|
||||||
|
tabBar.previousIndex = 0
|
||||||
|
} else {
|
||||||
|
PageController.closePage()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
|
@ -107,7 +122,7 @@ PageType {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onNoInstalledContainers() {
|
function onNoInstalledContainers() {
|
||||||
PageController.setTriggeredBtConnectButton(true)
|
PageController.setTriggeredByConnectButton(true)
|
||||||
|
|
||||||
ServersModel.processedIndex = ServersModel.getDefaultServerIndex()
|
ServersModel.processedIndex = ServersModel.getDefaultServerIndex()
|
||||||
InstallController.setShouldCreateServer(false)
|
InstallController.setShouldCreateServer(false)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ PLIST_NAME=$APP_NAME.plist
|
||||||
|
|
||||||
# Search Qt
|
# Search Qt
|
||||||
if [ -z "${QT_VERSION+x}" ]; then
|
if [ -z "${QT_VERSION+x}" ]; then
|
||||||
QT_VERSION=6.5.2;
|
QT_VERSION=6.6.2;
|
||||||
QT_BIN_DIR=$HOME/Qt/$QT_VERSION/ios/bin
|
QT_BIN_DIR=$HOME/Qt/$QT_VERSION/ios/bin
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ QMAKE_STASH_FILE=$PROJECT_DIR/.qmake_stash
|
||||||
|
|
||||||
# Search Qt
|
# Search Qt
|
||||||
if [ -z "${QT_VERSION+x}" ]; then
|
if [ -z "${QT_VERSION+x}" ]; then
|
||||||
QT_VERSION=5.15.2
|
QT_VERSION=6.6.2
|
||||||
if [ -f /opt/Qt/$QT_VERSION/gcc_64/bin/qmake ]; then
|
if [ -f /opt/Qt/$QT_VERSION/gcc_64/bin/qmake ]; then
|
||||||
QT_BIN_DIR=/opt/Qt/$QT_VERSION/gcc_64/bin
|
QT_BIN_DIR=/opt/Qt/$QT_VERSION/gcc_64/bin
|
||||||
elif [ -f $HOME/Qt/$QT_VERSION/gcc_64/bin/qmake ]; then
|
elif [ -f $HOME/Qt/$QT_VERSION/gcc_64/bin/qmake ]; then
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ DMG_FILENAME=$PROJECT_DIR/${APP_NAME}.dmg
|
||||||
|
|
||||||
# Search Qt
|
# Search Qt
|
||||||
if [ -z "${QT_VERSION+x}" ]; then
|
if [ -z "${QT_VERSION+x}" ]; then
|
||||||
QT_VERSION=6.5.1;
|
QT_VERSION=6.4.3;
|
||||||
QIF_VERSION=4.6
|
QIF_VERSION=4.6
|
||||||
QT_BIN_DIR=$HOME/Qt/$QT_VERSION/macos/bin
|
QT_BIN_DIR=$HOME/Qt/$QT_VERSION/macos/bin
|
||||||
QIF_BIN_DIR=$QT_BIN_DIR/../../../Tools/QtInstallerFramework/$QIF_VERSION/bin
|
QIF_BIN_DIR=$QT_BIN_DIR/../../../Tools/QtInstallerFramework/$QIF_VERSION/bin
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue