mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-18 08:55:41 +03:00
Compare commits
15 Commits
fix/make_d
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0299ca9fe | ||
|
|
c7b1c2809f | ||
|
|
c9ed0baf3b | ||
|
|
2a3e3126ac | ||
|
|
98771027b7 | ||
|
|
0433e03bdc | ||
|
|
cb48667b91 | ||
|
|
d0a1af0381 | ||
|
|
fd0c773918 | ||
|
|
06372c8fd7 | ||
|
|
009ca981d5 | ||
|
|
c0cae0ff01 | ||
|
|
c28452a5da | ||
|
|
396ce23228 | ||
|
|
b05ee0a654 |
38
.github/actions/apple-install-cert/action.yml
vendored
Normal file
38
.github/actions/apple-install-cert/action.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# .github/actions/apple-install-cert/action.yml
|
||||
|
||||
name: Setup apple keychain
|
||||
description: Creates and configures a temporary build keychain
|
||||
|
||||
inputs:
|
||||
keychain-path:
|
||||
description: Path to the keychain
|
||||
required: true
|
||||
keychain-password:
|
||||
description: Password to the keychain
|
||||
required: true
|
||||
cert-base64:
|
||||
description: Base64-encoded certificate
|
||||
required: true
|
||||
cert-password:
|
||||
description: Certificate password
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Create keychain
|
||||
shell: bash
|
||||
env:
|
||||
KEYCHAIN_PATH: ${{ inputs.keychain-path }}
|
||||
KEYCHAIN_PASSWORD: ${{ inputs.keychain-password }}
|
||||
CERT_BASE64: ${{ inputs.cert-base64 }}
|
||||
CERT_PASSWORD: ${{ inputs.cert-password }}
|
||||
run: |
|
||||
CERT_PATH=$(mktemp /tmp/cert_XXXXXX.p12)
|
||||
trap "rm -f '$CERT_PATH'" EXIT
|
||||
|
||||
echo -n "$CERT_BASE64" | base64 --decode -o "$CERT_PATH"
|
||||
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
security import "$CERT_PATH" -k "$KEYCHAIN_PATH" -P "$CERT_PASSWORD" -A -t cert -f pkcs12
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
BIN
.github/actions/apple-setup-keychain/AppleWWDRCAG3.cer
vendored
Normal file
BIN
.github/actions/apple-setup-keychain/AppleWWDRCAG3.cer
vendored
Normal file
Binary file not shown.
57
.github/actions/apple-setup-keychain/action.yml
vendored
Normal file
57
.github/actions/apple-setup-keychain/action.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
# .github/actions/apple-setup-keychain/action.yml
|
||||
|
||||
name: Setup apple keychain
|
||||
description: Creates and configures a temporary build keychain
|
||||
|
||||
inputs:
|
||||
keychain-name:
|
||||
description: Name of the keychain
|
||||
required: false
|
||||
default: "ci-amnezia"
|
||||
keychain-password:
|
||||
description: The keychain password
|
||||
required: true
|
||||
lock-timeout:
|
||||
description: A timeout after exceeding which the keychain would be locked
|
||||
required: false
|
||||
default: "0"
|
||||
|
||||
outputs:
|
||||
keychain-path:
|
||||
description: "Full path to the keychain created"
|
||||
value: ${{ steps.setup.outputs.keychain-path }}
|
||||
keychain-name:
|
||||
description: "Actual name of the keychain created"
|
||||
value: ${{ steps.setup.outputs.keychain-name }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Setup keychain
|
||||
id: setup
|
||||
shell: bash
|
||||
env:
|
||||
KEYCHAIN_NAME: ${{ inputs.keychain-name }}
|
||||
KEYCHAIN_PASSWORD: ${{ inputs.keychain-password }}
|
||||
LOCK_TIMEOUT: ${{ inputs.lock-timeout }}
|
||||
run: |
|
||||
KEYCHAIN_PATH="$HOME/Library/Keychains/$KEYCHAIN_NAME.keychain-db"
|
||||
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
|
||||
if [[ "$LOCK_TIMEOUT" == "0" ]]; then
|
||||
security set-keychain-settings "$KEYCHAIN_PATH"
|
||||
else
|
||||
security set-keychain-settings -u -t "$LOCK_TIMEOUT" "$KEYCHAIN_PATH"
|
||||
fi
|
||||
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
|
||||
security import "${{ github.action_path }}/DeveloperIDG2CA.cer" -k "$KEYCHAIN_PATH" -A
|
||||
security import "${{ github.action_path }}/AppleWWDRCAG3.cer" -k "$KEYCHAIN_PATH" -A
|
||||
|
||||
security list-keychains -d user -s "$KEYCHAIN_PATH"
|
||||
security default-keychain -s "$KEYCHAIN_PATH"
|
||||
|
||||
echo "keychain-name=$KEYCHAIN_NAME" >> $GITHUB_OUTPUT
|
||||
echo "keychain-path=$KEYCHAIN_PATH" >> $GITHUB_OUTPUT
|
||||
31
.github/actions/apple-setup-provisioning-profile/action.yml
vendored
Normal file
31
.github/actions/apple-setup-provisioning-profile/action.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# .github/actions/apple-setup-provisioning-profile/action.yml
|
||||
|
||||
name: Setup provisioning profiles
|
||||
description: Decodes and installs provisioning profiles
|
||||
|
||||
inputs:
|
||||
provisioning_profile_base64:
|
||||
description: Base64-encoded provisioning profile
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Setup provisioning profile
|
||||
shell: bash
|
||||
run: |
|
||||
PROFILES_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||
TEMP_FILE=$(mktemp)
|
||||
|
||||
echo "${{ inputs.provisioning_profile_base64 }}" | base64 --decode > "$TEMP_FILE"
|
||||
|
||||
PROFILE_UUID=$(grep UUID -A1 -a "$TEMP_FILE" | grep -io "[-A-F0-9]\{36\}")
|
||||
if [[ -z "$PROFILE_UUID" ]]; then
|
||||
echo "Failed to extract UUID from provisioning profile"
|
||||
rm -f "$TEMP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$PROFILES_DIR"
|
||||
mv "$TEMP_FILE" "$PROFILES_DIR/$PROFILE_UUID.mobileprovision"
|
||||
echo "Installed profile: $PROFILE_UUID"
|
||||
743
.github/workflows/deploy.yml
vendored
743
.github/workflows/deploy.yml
vendored
@@ -9,9 +9,60 @@ env:
|
||||
QT_MIRROR: https://mirrors.ocf.berkeley.edu/qt/ # https://download.qt.io/static/mirrorlist/
|
||||
|
||||
jobs:
|
||||
Detect-Changes:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
recipes_changed: ${{ steps.filter.outputs.recipes }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
base: ${{ github.event.before }}
|
||||
filters: |
|
||||
recipes:
|
||||
- 'recipes/**'
|
||||
- 'conanfile.py'
|
||||
|
||||
Bake-Prebuilts-Linux:
|
||||
runs-on: ubuntu-latest
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
shell: bash
|
||||
run: |
|
||||
for build_type in Release Debug; do
|
||||
cmake -S . -B build -DPREBUILTS_ONLY=1 "-DCMAKE_BUILD_TYPE=$build_type"
|
||||
done
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Linux-Ubuntu:
|
||||
runs-on: android-runner
|
||||
|
||||
needs: Bake-Prebuilts-Linux
|
||||
if: ${{ always() }}
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.10.1
|
||||
QIF_VERSION: 4.7
|
||||
@@ -39,7 +90,18 @@ jobs:
|
||||
set-env: 'true'
|
||||
aqtversion: '==3.3.0'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Setup python'
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Install system packages'
|
||||
run: sudo apt-get install libxkbcommon-x11-0 libsecret-1-dev
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
@@ -47,38 +109,17 @@ jobs:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
# - name: 'Setup ccache'
|
||||
# uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
sudo apt-get install libxkbcommon-x11-0 libsecret-1-dev
|
||||
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64/bin
|
||||
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
|
||||
bash deploy/build_linux.sh
|
||||
|
||||
- name: 'Pack installer'
|
||||
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin && zip AmneziaVPN_${VERSION}_linux_x64.tar.zip AmneziaVPN_Linux_Installer.tar
|
||||
shell: bash
|
||||
env:
|
||||
QT_INSTALL_DIR: ${{ runner.temp }}
|
||||
run: deploy/build.sh --generator Ninja --installer all
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
|
||||
path: deploy/AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_Linux_unpacked
|
||||
path: deploy/AppDir
|
||||
path: deploy/build/AmneziaVPN-*-Linux.run
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload translations artifact'
|
||||
@@ -88,15 +129,48 @@ jobs:
|
||||
path: client/translations
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-Windows:
|
||||
runs-on: windows-latest
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -DPREBUILTS_ONLY=1
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
needs: Bake-Prebuilts-Windows
|
||||
if: ${{ always() }}
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.10.1
|
||||
QIF_VERSION: 4.7
|
||||
BUILD_ARCH: 64
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
@@ -113,17 +187,6 @@ jobs:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
# - name: 'Setup ccache'
|
||||
# uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
@@ -160,50 +223,85 @@ jobs:
|
||||
$wixBinDir = Join-Path $env:USERPROFILE ".dotnet\tools"
|
||||
echo "WIX_BIN_DIR=$wixBinDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: 'Setup python'
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build project'
|
||||
shell: cmd
|
||||
env:
|
||||
QT_INSTALL_DIR: ${{ runner.temp }}
|
||||
run: |
|
||||
set BUILD_ARCH=${{ env.BUILD_ARCH }}
|
||||
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2022_64\\bin"
|
||||
set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
|
||||
set WIX_BIN_DIR=%USERPROFILE%\.dotnet\tools
|
||||
call deploy\\build_windows.bat
|
||||
set WIX_ROOT_PATH="${{ env.USERPROFILE }}\.dotnet\tools"
|
||||
deploy\build.bat --installer all
|
||||
|
||||
- name: 'Rename Windows installer'
|
||||
shell: cmd
|
||||
run: |
|
||||
copy AmneziaVPN_x${{ env.BUILD_ARCH }}.exe AmneziaVPN_%VERSION%_x64.exe
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: 'Upload WIX installer artifact'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: AmneziaVPN_${{ env.VERSION }}_x64.exe
|
||||
path: AmneziaVPN_${{ env.VERSION }}_x64.exe
|
||||
path: deploy/build/AmneziaVPN-*-win64.msi
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload IFW installer artifact'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/AmneziaVPN-*-win64.exe
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload MSI installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_Windows_MSI_installer
|
||||
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.msi
|
||||
retention-days: 7
|
||||
# ------------------------------------------------------
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
Bake-Prebuilts-iOS:
|
||||
runs-on: macos-latest
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
xcode-version: [26.0]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
name: AmneziaVPN_Windows_unpacked
|
||||
path: deploy\\build_${{ env.BUILD_ARCH }}\\client\\Release
|
||||
retention-days: 7
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1 -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-iOS:
|
||||
runs-on: macos-latest
|
||||
|
||||
needs: Bake-Prebuilts-iOS
|
||||
if: ${{ always() }}
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.10.1
|
||||
CC: cc
|
||||
CXX: c++
|
||||
KEYCHAIN_PASSWORD: ""
|
||||
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
@@ -214,6 +312,35 @@ jobs:
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: ./.github/actions/apple-setup-keychain
|
||||
id: setup-keychain
|
||||
with:
|
||||
keychain-password: ${{ env.KEYCHAIN_PASSWORD }}
|
||||
|
||||
- name: 'Install signing certificate'
|
||||
uses: ./.github/actions/apple-install-cert
|
||||
with:
|
||||
keychain-path: ${{ steps.setup-keychain.outputs.keychain-path }}
|
||||
keychain-password: ${{ env.KEYCHAIN_PASSWORD }}
|
||||
cert-base64: ${{ secrets.IOS_SIGNING_CERT_BASE64 }}
|
||||
cert-password: ${{ secrets.IOS_SIGNING_CERT_PASSWORD }}
|
||||
|
||||
- name: 'Install app provisioning profile'
|
||||
uses: ./.github/actions/apple-setup-provisioning-profile
|
||||
with:
|
||||
provisioning_profile_base64: ${{ secrets.IOS_APP_PROVISIONING_PROFILE }}
|
||||
|
||||
- name: 'Install NE provisioning profile'
|
||||
uses: ./.github/actions/apple-setup-provisioning-profile
|
||||
with:
|
||||
provisioning_profile_base64: ${{ secrets.IOS_NE_PROVISIONING_PROFILE }}
|
||||
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
@@ -243,159 +370,76 @@ jobs:
|
||||
set-env: 'true'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install go'
|
||||
uses: actions/setup-go@v5
|
||||
- name: 'Setup python'
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
go-version: '1.24'
|
||||
cache: false
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Setup gomobile'
|
||||
run: |
|
||||
export PATH=$PATH:~/go/bin
|
||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
gomobile init
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
# - name: 'Setup ccache'
|
||||
# uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: pip install jsonschema jinja2
|
||||
- name: 'Install deps'
|
||||
run: pip install "conan==2.26.2" jsonschema jinja2
|
||||
|
||||
- name: 'Build project'
|
||||
env:
|
||||
QT_INSTALL_DIR: ${{ runner.temp }}
|
||||
APPSTORE_CONNECT_KEY_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ID }}
|
||||
APPSTORE_CONNECT_ISSUER_ID: ${{ secrets.APPSTORE_CONNECT_ISSUER_ID }}
|
||||
APPSTORE_CONNECT_PRIVATE_KEY: ${{ secrets.APPSTORE_CONNECT_PRIVATE_KEY }}
|
||||
run: |
|
||||
git submodule update --init --recursive
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/ios/bin"
|
||||
export QT_MACOS_ROOT_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos"
|
||||
export PATH=$PATH:~/go/bin
|
||||
sh deploy/build_ios.sh | \
|
||||
sh deploy/build.sh -t ios | \
|
||||
sed -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/d' | \
|
||||
sed -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/d' | \
|
||||
sed -e '/-DPROD_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DPROD_AGW_PUBLIC_KEY/d' | \
|
||||
sed -e '/-DDEV_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DDEV_AGW_PUBLIC_KEY/d'
|
||||
env:
|
||||
IOS_TRUST_CERT_BASE64: ${{ secrets.IOS_TRUST_CERT_BASE64 }}
|
||||
IOS_SIGNING_CERT_BASE64: ${{ secrets.IOS_SIGNING_CERT_BASE64 }}
|
||||
IOS_SIGNING_CERT_PASSWORD: ${{ secrets.IOS_SIGNING_CERT_PASSWORD }}
|
||||
APPSTORE_CONNECT_KEY_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ID }}
|
||||
APPSTORE_CONNECT_ISSUER_ID: ${{ secrets.APPSTORE_CONNECT_ISSUER_ID }}
|
||||
APPSTORE_CONNECT_PRIVATE_KEY: ${{ secrets.APPSTORE_CONNECT_PRIVATE_KEY }}
|
||||
IOS_APP_PROVISIONING_PROFILE: ${{ secrets.IOS_APP_PROVISIONING_PROFILE }}
|
||||
IOS_NE_PROVISIONING_PROFILE: ${{ secrets.IOS_NE_PROVISIONING_PROFILE }}
|
||||
|
||||
# - name: 'Upload appstore .ipa and dSYMs to artifacts'
|
||||
# uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: app-store ipa & dsyms
|
||||
# path: |
|
||||
# ${{ github.workspace }}/AmneziaVPN-iOS.ipa
|
||||
# ${{ github.workspace }}/*.app.dSYM.zip
|
||||
# retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS-old:
|
||||
Bake-Prebuilts-MacOS:
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
|
||||
QT_VERSION: 6.4.3
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
|
||||
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
|
||||
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
|
||||
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
|
||||
|
||||
MAC_INSTALLER_SIGNER_CERT: ${{ secrets.MAC_INSTALLER_SIGNER_CERT }}
|
||||
MAC_INSTALLER_SIGNER_ID: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
|
||||
MAC_INSTALL_CERT_PW: ${{ secrets.MAC_INSTALL_CERT_PW }}
|
||||
|
||||
APPLE_DEV_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
|
||||
APPLE_DEV_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
|
||||
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
strategy:
|
||||
matrix:
|
||||
xcode-version: [16.2, 16.4]
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '15.4.0'
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
arch: 'clang_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
# - name: 'Setup ccache'
|
||||
# uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||
bash deploy/build_macos.sh -n
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_old_installer
|
||||
path: deploy/build/pkg/AmneziaVPN.pkg
|
||||
retention-days: 7
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_old_unpacked
|
||||
path: deploy/build/client/AmneziaVPN.app
|
||||
retention-days: 7
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS:
|
||||
runs-on: macos-latest
|
||||
|
||||
needs: Bake-Prebuilts-MacOS
|
||||
if: ${{ always() }}
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.10.1
|
||||
|
||||
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
|
||||
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
|
||||
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
|
||||
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
|
||||
|
||||
MAC_INSTALLER_SIGNER_CERT: ${{ secrets.MAC_INSTALLER_SIGNER_CERT }}
|
||||
MAC_INSTALLER_SIGNER_ID: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
|
||||
MAC_INSTALL_CERT_PW: ${{ secrets.MAC_INSTALL_CERT_PW }}
|
||||
|
||||
APPLE_DEV_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
|
||||
APPLE_DEV_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ""
|
||||
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
@@ -407,6 +451,31 @@ jobs:
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: ./.github/actions/apple-setup-keychain
|
||||
id: setup-keychain
|
||||
with:
|
||||
keychain-password: ${{ env.KEYCHAIN_PASSWORD }}
|
||||
|
||||
- uses: ./.github/actions/apple-install-cert
|
||||
with:
|
||||
keychain-path: ${{ steps.setup-keychain.outputs.keychain-path }}
|
||||
keychain-password: ${{ env.KEYCHAIN_PASSWORD }}
|
||||
cert-base64: ${{ secrets.MAC_APP_CERT_CERT }}
|
||||
cert-password: ${{ secrets.MAC_APP_CERT_PW }}
|
||||
|
||||
- uses: ./.github/actions/apple-install-cert
|
||||
with:
|
||||
keychain-path: ${{ steps.setup-keychain.outputs.keychain-path }}
|
||||
keychain-password: ${{ env.KEYCHAIN_PASSWORD }}
|
||||
cert-base64: ${{ secrets.MAC_INSTALLER_SIGNER_CERT }}
|
||||
cert-password: ${{ secrets.MAC_INSTALL_CERT_PW }}
|
||||
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
@@ -425,52 +494,79 @@ jobs:
|
||||
set-env: 'true'
|
||||
aqtversion: '==3.3.0'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Setup python'
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build project'
|
||||
env:
|
||||
QT_INSTALL_DIR: ${{ runner.temp }}
|
||||
CODESIGN_SIGNATURE: ${{ secrets.MAC_SIGNER_ID }}
|
||||
CODESIGN_INSTALLER_SIGNATURE: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
|
||||
NOTARYTOOL_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
NOTARYTOOL_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
|
||||
NOTARYTOOL_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
|
||||
shell: bash
|
||||
run: deploy/build.sh --generator Ninja --installer all
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/AmneziaVPN-*-Darwin.pkg
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-MacOS-NE:
|
||||
runs-on: macos-latest
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
xcode-version: [16.2, 16.4]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
# - name: 'Setup ccache'
|
||||
# uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||
bash deploy/build_macos.sh -n
|
||||
|
||||
- name: 'Pack macOS installer'
|
||||
run: |
|
||||
cd deploy/build/pkg
|
||||
zip -r ../../AmneziaVPN_${VERSION}_macos.zip AmneziaVPN.pkg
|
||||
cd ../../..
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
name: AmneziaVPN_${{ env.VERSION }}_macos.zip
|
||||
path: deploy/AmneziaVPN_${{ env.VERSION }}_macos.zip
|
||||
retention-days: 7
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_unpacked
|
||||
path: deploy/build/client/AmneziaVPN.app
|
||||
retention-days: 7
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1 -DMACOS_NE=TRUE
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS-NE:
|
||||
runs-on: macos-latest
|
||||
|
||||
needs: Bake-Prebuilts-MacOS-NE
|
||||
if: ${{ always() }}
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.10.1
|
||||
|
||||
@@ -490,6 +586,14 @@ jobs:
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- uses: ./.github/actions/apple-setup-provisioning-profile
|
||||
with:
|
||||
provisioning_profile_base64: ${{ secrets.MAC_APP_PROVISIONING_PROFILE }}
|
||||
|
||||
- uses: ./.github/actions/apple-setup-provisioning-profile
|
||||
with:
|
||||
provisioning_profile_base64: ${{ secrets.MAC_NE_PROVISIONING_PROFILE }}
|
||||
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
@@ -525,8 +629,13 @@ jobs:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
# - name: 'Setup ccache'
|
||||
# uses: hendrikmuhs/ccache-action@v1.2
|
||||
- name: 'Setup python'
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
@@ -540,13 +649,69 @@ jobs:
|
||||
path: deploy/build/client/AmneziaVPN.app
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-Android:
|
||||
runs-on: android-runner
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
env:
|
||||
ANDROID_PLATFORM: android-28
|
||||
NDK_VERSION: 27.0.11718014
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Setup Android SDK'
|
||||
uses: android-actions/setup-android@v4
|
||||
with:
|
||||
packages: "platforms;${{ env.ANDROID_PLATFORM }} ndk;${{ env.NDK_VERSION }}"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: |
|
||||
CMAKE_ANDROID_NDK="$ANDROID_HOME/ndk/$NDK_VERSION"
|
||||
args=(
|
||||
-G Ninja
|
||||
-DPREBUILTS_ONLY=1
|
||||
-DCMAKE_SYSTEM_NAME=Android
|
||||
"-DANDROID_PLATFORM=$ANDROID_PLATFORM"
|
||||
"-DCMAKE_ANDROID_NDK=$CMAKE_ANDROID_NDK"
|
||||
)
|
||||
|
||||
for abi in arm64-v8a armeabi-v7a x86 x86_64; do
|
||||
for build_type in Release Debug; do
|
||||
cmake -S . -B build_${abi//-/_} "${args[@]}" "-DCMAKE_ANDROID_ARCH_ABI=$abi" "-DCMAKE_BUILD_TYPE=$build_type"
|
||||
done
|
||||
done
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Android:
|
||||
runs-on: android-runner
|
||||
|
||||
needs: Bake-Prebuilts-Android
|
||||
if: ${{ always() }}
|
||||
|
||||
env:
|
||||
ANDROID_BUILD_PLATFORM: android-36
|
||||
ANDROID_PLATFORM: android-28
|
||||
NDK_VERSION: 27.0.11718014
|
||||
QT_VERSION: 6.10.1
|
||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
@@ -559,6 +724,11 @@ jobs:
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
@@ -619,38 +789,24 @@ jobs:
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Grant execute permission for qt-cmake'
|
||||
shell: bash
|
||||
run: |
|
||||
chmod +x ${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/android_x86_64/bin/qt-cmake
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
# - name: 'Setup ccache'
|
||||
# uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Setup Java'
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
# cache: 'gradle'
|
||||
|
||||
- name: 'Setup Android NDK'
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
- name: 'Setup Android SDK'
|
||||
uses: android-actions/setup-android@v4
|
||||
with:
|
||||
ndk-version: 'r26b'
|
||||
packages: "platforms;${{ env.ANDROID_PLATFORM }} ndk;${{ env.NDK_VERSION }}"
|
||||
|
||||
- name: 'Setup python'
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Decode keystore secret to file'
|
||||
env:
|
||||
@@ -661,61 +817,64 @@ jobs:
|
||||
|
||||
- name: 'Build project'
|
||||
env:
|
||||
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
QT_HOST_PATH: ${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64
|
||||
ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/android.keystore
|
||||
ANDROID_KEYSTORE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }}
|
||||
ANDROID_KEYSTORE_KEY_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }}
|
||||
QT_INSTALL_DIR: ${{ runner.temp }}
|
||||
QT_ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/android.keystore
|
||||
QT_ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }}
|
||||
QT_ANDROID_KEYSTORE_STORE_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }}
|
||||
shell: bash
|
||||
run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
|
||||
|
||||
- name: 'Rename Android APKs'
|
||||
run: |
|
||||
cd deploy/build
|
||||
mv AmneziaVPN-x86_64-release.apk AmneziaVPN_${VERSION}_android9+_x86_64.apk
|
||||
mv AmneziaVPN-x86-release.apk AmneziaVPN_${VERSION}_android9+_x86.apk
|
||||
mv AmneziaVPN-arm64-v8a-release.apk AmneziaVPN_${VERSION}_android9+_arm64-v8a.apk
|
||||
mv AmneziaVPN-armeabi-v7a-release.apk AmneziaVPN_${VERSION}_android9+_armeabi-v7a.apk
|
||||
cd ../..
|
||||
deploy/build.sh -t android --sign --aab
|
||||
|
||||
- name: 'Upload x86_64 apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
VERSION=$(grep CMAKE_PROJECT_VERSION:STATIC deploy/build/CMakeCache.txt | cut -d= -f2)
|
||||
|
||||
(cd deploy/build/client/android-build && mv AmneziaVPN.apk AmneziaVPN_${VERSION}_android9+_universal.apk)
|
||||
(cd deploy/build/client/android-build/build/outputs/bundle/release && mv android-build-release.aab AmneziaVPN_${VERSION}.aab)
|
||||
|
||||
for abi in arm64-v8a armeabi-v7a x86 x86_64; do
|
||||
deploy/build.sh -t android --sign --abi ${abi} --build ./deploy/build/${abi}
|
||||
(cd deploy/build/${abi}/client/android-build && mv AmneziaVPN.apk AmneziaVPN_${VERSION}_android9+_${abi}.apk)
|
||||
done
|
||||
|
||||
- name: 'Upload universal APK'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
|
||||
compression-level: 0
|
||||
path: deploy/build/client/android-build/*.apk
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload x86 apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: 'Upload AAB'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
|
||||
compression-level: 0
|
||||
path: deploy/build/client/android-build/build/outputs/bundle/release/*.aab
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload arm64-v8a apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: 'Upload arm64-v8a APK'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
|
||||
compression-level: 0
|
||||
path: deploy/build/arm64-v8a/client/android-build/*.apk
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload armeabi-v7a apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: 'Upload armeabi-v7a APK'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
|
||||
compression-level: 0
|
||||
path: deploy/build/armeabi-v7a/client/android-build/*.apk
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload aab'
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: 'Upload x86 APK'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: AmneziaVPN-android
|
||||
path: deploy/build/AmneziaVPN-release.aab
|
||||
compression-level: 0
|
||||
path: deploy/build/x86/client/android-build/*.apk
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload x86_64 APK'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/x86_64/client/android-build/*.apk
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
Extra:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -81,6 +81,7 @@ client/.DS_Store
|
||||
._.DS_Store
|
||||
._*
|
||||
*.dmg
|
||||
deploy/data/macos/pf/amn.400.allowPIA.conf
|
||||
|
||||
# tmp files
|
||||
*.*~
|
||||
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -4,10 +4,6 @@
|
||||
[submodule "client/3rd/SortFilterProxyModel"]
|
||||
path = client/3rd/SortFilterProxyModel
|
||||
url = https://github.com/mitchcurtis/SortFilterProxyModel.git
|
||||
[submodule "client/3rd-prebuilt"]
|
||||
path = client/3rd-prebuilt
|
||||
url = https://github.com/amnezia-vpn/3rd-prebuilt
|
||||
branch = feature/special-handshake
|
||||
[submodule "client/3rd/amneziawg-apple"]
|
||||
path = client/3rd/amneziawg-apple
|
||||
url = https://github.com/amnezia-vpn/amneziawg-apple
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.8.15.4)
|
||||
|
||||
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
|
||||
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
|
||||
${CMAKE_SOURCE_DIR}/cmake/platform_settings.cmake
|
||||
${CMAKE_SOURCE_DIR}/cmake/recipes_bootstrap.cmake
|
||||
${CMAKE_SOURCE_DIR}/cmake/conan_provider.cmake
|
||||
CACHE STRING "" FORCE)
|
||||
|
||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
|
||||
if (PREBUILTS_ONLY)
|
||||
# trigger conan to kick off `conan install`
|
||||
find_package(OpenSSL REQUIRED)
|
||||
return()
|
||||
endif()
|
||||
|
||||
string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
@@ -29,59 +45,34 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
|
||||
endif()
|
||||
|
||||
set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if(APPLE)
|
||||
if(IOS)
|
||||
set(CMAKE_OSX_ARCHITECTURES "arm64")
|
||||
elseif(MACOS_NE)
|
||||
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
|
||||
if(APPLE AND NOT IOS)
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(AMN_PF_RULE_IDENTITY "user { root }")
|
||||
else()
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64")
|
||||
set(AMN_PF_RULE_IDENTITY "group { amnvpn }")
|
||||
endif()
|
||||
|
||||
configure_file(
|
||||
"${CMAKE_SOURCE_DIR}/deploy/data/pf-templates/amn.400.allowPIA.conf.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/amn.400.allowPIA.conf"
|
||||
@ONLY
|
||||
)
|
||||
|
||||
file(COPY_FILE
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/amn.400.allowPIA.conf"
|
||||
"${CMAKE_SOURCE_DIR}/deploy/data/macos/pf/amn.400.allowPIA.conf"
|
||||
ONLY_IF_DIFFERENT
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
add_subdirectory(client)
|
||||
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
add_subdirectory(service)
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
|
||||
endif()
|
||||
|
||||
set(AMNEZIA_STAGE_DIR "${CMAKE_BINARY_DIR}/stage")
|
||||
|
||||
if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
file(TO_CMAKE_PATH "${AMNEZIA_STAGE_DIR}" AMNEZIA_STAGE_DIR_CMAKE)
|
||||
|
||||
set(CPACK_GENERATOR "WIX")
|
||||
set(CPACK_WIX_VERSION 4)
|
||||
set(CPACK_PACKAGE_NAME "AmneziaVPN")
|
||||
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
|
||||
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
|
||||
set(AMNEZIA_LICENSE_TXT "${CMAKE_BINARY_DIR}/LICENSE.txt")
|
||||
configure_file("${CMAKE_SOURCE_DIR}/LICENSE" "${AMNEZIA_LICENSE_TXT}" COPYONLY)
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${AMNEZIA_LICENSE_TXT}")
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
|
||||
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")
|
||||
set(CPACK_WIX_UPGRADE_GUID "{2D55AC62-96D6-4692-8C05-0D85BBF95485}")
|
||||
set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/client/images/app.ico")
|
||||
|
||||
# WiX patches
|
||||
set(_AMNEZIA_WIX_PATCH_SERVICE "${CMAKE_SOURCE_DIR}/deploy/installer/wix/service_install_patch.xml")
|
||||
set(_AMNEZIA_WIX_PATCH_CLOSE_APP "${CMAKE_SOURCE_DIR}/deploy/installer/wix/close_client_patch.xml")
|
||||
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_SERVICE}" _AMNEZIA_WIX_PATCH_SERVICE_CMAKE)
|
||||
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_CLOSE_APP}" _AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE)
|
||||
set(CPACK_WIX_PATCH_FILE "${_AMNEZIA_WIX_PATCH_SERVICE_CMAKE};${_AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE}")
|
||||
|
||||
# WiX v4 Util extension for CloseApplication + namespace for util
|
||||
set(CPACK_WIX_EXTENSIONS "${CPACK_WIX_EXTENSIONS};WixToolset.Util.wixext")
|
||||
set(CPACK_WIX_CUSTOM_XMLNS "util=http://wixtoolset.org/schemas/v4/wxs/util")
|
||||
|
||||
set(CPACK_INSTALLED_DIRECTORIES "${AMNEZIA_STAGE_DIR_CMAKE};/")
|
||||
|
||||
include(CPack)
|
||||
if ((LINUX AND NOT ANDROID) OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (WIN32))
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/CPack.cmake)
|
||||
endif()
|
||||
|
||||
167
README.md
167
README.md
@@ -53,24 +53,14 @@ AmneziaVPN uses several open-source projects to work:
|
||||
|
||||
- [OpenSSL](https://www.openssl.org/)
|
||||
- [OpenVPN](https://openvpn.net/)
|
||||
- [Shadowsocks](https://shadowsocks.org/)
|
||||
- [Qt](https://www.qt.io/)
|
||||
- [LibSsh](https://libssh.org) - forked from Qt Creator
|
||||
- [LibSsh](https://libssh.org)
|
||||
- [WireGuard](https://www.wireguard.com/)
|
||||
- [Xray-core](https://xtls.github.io/en/)
|
||||
- [Conan](https://conan.io/)
|
||||
- and more...
|
||||
|
||||
## Checking out the source code
|
||||
|
||||
Make sure to pull all submodules after checking out the repo.
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Want to contribute? Welcome!
|
||||
|
||||
### Help with translations
|
||||
## Help us with translations
|
||||
|
||||
Download the most actual translation files.
|
||||
|
||||
@@ -83,99 +73,98 @@ Each *.ts file contains strings for one corresponding language.
|
||||
Translate or correct some strings in one or multiple *.ts files and commit them back to this repository into the ``client/translations`` folder.
|
||||
You can do it via a web-interface or any other method you're familiar with.
|
||||
|
||||
### Building sources and deployment
|
||||
## Checking out the source code
|
||||
|
||||
Check deploy folder for build scripts.
|
||||
Make sure to pull all submodules after checking out the repo.
|
||||
|
||||
### 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.
|
||||
|
||||
2. We use QT to generate the XCode project. We need QT version 6.6.2. 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
|
||||
- iOS
|
||||
- Qt 5 Compatibility Module
|
||||
- Qt Shader Tools
|
||||
- Additional Libraries:
|
||||
- Qt Image Formats
|
||||
- Qt Multimedia
|
||||
- Qt Remote Objects
|
||||
|
||||
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 installed already,
|
||||
download go from the [official website](https://golang.org/dl/) or use Homebrew.
|
||||
The latest version is recommended. Install gomobile
|
||||
```bash
|
||||
export PATH=$PATH:~/go/bin
|
||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
gomobile init
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
5. Build the project
|
||||
## Hacking guide
|
||||
|
||||
Want to contribute? Welcome!
|
||||
|
||||
### Build requirements
|
||||
|
||||
* [`CMake`](https://cmake.org/download/)
|
||||
* Compiler and underlying build system, depending on the target:
|
||||
- [Linux] Any of `make` and `gcc`
|
||||
- [Apple] [`Xcode`](https://developer.apple.com/xcode/) or [`Xcode command line tools`](https://developer.apple.com/xcode/)
|
||||
- [Windows] [`Visual Studio 2022`](https://aka.ms/vs/17/release/vs_community.exe) or [`VS 2022 Build Tools`](https://aka.ms/vs/17/release/vs_buildtools.exe)
|
||||
- [Android] [`Android SDK`](#installing-android-sdk) and [`Ninja`](https://ninja-build.org/)
|
||||
* [`Qt 6.10+`](https://www.qt.io/download-open-source) with the following modules:
|
||||
- Core module for targeting platform (Desktop/Android/iOS)
|
||||
- Qt 5 Compatibility module
|
||||
- Qt Remote Objects
|
||||
* [`Conan`](https://conan.io/downloads) package manager
|
||||
- On MacOS is enough just to use `homebrew` or install it in `.venv` in project root
|
||||
- Other systems must have it in `PATH`
|
||||
* (Optional) Installer dependencies:
|
||||
- [Windows/Linux] [`Qt Installer Framework`](https://www.qt.io/download-open-source)
|
||||
- [Windows] [`WIX toolset`](https://github.com/wixtoolset/wix/releases)
|
||||
|
||||
### Building the project using scripts
|
||||
|
||||
* Run scripts located in `deploy` directory
|
||||
* Basically, if dependencies are located in default installation paths, the scripts will find them automatically.
|
||||
* If they differ, specify them using the following variables:
|
||||
- `QT_INSTALL_DIR` - Qt root installation folder
|
||||
- `QT_ROOT_PATH` - Qt framework root directory
|
||||
- `QIF_ROOT_PATH` - Qt Installer Framework root path
|
||||
- `ANDROID_HOME` - Path to Android SDK root folder
|
||||
- and others. Check scripts for more
|
||||
|
||||
Unix-like:
|
||||
```bash
|
||||
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_IOS_BIN=$QT_BIN_DIR
|
||||
export PATH=$PATH:~/go/bin
|
||||
mkdir build-ios
|
||||
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
|
||||
```
|
||||
Replace PATH-TO-QT-FOLDER and QT-VERSION to your environment
|
||||
# Build executables for the host platform
|
||||
deploy/build.sh
|
||||
|
||||
# Or just
|
||||
deploy/build.sh
|
||||
|
||||
If you get `gomobile: command not found` make sure to set PATH to the location
|
||||
of the bin folder where gomobile was installed. Usually, it's in `GOPATH`.
|
||||
```bash
|
||||
export PATH=$(PATH):/path/to/GOPATH/bin
|
||||
# Build executables and installers for the host platform
|
||||
deploy/build.sh --installer all
|
||||
|
||||
# Build Android APK and AAB
|
||||
deploy/build.sh -t android --aab
|
||||
|
||||
# Call for help
|
||||
deploy/build.sh -h
|
||||
```
|
||||
|
||||
6. Open the XCode project. You can then run /test/archive/ship the app.
|
||||
Windows:
|
||||
```batch
|
||||
:: Build executables for Windows
|
||||
deploy/build.bat
|
||||
|
||||
If the build fails with the following error
|
||||
```
|
||||
make: ***
|
||||
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
|
||||
Error 1
|
||||
```
|
||||
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`.
|
||||
:: Build executables with IFW installer for Windows
|
||||
deploy/build.bat --installer ifw
|
||||
|
||||
if the above error persists on your M1 Mac, then most probably you need to install arch based CMake
|
||||
```
|
||||
arch -arm64 brew install cmake
|
||||
:: Build executables with IFW and WIX installer for Windows
|
||||
deploy/build.bat --installer ifw --installer wix
|
||||
|
||||
:: Or just
|
||||
deploy/build.bat --installer all
|
||||
```
|
||||
|
||||
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
|
||||
require them. In this case, simply restart the build.
|
||||
### Developing the project in IDEs
|
||||
|
||||
## How to build the Android app
|
||||
* Basically, you can use any IDE that handles CMake and Qt kits properly to run configure and build steps, and to navigate through the code nicely. For example:
|
||||
- `Qt Creator`
|
||||
- `Visual Studio Code` with `Qt Extension Pack`
|
||||
- and so on
|
||||
|
||||
_Tested on Mac OS_
|
||||
* To use `Xcode`, you have to configure project first by using `cmake`. The easiest way to do it is to use `Qt Creator` for configuration. Then open `AmneziaVPN.xcodeproj` file from the build folder by using `Xcode`. Note that none of the files changed are saved - the files actually getting changed in build directory. Copy them manually if necessary
|
||||
|
||||
The Android app has the following requirements:
|
||||
* JDK 11
|
||||
* Android platform SDK 33
|
||||
* CMake 3.25.0
|
||||
* `Android studio` could be used in the same way - just configure the project by using `cmake` manually or by using `Qt Creator`. Open `<build-dir>/client/android-build` in `Android studio` then. Do not forget to copy the changes - everything you do is saved under the build directory actually.
|
||||
|
||||
After you have installed QT, QT Creator, and Android Studio, you need to configure QT Creator correctly.
|
||||
### Installing Android SDK
|
||||
|
||||
- Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`.
|
||||
- Set path to JDK 11
|
||||
- Set path to Android SDK (`$ANDROID_HOME`)
|
||||
|
||||
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`.
|
||||
|
||||
That's it! You should be ready to compile the project from QT Creator!
|
||||
|
||||
### Development flow
|
||||
|
||||
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.
|
||||
* Android SDK could be installed using the following methods:
|
||||
- Using `Qt Creator`. Use `Preferences`->`SDKs`
|
||||
- Using `Android studio`. By default it installs necessary `SDKs` automatically during the installation
|
||||
- Manually by using `sdk-manager`. Check [this](https://developer.android.com/tools) page for details
|
||||
|
||||
## License
|
||||
|
||||
|
||||
159
README_RU.md
159
README_RU.md
@@ -50,23 +50,14 @@ AmneziaVPN использует несколько проектов с откр
|
||||
|
||||
- [OpenSSL](https://www.openssl.org/)
|
||||
- [OpenVPN](https://openvpn.net/)
|
||||
- [Shadowsocks](https://shadowsocks.org/)
|
||||
- [Qt](https://www.qt.io/)
|
||||
- [LibSsh](https://libssh.org)
|
||||
- [WireGuard](https://www.wireguard.com/)
|
||||
- [Xray-core](https://xtls.github.io/en/)
|
||||
- [Conan](https://conan.io/)
|
||||
- и другие...
|
||||
|
||||
## Проверка исходного кода
|
||||
После клонирования репозитория обязательно загрузите все подмодули.
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
|
||||
## Разработка
|
||||
Хотите внести свой вклад? Добро пожаловать!
|
||||
|
||||
### Помощь с переводами
|
||||
## Помощь с переводами
|
||||
|
||||
Загрузите самые актуальные файлы перевода.
|
||||
|
||||
@@ -76,90 +67,98 @@ git submodule update --init --recursive
|
||||
|
||||
Переведите или исправьте строки в одном или нескольких файлах *.ts и загрузите их обратно в этот репозиторий в папку ``client/translations``. Это можно сделать через веб-интерфейс или любым другим знакомым вам способом.
|
||||
|
||||
### Сборка исходного кода и деплой
|
||||
Проверьте папку deploy для скриптов сборки.
|
||||
## Проверка исходного кода
|
||||
|
||||
### Как собрать iOS-приложение из исходного кода на MacOS
|
||||
1. Убедитесь, что у вас установлен Xcode версии 14 или выше.
|
||||
2. Для генерации проекта Xcode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
|
||||
- MacOS
|
||||
- iOS
|
||||
- Модуль совместимости с Qt 5
|
||||
- Qt Shader Tools
|
||||
- Дополнительные библиотеки:
|
||||
- Qt Image Formats
|
||||
- Qt Multimedia
|
||||
- Qt Remote Objects
|
||||
|
||||
|
||||
3. Установите CMake, если это необходимо. Рекомендуемая версия — 3.25. Скачать CMake можно здесь.
|
||||
4. Установите Go версии >= v1.16. Если Go ещё не установлен, скачайте его с [официального сайта](https://golang.org/dl/) или используйте Homebrew. Установите gomobile:
|
||||
После клонирования репозитория обязательно загрузите все подмодули.
|
||||
|
||||
```bash
|
||||
export PATH=$PATH:~/go/bin
|
||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
gomobile init
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
5. Соберите проект:
|
||||
## Руководство по разработке
|
||||
|
||||
Хотите внести свой вклад? Добро пожаловать!
|
||||
|
||||
### Требования для сборки
|
||||
|
||||
* [`CMake`](https://cmake.org/download/)
|
||||
* Компилятор и система сборки, в зависимости от таргета:
|
||||
- [Linux] Любые `make` и `gcc`
|
||||
- [Apple] [`Xcode`](https://developer.apple.com/xcode/) или [`Xcode command line tools`](https://developer.apple.com/xcode/)
|
||||
- [Windows] [`Visual Studio 2022`](https://aka.ms/vs/17/release/vs_community.exe) или [`VS 2022 Build Tools`](https://aka.ms/vs/17/release/vs_buildtools.exe)
|
||||
- [Android] [`Android SDK`](#установка-android-sdk) и [`Ninja`](https://ninja-build.org/)
|
||||
* [`Qt 6.10+`](https://www.qt.io/download-open-source) со следующими модулями:
|
||||
- Основные модули для таргета (Desktop/Android/iOS)
|
||||
- Qt 5 Compatibility module
|
||||
- Qt Remote Objects
|
||||
* Пакетный менеджер [`Conan`](https://conan.io/downloads)
|
||||
- На MacOS достаточно использовать `homebrew` или установить в `.venv` в корень проекта
|
||||
- Для остальных систем необходимо прописать пути в `PATH`
|
||||
* (Необязательно) Заивисимости для установщиков:
|
||||
- [Windows/Linux] [`Qt Installer Framework`](https://www.qt.io/download-open-source)
|
||||
- [Windows] [`WIX toolset`](https://github.com/wixtoolset/wix/releases)
|
||||
|
||||
### Сборка проекта через скрипты
|
||||
|
||||
* Запустите скрипты, находящиеся в папке `deploy`
|
||||
* Если все зависимости установлены в стандартных локациях, скрипт найдёт их самостоятельно
|
||||
* Если пути отличаются, их нужно явно указать используя:
|
||||
- `QT_INSTALL_DIR` - корневая папка установки Qt
|
||||
- `QT_ROOT_PATH` - корневая папка Qt Framework
|
||||
- `QIF_ROOT_PATH` - корневая папка Qt Installer Framework
|
||||
- `ANDROID_HOME` - путь к Android SDK
|
||||
- и другие. Их можно получить из вышеуказанных скриптов
|
||||
|
||||
Unix-like:
|
||||
```bash
|
||||
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_IOS_BIN=$QT_BIN_DIR
|
||||
export PATH=$PATH:~/go/bin
|
||||
mkdir build-ios
|
||||
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
|
||||
```
|
||||
Замените <PATH-TO-QT-FOLDER> и <QT-VERSION> на ваши значения.
|
||||
# Build executables for the host platform
|
||||
deploy/build.sh
|
||||
|
||||
Если появляется ошибка gomobile: command not found, убедитесь, что PATH настроен на папку bin, где установлен gomobile:
|
||||
```bash
|
||||
export PATH=$(PATH):/path/to/GOPATH/bin
|
||||
# Or just
|
||||
deploy/build.sh
|
||||
|
||||
# Build executables and installers for the host platform
|
||||
deploy/build.sh --installer all
|
||||
|
||||
# Build Android APK and AAB
|
||||
deploy/build.sh -t android --aab
|
||||
|
||||
# Call for help
|
||||
deploy/build.sh -h
|
||||
```
|
||||
|
||||
6. Откройте проект в Xcode. Теперь вы можете тестировать, архивировать или публиковать приложение.
|
||||
|
||||
Если сборка завершится с ошибкой:
|
||||
```
|
||||
make: ***
|
||||
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
|
||||
Error 1
|
||||
```
|
||||
Добавьте пользовательскую переменную PATH в настройки сборки для целей AmneziaVPN и WireGuardNetworkExtension с ключом `PATH` и значением `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
|
||||
Windows:
|
||||
```batch
|
||||
:: Build executables for Windows
|
||||
deploy/build.bat
|
||||
|
||||
Если ошибка повторяется на Mac с M1, установите версию CMake для архитектуры ARM:
|
||||
```
|
||||
arch -arm64 brew install cmake
|
||||
:: Build executables with IFW installer for Windows
|
||||
deploy/build.bat --installer ifw
|
||||
|
||||
:: Build executables with IFW and WIX installer for Windows
|
||||
deploy/build.bat --installer ifw --installer wix
|
||||
|
||||
:: Or just
|
||||
deploy/build.bat --installer all
|
||||
```
|
||||
|
||||
При первой попытке сборка может завершиться с ошибкой source files not found. Это происходит из-за параллельной компиляции зависимостей в XCode. Просто перезапустите сборку.
|
||||
### Разработка в IDE
|
||||
|
||||
* Можно использовать любые IDE которые умеют работать с CMake и находить Qt Kits. Например:
|
||||
- `Qt Creator`
|
||||
- `Visual Studio Code` with `Qt Extension Pack`
|
||||
- и так далее
|
||||
|
||||
## Как собрать Android-приложение
|
||||
Сборка тестировалась на MacOS. Требования:
|
||||
- JDK 11
|
||||
- Android SDK 33
|
||||
- CMake 3.25.0
|
||||
|
||||
Установите QT, QT Creator и Android Studio.
|
||||
Настройте QT Creator:
|
||||
* Для использования `Xcode` нужно сконфигурировать проект с помощью `cmake`. Самый простой способ это сделать - использовать `Qt Creator` для конфигурации. Затем, нужно открыть файл `AmneziaVPN.xcodeproj` из папки сборки с помощью `Xcode`. Учтите, что никакие файлы фактически не сохраняются - они сохраняются в директории сборки. Если требуется, скопируйте файлы вручную
|
||||
|
||||
- В меню QT Creator перейдите в `QT Creator` -> `Preferences` -> `Devices` ->`Android`.
|
||||
- Укажите путь к JDK 11.
|
||||
- Укажите путь к Android SDK (`$ANDROID_HOME`)
|
||||
* `Android studio` может быть использована подобным вышеуказанному способу - нужно использовать `cmake` вручную или через `Qt Creator` для конфигурации. Далее, откройте `<build-dir>/client/android-build` в `Android studio`. Не забудьте скопировать изменённые файлы в папку с исходным кодом - все файлы, изменённые в IDE, сохраняются фактически в папке сборки.
|
||||
|
||||
Если вы сталкиваетесь с ошибками, связанными с отсутствием SDK или сообщением «SDK manager not running», их нельзя исправить просто корректировкой путей. Если у вас есть несколько свободных гигабайт на диске, вы можете позволить Qt Creator установить все необходимые компоненты, выбрав пустую папку для расположения Android SDK и нажав кнопку **Set Up SDK**. Учтите: это установит второй Android SDK и NDK на вашем компьютере!
|
||||
|
||||
Убедитесь, что настроена правильная версия CMake: перейдите в **Qt Creator -> Preferences** и в боковом меню выберите пункт **Kits**. В центральной части окна, на вкладке **Kits**, найдите запись для инструмента **CMake Tool**. Если выбранная по умолчанию версия CMake ниже 3.25.0, установите на свою систему CMake версии 3.25.0 или выше, а затем выберите опцию **System CMake at <путь>** из выпадающего списка. Если этот пункт отсутствует, это может означать, что вы еще не установили CMake, или Qt Creator не смог найти путь к нему. В таком случае в окне **Preferences** перейдите в боковое меню **CMake**, затем во вкладку **Tools** в центральной части окна и нажмите кнопку **Add**, чтобы указать путь к установленному CMake.
|
||||
|
||||
Убедитесь, что для вашего проекта выбрана Android Platform SDK 33: в главном окне на боковой панели выберите пункт **Projects**, и слева вы увидите раздел **Build & Run**, показывающий различные целевые Android-платформы. Вы можете выбрать любую из них, так как настройка проекта Amnezia VPN разработана таким образом, чтобы все Android-цели могли быть собраны. Перейдите в подраздел **Build** и прокрутите центральную часть окна до раздела **Build Steps**. Нажмите **Details** в заголовке **Build Android APK** (кнопка **Details** может быть скрыта, если окно Qt Creator не запущено в полноэкранном режиме!). Вот здесь выберите **android-33** в качестве Android Build Platform SDK.
|
||||
|
||||
### Разработка Android-компонентов
|
||||
|
||||
После сборки QT Creator копирует проект в отдельную папку, например, `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`. Для разработки Android-компонентов откройте сгенерированный проект в Android Studio, указав папку `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` в качестве корневой.
|
||||
Изменения в сгенерированном проекте нужно вручную перенести в репозиторий. После этого можно коммитить изменения.
|
||||
Если возникают проблемы со сборкой в QT Creator после работы в Android Studio, выполните команду `./gradlew clean` в корневой папке сгенерированного проекта (`<path>/client/android-build/.`).
|
||||
### Установка Android SDK
|
||||
|
||||
* Android SDK может быть установлен следующими способами:
|
||||
- Используя `Qt Creator`, через настройки в пунктах `Preferences`->`SDKs`
|
||||
- Используя `Android studio`. По умолчанию необходимые `SDK` устанавливаются автоматически.
|
||||
- Вручную, используя `sdk-manager`. Подробности можно найти [здесь](https://developer.android.com/tools)
|
||||
|
||||
## Лицензия
|
||||
|
||||
|
||||
Submodule client/3rd-prebuilt deleted from 51bb4703a4
@@ -65,13 +65,12 @@ endif()
|
||||
qt6_add_resources(QRC ${QRC}
|
||||
${CMAKE_CURRENT_LIST_DIR}/images/images.qrc
|
||||
${CMAKE_CURRENT_LIST_DIR}/images/flagKit.qrc
|
||||
${CMAKE_CURRENT_LIST_DIR}/client_scripts/clientScripts.qrc
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qml/qml.qrc
|
||||
${CMAKE_CURRENT_LIST_DIR}/server_scripts/serverScripts.qrc
|
||||
)
|
||||
|
||||
# -- i18n begin
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(AMNEZIAVPN_TS_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru_RU.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
|
||||
@@ -83,20 +82,10 @@ set(AMNEZIAVPN_TS_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_hi_IN.ts
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
||||
list(FILTER AMNEZIAVPN_TS_SOURCES EXCLUDE REGEX "qtgamepad/examples")
|
||||
|
||||
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
|
||||
|
||||
set(QM_FILE_LIST "")
|
||||
foreach(FILE ${AMNEZIAVPN_QM_FILES})
|
||||
get_filename_component(QM_FILE_NAME ${FILE} NAME)
|
||||
list(APPEND QM_FILE_LIST "<file>${QM_FILE_NAME}</file>")
|
||||
endforeach()
|
||||
string(REPLACE ";" "" QM_FILE_LIST ${QM_FILE_LIST})
|
||||
|
||||
configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
|
||||
qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
|
||||
qt6_add_translations(${PROJECT}
|
||||
TS_FILES ${AMNEZIAVPN_TS_FILES}
|
||||
RESOURCE_PREFIX "/translations"
|
||||
)
|
||||
# -- i18n end
|
||||
|
||||
set(IS_CI ${CI})
|
||||
@@ -175,6 +164,10 @@ if(APPLE)
|
||||
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
|
||||
|
||||
if (BUILD_VPN_KEYCHAIN)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--keychain ${BUILD_VPN_KEYCHAIN}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(LINUX AND NOT ANDROID)
|
||||
@@ -200,45 +193,15 @@ elseif(APPLE)
|
||||
include(cmake/macos.cmake)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
|
||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
|
||||
|
||||
# deploy artifacts required to run the application to the debug build folder
|
||||
if(WIN32)
|
||||
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
|
||||
set(DEPLOY_PLATFORM_PATH "windows/x64")
|
||||
else()
|
||||
set(DEPLOY_PLATFORM_PATH "windows/x32")
|
||||
endif()
|
||||
elseif(LINUX)
|
||||
set(DEPLOY_PLATFORM_PATH "linux/client")
|
||||
elseif(APPLE AND NOT IOS)
|
||||
set(DEPLOY_PLATFORM_PATH "macos")
|
||||
endif()
|
||||
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
|
||||
${CMAKE_SOURCE_DIR}/deploy/data/${DEPLOY_PLATFORM_PATH}
|
||||
$<TARGET_FILE_DIR:${PROJECT}>
|
||||
COMMAND_EXPAND_LISTS
|
||||
)
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
|
||||
${CMAKE_SOURCE_DIR}/client/3rd-prebuilt/deploy-prebuilt/${DEPLOY_PLATFORM_PATH}
|
||||
$<TARGET_FILE_DIR:${PROJECT}>
|
||||
COMMAND_EXPAND_LISTS
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
list(APPEND SOURCES ${CMAKE_CURRENT_LIST_DIR}/main.cpp)
|
||||
|
||||
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
|
||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
|
||||
|
||||
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
||||
|
||||
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).
|
||||
@@ -250,3 +213,51 @@ if(COMMAND qt_finalize_executable)
|
||||
else()
|
||||
qt_finalize_target(${PROJECT})
|
||||
endif()
|
||||
|
||||
install(TARGETS ${PROJECT}
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
COMPONENT AmneziaVPN
|
||||
)
|
||||
install(FILES $<TARGET_RUNTIME_DLLS:${PROJECT}>
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
COMPONENT AmneziaVPN
|
||||
)
|
||||
|
||||
set(deploy_tool_options "")
|
||||
if(WIN32)
|
||||
set(deploy_tool_options "--force-openssl --force")
|
||||
endif()
|
||||
|
||||
qt_generate_deploy_qml_app_script(
|
||||
TARGET ${PROJECT}
|
||||
OUTPUT_SCRIPT QT_DEPLOY_SCRIPT
|
||||
NO_UNSUPPORTED_PLATFORM_ERROR
|
||||
DEPLOY_TOOL_OPTIONS ${deploy_tool_options}
|
||||
)
|
||||
install(SCRIPT ${QT_DEPLOY_SCRIPT}
|
||||
COMPONENT AmneziaVPN
|
||||
)
|
||||
|
||||
if (APPLE AND NOT IOS AND NOT MACOS_NE)
|
||||
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/macos/update-resolv-conf.sh")
|
||||
endif()
|
||||
if (LINUX AND NOT ANDROID)
|
||||
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/linux/update-resolv-conf.sh")
|
||||
endif()
|
||||
|
||||
if(OVPN_SCRIPTS)
|
||||
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${OVPN_SCRIPTS}
|
||||
"$<TARGET_FILE_DIR:${PROJECT}>"
|
||||
)
|
||||
|
||||
install(FILES ${OVPN_SCRIPTS}
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
COMPONENT AmneziaVPN
|
||||
PERMISSIONS
|
||||
OWNER_READ OWNER_EXECUTE
|
||||
GROUP_READ GROUP_EXECUTE
|
||||
WORLD_READ WORLD_EXECUTE
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -39,6 +39,7 @@ android {
|
||||
|
||||
// keeps language resources for only the locales specified below
|
||||
resourceConfigurations += listOf("en", "ru", "b+zh+Hans")
|
||||
ndk.abiFilters += qtTargetAbiList.split(",")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@@ -52,50 +53,12 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
register("release") {
|
||||
storeFile = providers.environmentVariable("ANDROID_KEYSTORE_PATH").orNull?.let { file(it) }
|
||||
storePassword = providers.environmentVariable("ANDROID_KEYSTORE_KEY_PASS").orNull
|
||||
keyAlias = providers.environmentVariable("ANDROID_KEYSTORE_KEY_ALIAS").orNull
|
||||
keyPassword = providers.environmentVariable("ANDROID_KEYSTORE_KEY_PASS").orNull
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// exclude coroutine debug resource from release build
|
||||
packaging {
|
||||
resources.excludes += "DebugProbesKt.bin"
|
||||
}
|
||||
signingConfig = signingConfigs["release"]
|
||||
}
|
||||
|
||||
create("fdroid") {
|
||||
initWith(getByName("release"))
|
||||
signingConfig = null
|
||||
matchingFallbacks += "release"
|
||||
}
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
isEnable = true
|
||||
reset()
|
||||
include(*qtTargetAbiList.split(',').toTypedArray())
|
||||
isUniversalApk = false
|
||||
}
|
||||
}
|
||||
|
||||
// fix for Qt Creator to allow deploying the application to a device
|
||||
// to enable this fix, add the line outputBaseName=android-build to local.properties
|
||||
if (outputBaseName.isNotEmpty()) {
|
||||
applicationVariants.all {
|
||||
outputs.map { it as BaseVariantOutputImpl }
|
||||
.forEach { output ->
|
||||
if (output.outputFileName.endsWith(".apk")) {
|
||||
output.outputFileName = "$outputBaseName-${buildType.name}.apk"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
[versions]
|
||||
agp = "8.5.2"
|
||||
agp = "8.6.1"
|
||||
kotlin = "1.9.24"
|
||||
androidx-core = "1.13.1"
|
||||
androidx-activity = "1.9.1"
|
||||
androidx-annotation = "1.8.2"
|
||||
androidx-biometric = "1.2.0-alpha05"
|
||||
androidx-camera = "1.3.4"
|
||||
androidx-camera = "1.5.3"
|
||||
androidx-fragment = "1.8.2"
|
||||
androidx-security-crypto = "1.1.0-alpha06"
|
||||
androidx-datastore = "1.1.1"
|
||||
|
||||
@@ -26,7 +26,6 @@ plugins {
|
||||
id("settings-property-delegate")
|
||||
}
|
||||
|
||||
rootProject.name = "AmneziaVPN"
|
||||
rootProject.buildFileName = "build.gradle.kts"
|
||||
|
||||
include(":qt")
|
||||
@@ -47,15 +46,7 @@ val qtMinSdkVersion: String by gradleProperties
|
||||
// set default values for all modules
|
||||
configure<SettingsExtension> {
|
||||
buildToolsVersion = androidBuildToolsVersion
|
||||
compileSdk = androidCompileSdkVersion.substringAfter('-').toInt()
|
||||
compileSdk = androidCompileSdkVersion.split('-')[1].toInt()
|
||||
minSdk = qtMinSdkVersion.toInt()
|
||||
ndkVersion = androidNdkVersion
|
||||
}
|
||||
|
||||
// stop Gradle running by androiddeployqt
|
||||
gradle.taskGraph.whenReady {
|
||||
if (providers.environmentVariable("ANDROIDDEPLOYQT_RUN").isPresent
|
||||
&& !providers.systemProperty("explicitRun").isPresent) {
|
||||
allTasks.forEach { it.enabled = false }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,10 +214,7 @@ class AmneziaActivity : QtActivity() {
|
||||
|
||||
private fun loadLibs() {
|
||||
listOf(
|
||||
"rsapss",
|
||||
"crypto_3",
|
||||
"ssl_3",
|
||||
"ssh"
|
||||
"rsapss"
|
||||
).forEach {
|
||||
loadSharedLibrary(this.applicationContext, it)
|
||||
}
|
||||
|
||||
5
client/client_scripts/clientScripts.qrc
Normal file
5
client/client_scripts/clientScripts.qrc
Normal file
@@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/client_scripts">
|
||||
<file>mac_installer.sh</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
42
client/client_scripts/mac_installer.sh
Normal file
42
client/client_scripts/mac_installer.sh
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
|
||||
EXTRACT_DIR="$1"
|
||||
INSTALLER_PATH="$2"
|
||||
|
||||
set -e
|
||||
|
||||
echo "[AmneziaVPN] Installer package: $INSTALLER_PATH"
|
||||
|
||||
if [ ! -f "$INSTALLER_PATH" ]; then
|
||||
echo "[AmneziaVPN] ERROR: Installer package not found: $INSTALLER_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PKG_PATH="$INSTALLER_PATH"
|
||||
echo "[AmneziaVPN] Using PKG: $PKG_PATH"
|
||||
|
||||
# Optional: basic signature/gatekeeper checks (non-fatal)
|
||||
if command -v pkgutil >/dev/null 2>&1; then
|
||||
pkgutil --check-signature "$PKG_PATH" || true
|
||||
fi
|
||||
if command -v spctl >/dev/null 2>&1; then
|
||||
spctl -a -vvv -t install "$PKG_PATH" || true
|
||||
fi
|
||||
|
||||
# Run installer with admin privileges via AppleScript (prompts for password)
|
||||
echo "[AmneziaVPN] Running installer..."
|
||||
OSA_CMD='do shell script "/usr/sbin/installer -pkg '"$PKG_PATH"' -target /" with administrator privileges'
|
||||
osascript -e "$OSA_CMD"
|
||||
|
||||
STATUS=$?
|
||||
if [ $STATUS -ne 0 ]; then
|
||||
echo "[AmneziaVPN] ERROR: installer exited with status $STATUS"
|
||||
exit $STATUS
|
||||
fi
|
||||
|
||||
echo "[AmneziaVPN] Cleaning up..."
|
||||
rm -f "$INSTALLER_PATH" || true
|
||||
rm -rf "$EXTRACT_DIR" 2>/dev/null || true
|
||||
|
||||
echo "[AmneziaVPN] Installation completed successfully"
|
||||
exit 0
|
||||
@@ -8,81 +8,11 @@ include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake)
|
||||
|
||||
include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake)
|
||||
|
||||
set(LIBSSH_ROOT_DIR "${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/")
|
||||
set(OPENSSL_ROOT_DIR "${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/")
|
||||
|
||||
set(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/lib")
|
||||
|
||||
if(WIN32)
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/windows/include")
|
||||
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/windows/x86_64/ssh.lib")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/windows/x86_64")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/windows/win64/libssl.lib")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win64/libcrypto.lib")
|
||||
else()
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/windows/x86/ssh.lib")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/windows/x86")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libssl.lib")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib")
|
||||
endif()
|
||||
elseif(APPLE AND NOT IOS)
|
||||
if(MACOS_NE)
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libz.a")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/universal2")
|
||||
else()
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
|
||||
endif()
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
|
||||
elseif(IOS)
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/ios/arm64")
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libz.a")
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/ios/iphone/include")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/ios/iphone/lib/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/ios/iphone/lib/libcrypto.a")
|
||||
elseif(ANDROID)
|
||||
set(abi ${CMAKE_ANDROID_ARCH_ABI})
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/android/${abi}")
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/android/${abi}/libssh.so")
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/android/include")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/android/${abi}/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/android/${abi}/libcrypto.a")
|
||||
set(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/android/${abi}")
|
||||
elseif(LINUX)
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/linux/x86_64")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/linux/x86_64/libz.a")
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/linux/x86_64/libssh.a")
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/linux/include")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libcrypto.a")
|
||||
endif()
|
||||
|
||||
file(COPY ${OPENSSL_LIB_SSL_PATH} ${OPENSSL_LIB_CRYPTO_PATH}
|
||||
DESTINATION ${OPENSSL_LIBRARIES_DIR})
|
||||
|
||||
set(OPENSSL_USE_STATIC_LIBS TRUE)
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
${LIBSSH_LIB_PATH}
|
||||
${ZLIB_LIB_PATH}
|
||||
)
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
${OPENSSL_LIB_SSL_PATH}
|
||||
${OPENSSL_LIB_CRYPTO_PATH}
|
||||
)
|
||||
|
||||
add_compile_definitions(_WINSOCKAPI_)
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||
set(BUILD_WITH_QT6 ON)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain EXCLUDE_FROM_ALL)
|
||||
|
||||
if(ANDROID)
|
||||
# Use qtgamepad from amnezia-vpn/qtgamepad repository
|
||||
@@ -106,12 +36,13 @@ endif()
|
||||
set(LIBS ${LIBS} qt6keychain)
|
||||
|
||||
include_directories(
|
||||
${OPENSSL_INCLUDE_DIR}
|
||||
${LIBSSH_INCLUDE_DIR}/include
|
||||
${LIBSSH_ROOT_DIR}/include
|
||||
${CLIENT_ROOT_DIR}/3rd/libssh/include
|
||||
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src/include
|
||||
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain
|
||||
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
|
||||
${CMAKE_CURRENT_BINARY_DIR}/3rd/libssh/include
|
||||
)
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
list(APPEND LIBS OpenSSL::SSL OpenSSL::Crypto)
|
||||
|
||||
find_package(libssh REQUIRED)
|
||||
list(APPEND LIBS ssh::ssh)
|
||||
|
||||
@@ -42,18 +42,14 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/core/utils/installedAppsImageProvider.cpp
|
||||
)
|
||||
|
||||
foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
|
||||
set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_EXTRA_LIBS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-go.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libck-ovpn-plugin.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpn3.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpnutil.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/librsapss.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/android/${abi}/libcrypto_3.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/android/${abi}/libssl_3.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so
|
||||
)
|
||||
endforeach()
|
||||
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/xray/android/libxray.aar
|
||||
DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/android/xray/libXray)
|
||||
find_package(awg-android REQUIRED)
|
||||
set(LIBS ${LIBS} amnezia::awg-android)
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY QT_ANDROID_EXTRA_LIBS ${AMNEZIA_ANDROID_LIBWG_PATH} ${AMNEZIA_ANDROID_LIBWG_QUICK_PATH})
|
||||
|
||||
find_package(amnezia-libxray REQUIRED)
|
||||
file(COPY ${AMNEZIA_LIBXRAY_PATH} DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/android/xray/libXray)
|
||||
|
||||
find_package(openvpn-pt-android REQUIRED)
|
||||
set(LIBS ${LIBS} amnezia::openvpn-pt-android)
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY QT_ANDROID_EXTRA_LIBS ${OPENVPN_PT_ANDROID_LIBCK_OVPN_PLUGIN_PATH})
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
message("Client iOS build")
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 13.0)
|
||||
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
|
||||
|
||||
enable_language(OBJC)
|
||||
enable_language(OBJCXX)
|
||||
enable_language(Swift)
|
||||
@@ -132,17 +130,8 @@ target_sources(${PROJECT} PRIVATE
|
||||
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
add_subdirectory(ios/networkextension)
|
||||
add_dependencies(${PROJECT} networkextension)
|
||||
|
||||
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework"
|
||||
)
|
||||
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/)
|
||||
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework")
|
||||
|
||||
|
||||
@@ -23,9 +23,6 @@ set_target_properties(${PROJECT} PROPERTIES
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
)
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
||||
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ui/utils/macosUtil.h
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
message("Client ==> MacOS NE build")
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
||||
|
||||
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
|
||||
@@ -140,7 +139,6 @@ target_sources(${PROJECT} PRIVATE
|
||||
)
|
||||
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
@@ -153,19 +151,6 @@ message(${QtCore_location})
|
||||
|
||||
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
|
||||
|
||||
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework"
|
||||
)
|
||||
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos)
|
||||
target_link_libraries("AmneziaVPNNetworkExtension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework")
|
||||
|
||||
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
|
||||
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
|
||||
COMMAND /usr/bin/codesign --force --sign "Apple Distribution: Privacy Technologies OU"
|
||||
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
|
||||
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMENT "Signing OpenVPNAdapter framework"
|
||||
)
|
||||
|
||||
@@ -15,7 +15,6 @@ set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/utils/constants/protocolConstants.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/constants/apiKeys.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/constants/apiConstants.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/apiEnums.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/errorStrings.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/scriptsRegistry.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/qrCodeUtils.h
|
||||
@@ -45,6 +44,7 @@ set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/servicesCatalogController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/subscriptionController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/newsController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/updateController.h
|
||||
${CLIENT_ROOT_DIR}/core/repositories/secureServersRepository.h
|
||||
${CLIENT_ROOT_DIR}/core/repositories/secureAppSettingsRepository.h
|
||||
${CLIENT_ROOT_DIR}/core/protocols/qmlRegisterProtocols.h
|
||||
@@ -119,6 +119,7 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/servicesCatalogController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/subscriptionController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/newsController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/updateController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/repositories/secureServersRepository.cpp
|
||||
${CLIENT_ROOT_DIR}/core/repositories/secureAppSettingsRepository.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/qAutoStart.cpp
|
||||
@@ -136,6 +137,7 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serverConfigUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/utilities.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/managementServer.cpp
|
||||
|
||||
@@ -241,7 +241,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||
connData.clientId = Utils::getRandomString(32);
|
||||
|
||||
int ret = 0;
|
||||
int nVersion = 1;
|
||||
int nVersion = 0;
|
||||
|
||||
QByteArray clientIdUtf8 = connData.clientId.toUtf8();
|
||||
|
||||
|
||||
@@ -1,51 +1,93 @@
|
||||
#include "newsController.h"
|
||||
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSet>
|
||||
#include <QSharedPointer>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
NewsController::NewsController(SecureAppSettingsRepository* appSettingsRepository,
|
||||
ServersController* serversController)
|
||||
: m_appSettingsRepository(appSettingsRepository), m_serversController(serversController)
|
||||
NewsController::NewsController(SecureAppSettingsRepository *appSettingsRepository,
|
||||
SecureServersRepository *serversRepository)
|
||||
: m_appSettingsRepository(appSettingsRepository),
|
||||
m_serversRepository(serversRepository)
|
||||
{
|
||||
}
|
||||
|
||||
QJsonObject NewsController::getServicesList() const
|
||||
{
|
||||
if (!m_serversRepository) {
|
||||
return {};
|
||||
}
|
||||
QSet<QString> userCountryCodes;
|
||||
QSet<QString> serviceTypes;
|
||||
const QVector<QString> ids = m_serversRepository->orderedServerIds();
|
||||
for (const QString &id : ids) {
|
||||
const auto apiV2 = m_serversRepository->apiV2Config(id);
|
||||
if (!apiV2.has_value()) {
|
||||
continue;
|
||||
}
|
||||
if (!apiV2->apiConfig.userCountryCode.isEmpty()) {
|
||||
userCountryCodes.insert(apiV2->apiConfig.userCountryCode);
|
||||
}
|
||||
const QString serviceType = apiV2->serviceType();
|
||||
if (!serviceType.isEmpty()) {
|
||||
serviceTypes.insert(serviceType);
|
||||
}
|
||||
}
|
||||
if (userCountryCodes.isEmpty() && serviceTypes.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
QJsonObject json;
|
||||
|
||||
QJsonArray userCountryCodesArray;
|
||||
for (const QString &code : userCountryCodes) {
|
||||
userCountryCodesArray.append(code);
|
||||
}
|
||||
json[apiDefs::key::userCountryCode] = userCountryCodesArray;
|
||||
|
||||
QJsonArray serviceTypesArray;
|
||||
for (const QString &type : serviceTypes) {
|
||||
serviceTypesArray.append(type);
|
||||
}
|
||||
json[apiDefs::key::serviceType] = serviceTypesArray;
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
QFuture<QPair<ErrorCode, QJsonArray>> NewsController::fetchNews()
|
||||
{
|
||||
if (!m_serversController) {
|
||||
qWarning() << "ServersController is null, skip fetchNews";
|
||||
if (!m_serversRepository) {
|
||||
qWarning() << "SecureServersRepository is null, skip fetchNews";
|
||||
return QtFuture::makeReadyFuture(qMakePair(ErrorCode::InternalError, QJsonArray()));
|
||||
}
|
||||
|
||||
const auto stacks = m_serversController->gatewayStacks();
|
||||
if (stacks.isEmpty()) {
|
||||
|
||||
const QJsonObject services = getServicesList();
|
||||
if (services.isEmpty()) {
|
||||
qDebug() << "No Gateway stacks, skip fetchNews";
|
||||
return QtFuture::makeReadyFuture(qMakePair(ErrorCode::NoError, QJsonArray()));
|
||||
}
|
||||
|
||||
auto gatewayController = QSharedPointer<GatewayController>::create(
|
||||
m_appSettingsRepository->getGatewayEndpoint(),
|
||||
m_appSettingsRepository->isDevGatewayEnv(),
|
||||
apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
|
||||
m_appSettingsRepository->getGatewayEndpoint(),
|
||||
m_appSettingsRepository->isDevGatewayEnv(),
|
||||
apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
|
||||
QJsonObject payload;
|
||||
payload.insert("locale", m_appSettingsRepository->getAppLanguage().name().split("_").first());
|
||||
|
||||
const QJsonObject stacksJson = stacks.toJson();
|
||||
if (stacksJson.contains(apiDefs::key::userCountryCode)) {
|
||||
payload.insert(apiDefs::key::userCountryCode, stacksJson.value(apiDefs::key::userCountryCode));
|
||||
if (services.contains(apiDefs::key::userCountryCode)) {
|
||||
payload.insert(apiDefs::key::userCountryCode, services.value(apiDefs::key::userCountryCode));
|
||||
}
|
||||
if (stacksJson.contains(apiDefs::key::serviceType)) {
|
||||
payload.insert(apiDefs::key::serviceType, stacksJson.value(apiDefs::key::serviceType));
|
||||
if (services.contains(apiDefs::key::serviceType)) {
|
||||
payload.insert(apiDefs::key::serviceType, services.value(apiDefs::key::serviceType));
|
||||
}
|
||||
|
||||
auto future = gatewayController->postAsync(QString("%1v1/news"), payload);
|
||||
@@ -69,4 +111,3 @@ QFuture<QPair<ErrorCode, QJsonArray>> NewsController::fetchNews()
|
||||
return qMakePair(ErrorCode::NoError, newsArray);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,26 +3,28 @@
|
||||
|
||||
#include <QFuture>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QPair>
|
||||
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
|
||||
class NewsController
|
||||
{
|
||||
public:
|
||||
explicit NewsController(SecureAppSettingsRepository* appSettingsRepository,
|
||||
ServersController* serversController);
|
||||
SecureServersRepository* serversRepository);
|
||||
|
||||
QFuture<QPair<ErrorCode, QJsonArray>> fetchNews();
|
||||
|
||||
private:
|
||||
QJsonObject getServicesList() const;
|
||||
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
ServersController* m_serversController;
|
||||
SecureServersRepository* m_serversRepository;
|
||||
};
|
||||
|
||||
#endif // NEWSCONTROLLER_H
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <limits>
|
||||
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "version.h"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "version.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/api/apiConfig.h"
|
||||
|
||||
@@ -196,7 +195,7 @@ void SubscriptionController::updateApiConfigInJson(QJsonObject &serverConfigJson
|
||||
apiConfig[apiDefs::key::serviceProtocol] = serviceProtocol;
|
||||
apiConfig[apiDefs::key::userCountryCode] = userCountryCode;
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) {
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() == serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
QJsonObject responseObj = QJsonDocument::fromJson(apiResponseBody).object();
|
||||
if (responseObj.contains(apiDefs::key::supportedProtocols)) {
|
||||
apiConfig.insert(apiDefs::key::supportedProtocols, responseObj.value(apiDefs::key::supportedProtocols).toArray());
|
||||
@@ -217,8 +216,7 @@ ErrorCode SubscriptionController::executeRequest(const QString &endpoint, const
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
ServerConfig &serverConfig)
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData)
|
||||
{
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
@@ -247,20 +245,18 @@ ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCo
|
||||
|
||||
updateApiConfigInJson(serverConfigJson, serviceType, serviceProtocol, userCountryCode, responseBody);
|
||||
|
||||
ServerConfig serverConfigModel = ServerConfig::fromJson(serverConfigJson);
|
||||
|
||||
if (!serverConfigModel.isApiV2()) {
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
m_serversRepository->addServer(serverConfigModel);
|
||||
serverConfig = serverConfigModel;
|
||||
ApiV2ServerConfig apiV2ServerConfig = ApiV2ServerConfig::fromJson(serverConfigJson);
|
||||
m_serversRepository->addServer(QString(), apiV2ServerConfig.toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2ServerConfig.toJson()));
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::importTrialFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &email,
|
||||
ServerConfig &serverConfig)
|
||||
const QString &serviceProtocol, const QString &email)
|
||||
{
|
||||
const QString trimmedEmail = email.trimmed();
|
||||
if (trimmedEmail.isEmpty()) {
|
||||
@@ -306,16 +302,19 @@ ErrorCode SubscriptionController::importTrialFromGateway(const QString &userCoun
|
||||
}
|
||||
|
||||
QJsonObject configObject = QJsonDocument::fromJson(configBytes).object();
|
||||
ServerConfig serverConfigModel = ServerConfig::fromJson(configObject);
|
||||
m_serversRepository->addServer(serverConfigModel);
|
||||
serverConfig = serverConfigModel;
|
||||
if (configObject.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ApiV2ServerConfig apiV2ServerConfig = ApiV2ServerConfig::fromJson(configObject);
|
||||
m_serversRepository->addServer(QString(), apiV2ServerConfig.toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2ServerConfig.toJson()));
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
const QString &transactionId, bool isTestPurchase,
|
||||
ServerConfig &serverConfig,
|
||||
int *duplicateServerIndex)
|
||||
{
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
@@ -332,13 +331,8 @@ ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userC
|
||||
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
|
||||
apiPayload[apiDefs::key::transactionId] = transactionId;
|
||||
|
||||
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(),
|
||||
m_appSettingsRepository->isDevGatewayEnv(),
|
||||
apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = gatewayController.post(QString("%1v1/subscriptions"), apiPayload, responseBody);
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/subscriptions"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
@@ -351,18 +345,15 @@ ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userC
|
||||
return ErrorCode::ApiPurchaseError;
|
||||
}
|
||||
|
||||
QString normalizedKey = key;
|
||||
normalizedKey.replace(QStringLiteral("vpn://"), QString());
|
||||
|
||||
// Check if server with this VPN key already exists
|
||||
for (int i = 0; i < m_serversRepository->serversCount(); ++i) {
|
||||
ServerConfig existingServerConfig = m_serversRepository->server(i);
|
||||
QString existingVpnKey;
|
||||
if (existingServerConfig.isApiV1()) {
|
||||
const ApiV1ServerConfig* apiV1 = existingServerConfig.as<ApiV1ServerConfig>();
|
||||
existingVpnKey = apiV1 ? apiV1->vpnKey() : QString();
|
||||
} else if (existingServerConfig.isApiV2()) {
|
||||
const ApiV2ServerConfig* apiV2 = existingServerConfig.as<ApiV2ServerConfig>();
|
||||
existingVpnKey = apiV2 ? apiV2->vpnKey() : QString();
|
||||
}
|
||||
if (existingVpnKey == key) {
|
||||
const auto apiV2 = m_serversRepository->apiV2Config(m_serversRepository->serverIdAt(i));
|
||||
QString existingVpnKey = apiV2.has_value() ? apiV2->vpnKey() : QString();
|
||||
existingVpnKey.replace(QStringLiteral("vpn://"), QString());
|
||||
if (!existingVpnKey.isEmpty() && existingVpnKey == normalizedKey) {
|
||||
if (duplicateServerIndex) {
|
||||
*duplicateServerIndex = i;
|
||||
}
|
||||
@@ -371,9 +362,6 @@ ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userC
|
||||
}
|
||||
}
|
||||
|
||||
QString normalizedKey = key;
|
||||
normalizedKey.replace(QStringLiteral("vpn://"), QString());
|
||||
|
||||
QByteArray configString = QByteArray::fromBase64(normalizedKey.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
QByteArray configUncompressed = qUncompress(configString);
|
||||
if (!configUncompressed.isEmpty()) {
|
||||
@@ -389,40 +377,31 @@ ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userC
|
||||
|
||||
quint16 crc = qChecksum(QJsonDocument(configObject).toJson());
|
||||
|
||||
ServerConfig serverConfigModel = ServerConfig::fromJson(configObject);
|
||||
|
||||
if (!serverConfigModel.isApiV2()) {
|
||||
if (configObject.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
|
||||
if (!apiV2) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ApiV2ServerConfig apiV2ServerConfig = ApiV2ServerConfig::fromJson(configObject);
|
||||
ApiV2ServerConfig* apiV2 = &apiV2ServerConfig;
|
||||
apiV2->apiConfig.vpnKey = normalizedKey;
|
||||
apiV2->apiConfig.isTestPurchase = isTestPurchase;
|
||||
apiV2->apiConfig.isInAppPurchase = true;
|
||||
apiV2->apiConfig.subscriptionExpiredByServer = false;
|
||||
apiV2->crc = crc;
|
||||
|
||||
m_serversRepository->addServer(serverConfigModel);
|
||||
serverConfig = serverConfigModel;
|
||||
m_serversRepository->addServer(QString(), apiV2ServerConfig.toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2ServerConfig.toJson()));
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::updateServiceFromGateway(int serverIndex, const QString &newCountryCode, bool isConnectEvent)
|
||||
ErrorCode SubscriptionController::updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent)
|
||||
{
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
|
||||
if (!serverConfigModel.isApiV2()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
|
||||
if (!apiV2) {
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QString serviceProtocol = apiV2->serviceProtocol();
|
||||
ProtocolData protocolData = generateProtocolData(serviceProtocol);
|
||||
|
||||
@@ -445,15 +424,13 @@ ErrorCode SubscriptionController::updateServiceFromGateway(int serverIndex, cons
|
||||
}
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
if (errorCode == ErrorCode::ApiSubscriptionExpiredError && !apiV2->apiConfig.isInAppPurchase) {
|
||||
ServerConfig expiredServerConfig = serverConfigModel;
|
||||
ApiV2ServerConfig *expiredApiV2 = expiredServerConfig.as<ApiV2ServerConfig>();
|
||||
if (expiredApiV2) {
|
||||
expiredApiV2->apiConfig.subscriptionExpiredByServer = true;
|
||||
m_serversRepository->editServer(serverIndex, expiredServerConfig);
|
||||
}
|
||||
ApiV2ServerConfig expiredApiV2 = *apiV2;
|
||||
expiredApiV2.apiConfig.subscriptionExpiredByServer = true;
|
||||
m_serversRepository->editServer(serverId, expiredApiV2.toJson(),
|
||||
serverConfigUtils::configTypeFromJson(expiredApiV2.toJson()));
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
@@ -466,16 +443,12 @@ ErrorCode SubscriptionController::updateServiceFromGateway(int serverIndex, cons
|
||||
|
||||
updateApiConfigInJson(serverConfigJson, apiV2->apiConfig.serviceType, serviceProtocol, apiV2->apiConfig.userCountryCode, responseBody);
|
||||
|
||||
ServerConfig newServerConfigModel = ServerConfig::fromJson(serverConfigJson);
|
||||
|
||||
if (!newServerConfigModel.isApiV2()) {
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ApiV2ServerConfig* newApiV2 = newServerConfigModel.as<ApiV2ServerConfig>();
|
||||
if (!newApiV2) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ApiV2ServerConfig newApiV2Config = ApiV2ServerConfig::fromJson(serverConfigJson);
|
||||
ApiV2ServerConfig* newApiV2 = &newApiV2Config;
|
||||
|
||||
newApiV2->apiConfig.vpnKey = apiV2->apiConfig.vpnKey;
|
||||
newApiV2->apiConfig.isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
@@ -490,20 +463,15 @@ ErrorCode SubscriptionController::updateServiceFromGateway(int serverIndex, cons
|
||||
newApiV2->nameOverriddenByUser = true;
|
||||
}
|
||||
|
||||
m_serversRepository->editServer(serverIndex, newServerConfigModel);
|
||||
m_serversRepository->editServer(serverId, newApiV2Config.toJson(),
|
||||
serverConfigUtils::configTypeFromJson(newApiV2Config.toJson()));
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::deactivateDevice(int serverIndex, bool isRemoveEvent)
|
||||
ErrorCode SubscriptionController::deactivateDevice(const QString &serverId)
|
||||
{
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
|
||||
if (!serverConfigModel.isApiV2()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
|
||||
if (!apiV2) {
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -524,29 +492,23 @@ ErrorCode SubscriptionController::deactivateDevice(int serverIndex, bool isRemov
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody);
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
serverConfigModel.visit([](auto& arg) {
|
||||
arg.containers.clear();
|
||||
});
|
||||
m_serversRepository->editServer(serverIndex, serverConfigModel);
|
||||
apiV2->containers.clear();
|
||||
m_serversRepository->editServer(serverId, apiV2->toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::deactivateExternalDevice(int serverIndex, const QString &uuid, const QString &serverCountryCode)
|
||||
ErrorCode SubscriptionController::deactivateExternalDevice(const QString &serverId, const QString &uuid, const QString &serverCountryCode)
|
||||
{
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
|
||||
if (!serverConfigModel.isApiV2()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
|
||||
if (!apiV2) {
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -567,34 +529,29 @@ ErrorCode SubscriptionController::deactivateExternalDevice(int serverIndex, cons
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody);
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
if (uuid == m_appSettingsRepository->getInstallationUuid(true)) {
|
||||
serverConfigModel.visit([](auto& arg) {
|
||||
arg.containers.clear();
|
||||
});
|
||||
m_serversRepository->editServer(serverIndex, serverConfigModel);
|
||||
apiV2->containers.clear();
|
||||
m_serversRepository->editServer(serverId, apiV2->toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::exportNativeConfig(int serverIndex, const QString &serverCountryCode, QString &nativeConfig)
|
||||
ErrorCode SubscriptionController::exportNativeConfig(const QString &serverId, const QString &serverCountryCode, QString &nativeConfig)
|
||||
{
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
|
||||
if (!serverConfigModel.isApiV2()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
|
||||
if (!apiV2) {
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QString protocol = configKey::awg;
|
||||
ProtocolData protocolData = generateProtocolData(protocol);
|
||||
|
||||
@@ -613,7 +570,7 @@ ErrorCode SubscriptionController::exportNativeConfig(int serverIndex, const QStr
|
||||
appendProtocolDataToApiPayload(protocol, protocolData, apiPayload);
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/native_config"), apiPayload, responseBody);
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/native_config"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
@@ -624,18 +581,13 @@ ErrorCode SubscriptionController::exportNativeConfig(int serverIndex, const QStr
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::revokeNativeConfig(int serverIndex, const QString &serverCountryCode)
|
||||
ErrorCode SubscriptionController::revokeNativeConfig(const QString &serverId, const QString &serverCountryCode)
|
||||
{
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
|
||||
if (!serverConfigModel.isApiV2()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
|
||||
if (!apiV2) {
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QString protocol = configKey::awg;
|
||||
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
@@ -652,7 +604,7 @@ ErrorCode SubscriptionController::revokeNativeConfig(int serverIndex, const QStr
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_native_config"), apiPayload, responseBody);
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_native_config"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
|
||||
return errorCode;
|
||||
}
|
||||
@@ -660,126 +612,54 @@ ErrorCode SubscriptionController::revokeNativeConfig(int serverIndex, const QStr
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::updateServiceFromTelegram(int serverIndex)
|
||||
ErrorCode SubscriptionController::prepareVpnKeyExport(const QString &serverId, QString &vpnKey)
|
||||
{
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
|
||||
if (!serverConfigModel.isApiV1()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
const ApiV1ServerConfig* apiV1 = serverConfigModel.as<ApiV1ServerConfig>();
|
||||
if (!apiV1) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
QString serviceProtocol = apiV1->protocol;
|
||||
ProtocolData protocolData = generateProtocolData(serviceProtocol);
|
||||
QString installationUuid = m_appSettingsRepository->getInstallationUuid(true);
|
||||
|
||||
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(), m_appSettingsRepository->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
|
||||
QJsonObject apiPayload;
|
||||
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
|
||||
apiPayload[apiDefs::key::uuid] = installationUuid;
|
||||
apiPayload[apiDefs::key::osVersion] = QSysInfo::productType();
|
||||
apiPayload[apiDefs::key::appVersion] = QString(APP_VERSION);
|
||||
apiPayload[configKey::accessToken] = apiV1->apiKey;
|
||||
apiPayload[apiDefs::key::apiEndpoint] = apiV1->apiEndpoint;
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = gatewayController.post(QString("%1v1/proxy_config"), apiPayload, responseBody);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QJsonObject serverConfigJson;
|
||||
errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ServerConfig newServerConfigModel = ServerConfig::fromJson(serverConfigJson);
|
||||
|
||||
if (!newServerConfigModel.isApiV1()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ApiV1ServerConfig* newApiV1 = newServerConfigModel.as<ApiV1ServerConfig>();
|
||||
if (!newApiV1) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
newApiV1->apiKey = apiV1->apiKey;
|
||||
newApiV1->apiEndpoint = apiV1->apiEndpoint;
|
||||
newApiV1->crc = apiV1->crc;
|
||||
|
||||
m_serversRepository->editServer(serverIndex, newServerConfigModel);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::prepareVpnKeyExport(int serverIndex, QString &vpnKey)
|
||||
{
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
|
||||
if (serverConfigModel.isApiV1()) {
|
||||
const ApiV1ServerConfig* apiV1 = serverConfigModel.as<ApiV1ServerConfig>();
|
||||
vpnKey = apiV1 ? apiV1->vpnKey() : QString();
|
||||
} else if (serverConfigModel.isApiV2()) {
|
||||
ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
|
||||
vpnKey = apiV2 ? apiV2->vpnKey() : QString();
|
||||
if (vpnKey.isEmpty()) {
|
||||
QJsonObject serverJson = serverConfigModel.toJson();
|
||||
vpnKey = apiUtils::getPremiumV2VpnKey(serverJson);
|
||||
if (vpnKey.isEmpty()) {
|
||||
return ErrorCode::ApiConfigEmptyError;
|
||||
}
|
||||
apiV2->apiConfig.vpnKey = vpnKey;
|
||||
m_serversRepository->editServer(serverIndex, serverConfigModel);
|
||||
}
|
||||
} else {
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::ApiConfigEmptyError;
|
||||
}
|
||||
vpnKey = apiV2->vpnKey();
|
||||
if (vpnKey.isEmpty()) {
|
||||
vpnKey = apiUtils::getPremiumV2VpnKey(apiV2->toJson());
|
||||
if (vpnKey.isEmpty()) {
|
||||
return ErrorCode::ApiConfigEmptyError;
|
||||
}
|
||||
apiV2->apiConfig.vpnKey = vpnKey;
|
||||
m_serversRepository->editServer(serverId, apiV2->toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::validateAndUpdateConfig(int serverIndex, bool hasInstalledContainers)
|
||||
ErrorCode SubscriptionController::validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers)
|
||||
{
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
|
||||
apiDefs::ConfigSource configSource;
|
||||
if (serverConfigModel.isApiV1()) {
|
||||
configSource = apiDefs::ConfigSource::Telegram;
|
||||
} else if (serverConfigModel.isApiV2()) {
|
||||
configSource = apiDefs::ConfigSource::AmneziaGateway;
|
||||
} else {
|
||||
if (!m_serversRepository->apiV2Config(serverId).has_value()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
if (configSource == apiDefs::ConfigSource::Telegram && !hasInstalledContainers) {
|
||||
removeApiConfig(serverIndex);
|
||||
return updateServiceFromTelegram(serverIndex);
|
||||
} else if (configSource == apiDefs::ConfigSource::AmneziaGateway && !hasInstalledContainers) {
|
||||
return updateServiceFromGateway(serverIndex, "", false);
|
||||
} else if (configSource && isApiKeyExpired(serverIndex)) {
|
||||
qDebug() << "attempt to update api config by expires_at event";
|
||||
if (configSource == apiDefs::ConfigSource::AmneziaGateway) {
|
||||
return updateServiceFromGateway(serverIndex, "", false);
|
||||
} else {
|
||||
removeApiConfig(serverIndex);
|
||||
return updateServiceFromTelegram(serverIndex);
|
||||
}
|
||||
if (!hasInstalledContainers) {
|
||||
return updateServiceFromGateway(serverId, "", true);
|
||||
}
|
||||
|
||||
if (isApiKeyExpired(serverId)) {
|
||||
qDebug() << "attempt to update api config by expires_at event";
|
||||
return updateServiceFromGateway(serverId, "", true);
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
void SubscriptionController::removeApiConfig(int serverIndex)
|
||||
void SubscriptionController::removeApiConfig(const QString &serverId)
|
||||
{
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
QString description = serverConfigModel.description();
|
||||
QString hostName = serverConfigModel.hostName();
|
||||
QString description = apiV2->description;
|
||||
QString hostName = apiV2->hostName;
|
||||
QString vpncName = QString("%1 (%2) %3")
|
||||
.arg(description)
|
||||
.arg(hostName)
|
||||
@@ -788,34 +668,42 @@ void SubscriptionController::removeApiConfig(int serverIndex)
|
||||
AmneziaVPN::removeVPNC(vpncName.toStdString());
|
||||
#endif
|
||||
|
||||
serverConfigModel.visit([](auto& arg) {
|
||||
arg.dns1.clear();
|
||||
arg.dns2.clear();
|
||||
arg.containers.clear();
|
||||
arg.hostName.clear();
|
||||
arg.defaultContainer = DockerContainer::None;
|
||||
});
|
||||
apiV2->dns1.clear();
|
||||
apiV2->dns2.clear();
|
||||
apiV2->containers.clear();
|
||||
apiV2->hostName.clear();
|
||||
apiV2->defaultContainer = DockerContainer::None;
|
||||
apiV2->apiConfig.publicKey = ApiConfig::PublicKeyInfo{};
|
||||
|
||||
if (serverConfigModel.isApiV2()) {
|
||||
ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
|
||||
if (apiV2) {
|
||||
apiV2->apiConfig.publicKey = ApiConfig::PublicKeyInfo{};
|
||||
}
|
||||
}
|
||||
|
||||
m_serversRepository->editServer(serverIndex, serverConfigModel);
|
||||
m_serversRepository->editServer(serverId, apiV2->toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
|
||||
}
|
||||
|
||||
bool SubscriptionController::isApiKeyExpired(int serverIndex) const
|
||||
bool SubscriptionController::removeServer(const QString &serverId)
|
||||
{
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
|
||||
if (!serverConfigModel.isApiV2()) {
|
||||
if (serverId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
|
||||
if (!apiV2) {
|
||||
if (!m_serversRepository->apiV2Config(serverId).has_value()) {
|
||||
qWarning().noquote() << "SubscriptionController::removeServer: not an Api V2 server, id" << serverId;
|
||||
return false;
|
||||
}
|
||||
|
||||
const ErrorCode revokeError = deactivateDevice(serverId);
|
||||
if (revokeError != ErrorCode::NoError && revokeError != ErrorCode::ApiNotFoundError) {
|
||||
qWarning().noquote() << "SubscriptionController::removeServer: deactivateDevice failed (error"
|
||||
<< static_cast<int>(revokeError) << "); removing locally anyway.";
|
||||
}
|
||||
|
||||
m_serversRepository->removeServer(serverId);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SubscriptionController::isApiKeyExpired(const QString &serverId) const
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return false;
|
||||
}
|
||||
const QString expiresAt = apiV2->apiConfig.publicKey.expiresAt;
|
||||
@@ -832,31 +720,24 @@ bool SubscriptionController::isApiKeyExpired(int serverIndex) const
|
||||
return false;
|
||||
}
|
||||
|
||||
void SubscriptionController::setCurrentProtocol(int serverIndex, const QString &protocolName)
|
||||
void SubscriptionController::setCurrentProtocol(const QString &serverId, const QString &protocolName)
|
||||
{
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
if (serverConfigModel.isApiV2()) {
|
||||
ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
|
||||
if (apiV2) {
|
||||
apiV2->apiConfig.serviceProtocol = protocolName;
|
||||
}
|
||||
m_serversRepository->editServer(serverIndex, serverConfigModel);
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (apiV2.has_value()) {
|
||||
apiV2->apiConfig.serviceProtocol = protocolName;
|
||||
m_serversRepository->editServer(serverId, apiV2->toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
|
||||
}
|
||||
}
|
||||
|
||||
bool SubscriptionController::isVlessProtocol(int serverIndex) const
|
||||
bool SubscriptionController::isVlessProtocol(const QString &serverId) const
|
||||
{
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
if (serverConfigModel.isApiV2()) {
|
||||
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
|
||||
return apiV2 && apiV2->serviceProtocol() == "vless";
|
||||
}
|
||||
return false;
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
return apiV2.has_value() && apiV2->serviceProtocol() == "vless";
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::processAppStorePurchase(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &productId,
|
||||
ServerConfig &serverConfig,
|
||||
int *duplicateServerIndex)
|
||||
{
|
||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
@@ -890,13 +771,12 @@ ErrorCode SubscriptionController::processAppStorePurchase(const QString &userCou
|
||||
|
||||
ProtocolData protocolData = generateProtocolData(serviceProtocol);
|
||||
return importServiceFromAppStore(userCountryCode, serviceType, serviceProtocol, protocolData,
|
||||
originalTransactionId, isTestPurchase, serverConfig, duplicateServerIndex);
|
||||
originalTransactionId, isTestPurchase, duplicateServerIndex);
|
||||
#else
|
||||
Q_UNUSED(userCountryCode);
|
||||
Q_UNUSED(serviceType);
|
||||
Q_UNUSED(serviceProtocol);
|
||||
Q_UNUSED(productId);
|
||||
Q_UNUSED(serverConfig);
|
||||
return ErrorCode::ApiPurchaseError;
|
||||
#endif
|
||||
}
|
||||
@@ -955,10 +835,9 @@ SubscriptionController::AppStoreRestoreResult SubscriptionController::processApp
|
||||
<< "originalTransactionId =" << originalTransactionId << "productId =" << transactionProductId;
|
||||
|
||||
ProtocolData protocolData = generateProtocolData(serviceProtocol);
|
||||
ServerConfig serverConfig;
|
||||
int currentDuplicateServerIndex = -1;
|
||||
ErrorCode errorCode = importServiceFromAppStore(userCountryCode, serviceType, serviceProtocol, protocolData,
|
||||
originalTransactionId, isTestPurchase, serverConfig,
|
||||
originalTransactionId, isTestPurchase,
|
||||
¤tDuplicateServerIndex);
|
||||
|
||||
if (errorCode == ErrorCode::ApiConfigAlreadyAdded) {
|
||||
@@ -990,16 +869,10 @@ SubscriptionController::AppStoreRestoreResult SubscriptionController::processApp
|
||||
#endif
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::getAccountInfo(int serverIndex, QJsonObject &accountInfo)
|
||||
ErrorCode SubscriptionController::getAccountInfo(const QString &serverId, QJsonObject &accountInfo)
|
||||
{
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
|
||||
if (!serverConfigModel.isApiV2()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
const ApiV2ServerConfig* apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
|
||||
if (!apiV2) {
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
@@ -1029,20 +902,13 @@ ErrorCode SubscriptionController::getAccountInfo(int serverIndex, QJsonObject &a
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QFuture<QPair<ErrorCode, QString>> SubscriptionController::getRenewalLink(int serverIndex)
|
||||
QFuture<QPair<ErrorCode, QString>> SubscriptionController::getRenewalLink(const QString &serverId)
|
||||
{
|
||||
auto promise = QSharedPointer<QPromise<QPair<ErrorCode, QString>>>::create();
|
||||
promise->start();
|
||||
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
if (!serverConfigModel.isApiV2()) {
|
||||
promise->addResult(qMakePair(ErrorCode::InternalError, QString()));
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
const ApiV2ServerConfig *apiV2 = serverConfigModel.as<ApiV2ServerConfig>();
|
||||
if (!apiV2) {
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
promise->addResult(qMakePair(ErrorCode::InternalError, QString()));
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
@@ -1062,6 +928,7 @@ QFuture<QPair<ErrorCode, QString>> SubscriptionController::getRenewalLink(int se
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
||||
apiPayload[apiDefs::key::subscriptionStatus] = getSubscriptionStatusForRenewal(apiV2->apiConfig);
|
||||
|
||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_appSettingsRepository->getGatewayEndpoint(isTestPurchase),
|
||||
m_appSettingsRepository->isDevGatewayEnv(isTestPurchase),
|
||||
@@ -1081,11 +948,7 @@ QFuture<QPair<ErrorCode, QString>> SubscriptionController::getRenewalLink(int se
|
||||
|
||||
QJsonObject responseJson = QJsonDocument::fromJson(responseBody).object();
|
||||
const QString url = responseJson.value("renewal_url").toString();
|
||||
if (url.isEmpty()) {
|
||||
promise->addResult(qMakePair(ErrorCode::InternalError, QString()));
|
||||
} else {
|
||||
promise->addResult(qMakePair(ErrorCode::NoError, url));
|
||||
}
|
||||
promise->addResult(qMakePair(ErrorCode::NoError, url));
|
||||
promise->finish();
|
||||
});
|
||||
watcher->setFuture(postFuture);
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
|
||||
class ServersController;
|
||||
|
||||
@@ -48,44 +47,40 @@ public:
|
||||
|
||||
ProtocolData generateProtocolData(const QString &protocol);
|
||||
void appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload);
|
||||
ErrorCode fillServerConfig(const QJsonObject &serverConfigJson, ServerConfig &serverConfig);
|
||||
|
||||
ErrorCode importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
ServerConfig &serverConfig);
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData);
|
||||
ErrorCode importTrialFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &email,
|
||||
ServerConfig &serverConfig);
|
||||
const QString &serviceProtocol, const QString &email);
|
||||
|
||||
ErrorCode importServiceFromAppStore(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
const QString &transactionId, bool isTestPurchase,
|
||||
ServerConfig &serverConfig,
|
||||
int *duplicateServerIndex = nullptr);
|
||||
|
||||
ErrorCode updateServiceFromGateway(int serverIndex, const QString &newCountryCode, bool isConnectEvent);
|
||||
ErrorCode updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent);
|
||||
|
||||
ErrorCode deactivateDevice(int serverIndex, bool isRemoveEvent);
|
||||
ErrorCode deactivateDevice(const QString &serverId);
|
||||
|
||||
ErrorCode deactivateExternalDevice(int serverIndex, const QString &uuid, const QString &serverCountryCode);
|
||||
ErrorCode deactivateExternalDevice(const QString &serverId, const QString &uuid, const QString &serverCountryCode);
|
||||
|
||||
ErrorCode exportNativeConfig(int serverIndex, const QString &serverCountryCode, QString &nativeConfig);
|
||||
ErrorCode exportNativeConfig(const QString &serverId, const QString &serverCountryCode, QString &nativeConfig);
|
||||
|
||||
ErrorCode revokeNativeConfig(int serverIndex, const QString &serverCountryCode);
|
||||
ErrorCode revokeNativeConfig(const QString &serverId, const QString &serverCountryCode);
|
||||
|
||||
ErrorCode updateServiceFromTelegram(int serverIndex);
|
||||
ErrorCode prepareVpnKeyExport(const QString &serverId, QString &vpnKey);
|
||||
|
||||
ErrorCode prepareVpnKeyExport(int serverIndex, QString &vpnKey);
|
||||
ErrorCode validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers);
|
||||
|
||||
ErrorCode validateAndUpdateConfig(int serverIndex, bool hasInstalledContainers);
|
||||
void removeApiConfig(const QString &serverId);
|
||||
|
||||
void removeApiConfig(int serverIndex);
|
||||
bool removeServer(const QString &serverId);
|
||||
|
||||
void setCurrentProtocol(int serverIndex, const QString &protocolName);
|
||||
bool isVlessProtocol(int serverIndex) const;
|
||||
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
|
||||
bool isVlessProtocol(const QString &serverId) const;
|
||||
|
||||
ErrorCode getAccountInfo(int serverIndex, QJsonObject &accountInfo);
|
||||
QFuture<QPair<ErrorCode, QString>> getRenewalLink(int serverIndex);
|
||||
ErrorCode getAccountInfo(const QString &serverId, QJsonObject &accountInfo);
|
||||
QFuture<QPair<ErrorCode, QString>> getRenewalLink(const QString &serverId);
|
||||
|
||||
struct AppStoreRestoreResult
|
||||
{
|
||||
@@ -98,7 +93,6 @@ public:
|
||||
|
||||
ErrorCode processAppStorePurchase(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &productId,
|
||||
ServerConfig &serverConfig,
|
||||
int *duplicateServerIndex = nullptr);
|
||||
|
||||
AppStoreRestoreResult processAppStoreRestore(const QString &userCountryCode, const QString &serviceType,
|
||||
@@ -106,7 +100,7 @@ public:
|
||||
|
||||
private:
|
||||
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false);
|
||||
bool isApiKeyExpired(int serverIndex) const;
|
||||
bool isApiKeyExpired(const QString &serverId) const;
|
||||
|
||||
ErrorCode extractServerConfigJsonFromResponse(const QByteArray &apiResponseBody, const QString &protocol,
|
||||
const ProtocolData &protocolData, QJsonObject &serverConfigJson);
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/utilities.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "version.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocolConfig.h"
|
||||
|
||||
@@ -51,7 +51,7 @@ void ConnectionController::setConnectionState(Vpn::ConnectionState state)
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::prepareConnection(int serverIndex,
|
||||
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container)
|
||||
{
|
||||
@@ -59,35 +59,98 @@ ErrorCode ConnectionController::prepareConnection(int serverIndex,
|
||||
return ErrorCode::AmneziaServiceNotRunning;
|
||||
}
|
||||
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
container = serverConfigModel.defaultContainer();
|
||||
ContainerConfig containerConfigModel;
|
||||
QPair<QString, QString> dns;
|
||||
QString hostName;
|
||||
QString description;
|
||||
int configVersion = 0;
|
||||
bool isApiConfig = false;
|
||||
|
||||
const auto kind = m_serversRepository->serverKind(serverId);
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
break;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
break;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
break;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
const auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
configVersion = serverConfigUtils::ConfigSource::AmneziaGateway;
|
||||
isApiConfig = true;
|
||||
break;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2:
|
||||
return ErrorCode::InternalError;
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
if (dns.first.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.first)) {
|
||||
if (m_appSettingsRepository->useAmneziaDns()) {
|
||||
dns.first = protocols::dns::amneziaDnsIp;
|
||||
} else {
|
||||
dns.first = m_appSettingsRepository->primaryDns();
|
||||
}
|
||||
}
|
||||
if (dns.second.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.second)) {
|
||||
dns.second = m_appSettingsRepository->secondaryDns();
|
||||
}
|
||||
|
||||
ContainerConfig containerConfigModel = m_serversRepository->containerConfig(serverIndex, container);
|
||||
|
||||
auto dns = serverConfigModel.getDnsPair(m_appSettingsRepository->useAmneziaDns(),
|
||||
m_appSettingsRepository->primaryDns(),
|
||||
m_appSettingsRepository->secondaryDns());
|
||||
|
||||
vpnConfiguration = createConnectionConfiguration(dns, serverConfigModel, containerConfigModel, container);
|
||||
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
|
||||
containerConfigModel, container);
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::openConnection(int serverIndex)
|
||||
ErrorCode ConnectionController::openConnection(const QString &serverId)
|
||||
{
|
||||
QJsonObject vpnConfiguration;
|
||||
DockerContainer container;
|
||||
|
||||
ErrorCode errorCode = prepareConnection(serverIndex, vpnConfiguration, container);
|
||||
ErrorCode errorCode = prepareConnection(serverId, vpnConfiguration, container);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
emit openConnectionRequested(serverIndex, container, vpnConfiguration);
|
||||
emit openConnectionRequested(serverId, container, vpnConfiguration);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -120,7 +183,10 @@ ErrorCode ConnectionController::lastConnectionError() const
|
||||
}
|
||||
|
||||
QJsonObject ConnectionController::createConnectionConfiguration(const QPair<QString, QString> &dns,
|
||||
const ServerConfig &serverConfig,
|
||||
bool isApiConfig,
|
||||
const QString &hostName,
|
||||
const QString &description,
|
||||
int configVersion,
|
||||
const ContainerConfig &containerConfig,
|
||||
DockerContainer container)
|
||||
{
|
||||
@@ -134,7 +200,7 @@ QJsonObject ConnectionController::createConnectionConfiguration(const QPair<QStr
|
||||
|
||||
ConnectionSettings connectionSettings = {
|
||||
{ dns.first, dns.second },
|
||||
serverConfig.isApiConfig(),
|
||||
isApiConfig,
|
||||
{
|
||||
m_appSettingsRepository->isSitesSplitTunnelingEnabled(),
|
||||
m_appSettingsRepository->routeMode()
|
||||
@@ -160,10 +226,9 @@ QJsonObject ConnectionController::createConnectionConfiguration(const QPair<QStr
|
||||
vpnConfiguration[configKey::dns1] = dns.first;
|
||||
vpnConfiguration[configKey::dns2] = dns.second;
|
||||
|
||||
vpnConfiguration[configKey::hostName] = serverConfig.hostName();
|
||||
vpnConfiguration[configKey::description] = serverConfig.description();
|
||||
|
||||
vpnConfiguration[configKey::configVersion] = serverConfig.configVersion();
|
||||
vpnConfiguration[configKey::hostName] = hostName;
|
||||
vpnConfiguration[configKey::description] = description;
|
||||
vpnConfiguration[configKey::configVersion] = configVersion;
|
||||
|
||||
return vpnConfiguration;
|
||||
}
|
||||
|
||||
@@ -30,11 +30,11 @@ public:
|
||||
QObject* parent = nullptr);
|
||||
~ConnectionController() = default;
|
||||
|
||||
ErrorCode prepareConnection(int serverIndex,
|
||||
ErrorCode prepareConnection(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container);
|
||||
|
||||
ErrorCode openConnection(int serverIndex);
|
||||
ErrorCode openConnection(const QString &serverId);
|
||||
|
||||
void closeConnection();
|
||||
|
||||
@@ -50,7 +50,10 @@ public:
|
||||
void setConnectionState(Vpn::ConnectionState state);
|
||||
|
||||
QJsonObject createConnectionConfiguration(const QPair<QString, QString> &dns,
|
||||
const ServerConfig &serverConfig,
|
||||
bool isApiConfig,
|
||||
const QString &hostName,
|
||||
const QString &description,
|
||||
int configVersion,
|
||||
const ContainerConfig &containerConfig,
|
||||
DockerContainer container);
|
||||
|
||||
@@ -60,7 +63,7 @@ public:
|
||||
|
||||
signals:
|
||||
void connectionStateChanged(Vpn::ConnectionState state);
|
||||
void openConnectionRequested(int serverIndex, DockerContainer container, const QJsonObject &vpnConfiguration);
|
||||
void openConnectionRequested(const QString &serverId, DockerContainer container, const QJsonObject &vpnConfiguration);
|
||||
void closeConnectionRequested();
|
||||
void setConnectionStateRequested(Vpn::ConnectionState state);
|
||||
void killSwitchModeChangedRequested(bool enabled);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "core/controllers/selfhosted/installController.h"
|
||||
#include "core/controllers/selfhosted/importController.h"
|
||||
#include "core/controllers/coreSignalHandlers.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "logger.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
@@ -145,7 +144,8 @@ void CoreController::initCoreControllers()
|
||||
m_allowedDnsController = new AllowedDnsController(m_appSettingsRepository);
|
||||
m_servicesCatalogController = new ServicesCatalogController(m_appSettingsRepository);
|
||||
m_subscriptionController = new SubscriptionController(m_serversRepository, m_appSettingsRepository);
|
||||
m_newsController = new NewsController(m_appSettingsRepository, m_serversController);
|
||||
m_newsController = new NewsController(m_appSettingsRepository, m_serversRepository);
|
||||
m_updateController = new UpdateController(m_appSettingsRepository, this);
|
||||
|
||||
m_installController = new InstallController(m_serversRepository, m_appSettingsRepository, this);
|
||||
m_exportController = new ExportController(m_serversRepository, m_appSettingsRepository, this);
|
||||
@@ -164,7 +164,7 @@ void CoreController::initControllers()
|
||||
setQmlContextProperty("FocusController", m_focusController);
|
||||
}
|
||||
|
||||
m_installUiController = new InstallUiController(m_installController, m_serversController, m_settingsController, m_protocolsModel, m_usersController,
|
||||
m_installUiController = new InstallUiController(m_installController, m_serversController, m_settingsController, m_protocolsModel, m_usersController,
|
||||
m_awgConfigModel, m_wireGuardConfigModel, m_openVpnConfigModel, m_xrayConfigModel, m_torConfigModel,
|
||||
#ifdef Q_OS_WINDOWS
|
||||
m_ikev2ConfigModel,
|
||||
@@ -212,6 +212,9 @@ void CoreController::initControllers()
|
||||
|
||||
m_apiNewsUiController = new ApiNewsUiController(m_newsModel, m_newsController, this);
|
||||
setQmlContextProperty("ApiNewsController", m_apiNewsUiController);
|
||||
|
||||
m_updateUiController = new UpdateUiController(m_updateController, this);
|
||||
setQmlContextProperty("UpdateController", m_updateUiController);
|
||||
}
|
||||
|
||||
void CoreController::initAndroidController()
|
||||
@@ -258,9 +261,12 @@ void CoreController::initSignalHandlers()
|
||||
{
|
||||
m_signalHandlers = new CoreSignalHandlers(this, this);
|
||||
m_signalHandlers->initAllHandlers();
|
||||
|
||||
|
||||
// Trigger initial update after handlers are connected
|
||||
m_serversUiController->updateModel();
|
||||
if (m_serversUiController->hasServersFromGatewayApi()) {
|
||||
m_apiNewsUiController->fetchNews(false);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreController::updateTranslator(const QLocale &locale)
|
||||
@@ -318,11 +324,16 @@ PageController* CoreController::pageController() const
|
||||
|
||||
void CoreController::openConnectionByIndex(int serverIndex)
|
||||
{
|
||||
const QString serverId =
|
||||
m_serversUiController ? m_serversUiController->getServerId(serverIndex) : QString();
|
||||
if (serverId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (m_serversModel) {
|
||||
m_serversModel->setProcessedServerIndex(serverIndex);
|
||||
}
|
||||
if (m_serversController) {
|
||||
m_serversController->setDefaultServerIndex(serverIndex);
|
||||
m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
m_connectionUiController->toggleConnection();
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "ui/controllers/ipSplitTunnelingUiController.h"
|
||||
#include "ui/controllers/systemController.h"
|
||||
#include "ui/controllers/languageUiController.h"
|
||||
#include "ui/controllers/updateUiController.h"
|
||||
#include "ui/controllers/api/servicesCatalogUiController.h"
|
||||
|
||||
#include "core/controllers/serversController.h"
|
||||
@@ -39,6 +40,7 @@
|
||||
#include "core/controllers/selfhosted/installController.h"
|
||||
#include "core/controllers/settingsController.h"
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "core/controllers/updateController.h"
|
||||
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
@@ -82,7 +84,6 @@ class TestDefaultServerChange;
|
||||
class TestServerEdgeCases;
|
||||
class TestSignalOrder;
|
||||
class TestServersModelSync;
|
||||
class TestGatewayStacks;
|
||||
class TestComplexOperations;
|
||||
class TestSettingsSignals;
|
||||
class TestUiServersModelAndController;
|
||||
@@ -99,7 +100,6 @@ class CoreController : public QObject
|
||||
friend class TestServerEdgeCases;
|
||||
friend class TestSignalOrder;
|
||||
friend class TestServersModelSync;
|
||||
friend class TestGatewayStacks;
|
||||
friend class TestComplexOperations;
|
||||
friend class TestSettingsSignals;
|
||||
friend class TestUiServersModelAndController;
|
||||
@@ -159,6 +159,7 @@ private:
|
||||
AppSplitTunnelingUiController* m_appSplitTunnelingUiController;
|
||||
AllowedDnsUiController* m_allowedDnsUiController;
|
||||
LanguageUiController* m_languageUiController;
|
||||
UpdateUiController* m_updateUiController;
|
||||
|
||||
SubscriptionUiController* m_subscriptionUiController;
|
||||
ApiNewsUiController* m_apiNewsUiController;
|
||||
@@ -173,6 +174,7 @@ private:
|
||||
ServicesCatalogController* m_servicesCatalogController;
|
||||
SubscriptionController* m_subscriptionController;
|
||||
NewsController* m_newsController;
|
||||
UpdateController* m_updateController;
|
||||
InstallController* m_installController;
|
||||
ExportController* m_exportController;
|
||||
ConnectionController* m_connectionController;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "ui/controllers/qml/pageController.h"
|
||||
@@ -20,6 +21,7 @@
|
||||
#include "ui/controllers/selfhosted/installUiController.h"
|
||||
#include "ui/controllers/importUiController.h"
|
||||
#include "ui/controllers/api/subscriptionUiController.h"
|
||||
#include "ui/controllers/updateUiController.h"
|
||||
#include "ui/models/serversModel.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "core/controllers/ipSplitTunnelingController.h"
|
||||
@@ -64,7 +66,6 @@ void CoreSignalHandlers::initAllHandlers()
|
||||
initImportControllerHandler();
|
||||
initApiCountryModelUpdateHandler();
|
||||
initSubscriptionRefreshHandler();
|
||||
initContainerModelUpdateHandler();
|
||||
initAdminConfigRevokedHandler();
|
||||
initPassphraseRequestHandler();
|
||||
initTranslationsUpdatedHandler();
|
||||
@@ -77,12 +78,14 @@ void CoreSignalHandlers::initAllHandlers()
|
||||
initAllowedDnsModelUpdateHandler();
|
||||
initAppSplitTunnelingModelUpdateHandler();
|
||||
initPrepareConfigHandler();
|
||||
initUnsupportedConnectDrawerHandler();
|
||||
initStrictKillSwitchHandler();
|
||||
initAndroidSettingsHandler();
|
||||
initAndroidConnectionHandler();
|
||||
initIosImportHandler();
|
||||
initIosSettingsHandler();
|
||||
initNotificationHandler();
|
||||
initUpdateFoundHandler();
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initErrorMessagesHandler()
|
||||
@@ -122,11 +125,9 @@ void CoreSignalHandlers::initInstallControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_installController, &InstallController::serverIsBusy, m_coreController->m_installUiController, &InstallUiController::serverIsBusy);
|
||||
connect(m_coreController->m_installUiController, &InstallUiController::cancelInstallation, m_coreController->m_installController, &InstallController::cancelInstallation);
|
||||
connect(m_coreController->m_installUiController, &InstallUiController::currentContainerUpdated, m_coreController->m_connectionUiController,
|
||||
&ConnectionUiController::onCurrentContainerUpdated);
|
||||
connect(m_coreController->m_serversUiController, &ServersUiController::processedServerIndexChanged,
|
||||
m_coreController->m_installUiController, [this](int index) {
|
||||
if (index >= 0) {
|
||||
m_coreController->m_installUiController, [this](int serverIndex) {
|
||||
if (serverIndex >= 0) {
|
||||
m_coreController->m_installUiController->clearProcessedServerCredentials();
|
||||
}
|
||||
});
|
||||
@@ -135,20 +136,20 @@ void CoreSignalHandlers::initInstallControllerHandler()
|
||||
void CoreSignalHandlers::initExportControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_exportController, &ExportController::appendClientRequested, this,
|
||||
[this](int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container) {
|
||||
m_coreController->m_usersController->appendClient(serverIndex, clientId, clientName, container);
|
||||
[this](const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container) {
|
||||
m_coreController->m_usersController->appendClient(serverId, clientId, clientName, container);
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::updateClientsRequested, this,
|
||||
[this](int serverIndex, DockerContainer container) {
|
||||
m_coreController->m_usersController->updateClients(serverIndex, container);
|
||||
[this](const QString &serverId, DockerContainer container) {
|
||||
m_coreController->m_usersController->updateClients(serverId, container);
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::revokeClientRequested, this,
|
||||
[this](int serverIndex, int row, DockerContainer container) {
|
||||
m_coreController->m_usersController->revokeClient(serverIndex, row, container);
|
||||
[this](const QString &serverId, int row, DockerContainer container) {
|
||||
m_coreController->m_usersController->revokeClient(serverId, row, container);
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::renameClientRequested, this,
|
||||
[this](int serverIndex, int row, const QString &clientName, DockerContainer container) {
|
||||
m_coreController->m_usersController->renameClient(serverIndex, row, clientName, container);
|
||||
[this](const QString &serverId, int row, const QString &clientName, DockerContainer container) {
|
||||
m_coreController->m_usersController->renameClient(serverId, row, clientName, container);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -157,9 +158,12 @@ void CoreSignalHandlers::initImportControllerHandler()
|
||||
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
|
||||
if (!m_coreController->m_connectionController->isConnected()) {
|
||||
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
m_coreController->m_serversController->setDefaultServerIndex(newServerIndex);
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerIndex(newServerIndex);
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -168,21 +172,18 @@ void CoreSignalHandlers::initImportControllerHandler()
|
||||
void CoreSignalHandlers::initApiCountryModelUpdateHandler()
|
||||
{
|
||||
connect(m_coreController->m_serversUiController, &ServersUiController::updateApiCountryModel, this, [this]() {
|
||||
int processedIndex = m_coreController->m_serversUiController->getProcessedServerIndex();
|
||||
if (processedIndex < 0 || processedIndex >= m_coreController->m_serversRepository->serversCount()) {
|
||||
const QString processedServerId = m_coreController->m_serversUiController->getProcessedServerId();
|
||||
if (processedServerId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerConfig server = m_coreController->m_serversRepository->server(processedIndex);
|
||||
QJsonArray availableCountries;
|
||||
QString serverCountryCode;
|
||||
|
||||
if (server.isApiV2()) {
|
||||
const ApiV2ServerConfig* apiV2 = server.as<ApiV2ServerConfig>();
|
||||
if (apiV2) {
|
||||
availableCountries = apiV2->apiConfig.availableCountries;
|
||||
serverCountryCode = apiV2->apiConfig.serverCountryCode;
|
||||
}
|
||||
|
||||
const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId);
|
||||
if (apiV2.has_value()) {
|
||||
availableCountries = apiV2->apiConfig.availableCountries;
|
||||
serverCountryCode = apiV2->apiConfig.serverCountryCode;
|
||||
}
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode);
|
||||
@@ -192,18 +193,9 @@ void CoreSignalHandlers::initApiCountryModelUpdateHandler()
|
||||
void CoreSignalHandlers::initSubscriptionRefreshHandler()
|
||||
{
|
||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::subscriptionRefreshNeeded, this, [this]() {
|
||||
const int defaultServerIndex = m_coreController->m_serversController->getDefaultServerIndex();
|
||||
if (defaultServerIndex >= 0) {
|
||||
m_coreController->m_subscriptionUiController->getAccountInfo(defaultServerIndex, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initContainerModelUpdateHandler()
|
||||
{
|
||||
connect(m_coreController->m_serversController, &ServersController::gatewayStacksExpanded, this, [this]() {
|
||||
if (m_coreController->m_serversUiController->hasServersFromGatewayApi()) {
|
||||
m_coreController->m_apiNewsUiController->fetchNews(false);
|
||||
const QString defaultServerId = m_coreController->m_serversController->getDefaultServerId();
|
||||
if (!defaultServerId.isEmpty()) {
|
||||
m_coreController->m_subscriptionUiController->getAccountInfo(defaultServerId, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -211,17 +203,17 @@ void CoreSignalHandlers::initContainerModelUpdateHandler()
|
||||
void CoreSignalHandlers::initAdminConfigRevokedHandler()
|
||||
{
|
||||
connect(m_coreController->m_installController, &InstallController::clientRevocationRequested, this,
|
||||
[this](int serverIndex, const ContainerConfig &containerConfig, DockerContainer container) {
|
||||
m_coreController->m_usersController->revokeClient(serverIndex, containerConfig, container);
|
||||
[this](const QString &serverId, const ContainerConfig &containerConfig, DockerContainer container) {
|
||||
m_coreController->m_usersController->revokeClient(serverId, containerConfig, container);
|
||||
});
|
||||
|
||||
connect(m_coreController->m_installController, &InstallController::clientAppendRequested, this,
|
||||
[this](int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container) {
|
||||
m_coreController->m_usersController->appendClient(serverIndex, clientId, clientName, container);
|
||||
[this](const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container) {
|
||||
m_coreController->m_usersController->appendClient(serverId, clientId, clientName, container);
|
||||
});
|
||||
|
||||
connect(m_coreController->m_usersController, &UsersController::adminConfigRevoked, m_coreController->m_serversController,
|
||||
&ServersController::clearCachedProfile);
|
||||
connect(m_coreController->m_usersController, &UsersController::adminConfigRevoked, m_coreController->m_installController,
|
||||
&InstallController::clearCachedProfile);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initPassphraseRequestHandler()
|
||||
@@ -249,7 +241,8 @@ void CoreSignalHandlers::initLanguageHandler()
|
||||
|
||||
void CoreSignalHandlers::initAutoConnectHandler()
|
||||
{
|
||||
if (m_coreController->m_settingsUiController->isAutoConnectEnabled() && m_coreController->m_serversController->getDefaultServerIndex() >= 0) {
|
||||
if (m_coreController->m_settingsUiController->isAutoConnectEnabled()
|
||||
&& !m_coreController->m_serversController->getDefaultServerId().isEmpty()) {
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); });
|
||||
}
|
||||
}
|
||||
@@ -269,16 +262,20 @@ void CoreSignalHandlers::initServersModelUpdateHandler()
|
||||
m_coreController->m_serversUiController, &ServersUiController::updateModel);
|
||||
connect(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged,
|
||||
m_coreController->m_serversUiController, &ServersUiController::onDefaultServerChanged);
|
||||
|
||||
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded,
|
||||
m_coreController->m_serversController, &ServersController::recomputeGatewayStacks);
|
||||
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited,
|
||||
m_coreController->m_serversController, &ServersController::recomputeGatewayStacks);
|
||||
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved,
|
||||
m_coreController->m_serversController, &ServersController::recomputeGatewayStacks);
|
||||
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::restoreBackupFinished,
|
||||
m_coreController->m_serversUiController, &ServersUiController::updateModel);
|
||||
|
||||
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded, this,
|
||||
[this](const QString &serverId) {
|
||||
if (m_coreController->m_serversRepository->apiV2Config(serverId).has_value()) {
|
||||
m_coreController->m_apiNewsUiController->fetchNews(false);
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::restoreBackupFinished, this, [this]() {
|
||||
m_coreController->m_serversUiController->updateModel();
|
||||
if (m_coreController->m_serversUiController->hasServersFromGatewayApi()) {
|
||||
m_coreController->m_apiNewsUiController->fetchNews(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initClientManagementModelUpdateHandler()
|
||||
@@ -313,7 +310,19 @@ void CoreSignalHandlers::initPrepareConfigHandler()
|
||||
connect(m_coreController->m_connectionUiController, &ConnectionUiController::prepareConfig, this, [this]() {
|
||||
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Preparing);
|
||||
|
||||
m_coreController->m_subscriptionUiController->validateConfig();
|
||||
const QString serverId = m_coreController->m_serversController->getDefaultServerId();
|
||||
if (serverId.isEmpty()) {
|
||||
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
return;
|
||||
}
|
||||
|
||||
const serverConfigUtils::ConfigType kind = m_coreController->m_serversRepository->serverKind(serverId);
|
||||
|
||||
if (serverConfigUtils::isApiV2Subscription(kind) || serverConfigUtils::isLegacyApiSubscription(kind)) {
|
||||
m_coreController->m_subscriptionUiController->validateConfig();
|
||||
} else {
|
||||
m_coreController->m_installUiController->validateConfig();
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::configValidated, this, [this](bool isValid) {
|
||||
@@ -322,7 +331,7 @@ void CoreSignalHandlers::initPrepareConfigHandler()
|
||||
return;
|
||||
}
|
||||
|
||||
m_coreController->m_installUiController->validateConfig();
|
||||
m_coreController->m_connectionUiController->openConnection();
|
||||
});
|
||||
|
||||
connect(m_coreController->m_installUiController, &InstallUiController::configValidated, this, [this](bool isValid) {
|
||||
@@ -335,6 +344,12 @@ void CoreSignalHandlers::initPrepareConfigHandler()
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initUnsupportedConnectDrawerHandler()
|
||||
{
|
||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initStrictKillSwitchHandler()
|
||||
{
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::strictKillSwitchEnabledChanged, m_coreController->m_connectionController,
|
||||
@@ -346,7 +361,10 @@ void CoreSignalHandlers::initAndroidSettingsHandler()
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
|
||||
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
|
||||
connect(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved, this,
|
||||
[](const QString &/*serverId*/, int removedIndex) {
|
||||
AndroidController::instance()->resetLastServer(removedIndex);
|
||||
});
|
||||
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
|
||||
#endif
|
||||
}
|
||||
@@ -410,3 +428,19 @@ void CoreSignalHandlers::initNotificationHandler()
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initUpdateFoundHandler()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
connect(m_coreController->m_apiNewsUiController, &ApiNewsUiController::fetchNewsFinished, m_coreController->m_updateUiController,
|
||||
&UpdateUiController::checkForUpdates);
|
||||
|
||||
connect(m_coreController->m_updateUiController, &UpdateUiController::updateFound, this, [this]() {
|
||||
const QString version = m_coreController->m_updateUiController->getVersion();
|
||||
const QString updateId = version.isEmpty() ? QStringLiteral("update") : QStringLiteral("update-%1").arg(version);
|
||||
m_coreController->m_newsModel->setUpdateNotification(
|
||||
updateId, m_coreController->m_updateUiController->getHeaderText(), m_coreController->m_updateUiController->getChangelogText());
|
||||
emit m_coreController->m_pageController->showChangelogDrawer();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ private:
|
||||
void initImportControllerHandler();
|
||||
void initApiCountryModelUpdateHandler();
|
||||
void initSubscriptionRefreshHandler();
|
||||
void initContainerModelUpdateHandler();
|
||||
void initAdminConfigRevokedHandler();
|
||||
void initPassphraseRequestHandler();
|
||||
void initTranslationsUpdatedHandler();
|
||||
@@ -34,12 +33,14 @@ private:
|
||||
void initAllowedDnsModelUpdateHandler();
|
||||
void initAppSplitTunnelingModelUpdateHandler();
|
||||
void initPrepareConfigHandler();
|
||||
void initUnsupportedConnectDrawerHandler();
|
||||
void initStrictKillSwitchHandler();
|
||||
void initAndroidSettingsHandler();
|
||||
void initAndroidConnectionHandler();
|
||||
void initIosImportHandler();
|
||||
void initIosSettingsHandler();
|
||||
void initNotificationHandler();
|
||||
void initUpdateFoundHandler();
|
||||
|
||||
CoreController* m_coreController;
|
||||
};
|
||||
|
||||
@@ -239,7 +239,7 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
|
||||
|
||||
connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
|
||||
|
||||
connect(reply, &QNetworkReply::finished, reply, [promise, sslErrors, encRequestData, endpoint, apiPayload, reply, this]() mutable {
|
||||
connect(reply, &QNetworkReply::finished, this, [promise, sslErrors, encRequestData, endpoint, apiPayload, reply, this]() mutable {
|
||||
QByteArray encryptedResponseBody = reply->readAll();
|
||||
QString replyErrorString = reply->errorString();
|
||||
auto replyError = reply->error();
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
|
||||
#include "core/configurators/configuratorBase.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/utils/qrCodeUtils.h"
|
||||
#include "core/utils/serialization/serialization.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/models/selfhosted/selfHostedAdminServerConfig.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocolConfig.h"
|
||||
|
||||
@@ -27,18 +26,20 @@ ExportController::ExportController(SecureServersRepository* serversRepository,
|
||||
{
|
||||
}
|
||||
|
||||
ExportController::ExportResult ExportController::generateFullAccessConfig(int serverIndex)
|
||||
ExportController::ExportResult ExportController::generateFullAccessConfig(const QString &serverId)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
|
||||
serverConfig.visit([](auto& arg) {
|
||||
for (auto it = arg.containers.begin(); it != arg.containers.end(); ++it) {
|
||||
it.value().protocolConfig.clearClientConfig();
|
||||
}
|
||||
});
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
for (auto it = adminConfig->containers.begin(); it != adminConfig->containers.end(); ++it) {
|
||||
it.value().protocolConfig.clearClientConfig();
|
||||
}
|
||||
|
||||
QJsonObject serverJson = serverConfig.toJson();
|
||||
QJsonObject serverJson = adminConfig->toJson();
|
||||
QByteArray compressedConfig = QJsonDocument(serverJson).toJson();
|
||||
compressedConfig = qCompress(compressedConfig, 8);
|
||||
result.config = generateVpnUrl(compressedConfig);
|
||||
@@ -47,13 +48,22 @@ ExportController::ExportResult ExportController::generateFullAccessConfig(int se
|
||||
return result;
|
||||
}
|
||||
|
||||
ExportController::ExportResult ExportController::generateConnectionConfig(int serverIndex, int containerIndex, const QString &clientName)
|
||||
ExportController::ExportResult ExportController::generateConnectionConfig(const QString &serverId, int containerIndex, const QString &clientName)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
const ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
ContainerConfig containerConfig = adminConfig->containerConfig(container);
|
||||
|
||||
if (ContainerUtils::containerService(container) != ServiceType::Other) {
|
||||
SshSession sshSession;
|
||||
@@ -74,35 +84,25 @@ ExportController::ExportResult ExportController::generateConnectionConfig(int se
|
||||
|
||||
QString clientId = newProtocolConfig.clientId();
|
||||
if (!clientId.isEmpty()) {
|
||||
emit appendClientRequested(serverIndex, clientId, clientName, container);
|
||||
emit appendClientRequested(serverId, clientId, clientName, container);
|
||||
}
|
||||
}
|
||||
|
||||
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
|
||||
serverConfig.visit([container, containerConfig](auto& arg) {
|
||||
arg.containers.clear();
|
||||
arg.containers[container] = containerConfig;
|
||||
arg.defaultContainer = container;
|
||||
});
|
||||
const QPair<QString, QString> dns = adminConfig->getDnsPair(m_appSettingsRepository->useAmneziaDns(),
|
||||
m_appSettingsRepository->primaryDns(),
|
||||
m_appSettingsRepository->secondaryDns());
|
||||
|
||||
if (serverConfig.isSelfHosted()) {
|
||||
SelfHostedServerConfig* selfHosted = serverConfig.as<SelfHostedServerConfig>();
|
||||
if (selfHosted) {
|
||||
selfHosted->userName.reset();
|
||||
selfHosted->password.reset();
|
||||
selfHosted->port.reset();
|
||||
}
|
||||
}
|
||||
adminConfig->containers.clear();
|
||||
adminConfig->containers[container] = containerConfig;
|
||||
adminConfig->defaultContainer = container;
|
||||
adminConfig->userName.clear();
|
||||
adminConfig->password.clear();
|
||||
adminConfig->port = 0;
|
||||
|
||||
auto dns = serverConfig.getDnsPair(m_appSettingsRepository->useAmneziaDns(),
|
||||
m_appSettingsRepository->primaryDns(),
|
||||
m_appSettingsRepository->secondaryDns());
|
||||
serverConfig.visit([&dns](auto& arg) {
|
||||
arg.dns1 = dns.first;
|
||||
arg.dns2 = dns.second;
|
||||
});
|
||||
adminConfig->dns1 = dns.first;
|
||||
adminConfig->dns2 = dns.second;
|
||||
|
||||
QJsonObject serverJson = serverConfig.toJson();
|
||||
QJsonObject serverJson = adminConfig->toJson();
|
||||
QByteArray compressedConfig = QJsonDocument(serverJson).toJson();
|
||||
compressedConfig = qCompress(compressedConfig, 8);
|
||||
result.config = generateVpnUrl(compressedConfig);
|
||||
@@ -111,7 +111,7 @@ ExportController::ExportResult ExportController::generateConnectionConfig(int se
|
||||
return result;
|
||||
}
|
||||
|
||||
ExportController::NativeConfigResult ExportController::generateNativeConfig(int serverIndex, DockerContainer container,
|
||||
ExportController::NativeConfigResult ExportController::generateNativeConfig(const QString &serverId, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const QString &clientName)
|
||||
{
|
||||
@@ -123,11 +123,19 @@ ExportController::NativeConfigResult ExportController::generateNativeConfig(int
|
||||
|
||||
Proto protocol = ContainerUtils::defaultProtocol(container);
|
||||
|
||||
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
|
||||
auto dns = serverConfig.getDnsPair(m_appSettingsRepository->useAmneziaDns(),
|
||||
m_appSettingsRepository->primaryDns(),
|
||||
m_appSettingsRepository->secondaryDns());
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
const ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
const QPair<QString, QString> dns = adminConfig->getDnsPair(m_appSettingsRepository->useAmneziaDns(),
|
||||
m_appSettingsRepository->primaryDns(),
|
||||
m_appSettingsRepository->secondaryDns());
|
||||
|
||||
ContainerConfig modifiedContainerConfig = containerConfig;
|
||||
modifiedContainerConfig.container = container;
|
||||
@@ -157,20 +165,25 @@ ExportController::NativeConfigResult ExportController::generateNativeConfig(int
|
||||
if (protocol == Proto::OpenVpn || protocol == Proto::WireGuard || protocol == Proto::Awg || protocol == Proto::Xray) {
|
||||
QString clientId = newProtocolConfig.clientId();
|
||||
if (!clientId.isEmpty()) {
|
||||
emit appendClientRequested(serverIndex, clientId, clientName, container);
|
||||
emit appendClientRequested(serverId, clientId, clientName, container);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ExportController::ExportResult ExportController::generateOpenVpnConfig(int serverIndex, const QString &clientName)
|
||||
ExportController::ExportResult ExportController::generateOpenVpnConfig(const QString &serverId, const QString &clientName)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
DockerContainer container = DockerContainer::OpenVpn;
|
||||
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
ContainerConfig containerConfig = adminConfig->containerConfig(container);
|
||||
|
||||
auto nativeResult = generateNativeConfig(serverIndex, container, containerConfig, clientName);
|
||||
auto nativeResult = generateNativeConfig(serverId, container, containerConfig, clientName);
|
||||
if (nativeResult.errorCode != ErrorCode::NoError) {
|
||||
result.errorCode = nativeResult.errorCode;
|
||||
return result;
|
||||
@@ -185,13 +198,18 @@ ExportController::ExportResult ExportController::generateOpenVpnConfig(int serve
|
||||
return result;
|
||||
}
|
||||
|
||||
ExportController::ExportResult ExportController::generateWireGuardConfig(int serverIndex, const QString &clientName)
|
||||
ExportController::ExportResult ExportController::generateWireGuardConfig(const QString &serverId, const QString &clientName)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, DockerContainer::WireGuard);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
ContainerConfig containerConfig = adminConfig->containerConfig(DockerContainer::WireGuard);
|
||||
|
||||
auto nativeResult = generateNativeConfig(serverIndex, DockerContainer::WireGuard, containerConfig, clientName);
|
||||
auto nativeResult = generateNativeConfig(serverId, DockerContainer::WireGuard, containerConfig, clientName);
|
||||
if (nativeResult.errorCode != ErrorCode::NoError) {
|
||||
result.errorCode = nativeResult.errorCode;
|
||||
return result;
|
||||
@@ -206,7 +224,7 @@ ExportController::ExportResult ExportController::generateWireGuardConfig(int ser
|
||||
return result;
|
||||
}
|
||||
|
||||
ExportController::ExportResult ExportController::generateAwgConfig(int serverIndex, int containerIndex, const QString &clientName)
|
||||
ExportController::ExportResult ExportController::generateAwgConfig(const QString &serverId, int containerIndex, const QString &clientName)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
@@ -215,9 +233,14 @@ ExportController::ExportResult ExportController::generateAwgConfig(int serverInd
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
ContainerConfig containerConfig = adminConfig->containerConfig(container);
|
||||
|
||||
auto nativeResult = generateNativeConfig(serverIndex, container, containerConfig, clientName);
|
||||
auto nativeResult = generateNativeConfig(serverId, container, containerConfig, clientName);
|
||||
if (nativeResult.errorCode != ErrorCode::NoError) {
|
||||
result.errorCode = nativeResult.errorCode;
|
||||
return result;
|
||||
@@ -233,13 +256,18 @@ ExportController::ExportResult ExportController::generateAwgConfig(int serverInd
|
||||
}
|
||||
|
||||
|
||||
ExportController::ExportResult ExportController::generateXrayConfig(int serverIndex, const QString &clientName)
|
||||
ExportController::ExportResult ExportController::generateXrayConfig(const QString &serverId, const QString &clientName)
|
||||
{
|
||||
ExportResult result;
|
||||
|
||||
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, DockerContainer::Xray);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
result.errorCode = ErrorCode::InternalError;
|
||||
return result;
|
||||
}
|
||||
ContainerConfig containerConfig = adminConfig->containerConfig(DockerContainer::Xray);
|
||||
|
||||
auto nativeResult = generateNativeConfig(serverIndex, DockerContainer::Xray, containerConfig, clientName);
|
||||
auto nativeResult = generateNativeConfig(serverId, DockerContainer::Xray, containerConfig, clientName);
|
||||
if (nativeResult.errorCode != ErrorCode::NoError) {
|
||||
result.errorCode = nativeResult.errorCode;
|
||||
return result;
|
||||
@@ -302,22 +330,22 @@ ExportController::ExportResult ExportController::generateXrayConfig(int serverIn
|
||||
return result;
|
||||
}
|
||||
|
||||
void ExportController::updateClientManagementModel(int serverIndex, int containerIndex)
|
||||
void ExportController::updateClientManagementModel(const QString &serverId, int containerIndex)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
emit updateClientsRequested(serverIndex, container);
|
||||
emit updateClientsRequested(serverId, container);
|
||||
}
|
||||
|
||||
void ExportController::revokeConfig(int row, int serverIndex, int containerIndex)
|
||||
void ExportController::revokeConfig(int row, const QString &serverId, int containerIndex)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
emit revokeClientRequested(serverIndex, row, container);
|
||||
emit revokeClientRequested(serverId, row, container);
|
||||
}
|
||||
|
||||
void ExportController::renameClient(int row, const QString &clientName, int serverIndex, int containerIndex)
|
||||
void ExportController::renameClient(int row, const QString &clientName, const QString &serverId, int containerIndex)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
emit renameClientRequested(serverIndex, row, clientName, container);
|
||||
emit renameClientRequested(serverId, row, clientName, container);
|
||||
}
|
||||
|
||||
QString ExportController::generateVpnUrl(const QByteArray &compressedConfig)
|
||||
|
||||
@@ -37,23 +37,23 @@ public:
|
||||
SecureAppSettingsRepository* appSettingsRepository,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
ExportResult generateFullAccessConfig(int serverIndex);
|
||||
ExportResult generateConnectionConfig(int serverIndex, int containerIndex, const QString &clientName);
|
||||
ExportResult generateOpenVpnConfig(int serverIndex, const QString &clientName);
|
||||
ExportResult generateWireGuardConfig(int serverIndex, const QString &clientName);
|
||||
ExportResult generateAwgConfig(int serverIndex, int containerIndex, const QString &clientName);
|
||||
ExportResult generateXrayConfig(int serverIndex, const QString &clientName);
|
||||
ExportResult generateFullAccessConfig(const QString &serverId);
|
||||
ExportResult generateConnectionConfig(const QString &serverId, int containerIndex, const QString &clientName);
|
||||
ExportResult generateOpenVpnConfig(const QString &serverId, const QString &clientName);
|
||||
ExportResult generateWireGuardConfig(const QString &serverId, const QString &clientName);
|
||||
ExportResult generateAwgConfig(const QString &serverId, int containerIndex, const QString &clientName);
|
||||
ExportResult generateXrayConfig(const QString &serverId, const QString &clientName);
|
||||
|
||||
signals:
|
||||
void appendClientRequested(int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container);
|
||||
void updateClientsRequested(int serverIndex, DockerContainer container);
|
||||
void revokeClientRequested(int serverIndex, int row, DockerContainer container);
|
||||
void renameClientRequested(int serverIndex, int row, const QString &clientName, DockerContainer container);
|
||||
void appendClientRequested(const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container);
|
||||
void updateClientsRequested(const QString &serverId, DockerContainer container);
|
||||
void revokeClientRequested(const QString &serverId, int row, DockerContainer container);
|
||||
void renameClientRequested(const QString &serverId, int row, const QString &clientName, DockerContainer container);
|
||||
|
||||
public slots:
|
||||
void updateClientManagementModel(int serverIndex, int containerIndex);
|
||||
void revokeConfig(int row, int serverIndex, int containerIndex);
|
||||
void renameClient(int row, const QString &clientName, int serverIndex, int containerIndex);
|
||||
void updateClientManagementModel(const QString &serverId, int containerIndex);
|
||||
void revokeConfig(int row, const QString &serverId, int containerIndex);
|
||||
void renameClient(int row, const QString &clientName, const QString &serverId, int containerIndex);
|
||||
|
||||
private:
|
||||
struct NativeConfigResult
|
||||
@@ -62,7 +62,7 @@ private:
|
||||
QJsonObject jsonNativeConfig;
|
||||
};
|
||||
|
||||
NativeConfigResult generateNativeConfig(int serverIndex, DockerContainer container,
|
||||
NativeConfigResult generateNativeConfig(const QString &serverId, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const QString &clientName);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
@@ -27,7 +27,6 @@
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/qrCodeUtils.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
|
||||
using namespace amnezia;
|
||||
using namespace ProtocolUtils;
|
||||
@@ -208,12 +207,18 @@ ImportController::ImportResult ImportController::extractConfigFromData(const QSt
|
||||
case ConfigTypes::Amnezia: {
|
||||
result.config = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||
|
||||
if (apiUtils::isServerFromApi(result.config)) {
|
||||
if (serverConfigUtils::isServerFromApi(result.config)) {
|
||||
auto apiConfig = result.config.value(apiDefs::key::apiConfig).toObject();
|
||||
apiConfig[apiDefs::key::vpnKey] = data;
|
||||
result.config[apiDefs::key::apiConfig] = apiConfig;
|
||||
}
|
||||
|
||||
if (serverConfigUtils::isLegacyApiSubscription(serverConfigUtils::configTypeFromJson(result.config))) {
|
||||
result.errorCode = ErrorCode::LegacyApiV1NotSupportedError;
|
||||
result.config = {};
|
||||
return result;
|
||||
}
|
||||
|
||||
processAmneziaConfig(result.config);
|
||||
if (!result.config.empty()) {
|
||||
checkForMaliciousStrings(result.config, result.maliciousWarningText);
|
||||
@@ -381,18 +386,29 @@ void ImportController::importConfig(const QJsonObject &config)
|
||||
credentials.secretData = config.value(configKey::password).toString();
|
||||
|
||||
if (credentials.isValid() || config.contains(configKey::containers)) {
|
||||
ServerConfig serverConfig = ServerConfig::fromJson(config);
|
||||
m_serversRepository->addServer(serverConfig);
|
||||
m_serversRepository->addServer(QString(), config, serverConfigUtils::configTypeFromJson(config));
|
||||
emit importFinished();
|
||||
} else if (config.contains(configKey::configVersion)) {
|
||||
quint16 crc = qChecksum(QJsonDocument(config).toJson());
|
||||
if (m_serversRepository->hasServerWithCrc(crc)) {
|
||||
bool hasServerWithCrc = false;
|
||||
const QVector<QString> ids = m_serversRepository->orderedServerIds();
|
||||
for (const QString &id : ids) {
|
||||
const auto apiV2 = m_serversRepository->apiV2Config(id);
|
||||
if (!apiV2.has_value()) {
|
||||
continue;
|
||||
}
|
||||
if (static_cast<quint16>(apiV2->crc) == crc) {
|
||||
hasServerWithCrc = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasServerWithCrc) {
|
||||
emit importErrorOccurred(ErrorCode::ApiConfigAlreadyAdded, true);
|
||||
} else {
|
||||
QJsonObject configWithCrc = config;
|
||||
configWithCrc.insert(configKey::crc, crc);
|
||||
ServerConfig serverConfig = ServerConfig::fromJson(configWithCrc);
|
||||
m_serversRepository->addServer(serverConfig);
|
||||
m_serversRepository->addServer(QString(), configWithCrc, serverConfigUtils::configTypeFromJson(configWithCrc));
|
||||
emit importFinished();
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocols/awgProtocolConfig.h"
|
||||
#include "ui/models/protocols/wireguardConfigModel.h"
|
||||
@@ -129,15 +128,27 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return startupContainerWorker(credentials, container, config, sshSession);
|
||||
}
|
||||
|
||||
ErrorCode InstallController::updateContainer(int serverIndex, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ErrorCode InstallController::updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ContainerConfig &newConfig)
|
||||
{
|
||||
if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) {
|
||||
m_serversRepository->setContainerConfig(serverIndex, container, newConfig);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
adminConfig->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
|
||||
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||
@@ -154,42 +165,51 @@ ErrorCode InstallController::updateContainer(int serverIndex, DockerContainer co
|
||||
}
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
clearCachedProfile(serverIndex, container);
|
||||
m_serversRepository->setContainerConfig(serverIndex, container, newConfig);
|
||||
clearCachedProfile(serverId, container);
|
||||
adminConfig->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
}
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
void InstallController::clearCachedProfile(int serverIndex, DockerContainer container)
|
||||
void InstallController::clearCachedProfile(const QString &serverId, DockerContainer container)
|
||||
{
|
||||
if (ContainerUtils::containerService(container) == ServiceType::Other) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContainerConfig containerConfigModel = m_serversRepository->containerConfig(serverIndex, container);
|
||||
|
||||
m_serversRepository->clearLastConnectionConfig(serverIndex, container);
|
||||
|
||||
emit clientRevocationRequested(serverIndex, containerConfigModel, container);
|
||||
}
|
||||
|
||||
ErrorCode InstallController::validateAndPrepareConfig(int serverIndex)
|
||||
{
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
|
||||
if (serverConfigModel.isApiConfig()) {
|
||||
return ErrorCode::NoError;
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DockerContainer container = serverConfigModel.defaultContainer();
|
||||
adminConfig->clearCachedClientProfile(container);
|
||||
const ContainerConfig containerConfigModel = adminConfig->containerConfig(container);
|
||||
|
||||
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
|
||||
emit clientRevocationRequested(serverId, containerConfigModel, container);
|
||||
}
|
||||
|
||||
ErrorCode InstallController::validateAndPrepareConfig(const QString &serverId)
|
||||
{
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
DockerContainer container = adminConfig->defaultContainer;
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
return ErrorCode::NoInstalledContainersError;
|
||||
}
|
||||
|
||||
ContainerConfig containerConfig = m_serversRepository->containerConfig(serverIndex, container);
|
||||
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
ContainerConfig containerConfig = adminConfig->containerConfig(container);
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession;
|
||||
|
||||
auto isProtocolConfigExists = [](const ContainerConfig &cfg) {
|
||||
@@ -198,20 +218,21 @@ ErrorCode InstallController::validateAndPrepareConfig(int serverIndex)
|
||||
|
||||
if (!isProtocolConfigExists(containerConfig)) {
|
||||
QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
|
||||
ErrorCode errorCode = processContainerForAdmin(container, containerConfig, credentials, sshSession, serverIndex, clientName);
|
||||
ErrorCode errorCode = processContainerForAdmin(container, containerConfig, credentials, sshSession, serverId, clientName);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
m_serversRepository->setContainerConfig(serverIndex, container, containerConfig);
|
||||
adminConfig->updateContainerConfig(container, containerConfig);
|
||||
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
void InstallController::validateConfig(int serverIndex)
|
||||
void InstallController::validateConfig(const QString &serverId)
|
||||
{
|
||||
QFuture<ErrorCode> future = QtConcurrent::run([this, serverIndex]() {
|
||||
return validateAndPrepareConfig(serverIndex);
|
||||
QFuture<ErrorCode> future = QtConcurrent::run([this, serverId]() {
|
||||
return validateAndPrepareConfig(serverId);
|
||||
});
|
||||
|
||||
auto *watcher = new QFutureWatcher<ErrorCode>(this);
|
||||
@@ -230,6 +251,21 @@ void InstallController::validateConfig(int serverIndex)
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
void InstallController::addEmptyServer(const ServerCredentials &credentials)
|
||||
{
|
||||
SelfHostedAdminServerConfig serverConfig;
|
||||
serverConfig.hostName = credentials.hostName;
|
||||
serverConfig.userName = credentials.userName;
|
||||
serverConfig.password = credentials.secretData;
|
||||
serverConfig.port = credentials.port;
|
||||
serverConfig.description = m_appSettingsRepository->nextAvailableServerName();
|
||||
serverConfig.displayName = serverConfig.description.isEmpty() ? serverConfig.hostName : serverConfig.description;
|
||||
serverConfig.defaultContainer = DockerContainer::None;
|
||||
|
||||
m_serversRepository->addServer(QString(), serverConfig.toJson(),
|
||||
serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
}
|
||||
|
||||
ErrorCode InstallController::prepareContainerConfig(DockerContainer container, const ServerCredentials &credentials, ContainerConfig &containerConfig, SshSession &sshSession)
|
||||
{
|
||||
if (!ContainerUtils::isSupportedByCurrentPlatform(container)) {
|
||||
@@ -257,7 +293,7 @@ ErrorCode InstallController::prepareContainerConfig(DockerContainer container, c
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
void InstallController::adminAppendRequested(int serverIndex, DockerContainer container,
|
||||
void InstallController::adminAppendRequested(const QString &serverId, DockerContainer container,
|
||||
const ContainerConfig &containerConfig, const QString &clientName)
|
||||
{
|
||||
if (ContainerUtils::containerService(container) == ServiceType::Other
|
||||
@@ -266,13 +302,13 @@ void InstallController::adminAppendRequested(int serverIndex, DockerContainer co
|
||||
}
|
||||
QString clientId = containerConfig.protocolConfig.clientId();
|
||||
if (!clientId.isEmpty()) {
|
||||
emit clientAppendRequested(serverIndex, clientId, clientName, container);
|
||||
emit clientAppendRequested(serverId, clientId, clientName, container);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode InstallController::processContainerForAdmin(DockerContainer container, ContainerConfig &containerConfig,
|
||||
const ServerCredentials &credentials, SshSession &sshSession,
|
||||
int serverIndex, const QString &clientName)
|
||||
const QString &serverId, const QString &clientName)
|
||||
{
|
||||
if (ContainerUtils::isSupportedByCurrentPlatform(container)) {
|
||||
ErrorCode errorCode = prepareContainerConfig(container, credentials, containerConfig, sshSession);
|
||||
@@ -280,7 +316,7 @@ ErrorCode InstallController::processContainerForAdmin(DockerContainer container,
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
adminAppendRequested(serverIndex, container, containerConfig, clientName);
|
||||
adminAppendRequested(serverId, container, containerConfig, clientName);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -618,7 +654,7 @@ ErrorCode InstallController::isUserInSudo(const ServerCredentials &credentials,
|
||||
return ErrorCode::ServerUserDirectoryNotAccessible;
|
||||
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
|
||||
return ErrorCode::ServerUserNotAllowedInSudoers;
|
||||
if (stdOut.contains("password is required"))
|
||||
if (stdOut.contains("password is required") || stdOut.contains("authentication is required"))
|
||||
return ErrorCode::ServerUserPasswordRequired;
|
||||
|
||||
return error;
|
||||
@@ -688,9 +724,16 @@ ErrorCode InstallController::setupServerFirewall(const ServerCredentials &creden
|
||||
amnezia::genBaseVars(credentials, DockerContainer::None, QString(), QString())));
|
||||
}
|
||||
|
||||
ErrorCode InstallController::rebootServer(int serverIndex)
|
||||
ErrorCode InstallController::rebootServer(const QString &serverId)
|
||||
{
|
||||
auto credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
const auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
|
||||
QString script = QString("sudo reboot");
|
||||
@@ -709,27 +752,38 @@ ErrorCode InstallController::rebootServer(int serverIndex)
|
||||
return sshSession.runScript(credentials, script, cbReadStdOut, cbReadStdErr);
|
||||
}
|
||||
|
||||
ErrorCode InstallController::removeAllContainers(int serverIndex)
|
||||
ErrorCode InstallController::removeAllContainers(const QString &serverId)
|
||||
{
|
||||
auto credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
ErrorCode errorCode = sshSession.runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers));
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
serverConfigModel.visit([](auto& arg) {
|
||||
arg.containers.clear();
|
||||
arg.defaultContainer = DockerContainer::None;
|
||||
});
|
||||
m_serversRepository->editServer(serverIndex, serverConfigModel);
|
||||
adminConfig->containers.clear();
|
||||
adminConfig->defaultContainer = DockerContainer::None;
|
||||
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
}
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode InstallController::removeContainer(int serverIndex, DockerContainer container)
|
||||
ErrorCode InstallController::removeContainer(const QString &serverId, DockerContainer container)
|
||||
{
|
||||
auto credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
ErrorCode errorCode = sshSession.runScript(
|
||||
credentials,
|
||||
@@ -737,11 +791,10 @@ ErrorCode InstallController::removeContainer(int serverIndex, DockerContainer co
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString())));
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
QMap<DockerContainer, ContainerConfig> containers = serverConfigModel.containers();
|
||||
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
|
||||
containers.remove(container);
|
||||
|
||||
DockerContainer defaultContainer = serverConfigModel.defaultContainer();
|
||||
|
||||
DockerContainer defaultContainer = adminConfig->defaultContainer;
|
||||
if (defaultContainer == container) {
|
||||
if (containers.isEmpty()) {
|
||||
defaultContainer = DockerContainer::None;
|
||||
@@ -749,12 +802,10 @@ ErrorCode InstallController::removeContainer(int serverIndex, DockerContainer co
|
||||
defaultContainer = containers.begin().key();
|
||||
}
|
||||
}
|
||||
|
||||
serverConfigModel.visit([&containers, defaultContainer](auto& arg) {
|
||||
arg.containers = containers;
|
||||
arg.defaultContainer = defaultContainer;
|
||||
});
|
||||
m_serversRepository->editServer(serverIndex, serverConfigModel);
|
||||
|
||||
adminConfig->containers = containers;
|
||||
adminConfig->defaultContainer = defaultContainer;
|
||||
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
}
|
||||
|
||||
return errorCode;
|
||||
@@ -815,9 +866,16 @@ bool InstallController::isUpdateDockerContainerRequired(DockerContainer containe
|
||||
return true;
|
||||
}
|
||||
|
||||
ErrorCode InstallController::scanServerForInstalledContainers(int serverIndex)
|
||||
ErrorCode InstallController::scanServerForInstalledContainers(const QString &serverId)
|
||||
{
|
||||
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
|
||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||
@@ -826,8 +884,7 @@ ErrorCode InstallController::scanServerForInstalledContainers(int serverIndex)
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ServerConfig serverConfigModel = m_serversRepository->server(serverIndex);
|
||||
QMap<DockerContainer, ContainerConfig> containers = serverConfigModel.containers();
|
||||
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
|
||||
bool hasNewContainers = false;
|
||||
|
||||
QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
|
||||
@@ -835,29 +892,25 @@ ErrorCode InstallController::scanServerForInstalledContainers(int serverIndex)
|
||||
if (!containers.contains(iterator.key())) {
|
||||
ContainerConfig containerConfig = iterator.value();
|
||||
errorCode = processContainerForAdmin(iterator.key(), containerConfig, credentials, sshSession,
|
||||
serverIndex, clientName);
|
||||
serverId, clientName);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
containers.insert(iterator.key(), containerConfig);
|
||||
hasNewContainers = true;
|
||||
|
||||
DockerContainer defaultContainer = serverConfigModel.defaultContainer();
|
||||
DockerContainer defaultContainer = adminConfig->defaultContainer;
|
||||
if (defaultContainer == DockerContainer::None
|
||||
&& ContainerUtils::containerService(iterator.key()) != ServiceType::Other
|
||||
&& ContainerUtils::isSupportedByCurrentPlatform(iterator.key())) {
|
||||
serverConfigModel.visit([iterator](auto& arg) {
|
||||
arg.defaultContainer = iterator.key();
|
||||
});
|
||||
adminConfig->defaultContainer = iterator.key();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasNewContainers) {
|
||||
serverConfigModel.visit([&containers](auto& arg) {
|
||||
arg.containers = containers;
|
||||
});
|
||||
m_serversRepository->editServer(serverIndex, serverConfigModel);
|
||||
adminConfig->containers = containers;
|
||||
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
@@ -899,7 +952,7 @@ ErrorCode InstallController::installServer(const ServerCredentials &credentials,
|
||||
preparedContainers.insert(container, containerConfig);
|
||||
}
|
||||
|
||||
SelfHostedServerConfig serverConfig;
|
||||
SelfHostedAdminServerConfig serverConfig;
|
||||
serverConfig.hostName = credentials.hostName;
|
||||
serverConfig.userName = credentials.userName;
|
||||
serverConfig.password = credentials.secretData;
|
||||
@@ -912,21 +965,29 @@ ErrorCode InstallController::installServer(const ServerCredentials &credentials,
|
||||
|
||||
serverConfig.defaultContainer = container;
|
||||
|
||||
m_serversRepository->addServer(ServerConfig(serverConfig));
|
||||
serverConfig.displayName = serverConfig.description.isEmpty() ? serverConfig.hostName : serverConfig.description;
|
||||
|
||||
int serverIndex = m_serversRepository->serversCount() - 1;
|
||||
const QString newServerId = m_serversRepository->addServer(QString(), serverConfig.toJson(),
|
||||
serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
|
||||
for (auto iterator = preparedContainers.begin(); iterator != preparedContainers.end(); iterator++) {
|
||||
adminAppendRequested(serverIndex, iterator.key(), iterator.value(), clientName);
|
||||
adminAppendRequested(newServerId, iterator.key(), iterator.value(), clientName);
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode InstallController::installContainer(int serverIndex, DockerContainer container, int port,
|
||||
ErrorCode InstallController::installContainer(const QString &serverId, DockerContainer container, int port,
|
||||
TransportProto transportProto, bool &wasContainerInstalled)
|
||||
{
|
||||
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
|
||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||
@@ -949,15 +1010,17 @@ ErrorCode InstallController::installContainer(int serverIndex, DockerContainer c
|
||||
|
||||
QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
|
||||
for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) {
|
||||
ContainerConfig existingConfigModel = m_serversRepository->containerConfig(serverIndex, iterator.key());
|
||||
ContainerConfig existingConfigModel = adminConfig->containerConfig(iterator.key());
|
||||
if (existingConfigModel.container == DockerContainer::None) {
|
||||
ContainerConfig containerConfig = iterator.value();
|
||||
errorCode = processContainerForAdmin(iterator.key(), containerConfig, credentials, sshSession,
|
||||
serverIndex, clientName);
|
||||
serverId, clientName);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
m_serversRepository->setContainerConfig(serverIndex, iterator.key(), containerConfig);
|
||||
adminConfig->updateContainerConfig(iterator.key(), containerConfig);
|
||||
m_serversRepository->editServer(serverId, adminConfig->toJson(),
|
||||
serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -993,7 +1056,15 @@ bool InstallController::isServerAlreadyExists(const ServerCredentials &credentia
|
||||
{
|
||||
int serversCount = m_serversRepository->serversCount();
|
||||
for (int i = 0; i < serversCount; i++) {
|
||||
const ServerCredentials existingCredentials = m_serversRepository->serverCredentials(i);
|
||||
const QString existingServerId = m_serversRepository->serverIdAt(i);
|
||||
const auto adminConfig = m_serversRepository->selfHostedAdminConfig(existingServerId);
|
||||
if (!adminConfig.has_value()) {
|
||||
continue;
|
||||
}
|
||||
const ServerCredentials existingCredentials = adminConfig->credentials();
|
||||
if (!existingCredentials.isValid()) {
|
||||
continue;
|
||||
}
|
||||
if (credentials.hostName == existingCredentials.hostName && credentials.port == existingCredentials.port) {
|
||||
existingServerIndex = i;
|
||||
return true;
|
||||
|
||||
@@ -33,22 +33,22 @@ public:
|
||||
~InstallController();
|
||||
|
||||
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
|
||||
ErrorCode updateContainer(int serverIndex, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
ErrorCode rebootServer(int serverIndex);
|
||||
ErrorCode removeAllContainers(int serverIndex);
|
||||
ErrorCode removeContainer(int serverIndex, DockerContainer container);
|
||||
ErrorCode rebootServer(const QString &serverId);
|
||||
ErrorCode removeAllContainers(const QString &serverId);
|
||||
ErrorCode removeContainer(const QString &serverId, DockerContainer container);
|
||||
|
||||
ContainerConfig generateConfig(DockerContainer container, int port, TransportProto transportProto);
|
||||
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, ContainerConfig> &installedContainers, SshSession &sshSession);
|
||||
|
||||
ErrorCode scanServerForInstalledContainers(int serverIndex);
|
||||
ErrorCode scanServerForInstalledContainers(const QString &serverId);
|
||||
|
||||
ErrorCode installContainer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto, ContainerConfig &config);
|
||||
|
||||
ErrorCode installServer(const ServerCredentials &credentials, DockerContainer container, int port, TransportProto transportProto,
|
||||
bool &wasContainerInstalled);
|
||||
ErrorCode installContainer(int serverIndex, DockerContainer container, int port, TransportProto transportProto,
|
||||
ErrorCode installContainer(const QString &serverId, DockerContainer container, int port, TransportProto transportProto,
|
||||
bool &wasContainerInstalled);
|
||||
|
||||
bool isUpdateDockerContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig);
|
||||
@@ -62,11 +62,13 @@ public:
|
||||
|
||||
void cancelInstallation();
|
||||
|
||||
void clearCachedProfile(int serverIndex, DockerContainer container);
|
||||
void clearCachedProfile(const QString &serverId, DockerContainer container);
|
||||
|
||||
ErrorCode validateAndPrepareConfig(int serverIndex);
|
||||
ErrorCode validateAndPrepareConfig(const QString &serverId);
|
||||
|
||||
void validateConfig(int serverIndex);
|
||||
void validateConfig(const QString &serverId);
|
||||
|
||||
void addEmptyServer(const ServerCredentials &credentials);
|
||||
|
||||
signals:
|
||||
void configValidated(bool isValid);
|
||||
@@ -74,8 +76,8 @@ signals:
|
||||
|
||||
void serverIsBusy(const bool isBusy);
|
||||
void cancelInstallationRequested();
|
||||
void clientRevocationRequested(int serverIndex, const ContainerConfig &containerConfig, DockerContainer container);
|
||||
void clientAppendRequested(int serverIndex, const QString &clientId, const QString &clientName, DockerContainer container);
|
||||
void clientRevocationRequested(const QString &serverId, const ContainerConfig &containerConfig, DockerContainer container);
|
||||
void clientAppendRequested(const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container);
|
||||
|
||||
private:
|
||||
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container, SshSession &sshSession);
|
||||
@@ -95,9 +97,9 @@ private:
|
||||
|
||||
ErrorCode processContainerForAdmin(DockerContainer container, ContainerConfig &containerConfig,
|
||||
const ServerCredentials &credentials, SshSession &sshSession,
|
||||
int serverIndex, const QString &clientName);
|
||||
const QString &serverId, const QString &clientName);
|
||||
|
||||
void adminAppendRequested(int serverIndex, DockerContainer container,
|
||||
void adminAppendRequested(const QString &serverId, DockerContainer container,
|
||||
const ContainerConfig &containerConfig, const QString &clientName);
|
||||
|
||||
static void updateContainerConfigAfterInstallation(DockerContainer container, ContainerConfig &containerConfig, const QString &stdOut);
|
||||
@@ -114,4 +116,3 @@ private:
|
||||
};
|
||||
|
||||
#endif // INSTALLCONTROLLER_H
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
|
||||
using namespace amnezia;
|
||||
@@ -292,11 +291,18 @@ ErrorCode UsersController::getXrayClients(const DockerContainer container, const
|
||||
return error;
|
||||
}
|
||||
|
||||
ErrorCode UsersController::updateClients(int serverIndex, const DockerContainer container)
|
||||
ErrorCode UsersController::updateClients(const QString &serverId, const DockerContainer container)
|
||||
{
|
||||
ErrorCode error = ErrorCode::NoError;
|
||||
SshSession sshSession;
|
||||
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
|
||||
if (container == DockerContainer::OpenVpn) {
|
||||
@@ -381,20 +387,27 @@ ErrorCode UsersController::updateClients(int serverIndex, const DockerContainer
|
||||
}
|
||||
|
||||
|
||||
ErrorCode UsersController::appendClient(int serverIndex, const QString &clientId, const QString &clientName, const DockerContainer container)
|
||||
ErrorCode UsersController::appendClient(const QString &serverId, const QString &clientId, const QString &clientName, const DockerContainer container)
|
||||
{
|
||||
ErrorCode error = ErrorCode::NoError;
|
||||
SshSession sshSession;
|
||||
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
error = updateClients(serverIndex, container);
|
||||
error = updateClients(serverId, container);
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
}
|
||||
|
||||
int existingIndex = clientIndexById(clientId, m_clientsTable);
|
||||
if (existingIndex >= 0) {
|
||||
return renameClient(serverIndex, existingIndex, clientName, container, true);
|
||||
return renameClient(serverId, existingIndex, clientName, container, true);
|
||||
}
|
||||
|
||||
QJsonObject client;
|
||||
@@ -426,7 +439,7 @@ ErrorCode UsersController::appendClient(int serverIndex, const QString &clientId
|
||||
return error;
|
||||
}
|
||||
|
||||
ErrorCode UsersController::renameClient(int serverIndex, const int row, const QString &clientName,
|
||||
ErrorCode UsersController::renameClient(const QString &serverId, const int row, const QString &clientName,
|
||||
const DockerContainer container, bool addTimeStamp)
|
||||
{
|
||||
if (row < 0 || row >= m_clientsTable.size()) {
|
||||
@@ -434,7 +447,14 @@ ErrorCode UsersController::renameClient(int serverIndex, const int row, const QS
|
||||
}
|
||||
|
||||
SshSession sshSession;
|
||||
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
auto client = m_clientsTable.at(row).toObject();
|
||||
auto userData = client[configKey::userData].toObject();
|
||||
@@ -470,7 +490,7 @@ ErrorCode UsersController::renameClient(int serverIndex, const int row, const QS
|
||||
}
|
||||
|
||||
ErrorCode UsersController::revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials,
|
||||
const int serverIndex, SshSession* sshSession, QJsonArray &clientsTable)
|
||||
SshSession* sshSession, QJsonArray &clientsTable)
|
||||
{
|
||||
if (row < 0 || row >= clientsTable.size()) {
|
||||
return ErrorCode::InternalError;
|
||||
@@ -689,14 +709,21 @@ ErrorCode UsersController::revokeXray(const int row,
|
||||
return error;
|
||||
}
|
||||
|
||||
ErrorCode UsersController::revokeClient(int serverIndex, const int index, const DockerContainer container)
|
||||
ErrorCode UsersController::revokeClient(const QString &serverId, const int index, const DockerContainer container)
|
||||
{
|
||||
if (index < 0 || index >= m_clientsTable.size()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
SshSession sshSession;
|
||||
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
QString clientId = m_clientsTable.at(index).toObject().value(configKey::clientId).toString();
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
@@ -704,7 +731,7 @@ ErrorCode UsersController::revokeClient(int serverIndex, const int index, const
|
||||
switch(container)
|
||||
{
|
||||
case DockerContainer::OpenVpn: {
|
||||
errorCode = revokeOpenVpn(index, container, credentials, serverIndex, &sshSession, m_clientsTable);
|
||||
errorCode = revokeOpenVpn(index, container, credentials, &sshSession, m_clientsTable);
|
||||
break;
|
||||
}
|
||||
case DockerContainer::WireGuard:
|
||||
@@ -724,12 +751,15 @@ ErrorCode UsersController::revokeClient(int serverIndex, const int index, const
|
||||
}
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
|
||||
ContainerConfig containerCfg = m_serversRepository->containerConfig(serverIndex, container);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ContainerConfig containerCfg = adminConfig->containerConfig(container);
|
||||
QString containerClientId = containerCfg.protocolConfig.clientId();
|
||||
|
||||
if (!clientId.isEmpty() && !containerClientId.isEmpty() && containerClientId.contains(clientId)) {
|
||||
emit adminConfigRevoked(serverIndex, container);
|
||||
emit adminConfigRevoked(serverId, container);
|
||||
}
|
||||
|
||||
emit clientRevoked(index);
|
||||
@@ -739,13 +769,20 @@ ErrorCode UsersController::revokeClient(int serverIndex, const int index, const
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode UsersController::revokeClient(int serverIndex, const ContainerConfig &containerConfig, const DockerContainer container)
|
||||
ErrorCode UsersController::revokeClient(const QString &serverId, const ContainerConfig &containerConfig, const DockerContainer container)
|
||||
{
|
||||
SshSession sshSession;
|
||||
ServerCredentials credentials = m_serversRepository->serverCredentials(serverIndex);
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
errorCode = updateClients(serverIndex, container);
|
||||
errorCode = updateClients(serverId, container);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
@@ -778,7 +815,7 @@ ErrorCode UsersController::revokeClient(int serverIndex, const ContainerConfig &
|
||||
switch (container)
|
||||
{
|
||||
case DockerContainer::OpenVpn: {
|
||||
errorCode = revokeOpenVpn(row, container, credentials, serverIndex, &sshSession, m_clientsTable);
|
||||
errorCode = revokeOpenVpn(row, container, credentials, &sshSession, m_clientsTable);
|
||||
break;
|
||||
}
|
||||
case DockerContainer::WireGuard:
|
||||
@@ -797,7 +834,7 @@ ErrorCode UsersController::revokeClient(int serverIndex, const ContainerConfig &
|
||||
}
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
emit adminConfigRevoked(serverIndex, container);
|
||||
emit adminConfigRevoked(serverId, container);
|
||||
emit clientRevoked(row);
|
||||
emit clientsUpdated(m_clientsTable);
|
||||
}
|
||||
|
||||
@@ -37,21 +37,21 @@ signals:
|
||||
void clientAdded(const QJsonObject &client);
|
||||
void clientRenamed(int row, const QString &newName);
|
||||
void clientRevoked(int row);
|
||||
void adminConfigRevoked(int serverIndex, DockerContainer container);
|
||||
void adminConfigRevoked(const QString &serverId, DockerContainer container);
|
||||
|
||||
public slots:
|
||||
ErrorCode updateClients(int serverIndex, const DockerContainer container);
|
||||
ErrorCode appendClient(int serverIndex, const QString &clientId, const QString &clientName, const DockerContainer container);
|
||||
ErrorCode renameClient(int serverIndex, const int row, const QString &userName, const DockerContainer container, bool addTimeStamp = false);
|
||||
ErrorCode revokeClient(int serverIndex, const int index, const DockerContainer container);
|
||||
ErrorCode revokeClient(int serverIndex, const ContainerConfig &containerConfig, const DockerContainer container);
|
||||
ErrorCode updateClients(const QString &serverId, const DockerContainer container);
|
||||
ErrorCode appendClient(const QString &serverId, const QString &clientId, const QString &clientName, const DockerContainer container);
|
||||
ErrorCode renameClient(const QString &serverId, const int row, const QString &userName, const DockerContainer container, bool addTimeStamp = false);
|
||||
ErrorCode revokeClient(const QString &serverId, const int index, const DockerContainer container);
|
||||
ErrorCode revokeClient(const QString &serverId, const ContainerConfig &containerConfig, const DockerContainer container);
|
||||
|
||||
private:
|
||||
bool isClientExists(const QString &clientId, const QJsonArray &clientsTable);
|
||||
int clientIndexById(const QString &clientId, const QJsonArray &clientsTable);
|
||||
void migration(const QByteArray &clientsTableString, QJsonArray &clientsTable);
|
||||
|
||||
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials, const int serverIndex,
|
||||
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials,
|
||||
SshSession* sshSession, QJsonArray &clientsTable);
|
||||
ErrorCode revokeWireGuard(const int row, const DockerContainer container, const ServerCredentials &credentials,
|
||||
SshSession* sshSession, QJsonArray &clientsTable);
|
||||
@@ -73,4 +73,3 @@ private:
|
||||
};
|
||||
|
||||
#endif // USERSCONTROLLER_H
|
||||
|
||||
|
||||
@@ -1,81 +1,268 @@
|
||||
#include "serversController.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
|
||||
#include "core/models/serverDescription.h"
|
||||
|
||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
#include <AmneziaVPN-Swift.h>
|
||||
#endif
|
||||
|
||||
|
||||
ServersController::ServersController(SecureServersRepository* serversRepository,
|
||||
SecureAppSettingsRepository* appSettingsRepository,
|
||||
QObject *parent)
|
||||
ServersController::ServersController(SecureServersRepository *serversRepository,
|
||||
SecureAppSettingsRepository *appSettingsRepository, QObject *parent)
|
||||
: QObject(parent), m_serversRepository(serversRepository), m_appSettingsRepository(appSettingsRepository)
|
||||
{
|
||||
recomputeGatewayStacks();
|
||||
ensureDefaultServerValid();
|
||||
}
|
||||
|
||||
void ServersController::addServer(const ServerConfig &server)
|
||||
void ServersController::ensureDefaultServerValid()
|
||||
{
|
||||
m_serversRepository->addServer(server);
|
||||
}
|
||||
|
||||
void ServersController::editServer(int index, const ServerConfig &server)
|
||||
{
|
||||
m_serversRepository->editServer(index, server);
|
||||
}
|
||||
|
||||
void ServersController::removeServer(int index)
|
||||
{
|
||||
m_serversRepository->removeServer(index);
|
||||
}
|
||||
|
||||
void ServersController::setDefaultServerIndex(int index)
|
||||
{
|
||||
m_serversRepository->setDefaultServer(index);
|
||||
}
|
||||
|
||||
void ServersController::setDefaultContainer(int serverIndex, DockerContainer container)
|
||||
{
|
||||
m_serversRepository->setDefaultContainer(serverIndex, container);
|
||||
}
|
||||
|
||||
void ServersController::updateContainerConfig(int serverIndex, DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
m_serversRepository->setContainerConfig(serverIndex, container, config);
|
||||
}
|
||||
|
||||
void ServersController::clearCachedProfile(int serverIndex, DockerContainer container)
|
||||
{
|
||||
m_serversRepository->clearLastConnectionConfig(serverIndex, container);
|
||||
}
|
||||
|
||||
QJsonArray ServersController::getServersArray() const
|
||||
{
|
||||
QJsonArray result;
|
||||
QVector<ServerConfig> servers = m_serversRepository->servers();
|
||||
for (const ServerConfig& server : servers) {
|
||||
result.append(server.toJson());
|
||||
if (!getServersCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString defaultId = getDefaultServerId();
|
||||
if (!defaultId.isEmpty() && indexOfServerId(defaultId) >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString firstId = getServerId(0);
|
||||
if (!firstId.isEmpty()) {
|
||||
setDefaultServer(firstId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QVector<ServerConfig> ServersController::getServers() const
|
||||
bool ServersController::renameServer(const QString &serverId, const QString &name)
|
||||
{
|
||||
return m_serversRepository->servers();
|
||||
const serverConfigUtils::ConfigType kind = m_serversRepository->serverKind(serverId);
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->description = name;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->description = name;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->description = name;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->name = name;
|
||||
cfg->nameOverriddenByUser = true;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2:
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ContainerConfig ServersController::getContainerConfig(int serverIndex, DockerContainer container) const
|
||||
void ServersController::removeServer(const QString &serverId)
|
||||
{
|
||||
return m_serversRepository->containerConfig(serverIndex, container);
|
||||
m_serversRepository->removeServer(serverId);
|
||||
}
|
||||
|
||||
void ServersController::setDefaultServer(const QString &serverId)
|
||||
{
|
||||
m_serversRepository->setDefaultServer(serverId);
|
||||
}
|
||||
|
||||
void ServersController::setDefaultContainer(const QString &serverId, DockerContainer container)
|
||||
{
|
||||
const serverConfigUtils::ConfigType kind = m_serversRepository->serverKind(serverId);
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) return;
|
||||
cfg->defaultContainer = container;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) return;
|
||||
cfg->defaultContainer = container;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) return;
|
||||
cfg->defaultContainer = container;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) return;
|
||||
cfg->defaultContainer = container;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2:
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QVector<ServerDescription> ServersController::buildServerDescriptions(bool isAmneziaDnsEnabled) const
|
||||
{
|
||||
QVector<ServerDescription> out;
|
||||
const QVector<QString> ids = m_serversRepository->orderedServerIds();
|
||||
out.reserve(ids.size());
|
||||
|
||||
for (const QString &id : ids) {
|
||||
ServerDescription d;
|
||||
using Kind = serverConfigUtils::ConfigType;
|
||||
const Kind kind = m_serversRepository->serverKind(id);
|
||||
switch (kind) {
|
||||
case Kind::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(id);
|
||||
if (!cfg) {
|
||||
continue;
|
||||
}
|
||||
d = buildServerDescription(*cfg, isAmneziaDnsEnabled);
|
||||
break;
|
||||
}
|
||||
case Kind::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(id);
|
||||
if (!cfg) {
|
||||
continue;
|
||||
}
|
||||
d = buildServerDescription(*cfg, isAmneziaDnsEnabled);
|
||||
break;
|
||||
}
|
||||
case Kind::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(id);
|
||||
if (!cfg) {
|
||||
continue;
|
||||
}
|
||||
d = buildServerDescription(*cfg, isAmneziaDnsEnabled);
|
||||
break;
|
||||
}
|
||||
case Kind::AmneziaPremiumV2:
|
||||
case Kind::AmneziaFreeV3:
|
||||
case Kind::ExternalPremium: {
|
||||
const auto cfg = m_serversRepository->apiV2Config(id);
|
||||
if (!cfg) {
|
||||
continue;
|
||||
}
|
||||
d = buildServerDescription(*cfg, isAmneziaDnsEnabled);
|
||||
break;
|
||||
}
|
||||
case Kind::AmneziaPremiumV1:
|
||||
case Kind::AmneziaFreeV2: {
|
||||
const auto cfg = m_serversRepository->legacyApiConfig(id);
|
||||
if (!cfg) {
|
||||
continue;
|
||||
}
|
||||
d = buildServerDescription(*cfg, isAmneziaDnsEnabled);
|
||||
break;
|
||||
}
|
||||
case Kind::Invalid:
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
d.serverId = id;
|
||||
out.append(d);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QMap<DockerContainer, ContainerConfig> ServersController::getServerContainersMap(const QString &serverId) const
|
||||
{
|
||||
switch (m_serversRepository->serverKind(serverId)) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
return cfg.has_value() ? cfg->containers : QMap<DockerContainer, ContainerConfig>{};
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
return cfg.has_value() ? cfg->containers : QMap<DockerContainer, ContainerConfig>{};
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
return cfg.has_value() ? cfg->containers : QMap<DockerContainer, ContainerConfig>{};
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
const auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
return cfg.has_value() ? cfg->containers : QMap<DockerContainer, ContainerConfig>{};
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2: {
|
||||
const auto cfg = m_serversRepository->legacyApiConfig(serverId);
|
||||
return cfg.has_value() ? cfg->containers : QMap<DockerContainer, ContainerConfig>{};
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
DockerContainer ServersController::getDefaultContainer(const QString &serverId) const
|
||||
{
|
||||
switch (m_serversRepository->serverKind(serverId)) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
return cfg.has_value() ? cfg->defaultContainer : DockerContainer::None;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
return cfg.has_value() ? cfg->defaultContainer : DockerContainer::None;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
return cfg.has_value() ? cfg->defaultContainer : DockerContainer::None;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
const auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
return cfg.has_value() ? cfg->defaultContainer : DockerContainer::None;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2: {
|
||||
const auto cfg = m_serversRepository->legacyApiConfig(serverId);
|
||||
return cfg.has_value() ? cfg->defaultContainer : DockerContainer::None;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return DockerContainer::None;
|
||||
}
|
||||
}
|
||||
|
||||
ContainerConfig ServersController::getContainerConfig(const QString &serverId, DockerContainer container) const
|
||||
{
|
||||
return getServerContainersMap(serverId).value(container);
|
||||
}
|
||||
|
||||
int ServersController::getDefaultServerIndex() const
|
||||
@@ -83,114 +270,131 @@ int ServersController::getDefaultServerIndex() const
|
||||
return m_serversRepository->defaultServerIndex();
|
||||
}
|
||||
|
||||
QString ServersController::getDefaultServerId() const
|
||||
{
|
||||
return m_serversRepository->defaultServerId();
|
||||
}
|
||||
|
||||
int ServersController::getServersCount() const
|
||||
{
|
||||
return m_serversRepository->serversCount();
|
||||
}
|
||||
|
||||
ServerConfig ServersController::getServerConfig(int serverIndex) const
|
||||
QString ServersController::getServerId(int serverIndex) const
|
||||
{
|
||||
return m_serversRepository->server(serverIndex);
|
||||
return m_serversRepository->serverIdAt(serverIndex);
|
||||
}
|
||||
|
||||
ServerCredentials ServersController::getServerCredentials(int serverIndex) const
|
||||
int ServersController::indexOfServerId(const QString &serverId) const
|
||||
{
|
||||
return m_serversRepository->serverCredentials(serverIndex);
|
||||
return m_serversRepository->indexOfServerId(serverId);
|
||||
}
|
||||
|
||||
QPair<QString, QString> ServersController::getDnsPair(int serverIndex, bool isAmneziaDnsEnabled) const
|
||||
QString ServersController::notificationDisplayName(const QString &serverId) const
|
||||
{
|
||||
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
|
||||
return serverConfig.getDnsPair(isAmneziaDnsEnabled,
|
||||
m_appSettingsRepository->primaryDns(),
|
||||
m_appSettingsRepository->secondaryDns());
|
||||
}
|
||||
if (serverId.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ServersController::GatewayStacksData ServersController::gatewayStacks() const
|
||||
{
|
||||
return m_gatewayStacks;
|
||||
}
|
||||
|
||||
void ServersController::recomputeGatewayStacks()
|
||||
{
|
||||
GatewayStacksData computed;
|
||||
bool hasNewTags = false;
|
||||
QVector<ServerConfig> servers = m_serversRepository->servers();
|
||||
|
||||
for (const ServerConfig& serverConfig : servers) {
|
||||
if (serverConfig.isApiV2()) {
|
||||
const ApiV2ServerConfig* apiV2 = serverConfig.as<ApiV2ServerConfig>();
|
||||
if (!apiV2) continue;
|
||||
const QString userCountryCode = apiV2->apiConfig.userCountryCode;
|
||||
const QString serviceType = apiV2->serviceType();
|
||||
|
||||
if (!userCountryCode.isEmpty()) {
|
||||
if (!m_gatewayStacks.userCountryCodes.contains(userCountryCode)) {
|
||||
hasNewTags = true;
|
||||
}
|
||||
computed.userCountryCodes.insert(userCountryCode);
|
||||
}
|
||||
|
||||
if (!serviceType.isEmpty()) {
|
||||
if (!m_gatewayStacks.serviceTypes.contains(serviceType)) {
|
||||
hasNewTags = true;
|
||||
}
|
||||
computed.serviceTypes.insert(serviceType);
|
||||
using Kind = serverConfigUtils::ConfigType;
|
||||
switch (m_serversRepository->serverKind(serverId)) {
|
||||
case Kind::SelfHostedAdmin: {
|
||||
if (const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId)) {
|
||||
if (!cfg->displayName.isEmpty()) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
m_gatewayStacks = std::move(computed);
|
||||
if (hasNewTags) {
|
||||
emit gatewayStacksExpanded();
|
||||
}
|
||||
}
|
||||
|
||||
bool ServersController::GatewayStacksData::operator==(const GatewayStacksData &other) const
|
||||
{
|
||||
return userCountryCodes == other.userCountryCodes && serviceTypes == other.serviceTypes;
|
||||
}
|
||||
|
||||
QJsonObject ServersController::GatewayStacksData::toJson() const
|
||||
{
|
||||
QJsonObject json;
|
||||
|
||||
QJsonArray userCountryCodesArray;
|
||||
for (const QString &code : userCountryCodes) {
|
||||
userCountryCodesArray.append(code);
|
||||
}
|
||||
json[apiDefs::key::userCountryCode] = userCountryCodesArray;
|
||||
|
||||
QJsonArray serviceTypesArray;
|
||||
for (const QString &type : serviceTypes) {
|
||||
serviceTypesArray.append(type);
|
||||
}
|
||||
json[apiDefs::key::serviceType] = serviceTypesArray;
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
bool ServersController::isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType, const QString &serviceProtocol) const
|
||||
{
|
||||
QVector<ServerConfig> servers = m_serversRepository->servers();
|
||||
for (const ServerConfig& serverConfig : servers) {
|
||||
if (serverConfig.isApiV2()) {
|
||||
const ApiV2ServerConfig* apiV2 = serverConfig.as<ApiV2ServerConfig>();
|
||||
if (!apiV2) return false;
|
||||
if (apiV2->apiConfig.userCountryCode == userCountryCode
|
||||
&& apiV2->serviceType() == serviceType
|
||||
&& apiV2->serviceProtocol() == serviceProtocol) {
|
||||
return true;
|
||||
case Kind::SelfHostedUser: {
|
||||
if (const auto cfg = m_serversRepository->selfHostedUserConfig(serverId)) {
|
||||
if (!cfg->displayName.isEmpty()) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Kind::Native: {
|
||||
if (const auto cfg = m_serversRepository->nativeConfig(serverId)) {
|
||||
if (!cfg->displayName.isEmpty()) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Kind::AmneziaPremiumV2:
|
||||
case Kind::AmneziaFreeV3:
|
||||
case Kind::ExternalPremium: {
|
||||
if (const auto cfg = m_serversRepository->apiV2Config(serverId)) {
|
||||
if (!cfg->displayName.isEmpty()) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Kind::AmneziaPremiumV1:
|
||||
case Kind::AmneziaFreeV2: {
|
||||
if (const auto cfg = m_serversRepository->legacyApiConfig(serverId)) {
|
||||
if (!cfg->displayName.isEmpty()) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const int idx = indexOfServerId(serverId);
|
||||
if (idx >= 0) {
|
||||
return QString::number(idx + 1);
|
||||
}
|
||||
return serverId;
|
||||
}
|
||||
|
||||
std::optional<ApiV2ServerConfig> ServersController::apiV2Config(const QString &serverId) const
|
||||
{
|
||||
return m_serversRepository->apiV2Config(serverId);
|
||||
}
|
||||
|
||||
std::optional<SelfHostedAdminServerConfig> ServersController::selfHostedAdminConfig(const QString &serverId) const
|
||||
{
|
||||
return m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
}
|
||||
|
||||
ServerCredentials ServersController::getServerCredentials(const QString &serverId) const
|
||||
{
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (cfg.has_value()) {
|
||||
const ServerCredentials creds = cfg->credentials();
|
||||
if (creds.isValid()) {
|
||||
return creds;
|
||||
}
|
||||
}
|
||||
return ServerCredentials {};
|
||||
}
|
||||
|
||||
bool ServersController::isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol) const
|
||||
{
|
||||
const QVector<QString> ids = m_serversRepository->orderedServerIds();
|
||||
for (const QString &id : ids) {
|
||||
const auto apiV2 = m_serversRepository->apiV2Config(id);
|
||||
if (!apiV2.has_value()) {
|
||||
continue;
|
||||
}
|
||||
if (apiV2->apiConfig.userCountryCode == userCountryCode && apiV2->serviceType() == serviceType
|
||||
&& apiV2->serviceProtocol() == serviceProtocol) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ServersController::hasInstalledContainers(int serverIndex) const
|
||||
bool ServersController::hasInstalledContainers(const QString &serverId) const
|
||||
{
|
||||
ServerConfig serverConfig = m_serversRepository->server(serverIndex);
|
||||
QMap<DockerContainer, ContainerConfig> containers = serverConfig.containers();
|
||||
const QMap<DockerContainer, ContainerConfig> containers = getServerContainersMap(serverId);
|
||||
|
||||
for (auto it = containers.begin(); it != containers.end(); ++it) {
|
||||
DockerContainer container = it.key();
|
||||
if (ContainerUtils::containerService(container) == ServiceType::Vpn) {
|
||||
@@ -203,3 +407,8 @@ bool ServersController::hasInstalledContainers(int serverIndex) const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ServersController::isLegacyApiV1Server(const QString &serverId) const
|
||||
{
|
||||
return !serverId.isEmpty()
|
||||
&& serverConfigUtils::isLegacyApiSubscription(m_serversRepository->serverKind(serverId));
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#ifndef SERVERSCONTROLLER_H
|
||||
#define SERVERSCONTROLLER_H
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QSet>
|
||||
#include <QVector>
|
||||
#include <QMap>
|
||||
|
||||
#include <QPair>
|
||||
|
||||
@@ -17,34 +17,18 @@
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/serverDescription.h"
|
||||
|
||||
class SshSession;
|
||||
class InstallController;
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
/**
|
||||
* @brief Core business logic controller for server operations
|
||||
*
|
||||
* This controller contains pure business logic for managing servers.
|
||||
*/
|
||||
class ServersController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct GatewayStacksData
|
||||
{
|
||||
QSet<QString> userCountryCodes;
|
||||
QSet<QString> serviceTypes;
|
||||
|
||||
bool isEmpty() const { return userCountryCodes.isEmpty() && serviceTypes.isEmpty(); }
|
||||
bool operator==(const GatewayStacksData &other) const;
|
||||
QJsonObject toJson() const;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit ServersController(SecureServersRepository* serversRepository,
|
||||
SecureAppSettingsRepository* appSettingsRepository = nullptr,
|
||||
@@ -52,44 +36,38 @@ public:
|
||||
~ServersController() = default;
|
||||
|
||||
// Server management
|
||||
void addServer(const ServerConfig &server);
|
||||
void editServer(int index, const ServerConfig &server);
|
||||
void removeServer(int index);
|
||||
void setDefaultServerIndex(int index);
|
||||
bool renameServer(const QString &serverId, const QString &name);
|
||||
void removeServer(const QString &serverId);
|
||||
void setDefaultServer(const QString &serverId);
|
||||
|
||||
// Container management
|
||||
void setDefaultContainer(int serverIndex, DockerContainer container);
|
||||
void updateContainerConfig(int serverIndex, DockerContainer container, const ContainerConfig &config);
|
||||
|
||||
// Cache management
|
||||
void clearCachedProfile(int serverIndex, DockerContainer container);
|
||||
void setDefaultContainer(const QString &serverId, DockerContainer container);
|
||||
|
||||
// Getters
|
||||
QJsonArray getServersArray() const;
|
||||
QVector<ServerConfig> getServers() const;
|
||||
QVector<ServerDescription> buildServerDescriptions(bool isAmneziaDnsEnabled) const;
|
||||
int getDefaultServerIndex() const;
|
||||
QString getDefaultServerId() const;
|
||||
int getServersCount() const;
|
||||
ServerConfig getServerConfig(int serverIndex) const;
|
||||
ServerCredentials getServerCredentials(int serverIndex) const;
|
||||
ContainerConfig getContainerConfig(int serverIndex, DockerContainer container) const;
|
||||
QPair<QString, QString> getDnsPair(int serverIndex, bool isAmneziaDnsEnabled) const;
|
||||
|
||||
GatewayStacksData gatewayStacks() const;
|
||||
QString getServerId(int serverIndex) const;
|
||||
int indexOfServerId(const QString &serverId) const;
|
||||
QString notificationDisplayName(const QString &serverId) const;
|
||||
std::optional<ApiV2ServerConfig> apiV2Config(const QString &serverId) const;
|
||||
std::optional<SelfHostedAdminServerConfig> selfHostedAdminConfig(const QString &serverId) const;
|
||||
ServerCredentials getServerCredentials(const QString &serverId) const;
|
||||
QMap<DockerContainer, ContainerConfig> getServerContainersMap(const QString &serverId) const;
|
||||
DockerContainer getDefaultContainer(const QString &serverId) const;
|
||||
ContainerConfig getContainerConfig(const QString &serverId, DockerContainer container) const;
|
||||
|
||||
// Validation
|
||||
bool isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType, const QString &serviceProtocol) const;
|
||||
bool hasInstalledContainers(int serverIndex) const;
|
||||
|
||||
signals:
|
||||
void gatewayStacksExpanded();
|
||||
|
||||
public slots:
|
||||
void recomputeGatewayStacks();
|
||||
bool hasInstalledContainers(const QString &serverId) const;
|
||||
bool isLegacyApiV1Server(const QString &serverId) const;
|
||||
|
||||
private:
|
||||
void ensureDefaultServerValid();
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
GatewayStacksData m_gatewayStacks;
|
||||
};
|
||||
|
||||
#endif // SERVERSCONTROLLER_H
|
||||
|
||||
@@ -179,12 +179,9 @@ QString SettingsController::getAppVersion() const
|
||||
|
||||
void SettingsController::clearSettings()
|
||||
{
|
||||
int serverCount = m_serversRepository->serversCount();
|
||||
|
||||
m_appSettingsRepository->clearSettings();
|
||||
|
||||
m_serversRepository->setServersArray(QJsonArray());
|
||||
m_serversRepository->setDefaultServer(0);
|
||||
|
||||
m_serversRepository->clearServers();
|
||||
|
||||
emit siteSplitTunnelingRouteModeChanged(RouteMode::VpnOnlyForwardSites);
|
||||
emit siteSplitTunnelingToggled(false);
|
||||
|
||||
363
client/core/controllers/updateController.cpp
Normal file
363
client/core/controllers/updateController.cpp
Normal file
@@ -0,0 +1,363 @@
|
||||
#include "updateController.h"
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QVersionNumber>
|
||||
#include <QUrl>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSysInfo>
|
||||
#include <QTimer>
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "logger.h"
|
||||
#include "version.h"
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/errorStrings.h"
|
||||
#include "core/utils/selfhosted/scriptsRegistry.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
Logger logger("UpdateController");
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN-%1-win64.exe");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN_installer.exe";
|
||||
#elif defined(Q_OS_MACOS)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN-%1-Darwin.pkg");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.pkg";
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN-%1-Linux.run");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.run";
|
||||
#endif
|
||||
}
|
||||
|
||||
UpdateController::UpdateController(SecureAppSettingsRepository* appSettingsRepository, QObject *parent)
|
||||
: QObject(parent), m_appSettingsRepository(appSettingsRepository)
|
||||
{
|
||||
}
|
||||
|
||||
QString UpdateController::getRawChangelogText() const
|
||||
{
|
||||
return m_changelogText;
|
||||
}
|
||||
|
||||
QString UpdateController::getReleaseDate() const
|
||||
{
|
||||
return m_releaseDate;
|
||||
}
|
||||
|
||||
QString UpdateController::getVersion() const
|
||||
{
|
||||
return m_version;
|
||||
}
|
||||
|
||||
void UpdateController::checkForUpdates()
|
||||
{
|
||||
if (m_updateCheckRunning || !m_appSettingsRepository) {
|
||||
return;
|
||||
}
|
||||
m_updateCheckRunning = true;
|
||||
|
||||
fetchGatewayUrl();
|
||||
}
|
||||
|
||||
void UpdateController::finishUpdateCheck()
|
||||
{
|
||||
m_updateCheckRunning = false;
|
||||
}
|
||||
|
||||
void UpdateController::doGetAsync(const QString &endpoint, std::function<void(bool, QByteArray)> onDone)
|
||||
{
|
||||
QString fullUrl = m_baseUrl + endpoint;
|
||||
|
||||
QNetworkRequest req;
|
||||
req.setTransferTimeout(7000);
|
||||
req.setUrl(QUrl(fullUrl));
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->get(req);
|
||||
setupNetworkErrorHandling(reply, endpoint);
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, endpoint, onDone]() {
|
||||
const bool ok = (reply->error() == QNetworkReply::NoError);
|
||||
QByteArray data;
|
||||
if (ok) {
|
||||
data = reply->readAll();
|
||||
} else {
|
||||
handleNetworkError(reply, endpoint);
|
||||
}
|
||||
reply->deleteLater();
|
||||
onDone(ok, data);
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateController::fetchGatewayUrl()
|
||||
{
|
||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_appSettingsRepository->getGatewayEndpoint(),
|
||||
m_appSettingsRepository->isDevGatewayEnv(),
|
||||
7000,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
|
||||
QJsonObject apiPayload;
|
||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
||||
apiPayload[apiDefs::key::osVersion] = QSysInfo::productType();
|
||||
apiPayload[apiDefs::key::installationUuid] = m_appSettingsRepository->getInstallationUuid(true);
|
||||
|
||||
// Workaround: wait before contacting gateway to avoid rate limit triggered by other requests (news etc.)
|
||||
QTimer::singleShot(1000, this, [this, gatewayController, apiPayload]() {
|
||||
gatewayController->postAsync(QStringLiteral("%1v1/updater_endpoint"), apiPayload)
|
||||
.then(this, [this](QPair<ErrorCode, QByteArray> result) {
|
||||
auto [err, gatewayResponse] = result;
|
||||
if (err != ErrorCode::NoError) {
|
||||
logger.error() << errorString(err);
|
||||
finishUpdateCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject gatewayData = QJsonDocument::fromJson(gatewayResponse).object();
|
||||
|
||||
QString baseUrl = gatewayData.value("url").toString();
|
||||
if (baseUrl.endsWith('/')) {
|
||||
baseUrl.chop(1);
|
||||
}
|
||||
m_baseUrl = baseUrl;
|
||||
|
||||
fetchVersionInfo();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateController::fetchVersionInfo()
|
||||
{
|
||||
doGetAsync("/VERSION", [this](bool ok, QByteArray data) {
|
||||
if (!ok) {
|
||||
finishUpdateCheck();
|
||||
return;
|
||||
}
|
||||
m_version = QString::fromUtf8(data).trimmed();
|
||||
|
||||
if (!isNewVersionAvailable()) {
|
||||
finishUpdateCheck();
|
||||
return;
|
||||
}
|
||||
fetchChangelog();
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateController::fetchChangelog()
|
||||
{
|
||||
doGetAsync("/CHANGELOG", [this](bool ok, QByteArray data) {
|
||||
if (!ok) {
|
||||
m_changelogText.clear();
|
||||
} else {
|
||||
m_changelogText = QString::fromUtf8(data);
|
||||
}
|
||||
fetchReleaseDate();
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateController::fetchReleaseDate()
|
||||
{
|
||||
doGetAsync("/RELEASE_DATE", [this](bool ok, QByteArray data) {
|
||||
if (ok) {
|
||||
m_releaseDate = QString::fromUtf8(data).trimmed();
|
||||
} else {
|
||||
m_releaseDate = QString();
|
||||
}
|
||||
|
||||
m_downloadUrl = composeDownloadUrl();
|
||||
emit updateFound();
|
||||
finishUpdateCheck();
|
||||
});
|
||||
}
|
||||
|
||||
bool UpdateController::isNewVersionAvailable() const
|
||||
{
|
||||
auto currentVersion = QVersionNumber::fromString(QString(APP_VERSION));
|
||||
auto newVersion = QVersionNumber::fromString(m_version);
|
||||
return newVersion > currentVersion;
|
||||
}
|
||||
|
||||
void UpdateController::setupNetworkErrorHandling(QNetworkReply* reply, const QString& operation)
|
||||
{
|
||||
QObject::connect(reply, &QNetworkReply::errorOccurred, [reply, operation](QNetworkReply::NetworkError error) {
|
||||
logger.error() << QString("Network error occurred while fetching %1: %2 %3")
|
||||
.arg(operation, reply->errorString(), QString::number(error));
|
||||
});
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::sslErrors, [operation](const QList<QSslError> &errors) {
|
||||
QStringList errorStrings;
|
||||
for (const QSslError &err : errors) {
|
||||
errorStrings << err.errorString();
|
||||
}
|
||||
logger.error() << QString("SSL errors while fetching %1: %2").arg(operation, errorStrings.join("; "));
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateController::handleNetworkError(QNetworkReply* reply, const QString& operation)
|
||||
{
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
logger.error() << errorString(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "Error message:" << err;
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
logger.error() << errorString(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
}
|
||||
|
||||
QString UpdateController::composeDownloadUrl() const
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
const QString fileName = QString(kInstallerRemoteFileNamePattern).arg(m_version);
|
||||
return m_baseUrl + "/" + fileName;
|
||||
#else
|
||||
return QString();
|
||||
#endif
|
||||
}
|
||||
|
||||
void UpdateController::runInstaller()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (m_downloadUrl.isEmpty()) {
|
||||
logger.error() << "Download URL is empty";
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(30000);
|
||||
request.setUrl(m_downloadUrl);
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->get(request);
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, [this, reply]() {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QFile file(kInstallerLocalPath);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
logger.error() << "Failed to open installer file for writing:" << kInstallerLocalPath << "Error:" << file.errorString();
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.write(reply->readAll()) == -1) {
|
||||
logger.error() << "Failed to write installer data to file:" << kInstallerLocalPath << "Error:" << file.errorString();
|
||||
file.close();
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
runWindowsInstaller(kInstallerLocalPath);
|
||||
#elif defined(Q_OS_MACOS)
|
||||
runMacInstaller(kInstallerLocalPath);
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
runLinuxInstaller(kInstallerLocalPath);
|
||||
#endif
|
||||
} else {
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
logger.error() << errorString(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
logger.error() << QString::fromUtf8(reply->readAll());
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "Error message:" << err;
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
logger.error() << errorString(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
int UpdateController::runWindowsInstaller(const QString &installerPath)
|
||||
{
|
||||
qint64 pid;
|
||||
bool success = QProcess::startDetached(installerPath, QStringList(), QString(), &pid);
|
||||
|
||||
if (success) {
|
||||
logger.info() << "Installation process started with PID:" << pid;
|
||||
} else {
|
||||
logger.error() << "Failed to start installation process";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
int UpdateController::runMacInstaller(const QString &installerPath)
|
||||
{
|
||||
// Create temporary directory for extraction
|
||||
QTemporaryDir extractDir;
|
||||
extractDir.setAutoRemove(false);
|
||||
if (!extractDir.isValid()) {
|
||||
logger.error() << "Failed to create temporary directory";
|
||||
return -1;
|
||||
}
|
||||
logger.info() << "Temporary directory created:" << extractDir.path();
|
||||
|
||||
// Create script file in the temporary directory
|
||||
QString scriptPath = extractDir.path() + "/mac_installer.sh";
|
||||
QFile scriptFile(scriptPath);
|
||||
if (!scriptFile.open(QIODevice::WriteOnly)) {
|
||||
logger.error() << "Failed to create script file";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get script content from registry
|
||||
QString scriptContent = amnezia::scriptData(amnezia::ClientScriptType::mac_installer);
|
||||
if (scriptContent.isEmpty()) {
|
||||
logger.error() << "macOS installer script content is empty";
|
||||
scriptFile.close();
|
||||
return -1;
|
||||
}
|
||||
|
||||
scriptFile.write(scriptContent.toUtf8());
|
||||
scriptFile.close();
|
||||
logger.info() << "Script file created:" << scriptPath;
|
||||
|
||||
// Make script executable
|
||||
QFile::setPermissions(scriptPath, QFile::permissions(scriptPath) | QFile::ExeUser);
|
||||
|
||||
// Start detached process
|
||||
qint64 pid;
|
||||
bool success =
|
||||
QProcess::startDetached("/bin/bash", QStringList() << scriptPath << extractDir.path() << installerPath, extractDir.path(), &pid);
|
||||
|
||||
if (success) {
|
||||
logger.info() << "Installation process started with PID:" << pid;
|
||||
} else {
|
||||
logger.error() << "Failed to start installation process";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
int UpdateController::runLinuxInstaller(const QString &installerPath)
|
||||
{
|
||||
QFile::setPermissions(installerPath, QFile::permissions(installerPath) | QFile::ExeUser);
|
||||
|
||||
qint64 pid;
|
||||
bool success = QProcess::startDetached(installerPath, QStringList(), QString(), &pid);
|
||||
|
||||
if (success) {
|
||||
logger.info() << "Installation process started with PID:" << pid;
|
||||
} else {
|
||||
logger.error() << "Failed to start installation process";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
57
client/core/controllers/updateController.h
Normal file
57
client/core/controllers/updateController.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef UPDATECONTROLLER_H
|
||||
#define UPDATECONTROLLER_H
|
||||
|
||||
#include <functional>
|
||||
#include <QObject>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
|
||||
class UpdateController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UpdateController(SecureAppSettingsRepository* appSettingsRepository, QObject *parent = nullptr);
|
||||
|
||||
QString getRawChangelogText() const;
|
||||
QString getReleaseDate() const;
|
||||
QString getVersion() const;
|
||||
|
||||
public slots:
|
||||
void checkForUpdates();
|
||||
void runInstaller();
|
||||
|
||||
signals:
|
||||
void updateFound();
|
||||
|
||||
private:
|
||||
void finishUpdateCheck();
|
||||
void fetchGatewayUrl();
|
||||
void fetchVersionInfo();
|
||||
void fetchChangelog();
|
||||
void fetchReleaseDate();
|
||||
void doGetAsync(const QString &endpoint, std::function<void(bool, QByteArray)> onDone);
|
||||
bool isNewVersionAvailable() const;
|
||||
void setupNetworkErrorHandling(QNetworkReply* reply, const QString& operation);
|
||||
void handleNetworkError(QNetworkReply* reply, const QString& operation);
|
||||
QString composeDownloadUrl() const;
|
||||
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
|
||||
QString m_baseUrl;
|
||||
QString m_changelogText;
|
||||
QString m_version;
|
||||
QString m_releaseDate;
|
||||
QString m_downloadUrl;
|
||||
bool m_updateCheckRunning = false;
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
int runWindowsInstaller(const QString &installerPath);
|
||||
#elif defined(Q_OS_MACOS)
|
||||
int runMacInstaller(const QString &installerPath);
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
int runLinuxInstaller(const QString &installerPath);
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // UPDATECONTROLLER_H
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
|
||||
@@ -58,7 +58,7 @@ struct ApiConfig
|
||||
|
||||
QString stackType;
|
||||
QString cliVersion;
|
||||
bool isTestPurchase;
|
||||
bool isTestPurchase = false;
|
||||
bool isInAppPurchase = false;
|
||||
bool subscriptionExpiredByServer = false;
|
||||
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
#include "apiV1ServerConfig.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
using namespace ContainerEnumNS;
|
||||
|
||||
bool ApiV1ServerConfig::isPremium() const
|
||||
{
|
||||
constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
|
||||
return apiEndpoint.contains(premiumV1Endpoint);
|
||||
}
|
||||
|
||||
bool ApiV1ServerConfig::isFree() const
|
||||
{
|
||||
constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
|
||||
return apiEndpoint.contains(freeV2Endpoint);
|
||||
}
|
||||
|
||||
QString ApiV1ServerConfig::vpnKey() const
|
||||
{
|
||||
QJsonObject json = toJson();
|
||||
return apiUtils::getPremiumV1VpnKey(json);
|
||||
}
|
||||
|
||||
bool ApiV1ServerConfig::hasContainers() const
|
||||
{
|
||||
return !containers.isEmpty();
|
||||
}
|
||||
|
||||
ContainerConfig ApiV1ServerConfig::containerConfig(DockerContainer container) const
|
||||
{
|
||||
if (!containers.contains(container)) {
|
||||
return ContainerConfig{};
|
||||
}
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
QJsonObject ApiV1ServerConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
|
||||
if (!name.isEmpty()) {
|
||||
obj[configKey::name] = name;
|
||||
}
|
||||
if (!description.isEmpty()) {
|
||||
obj[configKey::description] = description;
|
||||
}
|
||||
if (!protocol.isEmpty()) {
|
||||
obj[apiDefs::key::protocol] = protocol;
|
||||
}
|
||||
if (!apiEndpoint.isEmpty()) {
|
||||
obj[apiDefs::key::apiEndpoint] = apiEndpoint;
|
||||
}
|
||||
if (!apiKey.isEmpty()) {
|
||||
obj[apiDefs::key::apiKey] = apiKey;
|
||||
}
|
||||
|
||||
obj[configKey::configVersion] = configVersion;
|
||||
|
||||
if (!hostName.isEmpty()) {
|
||||
obj[configKey::hostName] = hostName;
|
||||
}
|
||||
|
||||
QJsonArray containersArray;
|
||||
for (auto it = containers.begin(); it != containers.end(); ++it) {
|
||||
QJsonObject containerObj = it.value().toJson();
|
||||
containersArray.append(containerObj);
|
||||
}
|
||||
if (!containersArray.isEmpty()) {
|
||||
obj[configKey::containers] = containersArray;
|
||||
}
|
||||
|
||||
if (defaultContainer != DockerContainer::None) {
|
||||
obj[configKey::defaultContainer] = ContainerUtils::containerToString(defaultContainer);
|
||||
}
|
||||
|
||||
if (!dns1.isEmpty()) {
|
||||
obj[configKey::dns1] = dns1;
|
||||
}
|
||||
if (!dns2.isEmpty()) {
|
||||
obj[configKey::dns2] = dns2;
|
||||
}
|
||||
|
||||
if (crc > 0) {
|
||||
obj[configKey::crc] = crc;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
ApiV1ServerConfig ApiV1ServerConfig::fromJson(const QJsonObject& json)
|
||||
{
|
||||
ApiV1ServerConfig config;
|
||||
|
||||
config.name = json.value(configKey::name).toString();
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.protocol = json.value(apiDefs::key::protocol).toString();
|
||||
config.apiEndpoint = json.value(apiDefs::key::apiEndpoint).toString();
|
||||
config.apiKey = json.value(apiDefs::key::apiKey).toString();
|
||||
config.configVersion = json.value(configKey::configVersion).toInt(1);
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
QJsonArray containersArray = json.value(configKey::containers).toArray();
|
||||
for (const QJsonValue& val : containersArray) {
|
||||
QJsonObject containerObj = val.toObject();
|
||||
ContainerConfig containerConfig = ContainerConfig::fromJson(containerObj);
|
||||
|
||||
QString containerStr = containerObj.value(configKey::container).toString();
|
||||
DockerContainer container = ContainerUtils::containerFromString(containerStr);
|
||||
|
||||
config.containers.insert(container, containerConfig);
|
||||
}
|
||||
|
||||
QString defaultContainerStr = json.value(configKey::defaultContainer).toString();
|
||||
config.defaultContainer = ContainerUtils::containerFromString(defaultContainerStr);
|
||||
|
||||
config.dns1 = json.value(configKey::dns1).toString();
|
||||
config.dns2 = json.value(configKey::dns2).toString();
|
||||
|
||||
config.crc = json.value(configKey::crc).toInt(0);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
#ifndef APIV1SERVERCONFIG_H
|
||||
#define APIV1SERVERCONFIG_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
using namespace ContainerEnumNS;
|
||||
|
||||
struct ApiV1ServerConfig {
|
||||
QString description;
|
||||
QString hostName;
|
||||
QMap<DockerContainer, ContainerConfig> containers;
|
||||
DockerContainer defaultContainer;
|
||||
QString dns1;
|
||||
QString dns2;
|
||||
|
||||
QString name;
|
||||
QString protocol;
|
||||
QString apiEndpoint;
|
||||
QString apiKey;
|
||||
int crc;
|
||||
int configVersion;
|
||||
|
||||
bool isPremium() const;
|
||||
bool isFree() const;
|
||||
QString vpnKey() const;
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
QJsonObject toJson() const;
|
||||
static ApiV1ServerConfig fromJson(const QJsonObject& json);
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // APIV1SERVERCONFIG_H
|
||||
|
||||
@@ -80,6 +80,9 @@ QJsonObject ApiV2ServerConfig::toJson() const
|
||||
if (!description.isEmpty()) {
|
||||
obj[configKey::description] = description;
|
||||
}
|
||||
if (!displayName.isEmpty()) {
|
||||
obj[configKey::displayName] = displayName;
|
||||
}
|
||||
|
||||
obj[configKey::configVersion] = configVersion;
|
||||
|
||||
@@ -131,6 +134,7 @@ ApiV2ServerConfig ApiV2ServerConfig::fromJson(const QJsonObject& json)
|
||||
config.name = json.value(configKey::name).toString();
|
||||
config.nameOverriddenByUser = json.value(configKey::nameOverriddenByUser).toBool(false);
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.configVersion = json.value(configKey::configVersion).toInt(2);
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
@@ -163,6 +167,10 @@ ApiV2ServerConfig ApiV2ServerConfig::fromJson(const QJsonObject& json)
|
||||
config.authData = AuthData::fromJson(authDataObj);
|
||||
}
|
||||
|
||||
if (config.displayName.isEmpty()) {
|
||||
config.displayName = config.name.isEmpty() ? config.description : config.name;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/api/apiConfig.h"
|
||||
#include "core/models/api/authData.h"
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
|
||||
@@ -21,6 +21,7 @@ using namespace ContainerEnumNS;
|
||||
|
||||
struct ApiV2ServerConfig {
|
||||
QString description;
|
||||
QString displayName;
|
||||
QString hostName;
|
||||
QMap<DockerContainer, ContainerConfig> containers;
|
||||
DockerContainer defaultContainer;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
|
||||
|
||||
43
client/core/models/api/legacyApiServerConfig.cpp
Normal file
43
client/core/models/api/legacyApiServerConfig.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "legacyApiServerConfig.h"
|
||||
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
bool LegacyApiServerConfig::hasContainers() const
|
||||
{
|
||||
return !containers.isEmpty();
|
||||
}
|
||||
|
||||
ContainerConfig LegacyApiServerConfig::containerConfig(DockerContainer container) const
|
||||
{
|
||||
if (!containers.contains(container)) {
|
||||
return ContainerConfig{};
|
||||
}
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
LegacyApiServerConfig LegacyApiServerConfig::fromJson(const QJsonObject &json)
|
||||
{
|
||||
LegacyApiServerConfig config;
|
||||
|
||||
config.name = json.value(configKey::name).toString();
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
config.crc = json.value(configKey::crc).toInt(0);
|
||||
|
||||
config.configVersion = json.value(configKey::configVersion).toInt(1);
|
||||
config.apiEndpoint = json.value(apiDefs::key::apiEndpoint).toString();
|
||||
|
||||
if (config.displayName.isEmpty()) {
|
||||
config.displayName = config.name.isEmpty() ? config.description : config.name;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
} // namespace amnezia
|
||||
38
client/core/models/api/legacyApiServerConfig.h
Normal file
38
client/core/models/api/legacyApiServerConfig.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef LEGACYAPISERVERCONFIG_H
|
||||
#define LEGACYAPISERVERCONFIG_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
using namespace ContainerEnumNS;
|
||||
|
||||
struct LegacyApiServerConfig {
|
||||
QString description;
|
||||
QString displayName;
|
||||
QString hostName;
|
||||
QMap<DockerContainer, ContainerConfig> containers;
|
||||
DockerContainer defaultContainer = DockerContainer::None;
|
||||
QString dns1;
|
||||
QString dns2;
|
||||
|
||||
QString name;
|
||||
int crc = 0;
|
||||
|
||||
int configVersion = 0;
|
||||
QString apiEndpoint;
|
||||
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
static LegacyApiServerConfig fromJson(const QJsonObject &json);
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // LEGACYAPISERVERCONFIG_H
|
||||
@@ -35,6 +35,9 @@ QJsonObject NativeServerConfig::toJson() const
|
||||
if (!description.isEmpty()) {
|
||||
obj[configKey::description] = this->description;
|
||||
}
|
||||
if (!displayName.isEmpty()) {
|
||||
obj[configKey::displayName] = displayName;
|
||||
}
|
||||
if (!hostName.isEmpty()) {
|
||||
obj[configKey::hostName] = hostName;
|
||||
}
|
||||
@@ -67,6 +70,7 @@ NativeServerConfig NativeServerConfig::fromJson(const QJsonObject& json)
|
||||
NativeServerConfig config;
|
||||
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
QJsonArray containersArray = json.value(configKey::containers).toArray();
|
||||
@@ -86,6 +90,10 @@ NativeServerConfig NativeServerConfig::fromJson(const QJsonObject& json)
|
||||
config.dns1 = json.value(configKey::dns1).toString();
|
||||
config.dns2 = json.value(configKey::dns2).toString();
|
||||
|
||||
if (config.displayName.isEmpty()) {
|
||||
config.displayName = config.description.isEmpty() ? config.hostName : config.description;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ using namespace ContainerEnumNS;
|
||||
|
||||
struct NativeServerConfig {
|
||||
QString description;
|
||||
QString displayName;
|
||||
QString hostName;
|
||||
QMap<DockerContainer, ContainerConfig> containers;
|
||||
DockerContainer defaultContainer;
|
||||
|
||||
170
client/core/models/selfhosted/selfHostedAdminServerConfig.cpp
Normal file
170
client/core/models/selfhosted/selfHostedAdminServerConfig.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#include "selfHostedAdminServerConfig.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
using namespace ContainerEnumNS;
|
||||
|
||||
bool SelfHostedAdminServerConfig::hasCredentials() const
|
||||
{
|
||||
return !userName.isEmpty() && !password.isEmpty() && port > 0;
|
||||
}
|
||||
|
||||
bool SelfHostedAdminServerConfig::isReadOnly() const
|
||||
{
|
||||
return !hasCredentials();
|
||||
}
|
||||
|
||||
ServerCredentials SelfHostedAdminServerConfig::credentials() const
|
||||
{
|
||||
ServerCredentials creds;
|
||||
creds.hostName = hostName;
|
||||
creds.userName = userName;
|
||||
creds.secretData = password;
|
||||
creds.port = port;
|
||||
return creds;
|
||||
}
|
||||
|
||||
bool SelfHostedAdminServerConfig::hasContainers() const
|
||||
{
|
||||
return !containers.isEmpty();
|
||||
}
|
||||
|
||||
ContainerConfig SelfHostedAdminServerConfig::containerConfig(DockerContainer container) const
|
||||
{
|
||||
if (!containers.contains(container)) {
|
||||
return ContainerConfig{};
|
||||
}
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
void SelfHostedAdminServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
containers[container] = config;
|
||||
}
|
||||
|
||||
void SelfHostedAdminServerConfig::clearCachedClientProfile(DockerContainer container)
|
||||
{
|
||||
if (ContainerUtils::containerService(container) == ServiceType::Other) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContainerConfig cleared = containerConfig(container);
|
||||
cleared.protocolConfig.clearClientConfig();
|
||||
containers[container] = cleared;
|
||||
}
|
||||
|
||||
QPair<QString, QString> SelfHostedAdminServerConfig::getDnsPair(bool isAmneziaDnsEnabled, const QString &primaryDns,
|
||||
const QString &secondaryDns) const
|
||||
{
|
||||
QString d1 = dns1;
|
||||
QString d2 = dns2;
|
||||
const bool dnsOnServer = containers.contains(DockerContainer::Dns);
|
||||
|
||||
if (d1.isEmpty() || !NetworkUtilities::checkIPv4Format(d1)) {
|
||||
d1 = (isAmneziaDnsEnabled && dnsOnServer) ? protocols::dns::amneziaDnsIp : primaryDns;
|
||||
}
|
||||
if (d2.isEmpty() || !NetworkUtilities::checkIPv4Format(d2)) {
|
||||
d2 = secondaryDns;
|
||||
}
|
||||
return { d1, d2 };
|
||||
}
|
||||
|
||||
QJsonObject SelfHostedAdminServerConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
|
||||
if (!description.isEmpty()) {
|
||||
obj[configKey::description] = this->description;
|
||||
}
|
||||
if (!displayName.isEmpty()) {
|
||||
obj[configKey::displayName] = displayName;
|
||||
}
|
||||
if (!hostName.isEmpty()) {
|
||||
obj[configKey::hostName] = hostName;
|
||||
}
|
||||
|
||||
QJsonArray containersArray;
|
||||
for (auto it = containers.begin(); it != containers.end(); ++it) {
|
||||
QJsonObject containerObj = it.value().toJson();
|
||||
containersArray.append(containerObj);
|
||||
}
|
||||
if (!containersArray.isEmpty()) {
|
||||
obj[configKey::containers] = containersArray;
|
||||
}
|
||||
|
||||
if (defaultContainer != DockerContainer::None) {
|
||||
obj[configKey::defaultContainer] = ContainerUtils::containerToString(defaultContainer);
|
||||
}
|
||||
|
||||
if (!dns1.isEmpty()) {
|
||||
obj[configKey::dns1] = dns1;
|
||||
}
|
||||
if (!dns2.isEmpty()) {
|
||||
obj[configKey::dns2] = dns2;
|
||||
}
|
||||
|
||||
if (!userName.isEmpty()) {
|
||||
obj[configKey::userName] = userName;
|
||||
}
|
||||
if (!password.isEmpty()) {
|
||||
obj[configKey::password] = password;
|
||||
}
|
||||
if (port > 0) {
|
||||
obj[configKey::port] = port;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
SelfHostedAdminServerConfig SelfHostedAdminServerConfig::fromJson(const QJsonObject &json)
|
||||
{
|
||||
SelfHostedAdminServerConfig config;
|
||||
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
QJsonArray containersArray = json.value(configKey::containers).toArray();
|
||||
for (const QJsonValue &val : containersArray) {
|
||||
QJsonObject containerObj = val.toObject();
|
||||
ContainerConfig cc = ContainerConfig::fromJson(containerObj);
|
||||
|
||||
QString containerStr = containerObj.value(configKey::container).toString();
|
||||
DockerContainer container = ContainerUtils::containerFromString(containerStr);
|
||||
|
||||
config.containers.insert(container, cc);
|
||||
}
|
||||
|
||||
QString defaultContainerStr = json.value(configKey::defaultContainer).toString();
|
||||
config.defaultContainer = ContainerUtils::containerFromString(defaultContainerStr);
|
||||
|
||||
config.dns1 = json.value(configKey::dns1).toString();
|
||||
config.dns2 = json.value(configKey::dns2).toString();
|
||||
|
||||
config.userName = json.value(configKey::userName).toString();
|
||||
config.password = json.value(configKey::password).toString();
|
||||
if (json.contains(configKey::port)) {
|
||||
config.port = json.value(configKey::port).toInt();
|
||||
} else {
|
||||
config.port = 0;
|
||||
}
|
||||
|
||||
if (config.displayName.isEmpty()) {
|
||||
config.displayName = config.description.isEmpty() ? config.hostName : config.description;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
} // namespace amnezia
|
||||
53
client/core/models/selfhosted/selfHostedAdminServerConfig.h
Normal file
53
client/core/models/selfhosted/selfHostedAdminServerConfig.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef SELFHOSTEDADMINSERVERCONFIG_H
|
||||
#define SELFHOSTEDADMINSERVERCONFIG_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
#include <QPair>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
using namespace ContainerEnumNS;
|
||||
|
||||
struct SelfHostedAdminServerConfig {
|
||||
QString description;
|
||||
QString displayName;
|
||||
QString hostName;
|
||||
QMap<DockerContainer, ContainerConfig> containers;
|
||||
DockerContainer defaultContainer;
|
||||
QString dns1;
|
||||
QString dns2;
|
||||
|
||||
QString userName;
|
||||
QString password;
|
||||
int port = 0;
|
||||
|
||||
bool hasCredentials() const;
|
||||
bool isReadOnly() const;
|
||||
ServerCredentials credentials() const;
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
|
||||
|
||||
void clearCachedClientProfile(DockerContainer container);
|
||||
|
||||
QPair<QString, QString> getDnsPair(bool isAmneziaDnsEnabled, const QString &primaryDns,
|
||||
const QString &secondaryDns) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static SelfHostedAdminServerConfig fromJson(const QJsonObject &json);
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // SELFHOSTEDADMINSERVERCONFIG_H
|
||||
@@ -1,53 +1,40 @@
|
||||
#include "selfHostedServerConfig.h"
|
||||
#include "selfHostedUserServerConfig.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
using namespace ContainerEnumNS;
|
||||
|
||||
bool SelfHostedServerConfig::hasCredentials() const
|
||||
bool SelfHostedUserServerConfig::hasCredentials() const
|
||||
{
|
||||
return userName.has_value() && password.has_value() && port.has_value();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SelfHostedServerConfig::isReadOnly() const
|
||||
bool SelfHostedUserServerConfig::isReadOnly() const
|
||||
{
|
||||
return !hasCredentials();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<ServerCredentials> SelfHostedServerConfig::credentials() const
|
||||
std::optional<ServerCredentials> SelfHostedUserServerConfig::credentials() const
|
||||
{
|
||||
if (!hasCredentials()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ServerCredentials creds;
|
||||
creds.hostName = hostName;
|
||||
creds.userName = userName.value();
|
||||
creds.secretData = password.value();
|
||||
creds.port = port.value();
|
||||
|
||||
return creds;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool SelfHostedServerConfig::hasContainers() const
|
||||
bool SelfHostedUserServerConfig::hasContainers() const
|
||||
{
|
||||
return !containers.isEmpty();
|
||||
}
|
||||
|
||||
ContainerConfig SelfHostedServerConfig::containerConfig(DockerContainer container) const
|
||||
ContainerConfig SelfHostedUserServerConfig::containerConfig(DockerContainer container) const
|
||||
{
|
||||
if (!containers.contains(container)) {
|
||||
return ContainerConfig{};
|
||||
@@ -55,17 +42,20 @@ ContainerConfig SelfHostedServerConfig::containerConfig(DockerContainer containe
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
QJsonObject SelfHostedServerConfig::toJson() const
|
||||
QJsonObject SelfHostedUserServerConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
|
||||
|
||||
if (!description.isEmpty()) {
|
||||
obj[configKey::description] = this->description;
|
||||
}
|
||||
if (!displayName.isEmpty()) {
|
||||
obj[configKey::displayName] = displayName;
|
||||
}
|
||||
if (!hostName.isEmpty()) {
|
||||
obj[configKey::hostName] = hostName;
|
||||
}
|
||||
|
||||
|
||||
QJsonArray containersArray;
|
||||
for (auto it = containers.begin(); it != containers.end(); ++it) {
|
||||
QJsonObject containerObj = it.value().toJson();
|
||||
@@ -74,67 +64,51 @@ QJsonObject SelfHostedServerConfig::toJson() const
|
||||
if (!containersArray.isEmpty()) {
|
||||
obj[configKey::containers] = containersArray;
|
||||
}
|
||||
|
||||
|
||||
if (defaultContainer != DockerContainer::None) {
|
||||
obj[configKey::defaultContainer] = ContainerUtils::containerToString(defaultContainer);
|
||||
}
|
||||
|
||||
|
||||
if (!dns1.isEmpty()) {
|
||||
obj[configKey::dns1] = dns1;
|
||||
}
|
||||
if (!dns2.isEmpty()) {
|
||||
obj[configKey::dns2] = dns2;
|
||||
}
|
||||
|
||||
if (userName.has_value()) {
|
||||
obj[configKey::userName] = userName.value();
|
||||
}
|
||||
if (password.has_value()) {
|
||||
obj[configKey::password] = password.value();
|
||||
}
|
||||
if (port.has_value()) {
|
||||
obj[configKey::port] = port.value();
|
||||
}
|
||||
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
SelfHostedServerConfig SelfHostedServerConfig::fromJson(const QJsonObject& json)
|
||||
SelfHostedUserServerConfig SelfHostedUserServerConfig::fromJson(const QJsonObject &json)
|
||||
{
|
||||
SelfHostedServerConfig config;
|
||||
|
||||
SelfHostedUserServerConfig config;
|
||||
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
|
||||
QJsonArray containersArray = json.value(configKey::containers).toArray();
|
||||
for (const QJsonValue& val : containersArray) {
|
||||
for (const QJsonValue &val : containersArray) {
|
||||
QJsonObject containerObj = val.toObject();
|
||||
ContainerConfig containerConfig = ContainerConfig::fromJson(containerObj);
|
||||
|
||||
ContainerConfig cc = ContainerConfig::fromJson(containerObj);
|
||||
|
||||
QString containerStr = containerObj.value(configKey::container).toString();
|
||||
DockerContainer container = ContainerUtils::containerFromString(containerStr);
|
||||
|
||||
config.containers.insert(container, containerConfig);
|
||||
|
||||
config.containers.insert(container, cc);
|
||||
}
|
||||
|
||||
|
||||
QString defaultContainerStr = json.value(configKey::defaultContainer).toString();
|
||||
config.defaultContainer = ContainerUtils::containerFromString(defaultContainerStr);
|
||||
|
||||
|
||||
config.dns1 = json.value(configKey::dns1).toString();
|
||||
config.dns2 = json.value(configKey::dns2).toString();
|
||||
|
||||
if (json.contains(configKey::userName)) {
|
||||
config.userName = json.value(configKey::userName).toString();
|
||||
|
||||
if (config.displayName.isEmpty()) {
|
||||
config.displayName = config.description.isEmpty() ? config.hostName : config.description;
|
||||
}
|
||||
if (json.contains(configKey::password)) {
|
||||
config.password = json.value(configKey::password).toString();
|
||||
}
|
||||
if (json.contains(configKey::port)) {
|
||||
config.port = json.value(configKey::port).toInt();
|
||||
}
|
||||
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SELFHOSTEDSERVERCONFIG_H
|
||||
#define SELFHOSTEDSERVERCONFIG_H
|
||||
#ifndef SELFHOSTEDUSERSERVERCONFIG_H
|
||||
#define SELFHOSTEDUSERSERVERCONFIG_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
@@ -9,8 +9,6 @@
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
|
||||
namespace amnezia
|
||||
@@ -18,28 +16,24 @@ namespace amnezia
|
||||
|
||||
using namespace ContainerEnumNS;
|
||||
|
||||
struct SelfHostedServerConfig {
|
||||
struct SelfHostedUserServerConfig {
|
||||
QString description;
|
||||
QString displayName;
|
||||
QString hostName;
|
||||
QMap<DockerContainer, ContainerConfig> containers;
|
||||
DockerContainer defaultContainer;
|
||||
QString dns1;
|
||||
QString dns2;
|
||||
|
||||
std::optional<QString> userName;
|
||||
std::optional<QString> password;
|
||||
std::optional<int> port;
|
||||
|
||||
|
||||
bool hasCredentials() const;
|
||||
bool isReadOnly() const;
|
||||
std::optional<ServerCredentials> credentials() const;
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
QJsonObject toJson() const;
|
||||
static SelfHostedServerConfig fromJson(const QJsonObject& json);
|
||||
static SelfHostedUserServerConfig fromJson(const QJsonObject &json);
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // SELFHOSTEDSERVERCONFIG_H
|
||||
|
||||
#endif // SELFHOSTEDUSERSERVERCONFIG_H
|
||||
@@ -1,234 +0,0 @@
|
||||
#include "serverConfig.h"
|
||||
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/models/selfhosted/selfHostedServerConfig.h"
|
||||
#include "core/models/selfhosted/nativeServerConfig.h"
|
||||
#include "core/models/api/apiV1ServerConfig.h"
|
||||
#include "core/models/api/apiV2ServerConfig.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
using namespace ContainerEnumNS;
|
||||
|
||||
QString ServerConfig::description() const
|
||||
{
|
||||
return std::visit([](const auto& v) { return v.description; }, data);
|
||||
}
|
||||
|
||||
QString ServerConfig::hostName() const
|
||||
{
|
||||
return std::visit([](const auto& v) { return v.hostName; }, data);
|
||||
}
|
||||
|
||||
QString ServerConfig::displayName() const
|
||||
{
|
||||
if (isApiV1()) {
|
||||
const auto *apiV1 = as<ApiV1ServerConfig>();
|
||||
return apiV1 ? apiV1->name : description();
|
||||
}
|
||||
if (isApiV2()) {
|
||||
const auto *apiV2 = as<ApiV2ServerConfig>();
|
||||
return apiV2 ? apiV2->name : description();
|
||||
}
|
||||
QString name = description();
|
||||
return name.isEmpty() ? hostName() : name;
|
||||
}
|
||||
|
||||
QMap<DockerContainer, ContainerConfig> ServerConfig::containers() const
|
||||
{
|
||||
return std::visit([](const auto& v) { return v.containers; }, data);
|
||||
}
|
||||
|
||||
DockerContainer ServerConfig::defaultContainer() const
|
||||
{
|
||||
return std::visit([](const auto& v) { return v.defaultContainer; }, data);
|
||||
}
|
||||
|
||||
QString ServerConfig::dns1() const
|
||||
{
|
||||
return std::visit([](const auto& v) { return v.dns1; }, data);
|
||||
}
|
||||
|
||||
QString ServerConfig::dns2() const
|
||||
{
|
||||
return std::visit([](const auto& v) { return v.dns2; }, data);
|
||||
}
|
||||
|
||||
bool ServerConfig::hasContainers() const
|
||||
{
|
||||
return std::visit([](const auto& v) { return v.hasContainers(); }, data);
|
||||
}
|
||||
|
||||
ContainerConfig ServerConfig::containerConfig(DockerContainer container) const
|
||||
{
|
||||
return std::visit([container](const auto& v) { return v.containerConfig(container); }, data);
|
||||
}
|
||||
|
||||
int ServerConfig::crc() const
|
||||
{
|
||||
return std::visit([](const auto& v) -> int {
|
||||
using T = std::decay_t<decltype(v)>;
|
||||
if constexpr (std::is_same_v<T, ApiV1ServerConfig> ||
|
||||
std::is_same_v<T, ApiV2ServerConfig>) {
|
||||
return v.crc;
|
||||
}
|
||||
return 0;
|
||||
}, data);
|
||||
}
|
||||
|
||||
int ServerConfig::configVersion() const
|
||||
{
|
||||
return std::visit([](const auto& v) -> int {
|
||||
using T = std::decay_t<decltype(v)>;
|
||||
if constexpr (std::is_same_v<T, ApiV1ServerConfig>) {
|
||||
return apiDefs::ConfigSource::Telegram;
|
||||
} else if constexpr (std::is_same_v<T, ApiV2ServerConfig>) {
|
||||
return apiDefs::ConfigSource::AmneziaGateway;
|
||||
}
|
||||
return 0; // SelfHostedServerConfig or NativeServerConfig
|
||||
}, data);
|
||||
}
|
||||
|
||||
bool ServerConfig::isSelfHosted() const
|
||||
{
|
||||
return std::holds_alternative<SelfHostedServerConfig>(data);
|
||||
}
|
||||
|
||||
bool ServerConfig::isNative() const
|
||||
{
|
||||
return std::holds_alternative<NativeServerConfig>(data);
|
||||
}
|
||||
|
||||
bool ServerConfig::isApiV1() const
|
||||
{
|
||||
return std::holds_alternative<ApiV1ServerConfig>(data);
|
||||
}
|
||||
|
||||
bool ServerConfig::isApiV2() const
|
||||
{
|
||||
return std::holds_alternative<ApiV2ServerConfig>(data);
|
||||
}
|
||||
|
||||
bool ServerConfig::isApiConfig() const
|
||||
{
|
||||
return isApiV1() || isApiV2();
|
||||
}
|
||||
|
||||
QJsonObject ServerConfig::toJson() const
|
||||
{
|
||||
return std::visit([](const auto& v) { return v.toJson(); }, data);
|
||||
}
|
||||
|
||||
ServerConfig ServerConfig::fromJson(const QJsonObject& json)
|
||||
{
|
||||
apiDefs::ConfigType configType = apiUtils::getConfigType(json);
|
||||
|
||||
switch (configType) {
|
||||
case apiDefs::ConfigType::SelfHosted: {
|
||||
bool hasThirdPartyConfig = false;
|
||||
QJsonArray containersArray = json.value(configKey::containers).toArray();
|
||||
for (const QJsonValue& val : containersArray) {
|
||||
QJsonObject containerObj = val.toObject();
|
||||
for (auto it = containerObj.begin(); it != containerObj.end(); ++it) {
|
||||
QString key = it.key();
|
||||
if (key != configKey::container) {
|
||||
QJsonObject protocolObj = it.value().toObject();
|
||||
if (protocolObj.contains(configKey::isThirdPartyConfig) &&
|
||||
protocolObj.value(configKey::isThirdPartyConfig).toBool()) {
|
||||
hasThirdPartyConfig = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasThirdPartyConfig) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasThirdPartyConfig) {
|
||||
return ServerConfig{NativeServerConfig::fromJson(json)};
|
||||
} else {
|
||||
return ServerConfig{SelfHostedServerConfig::fromJson(json)};
|
||||
}
|
||||
}
|
||||
case apiDefs::ConfigType::AmneziaPremiumV1:
|
||||
case apiDefs::ConfigType::AmneziaFreeV2:
|
||||
return ServerConfig{ApiV1ServerConfig::fromJson(json)};
|
||||
case apiDefs::ConfigType::AmneziaPremiumV2:
|
||||
case apiDefs::ConfigType::AmneziaFreeV3:
|
||||
case apiDefs::ConfigType::ExternalPremium:
|
||||
return ServerConfig{ApiV2ServerConfig::fromJson(json)};
|
||||
default: {
|
||||
// Check if any container has isThirdPartyConfig
|
||||
bool hasThirdPartyConfig = false;
|
||||
QJsonArray containersArray = json.value(configKey::containers).toArray();
|
||||
for (const QJsonValue& val : containersArray) {
|
||||
QJsonObject containerObj = val.toObject();
|
||||
// Check all protocol keys in the container object
|
||||
for (auto it = containerObj.begin(); it != containerObj.end(); ++it) {
|
||||
QString key = it.key();
|
||||
if (key != configKey::container) {
|
||||
QJsonObject protocolObj = it.value().toObject();
|
||||
if (protocolObj.contains(configKey::isThirdPartyConfig) &&
|
||||
protocolObj.value(configKey::isThirdPartyConfig).toBool()) {
|
||||
hasThirdPartyConfig = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasThirdPartyConfig) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasThirdPartyConfig) {
|
||||
return ServerConfig{NativeServerConfig::fromJson(json)};
|
||||
} else {
|
||||
return ServerConfig{SelfHostedServerConfig::fromJson(json)};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QPair<QString, QString> ServerConfig::getDnsPair(bool isAmneziaDnsEnabled,
|
||||
const QString &primaryDns,
|
||||
const QString &secondaryDns) const
|
||||
{
|
||||
QPair<QString, QString> dns;
|
||||
|
||||
QMap<DockerContainer, ContainerConfig> serverContainers = containers();
|
||||
|
||||
bool isDnsContainerInstalled = false;
|
||||
for (auto it = serverContainers.begin(); it != serverContainers.end(); ++it) {
|
||||
if (it.key() == DockerContainer::Dns) {
|
||||
isDnsContainerInstalled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dns.first = dns1();
|
||||
dns.second = dns2();
|
||||
|
||||
if (dns.first.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.first)) {
|
||||
if (isAmneziaDnsEnabled && isDnsContainerInstalled) {
|
||||
dns.first = protocols::dns::amneziaDnsIp;
|
||||
} else {
|
||||
dns.first = primaryDns;
|
||||
}
|
||||
}
|
||||
|
||||
if (dns.second.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.second)) {
|
||||
dns.second = secondaryDns;
|
||||
}
|
||||
|
||||
return dns;
|
||||
}
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
#ifndef SERVERCONFIG_H
|
||||
#define SERVERCONFIG_H
|
||||
|
||||
#include <variant>
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/models/selfhosted/selfHostedServerConfig.h"
|
||||
#include "core/models/selfhosted/nativeServerConfig.h"
|
||||
#include "core/models/api/apiV1ServerConfig.h"
|
||||
#include "core/models/api/apiV2ServerConfig.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
using namespace ContainerEnumNS;
|
||||
|
||||
struct ServerConfig {
|
||||
using Variant = std::variant<
|
||||
SelfHostedServerConfig,
|
||||
NativeServerConfig,
|
||||
ApiV1ServerConfig,
|
||||
ApiV2ServerConfig
|
||||
>;
|
||||
|
||||
Variant data;
|
||||
|
||||
ServerConfig() = default;
|
||||
ServerConfig(const Variant& v) : data(v) {}
|
||||
ServerConfig(Variant&& v) : data(std::move(v)) {}
|
||||
|
||||
template<typename T, typename = std::enable_if_t<!std::is_same<std::remove_cv_t<std::remove_reference_t<T>>, ServerConfig>::value>>
|
||||
ServerConfig(const T& v) : data(v) {}
|
||||
|
||||
template<typename T, typename = std::enable_if_t<!std::is_same<std::remove_cv_t<std::remove_reference_t<T>>, ServerConfig>::value>>
|
||||
ServerConfig(T&& v) : data(std::forward<T>(v)) {}
|
||||
|
||||
QString description() const;
|
||||
QString hostName() const;
|
||||
QString displayName() const;
|
||||
QMap<DockerContainer, ContainerConfig> containers() const;
|
||||
DockerContainer defaultContainer() const;
|
||||
QString dns1() const;
|
||||
QString dns2() const;
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
int crc() const;
|
||||
int configVersion() const;
|
||||
|
||||
bool isSelfHosted() const;
|
||||
bool isNative() const;
|
||||
bool isApiV1() const;
|
||||
bool isApiV2() const;
|
||||
bool isApiConfig() const;
|
||||
|
||||
template<typename T>
|
||||
T* as() {
|
||||
return std::get_if<T>(&data);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T* as() const {
|
||||
return std::get_if<T>(&data);
|
||||
}
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static ServerConfig fromJson(const QJsonObject& json);
|
||||
|
||||
template<typename Visitor>
|
||||
auto visit(Visitor&& visitor) {
|
||||
return std::visit(std::forward<Visitor>(visitor), data);
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
auto visit(Visitor&& visitor) const {
|
||||
return std::visit(std::forward<Visitor>(visitor), data);
|
||||
}
|
||||
|
||||
QPair<QString, QString> getDnsPair(bool isAmneziaDnsEnabled,
|
||||
const QString &primaryDns,
|
||||
const QString &secondaryDns) const;
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif // SERVERCONFIG_H
|
||||
|
||||
187
client/core/models/serverDescription.cpp
Normal file
187
client/core/models/serverDescription.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
#include "serverDescription.h"
|
||||
|
||||
#include <QMap>
|
||||
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/models/protocols/awgProtocolConfig.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool computeHasInstalledVpnContainers(const QMap<DockerContainer, ContainerConfig> &containers)
|
||||
{
|
||||
for (auto it = containers.begin(); it != containers.end(); ++it) {
|
||||
const DockerContainer container = it.key();
|
||||
if (ContainerUtils::containerService(container) == ServiceType::Vpn || container == DockerContainer::SSXray) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ServerDescription buildBaseDescription(const T &server)
|
||||
{
|
||||
ServerDescription row;
|
||||
row.hostName = server.hostName;
|
||||
row.defaultContainer = server.defaultContainer;
|
||||
row.primaryDnsIsAmnezia = (server.dns1 == protocols::dns::amneziaDnsIp);
|
||||
row.hasInstalledVpnContainers = computeHasInstalledVpnContainers(server.containers);
|
||||
return row;
|
||||
}
|
||||
|
||||
QString getBaseDescription(const QMap<DockerContainer, ContainerConfig> &containers,
|
||||
bool isAmneziaDnsEnabled,
|
||||
bool hasWriteAccess,
|
||||
bool primaryDnsIsAmnezia)
|
||||
{
|
||||
QString description;
|
||||
if (hasWriteAccess) {
|
||||
const bool isDnsInstalled = containers.contains(DockerContainer::Dns);
|
||||
if (isAmneziaDnsEnabled && isDnsInstalled) {
|
||||
description += QStringLiteral("Amnezia DNS | ");
|
||||
}
|
||||
} else if (primaryDnsIsAmnezia) {
|
||||
description += QStringLiteral("Amnezia DNS | ");
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
QString getProtocolName(DockerContainer defaultContainer, const QMap<DockerContainer, ContainerConfig> &containers)
|
||||
{
|
||||
QString containerName = ContainerUtils::containerHumanNames().value(defaultContainer);
|
||||
QString protocolVersion;
|
||||
|
||||
if (ContainerUtils::isAwgContainer(defaultContainer)) {
|
||||
const auto it = containers.constFind(defaultContainer);
|
||||
if (it != containers.cend()) {
|
||||
if (const AwgProtocolConfig *awg = it->getAwgProtocolConfig()) {
|
||||
protocolVersion = ProtocolUtils::getProtocolVersionString(awg->toJson());
|
||||
if (defaultContainer == DockerContainer::Awg && !awg->serverConfig.isThirdPartyConfig) {
|
||||
containerName = QStringLiteral("AmneziaWG Legacy");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return containerName + protocolVersion + QStringLiteral(" | ");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
ServerDescription buildServerDescription(const SelfHostedAdminServerConfig &server, bool isAmneziaDnsEnabled)
|
||||
{
|
||||
ServerDescription row = buildBaseDescription(server);
|
||||
row.selfHostedSshCredentials.hostName = server.hostName;
|
||||
row.selfHostedSshCredentials.userName = server.userName;
|
||||
row.selfHostedSshCredentials.secretData = server.password;
|
||||
row.selfHostedSshCredentials.port = server.port > 0 ? server.port : 22;
|
||||
|
||||
row.hasWriteAccess = !row.selfHostedSshCredentials.userName.isEmpty()
|
||||
&& !row.selfHostedSshCredentials.secretData.isEmpty();
|
||||
|
||||
row.serverName = server.displayName;
|
||||
row.baseDescription = getBaseDescription(server.containers, isAmneziaDnsEnabled, row.hasWriteAccess, row.primaryDnsIsAmnezia);
|
||||
|
||||
const QString protocolName = getProtocolName(server.defaultContainer, server.containers);
|
||||
row.expandedServerDescription = row.baseDescription + row.hostName;
|
||||
row.collapsedServerDescription = row.baseDescription + protocolName + row.hostName;
|
||||
return row;
|
||||
}
|
||||
|
||||
ServerDescription buildServerDescription(const SelfHostedUserServerConfig &server, bool isAmneziaDnsEnabled)
|
||||
{
|
||||
ServerDescription row = buildBaseDescription(server);
|
||||
row.selfHostedSshCredentials.hostName = server.hostName;
|
||||
row.selfHostedSshCredentials.port = 22;
|
||||
row.hasWriteAccess = false;
|
||||
|
||||
row.serverName = server.displayName;
|
||||
row.baseDescription = getBaseDescription(server.containers, isAmneziaDnsEnabled, row.hasWriteAccess, row.primaryDnsIsAmnezia);
|
||||
|
||||
const QString protocolName = getProtocolName(server.defaultContainer, server.containers);
|
||||
row.expandedServerDescription = row.baseDescription + row.hostName;
|
||||
row.collapsedServerDescription = row.baseDescription + protocolName + row.hostName;
|
||||
return row;
|
||||
}
|
||||
|
||||
ServerDescription buildServerDescription(const NativeServerConfig &server, bool isAmneziaDnsEnabled)
|
||||
{
|
||||
ServerDescription row = buildBaseDescription(server);
|
||||
row.hasWriteAccess = false;
|
||||
|
||||
row.serverName = server.displayName;
|
||||
row.baseDescription = getBaseDescription(server.containers, isAmneziaDnsEnabled, row.hasWriteAccess, row.primaryDnsIsAmnezia);
|
||||
|
||||
const QString protocolName = getProtocolName(server.defaultContainer, server.containers);
|
||||
row.expandedServerDescription = row.baseDescription + row.hostName;
|
||||
row.collapsedServerDescription = row.baseDescription + protocolName + row.hostName;
|
||||
return row;
|
||||
}
|
||||
|
||||
ServerDescription buildServerDescription(const LegacyApiServerConfig &server, bool /*isAmneziaDnsEnabled*/)
|
||||
{
|
||||
ServerDescription row = buildBaseDescription(server);
|
||||
row.configVersion = serverConfigUtils::ConfigSource::Telegram;
|
||||
row.isApiV1 = true;
|
||||
row.isServerFromGatewayApi = false;
|
||||
row.hasWriteAccess = false;
|
||||
|
||||
row.serverName = server.displayName;
|
||||
row.baseDescription = server.description;
|
||||
|
||||
const QString fullDescriptionForCollapsed = row.baseDescription;
|
||||
row.collapsedServerDescription = fullDescriptionForCollapsed;
|
||||
row.expandedServerDescription = fullDescriptionForCollapsed;
|
||||
return row;
|
||||
}
|
||||
|
||||
ServerDescription buildServerDescription(const ApiV2ServerConfig &server, bool /*isAmneziaDnsEnabled*/)
|
||||
{
|
||||
ServerDescription row = buildBaseDescription(server);
|
||||
row.configVersion = serverConfigUtils::ConfigSource::AmneziaGateway;
|
||||
row.isApiV2 = true;
|
||||
row.isServerFromGatewayApi = true;
|
||||
row.isPremium = server.isPremium() || server.isExternalPremium();
|
||||
row.hasWriteAccess = false;
|
||||
|
||||
row.serverName = server.displayName;
|
||||
row.baseDescription = server.apiConfig.serverCountryCode.isEmpty() ? server.description : server.apiConfig.serverCountryName;
|
||||
|
||||
row.isCountrySelectionAvailable = !server.apiConfig.availableCountries.isEmpty();
|
||||
row.apiAvailableCountries = server.apiConfig.availableCountries;
|
||||
row.apiServerCountryCode = server.apiConfig.serverCountryCode;
|
||||
|
||||
row.isAdVisible = server.apiConfig.serviceInfo.isAdVisible;
|
||||
row.adHeader = server.apiConfig.serviceInfo.adHeader;
|
||||
row.adDescription = server.apiConfig.serviceInfo.adDescription;
|
||||
row.adEndpoint = server.apiConfig.serviceInfo.adEndpoint;
|
||||
row.isRenewalAvailable = server.apiConfig.serviceInfo.isRenewalAvailable;
|
||||
|
||||
if (!server.apiConfig.isInAppPurchase) {
|
||||
if (server.apiConfig.subscriptionExpiredByServer) {
|
||||
row.isSubscriptionExpired = true;
|
||||
} else if (!server.apiConfig.subscription.endDate.isEmpty()) {
|
||||
row.isSubscriptionExpired = apiUtils::isSubscriptionExpired(server.apiConfig.subscription.endDate);
|
||||
row.isSubscriptionExpiringSoon = apiUtils::isSubscriptionExpiringSoon(server.apiConfig.subscription.endDate);
|
||||
}
|
||||
}
|
||||
|
||||
const QString fullDescriptionForCollapsed = row.baseDescription;
|
||||
row.collapsedServerDescription = fullDescriptionForCollapsed;
|
||||
row.expandedServerDescription = fullDescriptionForCollapsed;
|
||||
return row;
|
||||
}
|
||||
|
||||
} // namespace amnezia
|
||||
64
client/core/models/serverDescription.h
Normal file
64
client/core/models/serverDescription.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#ifndef SERVERDESCRIPTION_H
|
||||
#define SERVERDESCRIPTION_H
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/models/selfhosted/selfHostedAdminServerConfig.h"
|
||||
#include "core/models/selfhosted/selfHostedUserServerConfig.h"
|
||||
#include "core/models/selfhosted/nativeServerConfig.h"
|
||||
#include "core/models/api/legacyApiServerConfig.h"
|
||||
#include "core/models/api/apiV2ServerConfig.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
struct ServerDescription
|
||||
{
|
||||
QString serverId;
|
||||
|
||||
QString serverName;
|
||||
QString baseDescription;
|
||||
QString hostName;
|
||||
|
||||
int configVersion = 0;
|
||||
|
||||
ServerCredentials selfHostedSshCredentials;
|
||||
bool hasWriteAccess = false;
|
||||
|
||||
bool primaryDnsIsAmnezia = false;
|
||||
DockerContainer defaultContainer = DockerContainer::None;
|
||||
bool hasInstalledVpnContainers = false;
|
||||
|
||||
bool isApiV1 = false;
|
||||
bool isApiV2 = false;
|
||||
bool isServerFromGatewayApi = false;
|
||||
bool isPremium = false;
|
||||
|
||||
bool isCountrySelectionAvailable = false;
|
||||
QJsonArray apiAvailableCountries;
|
||||
QString apiServerCountryCode;
|
||||
|
||||
bool isAdVisible = false;
|
||||
QString adHeader;
|
||||
QString adDescription;
|
||||
QString adEndpoint;
|
||||
bool isRenewalAvailable = false;
|
||||
bool isSubscriptionExpired = false;
|
||||
bool isSubscriptionExpiringSoon = false;
|
||||
|
||||
QString collapsedServerDescription;
|
||||
QString expandedServerDescription;
|
||||
};
|
||||
|
||||
ServerDescription buildServerDescription(const SelfHostedAdminServerConfig &server, bool isAmneziaDnsEnabled);
|
||||
ServerDescription buildServerDescription(const SelfHostedUserServerConfig &server, bool isAmneziaDnsEnabled);
|
||||
ServerDescription buildServerDescription(const NativeServerConfig &server, bool isAmneziaDnsEnabled);
|
||||
ServerDescription buildServerDescription(const LegacyApiServerConfig &server, bool isAmneziaDnsEnabled);
|
||||
ServerDescription buildServerDescription(const ApiV2ServerConfig &server, bool isAmneziaDnsEnabled);
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
#endif
|
||||
@@ -170,11 +170,6 @@ ErrorCode OpenVpnProtocol::start()
|
||||
{
|
||||
OpenVpnProtocol::stop();
|
||||
|
||||
if (!QFileInfo::exists(Utils::openVpnExecPath())) {
|
||||
setLastError(ErrorCode::OpenVpnExecutableMissing);
|
||||
return lastError();
|
||||
}
|
||||
|
||||
if (!QFileInfo::exists(configPath())) {
|
||||
setLastError(ErrorCode::OpenVpnConfigMissing);
|
||||
return lastError();
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
#include "xrayProtocol.h"
|
||||
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/ipcClient.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/serialization/serialization.h"
|
||||
#include "ipc.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkInterface>
|
||||
#include <QJsonDocument>
|
||||
#include <QtCore/qlogging.h>
|
||||
#include <QtCore/qobjectdefs.h>
|
||||
#include <QtCore/qprocess.h>
|
||||
@@ -43,7 +43,17 @@ XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) :
|
||||
if (xrayConfiguration.isEmpty()) {
|
||||
xrayConfiguration = configuration.value(ProtocolUtils::key_proto_config_data(Proto::SSXray)).toObject();
|
||||
}
|
||||
m_xrayConfig = xrayConfiguration;
|
||||
|
||||
if (xrayConfiguration.isEmpty()) {
|
||||
qWarning() << "Xray config wrapper is empty";
|
||||
m_xrayConfig = {};
|
||||
}
|
||||
|
||||
m_xrayConfig = QJsonDocument::fromJson(xrayConfiguration.value(amnezia::configKey::config).toString().toUtf8()).object();
|
||||
if (m_xrayConfig.isEmpty()) {
|
||||
qWarning() << "Xray config string is not a valid JSON object";
|
||||
m_xrayConfig = {};
|
||||
}
|
||||
}
|
||||
|
||||
XrayProtocol::~XrayProtocol()
|
||||
@@ -65,9 +75,9 @@ ErrorCode XrayProtocol::start()
|
||||
qCritical() << "EnsureInboundAuth failed:" << e.what();
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
m_socksUser = creds.username;
|
||||
m_socksUser = creds.username;
|
||||
m_socksPassword = creds.password;
|
||||
m_socksPort = creds.port;
|
||||
m_socksPort = creds.port;
|
||||
|
||||
const QString xrayConfigStr = QJsonDocument(m_xrayConfig).toJson(QJsonDocument::Compact);
|
||||
if (xrayConfigStr.isEmpty()) {
|
||||
@@ -75,16 +85,16 @@ ErrorCode XrayProtocol::start()
|
||||
return ErrorCode::XrayExecutableCrashed;
|
||||
}
|
||||
|
||||
return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto xrayStart = iface->xrayStart(xrayConfigStr);
|
||||
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
|
||||
qCritical() << "Failed to start xray";
|
||||
return ErrorCode::XrayExecutableCrashed;
|
||||
}
|
||||
return startTun2Socks();
|
||||
}, [] () {
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
});
|
||||
return IpcClient::withInterface(
|
||||
[&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto xrayStart = iface->xrayStart(xrayConfigStr);
|
||||
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
|
||||
qCritical() << "Failed to start xray";
|
||||
return ErrorCode::XrayExecutableCrashed;
|
||||
}
|
||||
return startTun2Socks();
|
||||
},
|
||||
[]() { return ErrorCode::AmneziaServiceConnectionFailed; });
|
||||
}
|
||||
|
||||
void XrayProtocol::stop()
|
||||
@@ -143,129 +153,135 @@ ErrorCode XrayProtocol::startTun2Socks()
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
|
||||
const QString proxyUrl = QString("socks5://%1:%2@127.0.0.1:%3")
|
||||
.arg(m_socksUser, m_socksPassword, QString::number(m_socksPort));
|
||||
const QString proxyUrl = QString("socks5://%1:%2@127.0.0.1:%3").arg(m_socksUser, m_socksPassword, QString::number(m_socksPort));
|
||||
|
||||
m_tun2socksProcess->setProgram(PermittedProcess::Tun2Socks);
|
||||
m_tun2socksProcess->setArguments({"-device", QString("tun://%1").arg(tunName), "-proxy", proxyUrl});
|
||||
m_tun2socksProcess->setArguments({ "-device", QString("tun://%1").arg(tunName), "-proxy", proxyUrl });
|
||||
|
||||
connect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, [this]() {
|
||||
auto readAllStandardOutput = m_tun2socksProcess->readAllStandardOutput();
|
||||
if (!readAllStandardOutput.waitForFinished()) {
|
||||
qWarning() << "Failed to read output from tun2socks";
|
||||
return;
|
||||
}
|
||||
connect(
|
||||
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardError, this,
|
||||
[this]() {
|
||||
auto readAllStandardError = m_tun2socksProcess->readAllStandardError();
|
||||
if (!readAllStandardError.waitForFinished()) {
|
||||
qWarning() << "Failed to read output from tun2socks";
|
||||
return;
|
||||
}
|
||||
|
||||
const QString line = readAllStandardOutput.returnValue();
|
||||
const QString line = readAllStandardError.returnValue();
|
||||
|
||||
if (!line.contains("[TCP]") && !line.contains("[UDP]"))
|
||||
qDebug() << "[tun2socks]:" << line;
|
||||
|
||||
if (line.contains("[STACK] tun://") && line.contains("<-> socks5://")) {
|
||||
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, nullptr);
|
||||
if (!line.contains("[TCP]") && !line.contains("[UDP]"))
|
||||
qDebug() << "[tun2socks]:" << line;
|
||||
|
||||
if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) {
|
||||
if (line.contains("[STACK] tun://") && line.contains("<-> socks5://")) {
|
||||
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, nullptr);
|
||||
|
||||
if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) {
|
||||
stop();
|
||||
setLastError(res);
|
||||
} else {
|
||||
setConnectionState(Vpn::ConnectionState::Connected);
|
||||
}
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::finished, this,
|
||||
[this](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
if (exitStatus == QProcess::ExitStatus::CrashExit) {
|
||||
qCritical() << "Tun2socks process crashed!";
|
||||
} else {
|
||||
qCritical() << QString("Tun2socks process was closed with %1 exit code").arg(exitCode);
|
||||
}
|
||||
stop();
|
||||
setLastError(res);
|
||||
} else {
|
||||
setConnectionState(Vpn::ConnectionState::Connected);
|
||||
}
|
||||
}
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
connect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::finished, this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
if (exitStatus == QProcess::ExitStatus::CrashExit) {
|
||||
qCritical() << "Tun2socks process crashed!";
|
||||
} else {
|
||||
qCritical() << QString("Tun2socks process was closed with %1 exit code").arg(exitCode);
|
||||
}
|
||||
stop();
|
||||
setLastError(ErrorCode::Tun2SockExecutableCrashed);
|
||||
}, Qt::QueuedConnection);
|
||||
setLastError(ErrorCode::Tun2SockExecutableCrashed);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
m_tun2socksProcess->start();
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode XrayProtocol::setupRouting() {
|
||||
return IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> iface) -> ErrorCode {
|
||||
ErrorCode XrayProtocol::setupRouting()
|
||||
{
|
||||
return IpcClient::withInterface(
|
||||
[this](QSharedPointer<IpcInterfaceReplica> iface) -> ErrorCode {
|
||||
#ifdef Q_OS_WIN
|
||||
const int inetAdapterIndex = NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress));
|
||||
const int inetAdapterIndex = NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress));
|
||||
#endif
|
||||
auto createTun = iface->createTun(tunName, amnezia::protocols::xray::defaultLocalAddr);
|
||||
if (!createTun.waitForFinished() || !createTun.returnValue()) {
|
||||
qCritical() << "Failed to assign IP address for TUN";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
auto updateResolvers = iface->updateResolvers(tunName, m_dnsServers);
|
||||
if (!updateResolvers.waitForFinished() || !updateResolvers.returnValue()) {
|
||||
qCritical() << "Failed to set DNS resolvers for TUN";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
int vpnAdapterIndex = -1;
|
||||
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
|
||||
for (auto& netInterface : netInterfaces) {
|
||||
for (auto& address : netInterface.addressEntries()) {
|
||||
if (m_vpnLocalAddress == address.ip().toString())
|
||||
vpnAdapterIndex = netInterface.index();
|
||||
}
|
||||
}
|
||||
#else
|
||||
static const int vpnAdapterIndex = 0;
|
||||
#endif
|
||||
const bool killSwitchEnabled = QVariant(m_rawConfig.value(configKey::killSwitchOption).toString()).toBool();
|
||||
if (killSwitchEnabled) {
|
||||
if (vpnAdapterIndex != -1) {
|
||||
QJsonObject config = m_rawConfig;
|
||||
config.insert("vpnServer", m_remoteAddress);
|
||||
|
||||
auto enableKillSwitch = IpcClient::Interface()->enableKillSwitch(config, vpnAdapterIndex);
|
||||
if (!enableKillSwitch.waitForFinished() || !enableKillSwitch.returnValue()) {
|
||||
qCritical() << "Failed to enable killswitch";
|
||||
auto createTun = iface->createTun(tunName, amnezia::protocols::xray::defaultLocalAddr);
|
||||
if (!createTun.waitForFinished() || !createTun.returnValue()) {
|
||||
qCritical() << "Failed to assign IP address for TUN";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
} else
|
||||
qWarning() << "Failed to get vpnAdapterIndex. Killswitch disabled";
|
||||
}
|
||||
|
||||
if (m_routeMode == amnezia::RouteMode::VpnAllSites) {
|
||||
static const QStringList subnets = { "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1" };
|
||||
|
||||
auto routeAddList = iface->routeAddList(m_vpnGateway, subnets);
|
||||
if (!routeAddList.waitForFinished() || routeAddList.returnValue() != subnets.count()) {
|
||||
qCritical() << "Failed to set routes for TUN";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
auto StopRoutingIpv6 = iface->StopRoutingIpv6();
|
||||
if (!StopRoutingIpv6.waitForFinished() || !StopRoutingIpv6.returnValue()) {
|
||||
qCritical() << "Failed to disable IPv6 routing";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
auto updateResolvers = iface->updateResolvers(tunName, m_dnsServers);
|
||||
if (!updateResolvers.waitForFinished() || !updateResolvers.returnValue()) {
|
||||
qCritical() << "Failed to set DNS resolvers for TUN";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (inetAdapterIndex != -1 && vpnAdapterIndex != -1) {
|
||||
QJsonObject config = m_rawConfig;
|
||||
config.insert("inetAdapterIndex", inetAdapterIndex);
|
||||
config.insert("vpnAdapterIndex", vpnAdapterIndex);
|
||||
config.insert("vpnGateway", m_vpnGateway);
|
||||
config.insert("vpnServer", m_remoteAddress);
|
||||
|
||||
auto enablePeerTraffic = iface->enablePeerTraffic(config);
|
||||
if (!enablePeerTraffic.waitForFinished() || !enablePeerTraffic.returnValue()) {
|
||||
qCritical() << "Failed to enable peer traffic";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
} else
|
||||
qWarning() << "Failed to get adapter indexes. Split-tunneling disabled";
|
||||
int vpnAdapterIndex = -1;
|
||||
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
|
||||
for (auto &netInterface : netInterfaces) {
|
||||
for (auto &address : netInterface.addressEntries()) {
|
||||
if (m_vpnLocalAddress == address.ip().toString())
|
||||
vpnAdapterIndex = netInterface.index();
|
||||
}
|
||||
}
|
||||
#else
|
||||
static const int vpnAdapterIndex = 0;
|
||||
#endif
|
||||
return ErrorCode::NoError;
|
||||
},
|
||||
[] () {
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
});
|
||||
const bool killSwitchEnabled = QVariant(m_rawConfig.value(configKey::killSwitchOption).toString()).toBool();
|
||||
if (killSwitchEnabled) {
|
||||
if (vpnAdapterIndex != -1) {
|
||||
QJsonObject config = m_rawConfig;
|
||||
config.insert("vpnServer", m_remoteAddress);
|
||||
|
||||
auto enableKillSwitch = IpcClient::Interface()->enableKillSwitch(config, vpnAdapterIndex);
|
||||
if (!enableKillSwitch.waitForFinished() || !enableKillSwitch.returnValue()) {
|
||||
qCritical() << "Failed to enable killswitch";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
} else
|
||||
qWarning() << "Failed to get vpnAdapterIndex. Killswitch disabled";
|
||||
}
|
||||
|
||||
if (m_routeMode == amnezia::RouteMode::VpnAllSites) {
|
||||
static const QStringList subnets = { "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5",
|
||||
"16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1" };
|
||||
|
||||
auto routeAddList = iface->routeAddList(m_vpnGateway, subnets);
|
||||
if (!routeAddList.waitForFinished() || routeAddList.returnValue() != subnets.count()) {
|
||||
qCritical() << "Failed to set routes for TUN";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
auto StopRoutingIpv6 = iface->StopRoutingIpv6();
|
||||
if (!StopRoutingIpv6.waitForFinished() || !StopRoutingIpv6.returnValue()) {
|
||||
qCritical() << "Failed to disable IPv6 routing";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (inetAdapterIndex != -1 && vpnAdapterIndex != -1) {
|
||||
QJsonObject config = m_rawConfig;
|
||||
config.insert("inetAdapterIndex", inetAdapterIndex);
|
||||
config.insert("vpnAdapterIndex", vpnAdapterIndex);
|
||||
config.insert("vpnGateway", m_vpnGateway);
|
||||
config.insert("vpnServer", m_remoteAddress);
|
||||
|
||||
auto enablePeerTraffic = iface->enablePeerTraffic(config);
|
||||
if (!enablePeerTraffic.waitForFinished() || !enablePeerTraffic.returnValue()) {
|
||||
qCritical() << "Failed to enable peer traffic";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
} else
|
||||
qWarning() << "Failed to get adapter indexes. Split-tunneling disabled";
|
||||
#endif
|
||||
return ErrorCode::NoError;
|
||||
},
|
||||
[]() { return ErrorCode::AmneziaServiceConnectionFailed; });
|
||||
}
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
@@ -1,26 +1,44 @@
|
||||
#include "secureServersRepository.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonValue>
|
||||
#include <QUuid>
|
||||
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
|
||||
SecureServersRepository::SecureServersRepository(SecureQSettings* settings, QObject *parent)
|
||||
using namespace amnezia;
|
||||
|
||||
namespace {
|
||||
|
||||
QString readStorageServerId(const QJsonObject &json)
|
||||
{
|
||||
return json.value(QString(configKey::storageServerId)).toString().trimmed();
|
||||
}
|
||||
|
||||
QJsonObject withoutStorageServerId(const QJsonObject &json)
|
||||
{
|
||||
QJsonObject o = json;
|
||||
o.remove(QString(configKey::storageServerId));
|
||||
return o;
|
||||
}
|
||||
|
||||
QJsonObject embedStorageServerId(const QString &serverId, const QJsonObject &payloadSansId)
|
||||
{
|
||||
QJsonObject o = payloadSansId;
|
||||
o.insert(QString(configKey::storageServerId), serverId);
|
||||
return o;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SecureServersRepository::SecureServersRepository(SecureQSettings *settings, QObject *parent)
|
||||
: QObject(parent), m_settings(settings)
|
||||
{
|
||||
QJsonArray arr = QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array();
|
||||
for (const QJsonValue &val : arr) {
|
||||
m_servers.append(ServerConfig::fromJson(val.toObject()));
|
||||
}
|
||||
m_defaultServerIndex = value("Servers/defaultServerIndex", 0).toInt();
|
||||
loadFromStorage();
|
||||
persistDefaultServerFields();
|
||||
}
|
||||
|
||||
QVariant SecureServersRepository::value(const QString &key, const QVariant &defaultValue) const
|
||||
@@ -33,216 +51,322 @@ void SecureServersRepository::setValue(const QString &key, const QVariant &value
|
||||
m_settings->setValue(key, value);
|
||||
}
|
||||
|
||||
void SecureServersRepository::clearServerStateMaps()
|
||||
{
|
||||
m_serverJsonById.clear();
|
||||
m_orderedServerIds.clear();
|
||||
}
|
||||
|
||||
QString SecureServersRepository::normalizedOrGeneratedServerId(const QString &candidateId) const
|
||||
{
|
||||
const QString trimmed = candidateId.trimmed();
|
||||
if (!trimmed.isEmpty() && !m_serverJsonById.contains(trimmed)) {
|
||||
return trimmed;
|
||||
}
|
||||
return QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
}
|
||||
|
||||
void SecureServersRepository::updateDefaultServerFromStorage()
|
||||
{
|
||||
const QString storedDefaultId = value(QStringLiteral("Servers/defaultServerId"), QString()).toString();
|
||||
if (!storedDefaultId.isEmpty() && m_serverJsonById.contains(storedDefaultId)) {
|
||||
m_defaultServerId = storedDefaultId;
|
||||
return;
|
||||
}
|
||||
|
||||
const int storedDefaultIndex = value("Servers/defaultServerIndex", 0).toInt();
|
||||
if (storedDefaultIndex >= 0 && storedDefaultIndex < m_orderedServerIds.size()) {
|
||||
m_defaultServerId = m_orderedServerIds.at(storedDefaultIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_orderedServerIds.isEmpty()) {
|
||||
m_defaultServerId = m_orderedServerIds.first();
|
||||
return;
|
||||
}
|
||||
|
||||
m_defaultServerId.clear();
|
||||
}
|
||||
|
||||
void SecureServersRepository::persistDefaultServerFields()
|
||||
{
|
||||
if (m_orderedServerIds.isEmpty()) {
|
||||
m_defaultServerId.clear();
|
||||
} else if (!m_orderedServerIds.contains(m_defaultServerId)) {
|
||||
m_defaultServerId = m_orderedServerIds.first();
|
||||
}
|
||||
|
||||
setValue("Servers/defaultServerId", m_defaultServerId);
|
||||
}
|
||||
|
||||
void SecureServersRepository::loadFromStorage()
|
||||
{
|
||||
clearServerStateMaps();
|
||||
|
||||
const QJsonArray serversArray =
|
||||
QJsonDocument::fromJson(value(QStringLiteral("Servers/serversList"), QByteArray()).toByteArray())
|
||||
.array();
|
||||
|
||||
for (int i = 0; i < serversArray.size(); ++i) {
|
||||
const QJsonObject json = serversArray.at(i).toObject();
|
||||
const QString candidateId = readStorageServerId(json);
|
||||
const QString serverId = normalizedOrGeneratedServerId(candidateId);
|
||||
const QJsonObject strippedJson = withoutStorageServerId(json);
|
||||
const serverConfigUtils::ConfigType kind = serverConfigUtils::configTypeFromJson(strippedJson);
|
||||
|
||||
if (m_serverJsonById.contains(serverId) || kind == serverConfigUtils::ConfigType::Invalid) {
|
||||
continue;
|
||||
}
|
||||
m_serverJsonById.insert(serverId, embedStorageServerId(serverId, strippedJson));
|
||||
m_orderedServerIds.append(serverId);
|
||||
}
|
||||
|
||||
updateDefaultServerFromStorage();
|
||||
}
|
||||
|
||||
void SecureServersRepository::syncToStorage()
|
||||
{
|
||||
QJsonArray arr;
|
||||
for (const ServerConfig &cfg : m_servers) {
|
||||
arr.append(cfg.toJson());
|
||||
QJsonArray serversArray;
|
||||
|
||||
for (const QString &serverId : m_orderedServerIds) {
|
||||
if (!m_serverJsonById.contains(serverId)) {
|
||||
continue;
|
||||
}
|
||||
serversArray.append(m_serverJsonById.value(serverId));
|
||||
}
|
||||
setValue("Servers/serversList", QJsonDocument(arr).toJson());
|
||||
|
||||
setValue("Servers/serversList", QJsonDocument(serversArray).toJson());
|
||||
persistDefaultServerFields();
|
||||
}
|
||||
|
||||
void SecureServersRepository::invalidateCache()
|
||||
{
|
||||
m_servers.clear();
|
||||
QJsonArray arr = QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array();
|
||||
for (const QJsonValue &val : arr) {
|
||||
m_servers.append(ServerConfig::fromJson(val.toObject()));
|
||||
}
|
||||
m_defaultServerIndex = value("Servers/defaultServerIndex", 0).toInt();
|
||||
loadFromStorage();
|
||||
}
|
||||
|
||||
void SecureServersRepository::setServersArray(const QJsonArray &servers)
|
||||
void SecureServersRepository::clearServers()
|
||||
{
|
||||
m_servers.clear();
|
||||
for (const QJsonValue &val : servers) {
|
||||
m_servers.append(ServerConfig::fromJson(val.toObject()));
|
||||
}
|
||||
clearServerStateMaps();
|
||||
|
||||
m_defaultServerId.clear();
|
||||
|
||||
syncToStorage();
|
||||
}
|
||||
|
||||
void SecureServersRepository::addServer(const ServerConfig &server)
|
||||
QString SecureServersRepository::addServer(const QString &serverId, const QJsonObject &serverJson, serverConfigUtils::ConfigType kind)
|
||||
{
|
||||
m_servers.append(server);
|
||||
const QString id = normalizedOrGeneratedServerId(serverId);
|
||||
if (m_serverJsonById.contains(id) || kind == serverConfigUtils::ConfigType::Invalid) {
|
||||
return id;
|
||||
}
|
||||
const QJsonObject strippedJson = withoutStorageServerId(serverJson);
|
||||
if (serverConfigUtils::configTypeFromJson(strippedJson) != kind) {
|
||||
return id;
|
||||
}
|
||||
m_serverJsonById.insert(id, embedStorageServerId(id, strippedJson));
|
||||
|
||||
m_orderedServerIds.append(id);
|
||||
|
||||
if (m_defaultServerId.isEmpty()) {
|
||||
m_defaultServerId = id;
|
||||
}
|
||||
|
||||
syncToStorage();
|
||||
emit serverAdded(server);
|
||||
emit serverAdded(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
void SecureServersRepository::editServer(int index, const ServerConfig &server)
|
||||
void SecureServersRepository::editServer(const QString &serverId, const QJsonObject &serverJson, serverConfigUtils::ConfigType kind)
|
||||
{
|
||||
if (index < 0 || index >= m_servers.size()) {
|
||||
if (indexOfServerId(serverId) < 0 || kind == serverConfigUtils::ConfigType::Invalid) {
|
||||
return;
|
||||
}
|
||||
m_servers.replace(index, server);
|
||||
syncToStorage();
|
||||
emit serverEdited(index, server);
|
||||
}
|
||||
|
||||
void SecureServersRepository::removeServer(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_servers.size()) {
|
||||
if (!m_serverJsonById.contains(serverId)) {
|
||||
return;
|
||||
}
|
||||
int defaultIndex = m_defaultServerIndex;
|
||||
m_servers.removeAt(index);
|
||||
|
||||
if (defaultIndex == index) {
|
||||
setDefaultServer(0);
|
||||
} else if (defaultIndex > index) {
|
||||
setDefaultServer(defaultIndex - 1);
|
||||
const QJsonObject oldJson = m_serverJsonById.value(serverId);
|
||||
const serverConfigUtils::ConfigType oldKind = serverConfigUtils::configTypeFromJson(withoutStorageServerId(oldJson));
|
||||
|
||||
m_serverJsonById.remove(serverId);
|
||||
|
||||
const QJsonObject strippedNew = withoutStorageServerId(serverJson);
|
||||
if (serverConfigUtils::configTypeFromJson(strippedNew) != kind) {
|
||||
const QJsonObject strippedOld = withoutStorageServerId(oldJson);
|
||||
if (oldKind != serverConfigUtils::ConfigType::Invalid && serverConfigUtils::configTypeFromJson(strippedOld) == oldKind) {
|
||||
m_serverJsonById.insert(serverId, embedStorageServerId(serverId, strippedOld));
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_serverJsonById.insert(serverId, embedStorageServerId(serverId, strippedNew));
|
||||
|
||||
syncToStorage();
|
||||
emit serverEdited(serverId);
|
||||
}
|
||||
|
||||
void SecureServersRepository::removeServer(const QString &serverId)
|
||||
{
|
||||
const int removedIndex = indexOfServerId(serverId);
|
||||
if (removedIndex < 0) {
|
||||
return;
|
||||
}
|
||||
if (!m_serverJsonById.contains(serverId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_servers.isEmpty()) {
|
||||
setDefaultServer(0);
|
||||
const QString previousDefaultId = m_defaultServerId;
|
||||
const int previousDefaultIndex = defaultServerIndex();
|
||||
|
||||
m_serverJsonById.remove(serverId);
|
||||
m_orderedServerIds.removeAt(removedIndex);
|
||||
|
||||
if (m_orderedServerIds.isEmpty()) {
|
||||
m_defaultServerId.clear();
|
||||
} else if (m_defaultServerId == serverId) {
|
||||
const int fallbackIndex = qMin(removedIndex, m_orderedServerIds.size() - 1);
|
||||
m_defaultServerId = m_orderedServerIds.at(fallbackIndex);
|
||||
} else if (!m_orderedServerIds.contains(m_defaultServerId)) {
|
||||
m_defaultServerId = m_orderedServerIds.first();
|
||||
}
|
||||
|
||||
const int newDefaultIndex = defaultServerIndex();
|
||||
if (previousDefaultId != m_defaultServerId || previousDefaultIndex != newDefaultIndex) {
|
||||
emit defaultServerChanged(m_defaultServerId);
|
||||
}
|
||||
|
||||
syncToStorage();
|
||||
emit serverRemoved(index);
|
||||
emit serverRemoved(serverId, removedIndex);
|
||||
}
|
||||
|
||||
ServerConfig SecureServersRepository::server(int index) const
|
||||
serverConfigUtils::ConfigType SecureServersRepository::serverKind(const QString &serverId) const
|
||||
{
|
||||
if (index < 0 || index >= m_servers.size()) {
|
||||
return SelfHostedServerConfig{};
|
||||
const auto it = m_serverJsonById.constFind(serverId);
|
||||
if (it == m_serverJsonById.constEnd()) {
|
||||
return serverConfigUtils::ConfigType::Invalid;
|
||||
}
|
||||
return m_servers.at(index);
|
||||
return serverConfigUtils::configTypeFromJson(withoutStorageServerId(it.value()));
|
||||
}
|
||||
|
||||
QVector<ServerConfig> SecureServersRepository::servers() const
|
||||
std::optional<SelfHostedAdminServerConfig> SecureServersRepository::selfHostedAdminConfig(const QString &serverId) const
|
||||
{
|
||||
return m_servers;
|
||||
const auto it = m_serverJsonById.constFind(serverId);
|
||||
if (it == m_serverJsonById.constEnd()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const QJsonObject strippedJson = withoutStorageServerId(it.value());
|
||||
if (serverConfigUtils::configTypeFromJson(strippedJson) != serverConfigUtils::ConfigType::SelfHostedAdmin) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return SelfHostedAdminServerConfig::fromJson(strippedJson);
|
||||
}
|
||||
|
||||
std::optional<SelfHostedUserServerConfig> SecureServersRepository::selfHostedUserConfig(const QString &serverId) const
|
||||
{
|
||||
const auto it = m_serverJsonById.constFind(serverId);
|
||||
if (it == m_serverJsonById.constEnd()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const QJsonObject strippedJson = withoutStorageServerId(it.value());
|
||||
if (serverConfigUtils::configTypeFromJson(strippedJson) != serverConfigUtils::ConfigType::SelfHostedUser) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return SelfHostedUserServerConfig::fromJson(strippedJson);
|
||||
}
|
||||
|
||||
std::optional<NativeServerConfig> SecureServersRepository::nativeConfig(const QString &serverId) const
|
||||
{
|
||||
const auto it = m_serverJsonById.constFind(serverId);
|
||||
if (it == m_serverJsonById.constEnd()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const QJsonObject strippedJson = withoutStorageServerId(it.value());
|
||||
if (serverConfigUtils::configTypeFromJson(strippedJson) != serverConfigUtils::ConfigType::Native) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return NativeServerConfig::fromJson(strippedJson);
|
||||
}
|
||||
|
||||
std::optional<ApiV2ServerConfig> SecureServersRepository::apiV2Config(const QString &serverId) const
|
||||
{
|
||||
const auto it = m_serverJsonById.constFind(serverId);
|
||||
if (it == m_serverJsonById.constEnd()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const QJsonObject strippedJson = withoutStorageServerId(it.value());
|
||||
if (!serverConfigUtils::isApiV2Subscription(serverConfigUtils::configTypeFromJson(strippedJson))) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return ApiV2ServerConfig::fromJson(strippedJson);
|
||||
}
|
||||
|
||||
std::optional<LegacyApiServerConfig> SecureServersRepository::legacyApiConfig(const QString &serverId) const
|
||||
{
|
||||
const auto it = m_serverJsonById.constFind(serverId);
|
||||
if (it == m_serverJsonById.constEnd()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const QJsonObject strippedJson = withoutStorageServerId(it.value());
|
||||
if (!serverConfigUtils::isLegacyApiSubscription(serverConfigUtils::configTypeFromJson(strippedJson))) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return LegacyApiServerConfig::fromJson(strippedJson);
|
||||
}
|
||||
|
||||
int SecureServersRepository::serversCount() const
|
||||
{
|
||||
return m_servers.size();
|
||||
return m_orderedServerIds.size();
|
||||
}
|
||||
|
||||
QString SecureServersRepository::serverIdAt(int index) const
|
||||
{
|
||||
if (index < 0 || index >= m_orderedServerIds.size()) {
|
||||
return QString();
|
||||
}
|
||||
return m_orderedServerIds.at(index);
|
||||
}
|
||||
|
||||
QVector<QString> SecureServersRepository::orderedServerIds() const
|
||||
{
|
||||
return m_orderedServerIds;
|
||||
}
|
||||
|
||||
int SecureServersRepository::indexOfServerId(const QString &serverId) const
|
||||
{
|
||||
return m_orderedServerIds.indexOf(serverId);
|
||||
}
|
||||
|
||||
int SecureServersRepository::defaultServerIndex() const
|
||||
{
|
||||
return m_defaultServerIndex;
|
||||
if (m_orderedServerIds.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
const int idx = m_orderedServerIds.indexOf(m_defaultServerId);
|
||||
return idx >= 0 ? idx : 0;
|
||||
}
|
||||
|
||||
void SecureServersRepository::setDefaultServer(int index)
|
||||
QString SecureServersRepository::defaultServerId() const
|
||||
{
|
||||
if (index < 0) {
|
||||
return m_defaultServerId;
|
||||
}
|
||||
|
||||
void SecureServersRepository::setDefaultServer(const QString &serverId)
|
||||
{
|
||||
if (m_orderedServerIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (m_servers.size() > 0 && index >= m_servers.size()) {
|
||||
if (!m_serverJsonById.contains(serverId)) {
|
||||
return;
|
||||
}
|
||||
if (m_servers.isEmpty() && index != 0) {
|
||||
|
||||
if (indexOfServerId(serverId) < 0) {
|
||||
return;
|
||||
}
|
||||
if (m_defaultServerIndex == index) {
|
||||
|
||||
if (m_defaultServerId == serverId) {
|
||||
return;
|
||||
}
|
||||
m_defaultServerIndex = index;
|
||||
setValue("Servers/defaultServerIndex", index);
|
||||
emit defaultServerChanged(index);
|
||||
}
|
||||
|
||||
void SecureServersRepository::setDefaultContainer(int serverIndex, DockerContainer container)
|
||||
{
|
||||
ServerConfig config = server(serverIndex);
|
||||
config.visit([container](auto& arg) {
|
||||
arg.defaultContainer = container;
|
||||
});
|
||||
editServer(serverIndex, config);
|
||||
}
|
||||
|
||||
ContainerConfig SecureServersRepository::containerConfig(int serverIndex, DockerContainer container) const
|
||||
{
|
||||
ServerConfig config = server(serverIndex);
|
||||
return config.containerConfig(container);
|
||||
}
|
||||
|
||||
void SecureServersRepository::setContainerConfig(int serverIndex, DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
ServerConfig serverConfig = server(serverIndex);
|
||||
serverConfig.visit([container, &config](auto& arg) {
|
||||
arg.containers[container] = config;
|
||||
});
|
||||
editServer(serverIndex, serverConfig);
|
||||
}
|
||||
|
||||
void SecureServersRepository::clearLastConnectionConfig(int serverIndex, DockerContainer container)
|
||||
{
|
||||
ServerConfig serverConfig = server(serverIndex);
|
||||
ContainerConfig containerCfg = serverConfig.containerConfig(container);
|
||||
|
||||
containerCfg.protocolConfig.clearClientConfig();
|
||||
|
||||
setContainerConfig(serverIndex, container, containerCfg);
|
||||
}
|
||||
|
||||
ServerCredentials SecureServersRepository::serverCredentials(int index) const
|
||||
{
|
||||
ServerConfig config = server(index);
|
||||
|
||||
if (config.isSelfHosted()) {
|
||||
const SelfHostedServerConfig* selfHosted = config.as<SelfHostedServerConfig>();
|
||||
if (!selfHosted) return ServerCredentials();
|
||||
auto creds = selfHosted->credentials();
|
||||
if (creds.has_value()) {
|
||||
return creds.value();
|
||||
}
|
||||
}
|
||||
|
||||
return ServerCredentials{};
|
||||
}
|
||||
|
||||
bool SecureServersRepository::hasServerWithVpnKey(const QString &vpnKey) const
|
||||
{
|
||||
QString normalizedInput = vpnKey.trimmed();
|
||||
if (normalizedInput.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) {
|
||||
normalizedInput = normalizedInput.mid(QStringLiteral("vpn://").size());
|
||||
}
|
||||
if (normalizedInput.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QVector<ServerConfig> serversList = servers();
|
||||
for (const ServerConfig& serverConfig : serversList) {
|
||||
if (serverConfig.isApiV1()) {
|
||||
const ApiV1ServerConfig* apiV1 = serverConfig.as<ApiV1ServerConfig>();
|
||||
if (!apiV1) continue;
|
||||
QString storedKey = apiV1->vpnKey();
|
||||
if (storedKey.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
QString normalizedStored = storedKey.trimmed();
|
||||
if (normalizedStored.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) {
|
||||
normalizedStored = normalizedStored.mid(QStringLiteral("vpn://").size());
|
||||
}
|
||||
if (normalizedInput == normalizedStored) {
|
||||
return true;
|
||||
}
|
||||
} else if (serverConfig.isApiV2()) {
|
||||
const ApiV2ServerConfig* apiV2 = serverConfig.as<ApiV2ServerConfig>();
|
||||
if (!apiV2) continue;
|
||||
QString storedKey = apiV2->vpnKey();
|
||||
if (storedKey.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
QString normalizedStored = storedKey.trimmed();
|
||||
if (normalizedStored.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) {
|
||||
normalizedStored = normalizedStored.mid(QStringLiteral("vpn://").size());
|
||||
}
|
||||
if (normalizedInput == normalizedStored) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SecureServersRepository::hasServerWithCrc(quint16 crc) const
|
||||
{
|
||||
for (const ServerConfig& serverConfig : m_servers) {
|
||||
if (static_cast<quint16>(serverConfig.crc()) == crc) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
m_defaultServerId = serverId;
|
||||
persistDefaultServerFields();
|
||||
emit defaultServerChanged(m_defaultServerId);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
#ifndef SECURESERVERSREPOSITORY_H
|
||||
#define SECURESERVERSREPOSITORY_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QtGlobal>
|
||||
#include <optional>
|
||||
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/models/selfhosted/selfHostedAdminServerConfig.h"
|
||||
#include "core/models/selfhosted/selfHostedUserServerConfig.h"
|
||||
#include "core/models/selfhosted/nativeServerConfig.h"
|
||||
#include "core/models/api/apiV2ServerConfig.h"
|
||||
#include "core/models/api/legacyApiServerConfig.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
using namespace amnezia;
|
||||
@@ -18,47 +24,57 @@ class SecureServersRepository : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SecureServersRepository(SecureQSettings* settings, QObject *parent = nullptr);
|
||||
explicit SecureServersRepository(SecureQSettings *settings, QObject *parent = nullptr);
|
||||
|
||||
QString addServer(const QString &serverId, const QJsonObject &serverJson, serverConfigUtils::ConfigType kind);
|
||||
void editServer(const QString &serverId, const QJsonObject &serverJson, serverConfigUtils::ConfigType kind);
|
||||
void removeServer(const QString &serverId);
|
||||
serverConfigUtils::ConfigType serverKind(const QString &serverId) const;
|
||||
|
||||
std::optional<SelfHostedAdminServerConfig> selfHostedAdminConfig(const QString &serverId) const;
|
||||
std::optional<SelfHostedUserServerConfig> selfHostedUserConfig(const QString &serverId) const;
|
||||
std::optional<NativeServerConfig> nativeConfig(const QString &serverId) const;
|
||||
std::optional<ApiV2ServerConfig> apiV2Config(const QString &serverId) const;
|
||||
std::optional<LegacyApiServerConfig> legacyApiConfig(const QString &serverId) const;
|
||||
|
||||
void addServer(const ServerConfig &server);
|
||||
void editServer(int index, const ServerConfig &server);
|
||||
void removeServer(int index);
|
||||
ServerConfig server(int index) const;
|
||||
QVector<ServerConfig> servers() const;
|
||||
int serversCount() const;
|
||||
int indexOfServerId(const QString &serverId) const;
|
||||
QString serverIdAt(int index) const;
|
||||
QVector<QString> orderedServerIds() const;
|
||||
|
||||
int defaultServerIndex() const;
|
||||
void setDefaultServer(int index);
|
||||
QString defaultServerId() const;
|
||||
void setDefaultServer(const QString &serverId);
|
||||
|
||||
void setDefaultContainer(int serverIndex, DockerContainer container);
|
||||
ContainerConfig containerConfig(int serverIndex, DockerContainer container) const;
|
||||
void setContainerConfig(int serverIndex, DockerContainer container, const ContainerConfig &config);
|
||||
void clearLastConnectionConfig(int serverIndex, DockerContainer container);
|
||||
|
||||
ServerCredentials serverCredentials(int index) const;
|
||||
bool hasServerWithVpnKey(const QString &vpnKey) const;
|
||||
bool hasServerWithCrc(quint16 crc) const;
|
||||
|
||||
void setServersArray(const QJsonArray &servers);
|
||||
void clearServers();
|
||||
|
||||
void invalidateCache();
|
||||
|
||||
signals:
|
||||
void serverAdded(ServerConfig config);
|
||||
void serverEdited(int index, ServerConfig config);
|
||||
void serverRemoved(int index);
|
||||
void defaultServerChanged(int index);
|
||||
void serverAdded(const QString &serverId);
|
||||
void serverEdited(const QString &serverId);
|
||||
void serverRemoved(const QString &serverId, int removedIndex);
|
||||
void defaultServerChanged(const QString &defaultServerId);
|
||||
|
||||
private:
|
||||
void loadFromStorage();
|
||||
void updateDefaultServerFromStorage();
|
||||
void persistDefaultServerFields();
|
||||
|
||||
QString normalizedOrGeneratedServerId(const QString &candidateId) const;
|
||||
|
||||
void syncToStorage();
|
||||
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
|
||||
QVariant value(const QString &key, const QVariant &defaultValue) const;
|
||||
void setValue(const QString &key, const QVariant &value);
|
||||
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
QVector<ServerConfig> m_servers;
|
||||
int m_defaultServerIndex = 0;
|
||||
|
||||
void clearServerStateMaps();
|
||||
|
||||
SecureQSettings *m_settings;
|
||||
|
||||
QHash<QString, QJsonObject> m_serverJsonById;
|
||||
QVector<QString> m_orderedServerIds;
|
||||
|
||||
QString m_defaultServerId;
|
||||
};
|
||||
|
||||
#endif // SECURESERVERSREPOSITORY_H
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef APIENUMS_H
|
||||
#define APIENUMS_H
|
||||
|
||||
namespace apiDefs
|
||||
{
|
||||
enum ConfigType {
|
||||
AmneziaFreeV2 = 0,
|
||||
AmneziaFreeV3,
|
||||
AmneziaPremiumV1,
|
||||
AmneziaPremiumV2,
|
||||
AmneziaTrialV2,
|
||||
SelfHosted,
|
||||
ExternalPremium,
|
||||
ExternalTrial
|
||||
};
|
||||
|
||||
enum ConfigSource {
|
||||
Telegram = 1,
|
||||
AmneziaGateway
|
||||
};
|
||||
}
|
||||
|
||||
#endif // APIENUMS_H
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "apiUtils.h"
|
||||
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include <QLatin1Char>
|
||||
#include <QDateTime>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
@@ -75,63 +77,6 @@ bool apiUtils::isSubscriptionExpiringSoon(const QString &subscriptionEndDate, in
|
||||
return endDate <= nowUtc.addDays(withinDays);
|
||||
}
|
||||
|
||||
bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
auto configVersion = serverConfigObject.value(configKey::configVersion).toInt();
|
||||
switch (configVersion) {
|
||||
case apiDefs::ConfigSource::Telegram: return true;
|
||||
case apiDefs::ConfigSource::AmneziaGateway: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
auto configVersion = serverConfigObject.value(configKey::configVersion).toInt();
|
||||
|
||||
switch (configVersion) {
|
||||
case apiDefs::ConfigSource::Telegram: {
|
||||
constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
|
||||
constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
|
||||
|
||||
auto apiEndpoint = serverConfigObject.value(apiDefs::key::apiEndpoint).toString();
|
||||
|
||||
if (apiEndpoint.contains(premiumV1Endpoint)) {
|
||||
return apiDefs::ConfigType::AmneziaPremiumV1;
|
||||
} else if (apiEndpoint.contains(freeV2Endpoint)) {
|
||||
return apiDefs::ConfigType::AmneziaFreeV2;
|
||||
}
|
||||
};
|
||||
case apiDefs::ConfigSource::AmneziaGateway: {
|
||||
constexpr QLatin1String servicePremium("amnezia-premium");
|
||||
constexpr QLatin1String serviceFree("amnezia-free");
|
||||
constexpr QLatin1String serviceExternalPremium("external-premium");
|
||||
constexpr QLatin1String serviceExternalTrial("external-trial");
|
||||
|
||||
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
||||
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
|
||||
|
||||
if (serviceType == servicePremium) {
|
||||
return apiDefs::ConfigType::AmneziaPremiumV2;
|
||||
} else if (serviceType == serviceFree) {
|
||||
return apiDefs::ConfigType::AmneziaFreeV3;
|
||||
} else if (serviceType == serviceExternalPremium) {
|
||||
return apiDefs::ConfigType::ExternalPremium;
|
||||
} else if (serviceType == serviceExternalTrial) {
|
||||
return apiDefs::ConfigType::ExternalTrial;
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return apiDefs::ConfigType::SelfHosted;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(configKey::configVersion).toInt());
|
||||
}
|
||||
|
||||
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
|
||||
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
|
||||
const QByteArray &responseBody)
|
||||
@@ -197,14 +142,14 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
||||
|
||||
bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
|
||||
apiDefs::ConfigType::ExternalPremium, apiDefs::ConfigType::ExternalTrial };
|
||||
return premiumTypes.contains(getConfigType(serverConfigObject));
|
||||
static const QSet<serverConfigUtils::ConfigType> premiumTypes = { serverConfigUtils::ConfigType::AmneziaPremiumV1, serverConfigUtils::ConfigType::AmneziaPremiumV2,
|
||||
serverConfigUtils::ConfigType::ExternalPremium };
|
||||
return premiumTypes.contains(serverConfigUtils::configTypeFromJson(serverConfigObject));
|
||||
}
|
||||
|
||||
QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) {
|
||||
if (serverConfigUtils::configTypeFromJson(serverConfigObject) != serverConfigUtils::ConfigType::AmneziaPremiumV1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -242,9 +187,8 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
|
||||
|
||||
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
auto configType = apiUtils::getConfigType(serverConfigObject);
|
||||
if (configType != apiDefs::ConfigType::AmneziaPremiumV2 && configType != apiDefs::ConfigType::ExternalPremium
|
||||
&& configType != apiDefs::ConfigType::ExternalTrial) {
|
||||
auto configType = serverConfigUtils::configTypeFromJson(serverConfigObject);
|
||||
if (configType != serverConfigUtils::ConfigType::AmneziaPremiumV2 && configType != serverConfigUtils::ConfigType::ExternalPremium) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -289,3 +233,18 @@ QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
|
||||
|
||||
return vpnKeyText;
|
||||
}
|
||||
|
||||
QString apiUtils::countryCodeBaseForFlag(const QString &fullCountryCode)
|
||||
{
|
||||
const QString trimmed = fullCountryCode.trimmed();
|
||||
if (trimmed.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
const int dashIdx = trimmed.indexOf(QLatin1Char('-'));
|
||||
const QString base = dashIdx < 0 ? trimmed : trimmed.left(dashIdx);
|
||||
const QString normalized = base.trimmed();
|
||||
if (normalized.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
return normalized.toUpper();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
@@ -13,23 +13,21 @@
|
||||
|
||||
namespace apiUtils
|
||||
{
|
||||
bool isServerFromApi(const QJsonObject &serverConfigObject);
|
||||
|
||||
bool isSubscriptionExpired(const QString &subscriptionEndDate);
|
||||
|
||||
bool isSubscriptionExpiringSoon(const QString &subscriptionEndDate, int withinDays = 30);
|
||||
|
||||
bool isPremiumServer(const QJsonObject &serverConfigObject);
|
||||
|
||||
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
|
||||
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
|
||||
|
||||
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
|
||||
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
|
||||
const QByteArray &responseBody);
|
||||
|
||||
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
|
||||
QString getPremiumV2VpnKey(const QJsonObject &serverConfigObject);
|
||||
|
||||
// ISO2-style segment for flagKit assets (e.g. US-WEST -> US). Do not use in API request bodies.
|
||||
QString countryCodeBaseForFlag(const QString &fullCountryCode);
|
||||
}
|
||||
|
||||
#endif // APIUTILS_H
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
namespace apiDefs
|
||||
{
|
||||
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||
}
|
||||
|
||||
constexpr int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||
|
||||
} // namespace apiDefs
|
||||
|
||||
#endif // APICONSTANTS_H
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define APIKEYS_H
|
||||
|
||||
#include <QLatin1String>
|
||||
#include "core/utils/api/apiEnums.h"
|
||||
|
||||
namespace apiDefs
|
||||
{
|
||||
@@ -82,7 +81,7 @@ namespace apiDefs
|
||||
constexpr QLatin1String expiresAt("expires_at");
|
||||
constexpr QLatin1String isConnectEvent("is_connect_event");
|
||||
constexpr QLatin1String certificate("certificate");
|
||||
}
|
||||
}
|
||||
} // namespace key
|
||||
} // namespace apiDefs
|
||||
|
||||
#endif // APIKEYS_H
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace amnezia
|
||||
|
||||
constexpr QLatin1String serverIndex("serverIndex");
|
||||
constexpr QLatin1String description("description");
|
||||
constexpr QLatin1String displayName("displayName");
|
||||
constexpr QLatin1String name("name");
|
||||
constexpr QLatin1String cert("cert");
|
||||
constexpr QLatin1String accessToken("api_key");
|
||||
@@ -121,6 +122,8 @@ namespace amnezia
|
||||
constexpr QLatin1String latestHandshake("latestHandshake");
|
||||
constexpr QLatin1String dataReceived("dataReceived");
|
||||
constexpr QLatin1String dataSent("dataSent");
|
||||
|
||||
constexpr QLatin1String storageServerId("storageServerId");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,10 +71,11 @@ namespace amnezia
|
||||
|
||||
// import and install errors
|
||||
ImportInvalidConfigError = 900,
|
||||
ImportBackupFileUseRestoreInstead = 903,
|
||||
RestoreBackupInvalidError = 904,
|
||||
ImportOpenConfigError = 901,
|
||||
NoInstalledContainersError = 902,
|
||||
ImportBackupFileUseRestoreInstead = 903,
|
||||
RestoreBackupInvalidError = 904,
|
||||
LegacyApiV1NotSupportedError = 905,
|
||||
|
||||
// Android errors
|
||||
AndroidError = 1000,
|
||||
|
||||
@@ -59,6 +59,7 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
|
||||
case (ErrorCode::ImportBackupFileUseRestoreInstead): errorMessage = QObject::tr("Backup files cannot be imported here. Use 'Restore from backup' instead."); break;
|
||||
case (ErrorCode::RestoreBackupInvalidError): errorMessage = QObject::tr("Backup file is corrupted or has invalid format"); break;
|
||||
case (ErrorCode::LegacyApiV1NotSupportedError): errorMessage = QObject::tr("This legacy Amnezia subscription format is no longer supported"); break;
|
||||
case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break;
|
||||
case (ErrorCode::NoInstalledContainersError): errorMessage = QObject::tr("VPN Protocols is not installed.\n Please install VPN container at first"); break;
|
||||
|
||||
|
||||
@@ -73,6 +73,14 @@ QString amnezia::scriptName(ProtocolScriptType type)
|
||||
}
|
||||
}
|
||||
|
||||
QString amnezia::scriptName(ClientScriptType type)
|
||||
{
|
||||
switch (type) {
|
||||
case ClientScriptType::mac_installer: return QLatin1String("mac_installer.sh");
|
||||
default: return QString();
|
||||
}
|
||||
}
|
||||
|
||||
QString amnezia::scriptData(amnezia::SharedScriptType type)
|
||||
{
|
||||
QString fileName = QString(":/server_scripts/%1").arg(amnezia::scriptName(type));
|
||||
@@ -101,6 +109,22 @@ QString amnezia::scriptData(amnezia::ProtocolScriptType type, DockerContainer co
|
||||
return data;
|
||||
}
|
||||
|
||||
QString amnezia::scriptData(ClientScriptType type)
|
||||
{
|
||||
QString fileName = QString(":/client_scripts/%1").arg(amnezia::scriptName(type));
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qDebug() << "Warning: script missing" << fileName;
|
||||
return "";
|
||||
}
|
||||
QByteArray data = file.readAll();
|
||||
if (data.isEmpty()) {
|
||||
qDebug() << "Warning: script is empty" << fileName;
|
||||
}
|
||||
data.replace("\r", "");
|
||||
return data;
|
||||
}
|
||||
|
||||
amnezia::ScriptVars amnezia::genBaseVars(const ServerCredentials &credentials,
|
||||
DockerContainer container,
|
||||
const QString &primaryDns,
|
||||
|
||||
@@ -41,14 +41,20 @@ enum ProtocolScriptType {
|
||||
xray_template
|
||||
};
|
||||
|
||||
enum ClientScriptType {
|
||||
// Client-side scripts
|
||||
mac_installer
|
||||
};
|
||||
|
||||
QString scriptFolder(DockerContainer container);
|
||||
|
||||
QString scriptName(SharedScriptType type);
|
||||
QString scriptName(ProtocolScriptType type);
|
||||
QString scriptName(ClientScriptType type);
|
||||
|
||||
QString scriptData(SharedScriptType type);
|
||||
QString scriptData(ProtocolScriptType type, DockerContainer container);
|
||||
QString scriptData(ClientScriptType type);
|
||||
|
||||
ScriptVars genBaseVars(const ServerCredentials &credentials,
|
||||
DockerContainer container,
|
||||
|
||||
122
client/core/utils/serverConfigUtils.cpp
Normal file
122
client/core/utils/serverConfigUtils.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "serverConfigUtils.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include "core/models/selfhosted/selfHostedAdminServerConfig.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool hasThirdPartyConfig(const QJsonObject &json)
|
||||
{
|
||||
const QJsonArray containersArray = json.value(amnezia::configKey::containers).toArray();
|
||||
for (const QJsonValue &val : containersArray) {
|
||||
const QJsonObject containerObj = val.toObject();
|
||||
for (auto it = containerObj.begin(); it != containerObj.end(); ++it) {
|
||||
if (it.key() == amnezia::configKey::container) {
|
||||
continue;
|
||||
}
|
||||
const QJsonObject protocolObj = it.value().toObject();
|
||||
if (protocolObj.contains(amnezia::configKey::isThirdPartyConfig)
|
||||
&& protocolObj.value(amnezia::configKey::isThirdPartyConfig).toBool()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace serverConfigUtils
|
||||
{
|
||||
|
||||
bool isServerFromApi(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
const int configVersion = serverConfigObject.value(amnezia::configKey::configVersion).toInt();
|
||||
switch (configVersion) {
|
||||
case ConfigSource::Telegram:
|
||||
case ConfigSource::AmneziaGateway:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ConfigSource getConfigSource(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
return static_cast<ConfigSource>(serverConfigObject.value(amnezia::configKey::configVersion).toInt());
|
||||
}
|
||||
|
||||
ConfigType configTypeFromJson(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
const int configVersion = serverConfigObject.value(amnezia::configKey::configVersion).toInt();
|
||||
|
||||
switch (configVersion) {
|
||||
case ConfigSource::Telegram: {
|
||||
constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
|
||||
constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
|
||||
|
||||
const QString apiEndpointValue = serverConfigObject.value(apiDefs::key::apiEndpoint).toString();
|
||||
|
||||
if (apiEndpointValue.contains(premiumV1Endpoint)) {
|
||||
return ConfigType::AmneziaPremiumV1;
|
||||
}
|
||||
if (apiEndpointValue.contains(freeV2Endpoint)) {
|
||||
return ConfigType::AmneziaFreeV2;
|
||||
}
|
||||
}
|
||||
[[fallthrough]];
|
||||
case ConfigSource::AmneziaGateway: {
|
||||
constexpr QLatin1String servicePremium("amnezia-premium");
|
||||
constexpr QLatin1String serviceFree("amnezia-free");
|
||||
constexpr QLatin1String serviceExternalPremium("external-premium");
|
||||
|
||||
const QJsonObject apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
||||
const QString serviceTypeStr = apiConfigObject.value(apiDefs::key::serviceType).toString();
|
||||
|
||||
if (serviceTypeStr == servicePremium) {
|
||||
return ConfigType::AmneziaPremiumV2;
|
||||
}
|
||||
if (serviceTypeStr == serviceFree) {
|
||||
return ConfigType::AmneziaFreeV3;
|
||||
}
|
||||
if (serviceTypeStr == serviceExternalPremium) {
|
||||
return ConfigType::ExternalPremium;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasThirdPartyConfig(serverConfigObject)) {
|
||||
return ConfigType::Native;
|
||||
}
|
||||
|
||||
const amnezia::SelfHostedAdminServerConfig adminProbe =
|
||||
amnezia::SelfHostedAdminServerConfig::fromJson(serverConfigObject);
|
||||
return adminProbe.hasCredentials() ? ConfigType::SelfHostedAdmin : ConfigType::SelfHostedUser;
|
||||
}
|
||||
|
||||
bool isLegacyApiSubscription(ConfigType configType)
|
||||
{
|
||||
return configType == ConfigType::AmneziaPremiumV1 || configType == ConfigType::AmneziaFreeV2;
|
||||
}
|
||||
|
||||
bool isApiV2Subscription(ConfigType configType)
|
||||
{
|
||||
switch (configType) {
|
||||
case ConfigType::AmneziaPremiumV2:
|
||||
case ConfigType::AmneziaFreeV3:
|
||||
case ConfigType::ExternalPremium:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace serverConfigUtils
|
||||
40
client/core/utils/serverConfigUtils.h
Normal file
40
client/core/utils/serverConfigUtils.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef SERVERCONFIGUTILS_H
|
||||
#define SERVERCONFIGUTILS_H
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace serverConfigUtils
|
||||
{
|
||||
|
||||
enum ConfigType {
|
||||
AmneziaFreeV2 = 0,
|
||||
AmneziaFreeV3,
|
||||
AmneziaPremiumV1,
|
||||
AmneziaPremiumV2,
|
||||
SelfHosted,
|
||||
ExternalPremium,
|
||||
|
||||
SelfHostedAdmin = 8,
|
||||
SelfHostedUser,
|
||||
Native,
|
||||
Invalid
|
||||
};
|
||||
|
||||
enum ConfigSource {
|
||||
Telegram = 1,
|
||||
AmneziaGateway
|
||||
};
|
||||
|
||||
bool isServerFromApi(const QJsonObject &serverConfigObject);
|
||||
|
||||
ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
|
||||
|
||||
ConfigType configTypeFromJson(const QJsonObject &serverConfigObject);
|
||||
|
||||
bool isLegacyApiSubscription(ConfigType configType);
|
||||
|
||||
bool isApiV2Subscription(ConfigType configType);
|
||||
|
||||
} // namespace serverConfigUtils
|
||||
|
||||
#endif // SERVERCONFIGUTILS_H
|
||||
@@ -259,15 +259,7 @@ bool Utils::killProcessByName(const QString &name)
|
||||
|
||||
QString Utils::openVpnExecPath()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return Utils::executable("openvpn/openvpn", true);
|
||||
#elif defined Q_OS_LINUX
|
||||
// We have service that runs OpenVPN on Linux. We need to make same
|
||||
// path for client and service.
|
||||
return Utils::executable("../../client/bin/openvpn", true);
|
||||
#else
|
||||
return Utils::executable("/openvpn", true);
|
||||
#endif
|
||||
return Utils::executable("openvpn", true);
|
||||
}
|
||||
|
||||
QString Utils::wireguardExecPath()
|
||||
@@ -295,15 +287,7 @@ QString Utils::certUtilPath()
|
||||
|
||||
QString Utils::tun2socksPath()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return Utils::executable("xray/tun2socks", true);
|
||||
#elif defined Q_OS_LINUX
|
||||
// We have service that runs OpenVPN on Linux. We need to make same
|
||||
// path for client and service.
|
||||
return Utils::executable("../../client/bin/tun2socks", true);
|
||||
#else
|
||||
return Utils::executable("/tun2socks", true);
|
||||
#endif
|
||||
return Utils::executable("tun2socks", true);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
|
||||
@@ -110,21 +110,14 @@ set_property(TARGET networkextension APPEND PROPERTY RESOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
## Build wireguard-go-version.h
|
||||
execute_process(
|
||||
COMMAND go list -m golang.zx2c4.com/wireguard
|
||||
WORKING_DIRECTORY ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo
|
||||
OUTPUT_VARIABLE WG_VERSION_FULL
|
||||
)
|
||||
string(REGEX REPLACE ".*v\([0-9.]*\).*" "\\1" WG_VERSION_STRING 1.1.1)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/wireguard-go-version.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
|
||||
target_sources(networkextension PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
|
||||
|
||||
target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR})
|
||||
target_include_directories(networkextension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
target_link_libraries(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/ios/arm64/libwg-go.a)
|
||||
find_package(openvpnadapter REQUIRED)
|
||||
target_link_libraries(networkextension PRIVATE amnezia::openvpnadapter)
|
||||
|
||||
target_link_libraries(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework)
|
||||
find_package(awg-apple REQUIRED)
|
||||
target_link_libraries(networkextension PRIVATE amnezia::awg-apple)
|
||||
|
||||
find_package(hev-socks5-tunnel REQUIRED)
|
||||
target_link_libraries(networkextension PRIVATE heiher::hev-socks5-tunnel)
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#ifndef WIREGUARD_GO_VERSION
|
||||
#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@"
|
||||
#endif // WIREGUARD_GO_VERSION
|
||||
@@ -114,25 +114,14 @@ set_property(TARGET AmneziaVPNNetworkExtension APPEND PROPERTY RESOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
## Build wireguard-go-version.h
|
||||
execute_process(
|
||||
COMMAND go list -m golang.zx2c4.com/wireguard
|
||||
WORKING_DIRECTORY ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo
|
||||
OUTPUT_VARIABLE WG_VERSION_FULL
|
||||
)
|
||||
string(REGEX REPLACE ".*v\([0-9.]*\).*" "\\1" WG_VERSION_STRING 1.1.1)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/wireguard-go-version.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
|
||||
target_sources(AmneziaVPNNetworkExtension PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
|
||||
|
||||
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR})
|
||||
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/macos/universal2/libwg-go.a)
|
||||
find_package(openvpnadapter REQUIRED)
|
||||
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE amnezia::openvpnadapter)
|
||||
|
||||
message(${CLIENT_ROOT_DIR})
|
||||
message(${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
|
||||
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
|
||||
find_package(awg-apple REQUIRED)
|
||||
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE amnezia::awg-apple)
|
||||
|
||||
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/Headers)
|
||||
find_package(hev-socks5-tunnel REQUIRED)
|
||||
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE heiher::hev-socks5-tunnel)
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#ifndef WIREGUARD_GO_VERSION
|
||||
#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@"
|
||||
#endif // WIREGUARD_GO_VERSION
|
||||
@@ -75,7 +75,6 @@ int main(int argc, char *argv[])
|
||||
|
||||
qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
|
||||
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
|
||||
qInfo().noquote() << QString("SSL backend: %1").arg(QSslSocket::sslLibraryVersionString());
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -977,7 +977,9 @@ bool IosController::shareText(const QStringList& filesToSend) {
|
||||
}
|
||||
#if !MACOS_NE
|
||||
UIViewController *qtController = getViewController();
|
||||
if (!qtController) return;
|
||||
if (!qtController) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
|
||||
#endif
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
#include "linuxfirewall.h"
|
||||
#include "logger.h"
|
||||
#include "xray_defs.h"
|
||||
#include <QProcess>
|
||||
|
||||
#define BRAND_CODE "amn"
|
||||
@@ -282,6 +283,10 @@ void LinuxFirewall::install()
|
||||
QStringLiteral("-o tun2+ -j ACCEPT"),
|
||||
});
|
||||
|
||||
installAnchor(Both, QStringLiteral("130.allowMarkedXray"), {
|
||||
QStringLiteral("-m mark --mark %1 -j ACCEPT").arg(amnezia::xray::xrayTrafficMark),
|
||||
});
|
||||
|
||||
installAnchor(IPv4, QStringLiteral("120.blockNets"), {});
|
||||
|
||||
installAnchor(IPv4, QStringLiteral("110.allowNets"), {});
|
||||
@@ -358,6 +363,7 @@ void LinuxFirewall::uninstall()
|
||||
uninstallAnchor(IPv6, QStringLiteral("250.blockIPv6"));
|
||||
uninstallAnchor(Both, QStringLiteral("200.allowVPN"));
|
||||
uninstallAnchor(IPv4, QStringLiteral("120.blockNets"));
|
||||
uninstallAnchor(Both, QStringLiteral("130.allowMarkedXray"));
|
||||
uninstallAnchor(IPv4, QStringLiteral("110.allowNets"));
|
||||
uninstallAnchor(Both, QStringLiteral("100.blockAll"));
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
||||
|
||||
QDir appPath(QCoreApplication::applicationDirPath());
|
||||
QStringList wgArgs = {"-f", "amn0"};
|
||||
m_tunnel.start(appPath.filePath("../../client/bin/wireguard-go"), wgArgs);
|
||||
m_tunnel.start(appPath.filePath("amneziawg-go"), wgArgs);
|
||||
if (!m_tunnel.waitForStarted(WG_TUN_PROC_TIMEOUT)) {
|
||||
logger.error() << "Unable to start tunnel process due to timeout";
|
||||
m_tunnel.kill();
|
||||
|
||||
@@ -79,7 +79,7 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
||||
|
||||
QDir appPath(QCoreApplication::applicationDirPath());
|
||||
QStringList wgArgs = {"-f", "utun"};
|
||||
m_tunnel.start(appPath.filePath("wireguard-go"), wgArgs);
|
||||
m_tunnel.start(appPath.filePath("amneziawg-go"), wgArgs);
|
||||
if (!m_tunnel.waitForStarted(WG_TUN_PROC_TIMEOUT)) {
|
||||
logger.error() << "Unable to start tunnel process due to timeout";
|
||||
m_tunnel.kill();
|
||||
|
||||
@@ -95,15 +95,6 @@ target_link_libraries(test_servers_model_sync PRIVATE
|
||||
test_common
|
||||
)
|
||||
|
||||
add_executable(test_gateway_stacks
|
||||
testGatewayStacks.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(test_gateway_stacks PRIVATE
|
||||
Qt6::Test
|
||||
test_common
|
||||
)
|
||||
|
||||
add_executable(test_complex_operations
|
||||
testComplexOperations.cpp
|
||||
)
|
||||
@@ -148,7 +139,6 @@ add_test(NAME DefaultServerChangeTest COMMAND test_default_server_change)
|
||||
add_test(NAME ServerEdgeCasesTest COMMAND test_server_edge_cases)
|
||||
add_test(NAME SignalOrderTest COMMAND test_signal_order)
|
||||
add_test(NAME ServersModelSyncTest COMMAND test_servers_model_sync)
|
||||
add_test(NAME GatewayStacksTest COMMAND test_gateway_stacks)
|
||||
add_test(NAME ComplexOperationsTest COMMAND test_complex_operations)
|
||||
add_test(NAME SettingsSignalsTest COMMAND test_settings_signals)
|
||||
add_test(NAME UiServersModelAndControllerTest COMMAND test_ui_servers_model_and_controller)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <QSignalSpy>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
@@ -117,8 +117,8 @@ private slots:
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 0, "defaultServerChanged signal should NOT be emitted (default is already 0)");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() > 0, "Server should be added");
|
||||
|
||||
int serverIndex = m_coreController->m_serversRepository->defaultServerIndex();
|
||||
auto exportResult = m_coreController->m_exportController->generateFullAccessConfig(serverIndex);
|
||||
const QString serverId = m_coreController->m_serversRepository->defaultServerId();
|
||||
auto exportResult = m_coreController->m_exportController->generateFullAccessConfig(serverId);
|
||||
|
||||
QVERIFY2(exportResult.errorCode == ErrorCode::NoError, "Export should succeed");
|
||||
QVERIFY2(!exportResult.config.isEmpty(), "Exported config should not be empty");
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
#include <QSignalSpy>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/models/serverDescription.h"
|
||||
#include "tests/testServerRepositoryHelpers.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
@@ -37,7 +38,7 @@ private slots:
|
||||
void init() {
|
||||
m_settings->clearSettings();
|
||||
if (m_coreController->m_serversModel) {
|
||||
m_coreController->m_serversModel->updateModel(QVector<ServerConfig>(), -1, false);
|
||||
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,35 +66,33 @@ private slots:
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 3, "Should have 3 servers");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 2, "Default should be index 2");
|
||||
|
||||
ServerConfig server0 = m_coreController->m_serversController->getServerConfig(0);
|
||||
server0.visit([](auto& arg) {
|
||||
arg.description = "Edited First Server";
|
||||
});
|
||||
m_coreController->m_serversController->editServer(0, server0);
|
||||
amnezia::test::setServerDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversController->getServerId(0),
|
||||
QStringLiteral("Edited First Server"));
|
||||
|
||||
QVERIFY2(serverEditedSpy.count() == 1, "serverEdited should be emitted");
|
||||
QString editedDesc0 = m_coreController->m_serversRepository->server(0).description();
|
||||
QString editedDesc0 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(0));
|
||||
QVERIFY2(editedDesc0 == "Edited First Server", "First server should be edited");
|
||||
|
||||
m_coreController->m_serversController->removeServer(1);
|
||||
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(1));
|
||||
|
||||
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved should be emitted");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 2, "Should have 2 servers");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Default should be index 1 (was 2, removed 1)");
|
||||
|
||||
m_coreController->m_serversController->setDefaultServerIndex(0);
|
||||
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(0));
|
||||
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 4, "defaultServerChanged should be emitted again");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default should be index 0");
|
||||
|
||||
ServerConfig server0After = m_coreController->m_serversController->getServerConfig(0);
|
||||
server0After.visit([](auto& arg) {
|
||||
arg.description = "Final Edited Server";
|
||||
});
|
||||
m_coreController->m_serversController->editServer(0, server0After);
|
||||
amnezia::test::setServerDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversController->getServerId(0),
|
||||
QStringLiteral("Final Edited Server"));
|
||||
|
||||
QVERIFY2(serverEditedSpy.count() == 2, "serverEdited should be emitted again");
|
||||
QString finalDesc0 = m_coreController->m_serversRepository->server(0).description();
|
||||
QString finalDesc0 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(0));
|
||||
QVERIFY2(finalDesc0 == "Final Edited Server", "First server should be edited again");
|
||||
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 2, "Final servers count should be 2");
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
#include <QSignalSpy>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "core/models/serverDescription.h"
|
||||
#include "tests/testServerRepositoryHelpers.h"
|
||||
#include "ui/models/serversModel.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "secureQSettings.h"
|
||||
@@ -39,7 +40,7 @@ private slots:
|
||||
m_settings->clearSettings();
|
||||
m_coreController->m_serversRepository->invalidateCache();
|
||||
if (m_coreController->m_serversModel) {
|
||||
m_coreController->m_serversModel->updateModel(QVector<ServerConfig>(), -1, false);
|
||||
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,9 +61,10 @@ private slots:
|
||||
|
||||
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
|
||||
|
||||
m_coreController->m_serversController->setDefaultServerIndex(0);
|
||||
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(0));
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 1, "defaultServerChanged signal should be emitted");
|
||||
QVERIFY2(defaultServerChangedSpy.at(0).at(0).toInt() == 0, "defaultServerChanged should emit index 0");
|
||||
QVERIFY2(defaultServerChangedSpy.at(0).at(0).toString() == m_coreController->m_serversController->getServerId(0),
|
||||
"defaultServerChanged should emit new default server id");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default server index should be 0");
|
||||
|
||||
if (m_coreController->m_serversModel) {
|
||||
@@ -70,9 +72,10 @@ private slots:
|
||||
QVERIFY2(modelDefaultIndex == 0, "Model should reflect default server");
|
||||
}
|
||||
|
||||
m_coreController->m_serversController->setDefaultServerIndex(2);
|
||||
m_coreController->m_serversController->setDefaultServer(m_coreController->m_serversController->getServerId(2));
|
||||
QVERIFY2(defaultServerChangedSpy.count() == 2, "defaultServerChanged signal should be emitted again");
|
||||
QVERIFY2(defaultServerChangedSpy.at(1).at(0).toInt() == 2, "defaultServerChanged should emit index 2");
|
||||
QVERIFY2(defaultServerChangedSpy.at(1).at(0).toString() == m_coreController->m_serversController->getServerId(2),
|
||||
"defaultServerChanged should emit new default server id");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 2, "Default server index should be 2");
|
||||
}
|
||||
|
||||
@@ -94,28 +97,28 @@ private slots:
|
||||
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
|
||||
QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved);
|
||||
|
||||
m_coreController->m_serversController->removeServer(0);
|
||||
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(0));
|
||||
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 2, "Should have 2 servers");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 1, "Default should be index 1 (was 2, removed 0)");
|
||||
|
||||
ServerConfig remainingServer1 = m_coreController->m_serversRepository->server(0);
|
||||
ServerConfig remainingServer2 = m_coreController->m_serversRepository->server(1);
|
||||
QString desc1 = remainingServer1.description();
|
||||
QString desc2 = remainingServer2.description();
|
||||
QString desc1 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(0));
|
||||
QString desc2 = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(1));
|
||||
QVERIFY2(desc1 == "Xray Server", "First remaining server should be Xray");
|
||||
QVERIFY2(desc2 == "WireGuard Server", "Second remaining server should be WireGuard");
|
||||
|
||||
defaultServerChangedSpy.clear();
|
||||
serverRemovedSpy.clear();
|
||||
|
||||
m_coreController->m_serversController->removeServer(0);
|
||||
m_coreController->m_serversController->removeServer(m_coreController->m_serversController->getServerId(0));
|
||||
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_serversRepository->serversCount() == 1, "Should have 1 server");
|
||||
QVERIFY2(m_coreController->m_serversRepository->defaultServerIndex() == 0, "Default should be index 0 (was 1, removed 0)");
|
||||
|
||||
ServerConfig lastServer = m_coreController->m_serversRepository->server(0);
|
||||
QString lastDesc = lastServer.description();
|
||||
QString lastDesc = amnezia::test::serverDescription(m_coreController->m_serversRepository,
|
||||
m_coreController->m_serversRepository->serverIdAt(0));
|
||||
QVERIFY2(lastDesc == "WireGuard Server", "Last server should be WireGuard");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
#include <QTest>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
#include <QSignalSpy>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "core/models/serverConfig.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "secureQSettings.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
class TestGatewayStacks : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CoreController* m_coreController;
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
|
||||
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
|
||||
|
||||
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
|
||||
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
|
||||
}
|
||||
|
||||
void cleanupTestCase() {
|
||||
m_settings->clearSettings();
|
||||
delete m_coreController;
|
||||
delete m_settings;
|
||||
}
|
||||
|
||||
void init() {
|
||||
m_settings->clearSettings();
|
||||
if (m_coreController->m_serversModel) {
|
||||
m_coreController->m_serversModel->updateModel(QVector<ServerConfig>(), -1, false);
|
||||
}
|
||||
}
|
||||
|
||||
void testGatewayStacksRecomputeOnServerOperations() {
|
||||
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
|
||||
|
||||
QSignalSpy gatewayStacksExpandedSpy(m_coreController->m_serversController, &ServersController::gatewayStacksExpanded);
|
||||
QSignalSpy serverAddedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded);
|
||||
QSignalSpy serverEditedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited);
|
||||
QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved);
|
||||
|
||||
auto importResult = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
|
||||
m_coreController->m_importCoreController->importConfig(importResult.config);
|
||||
|
||||
QVERIFY2(serverAddedSpy.count() == 1, "serverAdded signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_serversController->gatewayStacks().isEmpty(), "Gateway stacks should be empty for self-hosted servers");
|
||||
|
||||
ServerConfig serverConfig = m_coreController->m_serversController->getServerConfig(0);
|
||||
serverConfig.visit([](auto& arg) {
|
||||
arg.description = "Edited Server";
|
||||
});
|
||||
m_coreController->m_serversController->editServer(0, serverConfig);
|
||||
|
||||
QVERIFY2(serverEditedSpy.count() == 1, "serverEdited signal should be emitted");
|
||||
|
||||
m_coreController->m_serversController->removeServer(0);
|
||||
|
||||
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted");
|
||||
QVERIFY2(m_coreController->m_serversController->gatewayStacks().isEmpty(), "Gateway stacks should remain empty");
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestGatewayStacks)
|
||||
#include "testGatewayStacks.moc"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user