Compare commits

...

73 Commits

Author SHA1 Message Date
lunardunno 393926c78e Merge branch 'support-podman' into support_podman 2025-03-18 18:25:08 +04:00
lunardunno ecf5b7c68f Checking sudo docker and status of the service
Checking presence Sudo and Docker on the server.
Checking the status of the Docker service.
2025-03-18 16:12:20 +04:00
lunardunno 0b6822d47b Merge branch 'support-podman' into support_podman 2025-03-18 14:14:18 +04:00
lunardunno ae32b26b83 Adding variables
Adding variables for the Docker service name and for checking the Docker variant being prepared for installation.
2025-03-18 14:01:41 +04:00
lunardunno 0c42563389 Removing check installation result
Removing check installation result: sudo psmisc lsof
2025-03-18 12:47:19 +04:00
lunardunno b14431662f Simplifying check system locale
Simplifying to check and change the system locale.
2025-03-18 12:31:40 +04:00
lunardunno 58a803335d Implementing podman support
Implementing podman support for X-ray
2025-03-18 11:01:08 +04:00
lunardunno 8a3183f101 Merge branch 'dev' into support_podman 2025-03-15 03:40:39 +04:00
Yaroslav 9fbea76b74 There's a common issue of building iOS apps on Qt 6.8 because of new introduced ffmpeg dependency in multimedia Qt package (#1414)
ref: https://community.esri.com/t5/qt-maps-sdk-questions/build-failure-on-ios-with-qt-6-8/m-p/1548701#M5339
2025-03-14 20:40:27 +07:00
lunardunno b3ff120bcf Checking server user permissions to use sudo (#1442)
* Username if whoami returns an error

Сommand to use home directory name if whoami returns error or is missing for prepare_host.sh.

* Update check_user_in_sudo.sh

Сommand to use home directory name if whoami returns error or is missing for check_user_in_sudo.sh.
Checking server user permissions to use sudo using a package manager or using uname.
Сhecking and redefining the system language.
Checking requirements for sudo users or root in script.

* Cases have been changed and added.

Changed description of the “Server User Not In Sudo” case.
Corrected the name and description of the "ServerPacketManagerError" case. Packet to Package.
Adding a "SudoPackageIsNotPreinstalled" case.
Adding a "ServerUserNotAllowedInSudoers" case.
Adding a "ServerUserPasswordRequired" case.

* Serves errors have been changed and added.

Corrected the name of the "ServerPacketManagerError" error to "ServerPackageManagerError".
Adding a "SudoPackageIsNotPreinstalled" error.
Adding a "ServerUserNotAllowedInSudoers" error.
Adding a "ServerUserPasswordRequired" error.

* Return ServerPacketManagerError

Return to the name "ServerPacketManagerError".

* Added errors handling 

Added new errors' handling to serverController.cpp.
Permission checks are also performed for the root user.

* Update translations

Updating translations for two existing server errors.

* Myanmar translation update

* Update for my_MM.ts

* checking for not allowed

Checking for "not allowed" in stdOut

* Removed "not allowed"

Removed check for "not allowed" in stdOut

* Removed nested launch

Removed nested launch via sudo

* Returned nested launch

Returned nested launch via sudo

* All checks with sudo

Both checks with sudo always run.

* Moved removing timestamp sudo

Removing the sudo timestamp is done every time.

* Checking the user directory

Checking the accessibility of the user's home directory

* Polishing

Изменение порядка обработки ошибок.

* changing detection order 

change the order of detection of inconsistencies:
1. sudo not preinstalled. (if user != root)
2. user not in sudo or wheel group. (if user != root)
3. user's directory is not accessible. (for all)
4. user not allowed in sudoers. (for all)
5. user password required. (for all)

* Packet to Package

* chore: bump version (#1463)

* fix for sh (#1462)

Fix for servers where sh is used as default shell.

* Username if whoami returns an error

Сommand to use home directory name if whoami returns error or is missing for prepare_host.sh.

* Update check_user_in_sudo.sh

Сommand to use home directory name if whoami returns error or is missing for check_user_in_sudo.sh.
Checking server user permissions to use sudo using a package manager or using uname.
Сhecking and redefining the system language.
Checking requirements for sudo users or root in script.

* Cases have been changed and added.

Changed description of the “Server User Not In Sudo” case.
Corrected the name and description of the "ServerPacketManagerError" case. Packet to Package.
Adding a "SudoPackageIsNotPreinstalled" case.
Adding a "ServerUserNotAllowedInSudoers" case.
Adding a "ServerUserPasswordRequired" case.

* Serves errors have been changed and added.

Corrected the name of the "ServerPacketManagerError" error to "ServerPackageManagerError".
Adding a "SudoPackageIsNotPreinstalled" error.
Adding a "ServerUserNotAllowedInSudoers" error.
Adding a "ServerUserPasswordRequired" error.

* Return ServerPacketManagerError

Return to the name "ServerPacketManagerError".

* Update translations

Updating translations for two existing server errors.

* Added errors handling 

Added new errors' handling to serverController.cpp.
Permission checks are also performed for the root user.

* Myanmar translation update

* Update for my_MM.ts

* checking for not allowed

Checking for "not allowed" in stdOut

* Removed "not allowed"

Removed check for "not allowed" in stdOut

* Removed nested launch

Removed nested launch via sudo

* Returned nested launch

Returned nested launch via sudo

* All checks with sudo

Both checks with sudo always run.

* Moved removing timestamp sudo

Removing the sudo timestamp is done every time.

* Checking the user directory

Checking the accessibility of the user's home directory

* Polishing

Изменение порядка обработки ошибок.

* changing detection order 

change the order of detection of inconsistencies:
1. sudo not preinstalled. (if user != root)
2. user not in sudo or wheel group. (if user != root)
3. user's directory is not accessible. (for all)
4. user not allowed in sudoers. (for all)
5. user password required. (for all)

* Undoing unintended changes

Undoing unintended changes.

* Undoing unintended change

Undoing unintended change.

* not allowed to use sudo

The user is not allowed to use sudo on this server.

* Capital letters in the error

Capital letters in the error description.

---------

Co-authored-by: albexk <albexk@proton.me>
2025-03-14 20:39:58 +07:00
lunardunno 9b695df78d Merge branch 'dev' into support_podman 2025-03-14 16:57:02 +04:00
paldeflex 9dea98f020 chore: README typo fixes (#1467) 2025-03-10 23:22:09 +07:00
Mykola Baibuz c4701d4e7a Update XRay for Desktops (#1459)
version 25.3.6
2025-03-10 15:11:26 +07:00
Nethius 48903ca3a1 chore: fixed proxyStorageUrl typo (#1466) 2025-03-09 13:36:21 +07:00
Nethius 0c9fd4aef4 feature: added multiply proxy storage support (#1465) 2025-03-09 13:07:08 +07:00
lunardunno b2af2e46ac fix for sh (#1462)
Fix for servers where sh is used as default shell.
2025-03-09 12:34:00 +07:00
albexk efc76a0683 chore: bump version (#1463) 2025-03-09 10:30:43 +07:00
Nethius c4a553c166 chore: error body processing (#1458) 2025-03-07 10:39:12 +07:00
Cyril Anisimov 69a00b0252 feature: remove the limit of ip addresses = 254 (#1438) 2025-03-06 21:43:47 +07:00
KsZnak 4257c08b43 Update amneziavpn_ru_RU.ts (#1457) 2025-03-06 21:38:42 +07:00
Mykola Baibuz c9e5b92f79 Remove unneeded flushDns (#1443) 2025-03-05 13:21:39 +07:00
Mykola Baibuz 99818c2ad8 Fixes for native OpenVPN config import (#1444)
* Remote address in OpenVPN config can be host name

* Protocol parameter in OpenVPN config is not mandatory
2025-03-05 13:20:46 +07:00
shiroow 99e3afabad chore: update eng text (#1456)
chore: update eng text
2025-03-05 10:11:31 +07:00
Yaroslav d3339a7f3a fix: iOS/iPadOS crashes on a start of the app because of there's no keyFrame set (#1448)
So setting one if it's not set.
2025-03-04 18:13:04 +07:00
Nethius 678bfffe49 chore: minor ui fixes (#1446)
* chore: minor ui fixes

* chore: update ru translation file

* bugfix: fixed config update by ttl for gateway configs

* bugfix: fixed proxy bypassing

* chore: minor ui fixes

* chore: update ru translation file

* chore: bump version
2025-03-04 13:33:35 +07:00
Nethius 728b48044c Merge pull request #1440 from amnezia-vpn/feature/subscription-settings-page
feature/subscription settings page
2025-02-28 22:17:43 +07:00
Nethius 7ccbfa48bc bugfix: fixed mobile controllers initialization (#1436)
* bugfix: fixed mobile controllers initialization

* chore: bump version
2025-02-25 22:29:58 +07:00
lunardunno edee3fec10 Removing service for podman 2024-07-22 02:12:52 +04:00
lunardunno 92e33f62c5 Clearing server for podman 2024-07-22 01:57:38 +04:00
lunardunno 76b01bc09a removing service for podman 2024-07-21 07:49:08 +04:00
lunardunno 343a5fdc1a renaming service file for podman 2024-07-21 07:46:17 +04:00
lunardunno f42fe9dde4 restart-policy always for podman 2024-07-21 07:42:39 +04:00
lunardunno 3676027ae1 removing space 2024-07-21 05:04:21 +04:00
lunardunno 3c239ec28d enable now service for podman 2024-07-21 04:35:51 +04:00
lunardunno 65b3015078 removing stderr about creating services
removing the output of information about creating services for podman-docker
2024-07-20 09:34:12 +04:00
lunardunno 0f0b644630 some fix 2024-07-20 09:26:12 +04:00
lunardunno a9bf2dfcb9 removing stdout about creating services
removing the output of information about creating services for podman-docker
2024-07-20 09:09:19 +04:00
lunardunno c34c7f21b0 removed creating services
creation of services for podman in this place has been removed
2024-07-20 09:01:07 +04:00
lunardunno 88b166443f changing restart method
changing restart method for podman-docker
2024-07-20 08:54:13 +04:00
lunardunno 284019f954 Create service for podman 2024-07-20 07:10:41 +04:00
lunardunno 4f77d7ba70 creating services for podman
creating systemd service to autostart container when the server is rebooted, when using podman-docker
2024-07-20 06:04:17 +04:00
lunardunno 3fc1a51a4c docker search with sudo 2024-07-20 00:39:42 +04:00
lunardunno ef902e6ddb Changes 2 2024-07-19 17:18:55 +04:00
lunardunno df10271be0 Changes 1 2024-07-18 21:03:15 +04:00
lunardunno df743b0b39 Improved code readability 2024-07-18 18:56:17 +04:00
lunardunno d1b3c05e02 Removing quotes 2024-07-18 04:26:38 +04:00
lunardunno fa5e254d83 check sudo or wheel group 2024-07-18 03:34:03 +04:00
lunardunno 467459e6f3 check wheel group 2024-07-18 03:08:51 +04:00
lunardunno 40e113023e Using grep exit code 2024-07-18 00:59:41 +04:00
lunardunno 37cddd73ce Using grep exit code 2024-07-17 21:16:55 +04:00
lunardunno 2f91240766 printf aliases 2024-07-01 17:00:14 +04:00
lunardunno 5c9ab510cb tests removed 2024-07-01 15:13:58 +04:00
lunardunno 886ca3f6ac test2 2024-07-01 13:55:18 +04:00
lunardunno 95b330bd56 sudo for docker version, plus test 2024-07-01 12:02:22 +04:00
lunardunno 91bb378806 quotes 2024-07-01 09:05:56 +04:00
lunardunno d33b84f637 docker version with sudo for podman 2024-07-01 05:58:22 +04:00
lunardunno 388fd6ac46 docker version with sudo 2024-07-01 05:33:42 +04:00
lunardunno 2717d9f5c2 some change 2024-07-01 05:02:58 +04:00
lunardunno a480c17f38 some changes 2024-07-01 04:29:01 +04:00
lunardunno 0a3f69a477 sudo test 2024-07-01 02:37:46 +04:00
lunardunno 832a525065 improved code readability 2024-06-30 16:40:05 +04:00
lunardunno 520094d430 aliases reworked
reworked the creation of aliases for podman for users with sudo
2024-06-30 12:45:40 +04:00
lunardunno 8599f8f0a9 hub search parameters have been cancelled
Changes to container hub search parameters have been cancelled.
2024-06-30 02:24:58 +04:00
lunardunno 40bb2e8ee4 some fix 2024-06-29 06:50:38 +04:00
lunardunno fe5ffec7c3 Removing aliases for podman
Removing AmneziaVPN aliases for podman-docker when cleaning the server.
2024-06-29 05:47:12 +04:00
lunardunno d502f1c4d2 correction of comment used 2024-06-29 05:31:01 +04:00
lunardunno c3e59bbaea add aliases for podman 2024-06-29 05:23:10 +04:00
lunardunno 5f98d388ef default value for Check service
the default value for the verification service is set to docker
2024-06-28 08:45:00 +04:00
lunardunno 2fc62cfa24 some changes 2024-06-28 07:18:13 +04:00
lunardunno b819d87245 docker.io for apt-get 2024-06-27 13:24:02 +04:00
lunardunno f39b4ecdc4 checking the result of actions, etc.
Change the stub command for check for available packages for Arch Linux.
Checking and changing the locale for correct error handling by the server controller.
Introduction of direct checking of package installation results.
Check which containerization application will be installed.
Checking of docker installation results and service or socket status.
2024-06-27 07:14:56 +04:00
lunardunno 762603f0ad improving podman compatibility
1) Disabling message: Emulate Docker CLI using podman.
2) Changed short-name-mode operating mode in podman.
3) temporary replacement for the docker service whose activity is being checked
2024-06-26 11:01:11 +04:00
lunardunno a3601d4dea enable podman.socket 2024-06-26 06:55:36 +04:00
54 changed files with 1759 additions and 676 deletions
+2 -2
View File
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
project(${PROJECT} VERSION 4.8.4.0 project(${PROJECT} VERSION 4.8.4.3
DESCRIPTION "AmneziaVPN" DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/" HOMEPAGE_URL "https://amnezia.org/"
) )
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}") set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2077) set(APP_ANDROID_VERSION_CODE 2080)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")
+10 -10
View File
@@ -6,11 +6,11 @@
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client) [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский ### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
[AmneziaVPN](https://amnezia.org) — это open sourse VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере. [AmneziaVPN](https://amnezia.org) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org) [![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Сайт](https://amnezia.org) | [Зеркало на сайт](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting) ### [Сайт](https://amnezia.org) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
> [!TIP] > [!TIP]
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org). > Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
@@ -30,7 +30,7 @@
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2. - Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay. - Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них. - Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
- Поддерживает платформы: Windows, MacOS, Linux, Android, iOS. - Поддерживает платформы: Windows, macOS, Linux, Android, iOS.
- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved). - Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
## Ссылки ## Ссылки
@@ -38,10 +38,10 @@
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org) - [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация - [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit - [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддржки в Telegram (Английский) - [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддржки в Telegram (Фарси) - [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддржки в Telegram (Мьянма) - [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддржки в Telegram (Русский) - [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\) - [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
## Технологии ## Технологии
@@ -80,8 +80,8 @@ git submodule update --init --recursive
Проверьте папку deploy для скриптов сборки. Проверьте папку deploy для скриптов сборки.
### Как собрать iOS-приложение из исходного кода на MacOS ### Как собрать iOS-приложение из исходного кода на MacOS
1. Убедитесь, что у вас установлен XCode версии 14 или выше. 1. Убедитесь, что у вас установлен Xcode версии 14 или выше.
2. Для генерации проекта XCode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули: 2. Для генерации проекта Xcode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
- MacOS - MacOS
- iOS - iOS
- Модуль совместимости с Qt 5 - Модуль совместимости с Qt 5
@@ -117,7 +117,7 @@ $QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
export PATH=$(PATH):/path/to/GOPATH/bin export PATH=$(PATH):/path/to/GOPATH/bin
``` ```
6. Откройте проект в XCode. Теперь вы можете тестировать, архивировать или публиковать приложение. 6. Откройте проект в Xcode. Теперь вы можете тестировать, архивировать или публиковать приложение.
Если сборка завершится с ошибкой: Если сборка завершится с ошибкой:
``` ```
-8
View File
@@ -31,10 +31,6 @@ add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}") add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}") add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
if(IOS)
set(PACKAGES ${PACKAGES} Multimedia)
endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(PACKAGES ${PACKAGES} Widgets) set(PACKAGES ${PACKAGES} Widgets)
endif() endif()
@@ -48,10 +44,6 @@ set(LIBS ${LIBS}
Qt6::Core5Compat Qt6::Concurrent Qt6::Core5Compat Qt6::Concurrent
) )
if(IOS)
set(LIBS ${LIBS} Qt6::Multimedia)
endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(LIBS ${LIBS} Qt6::Widgets) set(LIBS ${LIBS} Qt6::Widgets)
endif() endif()
+74 -65
View File
@@ -3,6 +3,7 @@
#include <QDebug> #include <QDebug>
#include <QJsonDocument> #include <QJsonDocument>
#include <QProcess> #include <QProcess>
#include <QRegularExpression>
#include <QString> #include <QString>
#include <QTemporaryDir> #include <QTemporaryDir>
#include <QTemporaryFile> #include <QTemporaryFile>
@@ -19,13 +20,17 @@
#include "settings.h" #include "settings.h"
#include "utilities.h" #include "utilities.h"
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings,
bool isAwg, QObject *parent) const QSharedPointer<ServerController> &serverController, bool isAwg,
QObject *parent)
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg) : ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
{ {
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath; m_serverConfigPath =
m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath; m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath; m_serverPublicKeyPath =
m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
m_serverPskKeyPath =
m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template; m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard; m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
@@ -63,9 +68,31 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
return connData; return connData;
} }
QList<QHostAddress> WireguardConfigurator::getIpsFromConf(const QString &input)
{
QRegularExpression regex("AllowedIPs = (\\d+\\.\\d+\\.\\d+\\.\\d+)");
QRegularExpressionMatchIterator matchIterator = regex.globalMatch(input);
QList<QHostAddress> ips;
while (matchIterator.hasNext()) {
QRegularExpressionMatch match = matchIterator.next();
const QString address_string { match.captured(1) };
const QHostAddress address { address_string };
if (address.isNull()) {
qWarning() << "Couldn't recognize the ip address: " << address_string;
} else {
ips << address;
}
}
return ips;
}
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials, WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
DockerContainer container, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode) const QJsonObject &containerConfig,
ErrorCode &errorCode)
{ {
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys(); WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
connData.host = credentials.hostName; connData.host = credentials.hostName;
@@ -76,65 +103,45 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
return connData; return connData;
} }
// Get list of already created clients (only IP addresses) QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
QString nextIpNumber; QString stdOut;
{ auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
QString script = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath); stdOut += data + "\n";
QString stdOut; return ErrorCode::NoError;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) { };
stdOut += data + "\n";
return ErrorCode::NoError;
};
errorCode = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut); errorCode = m_serverController->runContainerScript(credentials, container, getIpsScript, cbReadStdOut);
if (errorCode != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
return connData; return connData;
} }
auto ips = getIpsFromConf(stdOut);
stdOut.replace("AllowedIPs = ", ""); QHostAddress nextIp = [&] {
stdOut.replace("/32", ""); QHostAddress result;
QStringList ips = stdOut.split("\n", Qt::SkipEmptyParts); QHostAddress lastIp;
if (ips.empty()) {
// remove extra IPs from each line for case when user manually edited the wg0.conf lastIp.setAddress(containerConfig.value(m_protocolName)
// and added there more IPs for route his itnernal networks, like: .toObject()
// ... .value(config_key::subnet_address)
// AllowedIPs = 10.8.1.6/32, 192.168.1.0/24, 192.168.2.0/24, ... .toString(protocols::wireguard::defaultSubnetAddress));
// ...
// without this code - next IP would be 1 if last item in 'ips' has format above
QStringList vpnIps;
for (const auto &ip : ips) {
vpnIps.append(ip.split(",", Qt::SkipEmptyParts).first().trimmed());
}
ips = vpnIps;
// Calc next IP address
if (ips.isEmpty()) {
nextIpNumber = "2";
} else { } else {
int next = ips.last().split(".").last().toInt() + 1; lastIp = ips.last();
if (next > 254) {
errorCode = ErrorCode::AddressPoolError;
return connData;
}
nextIpNumber = QString::number(next);
} }
} quint8 lastOctet = static_cast<quint8>(lastIp.toIPv4Address());
switch (lastOctet) {
QString subnetIp = containerConfig.value(m_protocolName).toObject().value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); case 254: result.setAddress(lastIp.toIPv4Address() + 3); break;
{ case 255: result.setAddress(lastIp.toIPv4Address() + 2); break;
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts); default: result.setAddress(lastIp.toIPv4Address() + 1); break;
if (l.isEmpty()) {
errorCode = ErrorCode::AddressPoolError;
return connData;
} }
l.removeLast();
l.append(nextIpNumber);
connData.clientIP = l.join("."); return result;
} }();
connData.clientIP = nextIp.toString();
// Get keys // Get keys
connData.serverPubKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode); connData.serverPubKey =
m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
connData.serverPubKey.replace("\n", ""); connData.serverPubKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
return connData; return connData;
@@ -161,10 +168,12 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
return connData; return connData;
} }
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'").arg(m_serverConfigPath); QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
.arg(m_serverConfigPath);
errorCode = m_serverController->runScript( errorCode = m_serverController->runScript(
credentials, m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container))); credentials,
m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
return connData; return connData;
} }
@@ -173,8 +182,8 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
const QJsonObject &containerConfig, ErrorCode &errorCode) const QJsonObject &containerConfig, ErrorCode &errorCode)
{ {
QString scriptData = amnezia::scriptData(m_configTemplate, container); QString scriptData = amnezia::scriptData(m_configTemplate, container);
QString config = QString config = m_serverController->replaceVars(
m_serverController->replaceVars(scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig)); scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode); ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
if (errorCode != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
@@ -208,16 +217,16 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
return QJsonDocument(jConfig).toJson(); return QJsonDocument(jConfig).toJson();
} }
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns,
QString &protocolConfigString) const bool isApiConfig, QString &protocolConfigString)
{ {
processConfigWithDnsSettings(dns, protocolConfigString); processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString; return protocolConfigString;
} }
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns,
QString &protocolConfigString) const bool isApiConfig, QString &protocolConfigString)
{ {
processConfigWithDnsSettings(dns, protocolConfigString); processConfigWithDnsSettings(dns, protocolConfigString);
+10 -6
View File
@@ -1,6 +1,7 @@
#ifndef WIREGUARD_CONFIGURATOR_H #ifndef WIREGUARD_CONFIGURATOR_H
#define WIREGUARD_CONFIGURATOR_H #define WIREGUARD_CONFIGURATOR_H
#include <QHostAddress>
#include <QObject> #include <QObject>
#include <QProcessEnvironment> #include <QProcessEnvironment>
@@ -12,8 +13,8 @@ class WireguardConfigurator : public ConfiguratorBase
{ {
Q_OBJECT Q_OBJECT
public: public:
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, bool isAwg, WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
QObject *parent = nullptr); bool isAwg, QObject *parent = nullptr);
struct ConnectionData struct ConnectionData
{ {
@@ -26,15 +27,18 @@ public:
QString port; QString port;
}; };
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, QString createConfig(const ServerCredentials &credentials, DockerContainer container,
ErrorCode &errorCode); const QJsonObject &containerConfig, ErrorCode &errorCode);
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString); QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString); QString &protocolConfigString);
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
static ConnectionData genClientKeys(); static ConnectionData genClientKeys();
private: private:
QList<QHostAddress> getIpsFromConf(const QString &input);
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container, ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode); const QJsonObject &containerConfig, ErrorCode &errorCode);
+8 -1
View File
@@ -20,6 +20,9 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
initControllers(); initControllers();
initSignalHandlers(); initSignalHandlers();
initAndroidController();
initAppleController();
initNotificationHandler(); initNotificationHandler();
auto locale = m_settings->getAppLanguage(); auto locale = m_settings->getAppLanguage();
@@ -90,6 +93,9 @@ void CoreController::initModels()
m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this)); m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this));
m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get()); m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get());
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
} }
void CoreController::initControllers() void CoreController::initControllers()
@@ -129,7 +135,8 @@ void CoreController::initControllers()
m_systemController.reset(new SystemController(m_settings)); m_systemController.reset(new SystemController(m_settings));
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
m_apiSettingsController.reset(new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_settings)); m_apiSettingsController.reset(
new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_settings));
m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get()); m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get());
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings)); m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
+3 -1
View File
@@ -25,8 +25,9 @@
#include "ui/models/protocols/ikev2ConfigModel.h" #include "ui/models/protocols/ikev2ConfigModel.h"
#endif #endif
#include "ui/models/api/apiAccountInfoModel.h" #include "ui/models/api/apiAccountInfoModel.h"
#include "ui/models/api/apiServicesModel.h"
#include "ui/models/api/apiCountryModel.h" #include "ui/models/api/apiCountryModel.h"
#include "ui/models/api/apiDevicesModel.h"
#include "ui/models/api/apiServicesModel.h"
#include "ui/models/appSplitTunnelingModel.h" #include "ui/models/appSplitTunnelingModel.h"
#include "ui/models/clientManagementModel.h" #include "ui/models/clientManagementModel.h"
#include "ui/models/protocols/awgConfigModel.h" #include "ui/models/protocols/awgConfigModel.h"
@@ -117,6 +118,7 @@ private:
QSharedPointer<ApiServicesModel> m_apiServicesModel; QSharedPointer<ApiServicesModel> m_apiServicesModel;
QSharedPointer<ApiCountryModel> m_apiCountryModel; QSharedPointer<ApiCountryModel> m_apiCountryModel;
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel; QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel; QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel; QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
+54 -41
View File
@@ -26,6 +26,10 @@ namespace
constexpr char apiPayload[] = "api_payload"; constexpr char apiPayload[] = "api_payload";
constexpr char keyPayload[] = "key_payload"; constexpr char keyPayload[] = "key_payload";
} }
constexpr QLatin1String errorResponsePattern1("No active configuration found for");
constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for");
constexpr QLatin1String errorResponsePattern3("Account not found.");
} }
GatewayController::GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent) GatewayController::GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent)
@@ -148,7 +152,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
QByteArray encryptedResponseBody = reply->readAll(); QByteArray encryptedResponseBody = reply->readAll();
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, false)) { if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) { auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
request.setUrl(url); request.setUrl(url);
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson()); return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
@@ -157,12 +161,12 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt, auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) { this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
encryptedResponseBody = nestedReply->readAll(); encryptedResponseBody = nestedReply->readAll();
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) { reply = nestedReply;
if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
sslErrors = nestedSslErrors; sslErrors = nestedSslErrors;
reply = nestedReply; return false;
return true;
} }
return false; return true;
}; };
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction); bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
@@ -194,16 +198,16 @@ QStringList GatewayController::getProxyUrls()
QList<QSslError> sslErrors; QList<QSslError> sslErrors;
QNetworkReply *reply; QNetworkReply *reply;
QStringList proxyStorageUrl; QStringList proxyStorageUrls;
if (m_isDevEnvironment) { if (m_isDevEnvironment) {
proxyStorageUrl = QStringList { DEV_S3_ENDPOINT }; proxyStorageUrls = QString(DEV_S3_ENDPOINT).split(", ");
} else { } else {
proxyStorageUrl = QStringList { PROD_S3_ENDPOINT }; proxyStorageUrls = QString(PROD_S3_ENDPOINT).split(", ");
} }
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY; QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
for (const auto &proxyStorageUrl : proxyStorageUrl) { for (const auto &proxyStorageUrl : proxyStorageUrls) {
request.setUrl(proxyStorageUrl); request.setUrl(proxyStorageUrl);
reply = amnApp->networkManager()->get(request); reply = amnApp->networkManager()->get(request);
@@ -212,45 +216,45 @@ QStringList GatewayController::getProxyUrls()
wait.exec(); wait.exec();
if (reply->error() == QNetworkReply::NetworkError::NoError) { if (reply->error() == QNetworkReply::NetworkError::NoError) {
break; auto encryptedResponseBody = reply->readAll();
} reply->deleteLater();
reply->deleteLater();
}
auto encryptedResponseBody = reply->readAll(); EVP_PKEY *privateKey = nullptr;
reply->deleteLater(); QByteArray responseBody;
try {
if (!m_isDevEnvironment) {
QCryptographicHash hash(QCryptographicHash::Sha512);
hash.addData(key);
QByteArray hashResult = hash.result().toHex();
EVP_PKEY *privateKey = nullptr; QByteArray key = QByteArray::fromHex(hashResult.left(64));
QByteArray responseBody; QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
try {
if (!m_isDevEnvironment) {
QCryptographicHash hash(QCryptographicHash::Sha512);
hash.addData(key);
QByteArray hashResult = hash.result().toHex();
QByteArray key = QByteArray::fromHex(hashResult.left(64)); QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody); QSimpleCrypto::QBlockCipher blockCipher;
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
} else {
responseBody = encryptedResponseBody;
}
} catch (...) {
Utils::logException();
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
continue;
}
QSimpleCrypto::QBlockCipher blockCipher; auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
QStringList endpoints;
for (const auto &endpoint : endpointsArray) {
endpoints.push_back(endpoint.toString());
}
return endpoints;
} else { } else {
responseBody = encryptedResponseBody; reply->deleteLater();
} }
} catch (...) {
Utils::logException();
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
return {};
} }
return {};
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
QStringList endpoints;
for (const auto &endpoint : endpointsArray) {
endpoints.push_back(endpoint.toString());
}
return endpoints;
} }
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key, bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
@@ -262,6 +266,15 @@ bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray
} else if (responseBody.contains("html")) { } else if (responseBody.contains("html")) {
qDebug() << "The response contains an html tag"; qDebug() << "The response contains an html tag";
return true; return true;
} else if (reply->error() == QNetworkReply::NetworkError::ContentNotFoundError) {
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|| responseBody.contains(errorResponsePattern3)) {
return false;
} else {
return true;
}
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
return true;
} else if (checkEncryption) { } else if (checkEncryption) {
try { try {
QSimpleCrypto::QBlockCipher blockCipher; QSimpleCrypto::QBlockCipher blockCipher;
@@ -296,7 +309,7 @@ void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *repl
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; }); connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec(); wait.exec();
if (!replyProcessingFunction(reply, sslErrors)) { if (replyProcessingFunction(reply, sslErrors)) {
break; break;
} }
} }
+12 -6
View File
@@ -409,8 +409,10 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut; qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
if (stdOut.contains("lock")) if (stdOut.contains("lock"))
return ErrorCode::ServerPacketManagerError; return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("command not found")) if (stdOut.contains("sudo:") && stdOut.contains("not found"))
return ErrorCode::ServerDockerFailedError; return ErrorCode::ServerDockerFailedError;
if (stdOut.contains("Failed docker status"))
return ErrorCode::ServerDockerStatusNotActive;
return error; return error;
} }
@@ -757,10 +759,6 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container) ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
{ {
if (credentials.userName == "root") {
return ErrorCode::NoError;
}
QString stdOut; QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) { auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n"; stdOut += data + "\n";
@@ -774,8 +772,16 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo); const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
if (!stdOut.contains("sudo")) if (credentials.userName != "root" && stdOut.contains("sudo:") && !stdOut.contains("uname:") && stdOut.contains("not found"))
return ErrorCode::SudoPackageIsNotPreinstalled;
if (credentials.userName != "root" && !stdOut.contains("sudo") && !stdOut.contains("wheel"))
return ErrorCode::ServerUserNotInSudo; return ErrorCode::ServerUserNotInSudo;
if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory"))
return ErrorCode::ServerUserDirectoryNotAccessible;
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
return ErrorCode::ServerUserNotAllowedInSudoers;
if (stdOut.contains("password is required"))
return ErrorCode::ServerUserPasswordRequired;
return error; return error;
} }
+5
View File
@@ -54,6 +54,11 @@ namespace amnezia
ServerCancelInstallation = 204, ServerCancelInstallation = 204,
ServerUserNotInSudo = 205, ServerUserNotInSudo = 205,
ServerPacketManagerError = 206, ServerPacketManagerError = 206,
SudoPackageIsNotPreinstalled = 207,
ServerUserDirectoryNotAccessible = 208,
ServerUserNotAllowedInSudoers = 209,
ServerUserPasswordRequired = 210,
ServerDockerStatusNotActive = 211,
// Ssh connection errors // Ssh connection errors
SshRequestDeniedError = 300, SshRequestDeniedError = 300,
+7 -2
View File
@@ -20,8 +20,13 @@ QString errorString(ErrorCode code) {
case(ErrorCode::ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break; case(ErrorCode::ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break;
case(ErrorCode::ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break; case(ErrorCode::ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
case(ErrorCode::ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break; case(ErrorCode::ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
case(ErrorCode::ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break; case(ErrorCode::ServerUserNotInSudo): errorMessage = QObject::tr("The user is not a member of the sudo group"); break;
case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break; case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Package manager error"); break;
case(ErrorCode::SudoPackageIsNotPreinstalled): errorMessage = QObject::tr("The sudo package is not pre-installed"); break;
case(ErrorCode::ServerUserDirectoryNotAccessible): errorMessage = QObject::tr("The server user's home directory is not accessible"); break;
case(ErrorCode::ServerUserNotAllowedInSudoers): errorMessage = QObject::tr("Action not allowed in sudoers"); break;
case(ErrorCode::ServerUserPasswordRequired): errorMessage = QObject::tr("The user's password is required"); break;
case(ErrorCode::ServerDockerStatusNotActive): errorMessage = QObject::tr("Docker service status is not active"); break;
// Libssh errors // Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break; case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
+5
View File
@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 3H4C2.89543 3 2 3.89543 2 5V15C2 16.1046 2.89543 17 4 17H20C21.1046 17 22 16.1046 22 15V5C22 3.89543 21.1046 3 20 3Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 21H16" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 17V21" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 522 B

+7 -2
View File
@@ -14,10 +14,15 @@ extension UIApplication {
var keyWindows: [UIWindow] { var keyWindows: [UIWindow] {
connectedScenes connectedScenes
.compactMap { .compactMap {
guard let windowScene = $0 as? UIWindowScene else { return nil }
if #available(iOS 15.0, *) { if #available(iOS 15.0, *) {
($0 as? UIWindowScene)?.keyWindow guard let keywindow = windowScene.keyWindow else {
windowScene.windows.first?.makeKey()
return windowScene.windows.first
}
return keywindow
} else { } else {
($0 as? UIWindowScene)?.windows.first { $0.isKeyWindow } return windowScene.windows.first { $0.isKeyWindow }
} }
} }
} }
+2
View File
@@ -229,6 +229,8 @@
<file>ui/qml/Pages2/PageSettingsApiSupport.qml</file> <file>ui/qml/Pages2/PageSettingsApiSupport.qml</file>
<file>ui/qml/Pages2/PageSettingsApiInstructions.qml</file> <file>ui/qml/Pages2/PageSettingsApiInstructions.qml</file>
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file> <file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
<file>ui/qml/Pages2/PageSettingsApiDevices.qml</file>
<file>images/controls/monitor.svg</file>
</qresource> </qresource>
<qresource prefix="/countriesFlags"> <qresource prefix="/countriesFlags">
<file>images/flagKit/ZW.svg</file> <file>images/flagKit/ZW.svg</file>
+6 -1
View File
@@ -11,8 +11,13 @@ sudo docker run -d \
--name $CONTAINER_NAME \ --name $CONTAINER_NAME \
$CONTAINER_NAME $CONTAINER_NAME
# Create service for podman
if [ -n "$(sudo docker --version 2>/dev/null | grep podman)" ]; then \
sudo sh -c "podman generate systemd --restart-policy=always -t 1 --name $CONTAINER_NAME 2>/dev/null > $DOCKERFILE_FOLDER/container-$CONTAINER_NAME.service";\
sudo sh -c "systemctl enable --now $DOCKERFILE_FOLDER/container-$CONTAINER_NAME.service && docker update --restart no $CONTAINER_NAME > /dev/null";\
fi
sudo docker network connect amnezia-dns-net $CONTAINER_NAME sudo docker network connect amnezia-dns-net $CONTAINER_NAME
# Prevent to route packets outside of the container in case if server behind of the NAT # Prevent to route packets outside of the container in case if server behind of the NAT
#sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up" #sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up"
+17
View File
@@ -1 +1,18 @@
if [ -n "$(sudo docker --version 2>/dev/null | grep podman)" ]; then sudo sh -c "\
test -d /var/cache/containers || mkdir -m 700 -p /var/cache/containers;\
test -f /var/cache/containers/short-name-aliases.conf || chmod 600 /var/cache/containers/short-name-aliases.conf>>/var/cache/containers/short-name-aliases.conf;\
grep -q '\[aliases\]' /var/cache/containers/short-name-aliases.conf || echo '[aliases]' >> /var/cache/containers/short-name-aliases.conf;\
grep -q ' # Amnezia start' /var/cache/containers/short-name-aliases.conf || printf '%s\n' \
' # Amnezia start' \
' \"3proxy/3proxy\" = \"docker.io/3proxy/3proxy\"' \
' \"amneziavpn/amnezia-wg\" = \"docker.io/amneziavpn/amnezia-wg\"' \
' \"amneziavpn/amneziawg-go\" = \"docker.io/amneziavpn/amneziawg-go\"' \
' \"amneziavpn/ipsec-server\" = \"docker.io/amneziavpn/ipsec-server\"' \
' \"amneziavpn/torpress\" = \"docker.io/amneziavpn/torpress\"' \
' \"atmoz/sftp\" = \"docker.io/atmoz/sftp\"' \
' \"mvance/unbound\" = \"docker.io/mvance/unbound\"' \
' \"alpine\" = \"docker.io/library/alpine\"' \
' # Amnezia finish' \
>> /var/cache/containers/short-name-aliases.conf";\
fi;\
sudo docker build --no-cache --pull -t $CONTAINER_NAME $DOCKERFILE_FOLDER sudo docker build --no-cache --pull -t $CONTAINER_NAME $DOCKERFILE_FOLDER
+13 -2
View File
@@ -1,2 +1,13 @@
CUR_USER=$(whoami);\ if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\
groups $CUR_USER elif which dnf > /dev/null 2>&1; then pm=$(which dnf); opt="--version";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\
else pm="uname"; opt="-a";\
fi;\
CUR_USER=$(whoami 2>/dev/null || echo ~ | sed 's/.*\///');\
echo $LANG | grep -qE '^(en_US.UTF-8|C.UTF-8|C)$' || export LC_ALL=C;\
sudo -K;\
cd ~;\
if [ "$CUR_USER" = "root" ] || ( groups "$CUR_USER" | grep -E '\<(sudo|wheel)\>' ); then \
sudo -nu $CUR_USER $pm $opt > /dev/null; sudo -n $pm $opt > /dev/null;\
fi
+25 -11
View File
@@ -1,23 +1,37 @@
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\ if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install"; check_pkgs="-yq update"; wh_pkg="-s install"; docker_pkg="docker.io"; check_srv="docker"; dist="debian";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\ elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; wh_pkg="--assumeno install --setopt=tsflags=test"; docker_pkg="docker"; check_srv="docker"; dist="fedora";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\ elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; wh_pkg="--assumeno install --setopt=tsflags=test"; docker_pkg="docker"; check_srv="docker"; dist="centos";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\ elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; wh_pkg="-Sp"; docker_pkg="docker"; check_srv="docker"; dist="archlinux";\
else echo "Packet manager not found"; exit 1; fi;\ else echo "Packet manager not found"; exit 1; fi;\
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\ echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, What pkg command: $wh_pkg, Docker pkg: $docker_pkg, Check service: $check_srv";\
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\ if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
echo $LANG | grep -qE '^(en_US.UTF-8|C.UTF-8|C)$' || export LC_ALL=C;\
if ! command -v sudo > /dev/null 2>&1; then $pm $check_pkgs; $pm $silent_inst sudo; fi;\ if ! command -v sudo > /dev/null 2>&1; then $pm $check_pkgs; $pm $silent_inst sudo; fi;\
if ! command -v fuser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst psmisc; fi;\ if ! command -v fuser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst psmisc; fi;\
if ! command -v lsof > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst lsof; fi;\ if ! command -v lsof > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst lsof; fi;\
if ! command -v docker > /dev/null 2>&1; then \ if ! command -v docker > /dev/null 2>&1; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\ sudo $pm $check_pkgs;\
sleep 5; sudo systemctl enable --now docker; sleep 5;\ if [ -n "$(sudo $pm $wh_pkg $docker_pkg 2>/dev/null | grep moby-engine)" ]; \
then echo "Docker is not supported"; exit 1;\
else sudo $pm $silent_inst $docker_pkg;\
fi;\
if [ -n "$(sudo docker --version 2>/dev/null | grep podman)" ]; then docker_pkg="podman-docker"; check_srv="podman.socket podman";\
if [ -n "$(sudo docker --version 2>&1 | grep /etc/containers/nodocker)" ]; then sudo touch /etc/containers/nodocker; fi;\
fi;\
sleep 5; sudo systemctl enable --now $check_srv 2>/dev/null; sleep 5;\
fi;\
if [ -n "$(sudo docker --version 2>&1 | grep moby-engine)" ]; then echo "Docker is not supported"; exit 1;\
elif [ -n "$(sudo docker --version 2>&1 | grep podman)" ]; then check_srv="podman.socket podman"; docker_pkg="podman-docker";\
if [ -n "$(sudo docker --version 2>&1 | grep /etc/containers/nodocker)" ]; then sudo touch /etc/containers/nodocker; fi;\
fi;\ fi;\
if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \ if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
if ! command -v apparmor_parser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst apparmor; fi;\ if ! command -v apparmor_parser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst apparmor; fi;\
fi;\ fi;\
if [ "$(systemctl is-active docker)" != "active" ]; then \ if [ "$(systemctl is-active $check_srv | head -n1)" != "active" ]; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\ sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
sleep 5; sudo systemctl start docker; sleep 5;\ sleep 5; sudo systemctl start $check_srv; sleep 5;\
if [ "$(systemctl is-active $check_srv | head -n1)" != "active" ]; then echo "Failed docker status"; fi;\
fi;\ fi;\
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; fi;\ sudo docker --version
docker --version
# To allow autoinstallation of podman-docker, remove ' || [ -n "$(sudo $pm $wh_pkg $docker_pkg 2>/dev/null | grep podman-docker)" ]' and ' || [ -n "$(sudo docker --version 2>&1 | grep podman)" ]'
+1 -1
View File
@@ -1,4 +1,4 @@
CUR_USER=$(whoami);\ CUR_USER=$(whoami 2>/dev/null || echo ~ | sed 's/.*\///');\
sudo mkdir -p $DOCKERFILE_FOLDER;\ sudo mkdir -p $DOCKERFILE_FOLDER;\
sudo chown $CUR_USER $DOCKERFILE_FOLDER;\ sudo chown $CUR_USER $DOCKERFILE_FOLDER;\
if ! sudo docker network ls | grep -q amnezia-dns-net; then sudo docker network create \ if ! sudo docker network ls | grep -q amnezia-dns-net; then sudo docker network create \
@@ -1,4 +1,8 @@
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\ sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
sudo docker --version 2>/dev/null | grep -q podman && \
sudo systemctl list-units | grep amnezia | awk '{print $1}' | xargs sudo systemctl disable --now && \
sudo systemctl daemon-reload && sudo systemctl reset-failed && \
sudo sed -i '/^ # Amnezia start/,/^ # Amnezia finish$/d' /var/cache/containers/short-name-aliases.conf;\
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\ sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
sudo docker images -a | grep amnezia | awk '{print $3}' | xargs sudo docker rmi;\ sudo docker images -a | grep amnezia | awk '{print $3}' | xargs sudo docker rmi;\
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\ sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
@@ -1,3 +1,7 @@
sudo docker stop $CONTAINER_NAME;\ sudo docker stop $CONTAINER_NAME;\
sudo docker --version 2>/dev/null | grep -q podman && \
sudo systemctl disable --now container-$CONTAINER_NAME.service && \
sudo systemctl daemon-reload && sudo systemctl reset-failed && \
sudo rm -f $DOCKERFILE_FOLDER/container-$CONTAINER_NAME.service;\
sudo docker rm -fv $CONTAINER_NAME;\ sudo docker rm -fv $CONTAINER_NAME;\
sudo docker rmi $CONTAINER_NAME sudo docker rmi $CONTAINER_NAME
+7 -1
View File
@@ -12,6 +12,12 @@ sudo docker network connect amnezia-dns-net $CONTAINER_NAME
# Create tun device if not exist # Create tun device if not exist
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi' sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi'
# Create service for podman
if [ -n "$(sudo docker --version 2>/dev/null | grep podman)" ]; then \
sudo sh -c "podman generate systemd --new --name $CONTAINER_NAME 2>/dev/null > $DOCKERFILE_FOLDER/container-$CONTAINER_NAME.service";\
sudo mv $DOCKERFILE_FOLDER/container-$CONTAINER_NAME.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now container-$CONTAINER_NAME.service;\
fi
# Prevent to route packets outside of the container in case if server behind of the NAT # Prevent to route packets outside of the container in case if server behind of the NAT
#sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up" #sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up"
+3 -3
View File
@@ -3334,8 +3334,8 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="22"/> <location filename="../core/errorstrings.cpp" line="22"/>
<source>The user does not have permission to use sudo</source> <source>The user is not a member of the sudo group</source>
<translation>ليس لدي المستخدم الصلحيات لأستخدام sudo</translation> <translation>المستخدم ليس عضوًا في مجموعة sudo</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="26"/> <location filename="../core/errorstrings.cpp" line="26"/>
@@ -3399,7 +3399,7 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="23"/> <location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source> <source>Server error: Package manager error</source>
<translation>خطأ في الخادم: خطأ في مدير الحزم</translation> <translation>خطأ في الخادم: خطأ في مدير الحزم</translation>
</message> </message>
<message> <message>
+4 -4
View File
@@ -3468,8 +3468,8 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="22"/> <location filename="../core/errorstrings.cpp" line="22"/>
<source>The user does not have permission to use sudo</source> <source>The user is not a member of the sudo group</source>
<translation>The user does not have permission to use sudo</translation> <translation>کاربر عضو گروه sudo نیست</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="26"/> <location filename="../core/errorstrings.cpp" line="26"/>
@@ -3590,8 +3590,8 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="23"/> <location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source> <source>Server error: Package manager error</source>
<translation>Server error: Packet manager error</translation> <translation>خطای سرور: خطای مدیر بسته</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="34"/> <location filename="../core/errorstrings.cpp" line="34"/>
+4 -4
View File
@@ -3434,13 +3434,13 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="22"/> <location filename="../core/errorstrings.cpp" line="22"/>
<source>The user does not have permission to use sudo</source> <source>The user is not a member of the sudo group</source>
<translation> sudo ि </translation> <translation> sudo </translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="23"/> <location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source> <source>Server error: Package manager error</source>
<translation> ि: ि</translation> <translation> ि: ि</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="26"/> <location filename="../core/errorstrings.cpp" line="26"/>
+4 -4
View File
@@ -3330,8 +3330,8 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="22"/> <location filename="../core/errorstrings.cpp" line="22"/>
<source>The user does not have permission to use sudo</source> <source>The user is not a member of the sudo group</source>
<translation> sudo ကက</translation> <translation> sudo </translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="26"/> <location filename="../core/errorstrings.cpp" line="26"/>
@@ -3395,8 +3395,8 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="23"/> <location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source> <source>Server error: Package manager error</source>
<translation> မှု: Packet Manager </translation> <translation> - Package manager </translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="34"/> <location filename="../core/errorstrings.cpp" line="34"/>
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -3700,13 +3700,13 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="22"/> <location filename="../core/errorstrings.cpp" line="22"/>
<source>The user does not have permission to use sudo</source> <source>The user is not a member of the sudo group</source>
<translation>The user does not have permission to use sudo</translation> <translation>Користувач не входить до групи sudo</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="23"/> <location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source> <source>Server error: Package manager error</source>
<translation type="unfinished"></translation> <translation>Помилка сервера: Помилка менеджера пакетів</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="26"/> <location filename="../core/errorstrings.cpp" line="26"/>
+3 -3
View File
@@ -3433,8 +3433,8 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="22"/> <location filename="../core/errorstrings.cpp" line="22"/>
<source>The user does not have permission to use sudo</source> <source>The user is not a member of the sudo group</source>
<translation>صارف کو sudo استعمال کرنے کی اجازت نہیں ہے</translation> <translation>صارف sudo گروپ کا رکن نہیں ہے</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="26"/> <location filename="../core/errorstrings.cpp" line="26"/>
@@ -3498,7 +3498,7 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="23"/> <location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source> <source>Server error: Package manager error</source>
<translation>سرور خطا: پیکیج منیجر خطا</translation> <translation>سرور خطا: پیکیج منیجر خطا</translation>
</message> </message>
<message> <message>
+4 -4
View File
@@ -3675,13 +3675,13 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="22"/> <location filename="../core/errorstrings.cpp" line="22"/>
<source>The user does not have permission to use sudo</source> <source>The user is not a member of the sudo group</source>
<translation>root权限</translation> <translation> sudo </translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="23"/> <location filename="../core/errorstrings.cpp" line="23"/>
<source>Server error: Packet manager error</source> <source>Server error: Package manager error</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="26"/> <location filename="../core/errorstrings.cpp" line="26"/>
@@ -1,7 +1,7 @@
#include "apiConfigsController.h" #include "apiConfigsController.h"
#include <QEventLoop>
#include <QClipboard> #include <QClipboard>
#include <QEventLoop>
#include "amnezia_application.h" #include "amnezia_application.h"
#include "configurators/wireguard_configurator.h" #include "configurators/wireguard_configurator.h"
@@ -251,6 +251,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
newServerConfig.insert(configKey::apiConfig, newApiConfig); newServerConfig.insert(configKey::apiConfig, newApiConfig);
newServerConfig.insert(configKey::authData, authData); newServerConfig.insert(configKey::authData, authData);
// newServerConfig.insert(
m_serversModel->editServer(newServerConfig, serverIndex); m_serversModel->editServer(newServerConfig, serverIndex);
if (reloadServiceConfig) { if (reloadServiceConfig) {
@@ -354,6 +355,43 @@ bool ApiConfigsController::deactivateDevice()
return true; return true;
} }
bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs);
auto serverIndex = m_serversModel->getProcessedServerIndex();
auto serverConfigObject = m_serversModel->getServerConfig(serverIndex);
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
return true;
}
QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString();
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode);
apiPayload[configKey::serverCountryCode] = serverCountryCode;
apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType);
apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData);
apiPayload[configKey::uuid] = uuid;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody);
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
emit errorOccurred(errorCode);
return false;
}
if (uuid == m_settings->getInstallationUuid(true)) {
serverConfigObject.remove(config_key::containers);
m_serversModel->editServer(serverConfigObject, serverIndex);
}
return true;
}
bool ApiConfigsController::isConfigValid() bool ApiConfigsController::isConfigValid()
{ {
int serverIndex = m_serversModel->getDefaultServerIndex(); int serverIndex = m_serversModel->getDefaultServerIndex();
@@ -369,7 +407,7 @@ bool ApiConfigsController::isConfigValid()
return updateServiceFromGateway(serverIndex, "", ""); return updateServiceFromGateway(serverIndex, "", "");
} else if (configSource && m_serversModel->isApiKeyExpired(serverIndex)) { } else if (configSource && m_serversModel->isApiKeyExpired(serverIndex)) {
qDebug() << "attempt to update api config by expires_at event"; qDebug() << "attempt to update api config by expires_at event";
if (configSource == apiDefs::ConfigSource::Telegram) { if (configSource == apiDefs::ConfigSource::AmneziaGateway) {
return updateServiceFromGateway(serverIndex, "", ""); return updateServiceFromGateway(serverIndex, "", "");
} else { } else {
m_serversModel->removeApiConfig(serverIndex); m_serversModel->removeApiConfig(serverIndex);
@@ -31,6 +31,7 @@ public slots:
bool reloadServiceConfig = false); bool reloadServiceConfig = false);
bool updateServiceFromTelegram(const int serverIndex); bool updateServiceFromTelegram(const int serverIndex);
bool deactivateDevice(); bool deactivateDevice();
bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode);
bool isConfigValid(); bool isConfigValid();
@@ -25,11 +25,13 @@ namespace
ApiSettingsController::ApiSettingsController(const QSharedPointer<ServersModel> &serversModel, ApiSettingsController::ApiSettingsController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel, const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
const QSharedPointer<ApiCountryModel> &apiCountryModel, const QSharedPointer<ApiCountryModel> &apiCountryModel,
const QSharedPointer<ApiDevicesModel> &apiDevicesModel,
const std::shared_ptr<Settings> &settings, QObject *parent) const std::shared_ptr<Settings> &settings, QObject *parent)
: QObject(parent), : QObject(parent),
m_serversModel(serversModel), m_serversModel(serversModel),
m_apiAccountInfoModel(apiAccountInfoModel), m_apiAccountInfoModel(apiAccountInfoModel),
m_apiCountryModel(apiCountryModel), m_apiCountryModel(apiCountryModel),
m_apiDevicesModel(apiDevicesModel),
m_settings(settings) m_settings(settings)
{ {
} }
@@ -73,6 +75,7 @@ bool ApiSettingsController::getAccountInfo(bool reload)
if (reload) { if (reload) {
updateApiCountryModel(); updateApiCountryModel();
updateApiDevicesModel();
} }
return true; return true;
@@ -83,3 +86,8 @@ void ApiSettingsController::updateApiCountryModel()
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), ""); m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");
m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo()); m_apiCountryModel->updateIssuedConfigsInfo(m_apiAccountInfoModel->getIssuedConfigsInfo());
} }
void ApiSettingsController::updateApiDevicesModel()
{
m_apiDevicesModel->updateModel(m_apiAccountInfoModel->getIssuedConfigsInfo());
}
@@ -5,6 +5,7 @@
#include "ui/models/api/apiAccountInfoModel.h" #include "ui/models/api/apiAccountInfoModel.h"
#include "ui/models/api/apiCountryModel.h" #include "ui/models/api/apiCountryModel.h"
#include "ui/models/api/apiDevicesModel.h"
#include "ui/models/servers_model.h" #include "ui/models/servers_model.h"
class ApiSettingsController : public QObject class ApiSettingsController : public QObject
@@ -12,13 +13,14 @@ class ApiSettingsController : public QObject
Q_OBJECT Q_OBJECT
public: public:
ApiSettingsController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel, ApiSettingsController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ApiAccountInfoModel> &apiAccountInfoModel,
const QSharedPointer<ApiCountryModel> &apiCountryModel, const std::shared_ptr<Settings> &settings, const QSharedPointer<ApiCountryModel> &apiCountryModel, const QSharedPointer<ApiDevicesModel> &apiDevicesModel,
QObject *parent = nullptr); const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
~ApiSettingsController(); ~ApiSettingsController();
public slots: public slots:
bool getAccountInfo(bool reload); bool getAccountInfo(bool reload);
void updateApiCountryModel(); void updateApiCountryModel();
void updateApiDevicesModel();
signals: signals:
void errorOccurred(ErrorCode errorCode); void errorOccurred(ErrorCode errorCode);
@@ -27,6 +29,7 @@ private:
QSharedPointer<ServersModel> m_serversModel; QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel; QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
QSharedPointer<ApiCountryModel> m_apiCountryModel; QSharedPointer<ApiCountryModel> m_apiCountryModel;
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;
}; };
+4 -7
View File
@@ -27,8 +27,6 @@ namespace
ConfigTypes checkConfigFormat(const QString &config) ConfigTypes checkConfigFormat(const QString &config)
{ {
const QString openVpnConfigPatternCli = "client"; const QString openVpnConfigPatternCli = "client";
const QString openVpnConfigPatternProto1 = "proto tcp";
const QString openVpnConfigPatternProto2 = "proto udp";
const QString openVpnConfigPatternDriver1 = "dev tun"; const QString openVpnConfigPatternDriver1 = "dev tun";
const QString openVpnConfigPatternDriver2 = "dev tap"; const QString openVpnConfigPatternDriver2 = "dev tap";
@@ -53,14 +51,13 @@ namespace
|| (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName) || (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
&& config.contains(amneziaConfigPatternPassword))) { && config.contains(amneziaConfigPatternPassword))) {
return ConfigTypes::Amnezia; return ConfigTypes::Amnezia;
} else if (config.contains(openVpnConfigPatternCli)
&& (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2))
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
return ConfigTypes::OpenVpn;
} else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) { } else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) {
return ConfigTypes::WireGuard; return ConfigTypes::WireGuard;
} else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) { } else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) {
return ConfigTypes::Xray; return ConfigTypes::Xray;
} else if (config.contains(openVpnConfigPatternCli)
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
return ConfigTypes::OpenVpn;
} }
return ConfigTypes::Invalid; return ConfigTypes::Invalid;
} }
@@ -345,7 +342,7 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data)
arr.push_back(containers); arr.push_back(containers);
QString hostName; QString hostName;
const static QRegularExpression hostNameRegExp("remote (.*) [0-9]*"); const static QRegularExpression hostNameRegExp("remote\\s+([^\\s]+)");
QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data); QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data);
if (hostNameMatch.hasMatch()) { if (hostNameMatch.hasMatch()) {
hostName = hostNameMatch.captured(1); hostName = hostNameMatch.captured(1);
+1
View File
@@ -36,6 +36,7 @@ namespace PageLoader
PageSettingsApiSupport, PageSettingsApiSupport,
PageSettingsApiInstructions, PageSettingsApiInstructions,
PageSettingsApiNativeConfigs, PageSettingsApiNativeConfigs,
PageSettingsApiDevices,
PageServiceSftpSettings, PageServiceSftpSettings,
PageServiceTorWebsiteSettings, PageServiceTorWebsiteSettings,
@@ -44,7 +44,6 @@ void SitesController::addSite(QString hostname)
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
Q_ARG(QStringList, QStringList() << hostname)); Q_ARG(QStringList, QStringList() << hostname));
} }
QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection);
}; };
const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) { const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) {
@@ -75,7 +74,6 @@ void SitesController::removeSite(int index)
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection, QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection,
Q_ARG(QStringList, QStringList() << hostname)); Q_ARG(QStringList, QStringList() << hostname));
QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection);
emit finished(tr("Site removed: %1").arg(hostname)); emit finished(tr("Site removed: %1").arg(hostname));
} }
@@ -124,7 +122,6 @@ void SitesController::importSites(const QString &fileName, bool replaceExisting)
m_sitesModel->addSites(sites, replaceExisting); m_sitesModel->addSites(sites, replaceExisting);
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips)); QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips));
QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection);
emit finished(tr("Import completed")); emit finished(tr("Import completed"));
} }
+16 -2
View File
@@ -48,8 +48,8 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
} }
case ServiceDescriptionRole: { case ServiceDescriptionRole: {
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) { if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) {
return tr("Classic VPN for comfortable work, downloading large files and watching videos. Works for any sites. Speed up to 200 " return tr("Classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online resources. "
"Mb/s"); "Speeds up to 200 Mbps");
} else if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) { } else if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
return tr("Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and " return tr("Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and "
"more. YouTube is not included in the free plan."); "more. YouTube is not included in the free plan.");
@@ -58,6 +58,19 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
case IsComponentVisibleRole: { case IsComponentVisibleRole: {
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2; return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2;
} }
case HasExpiredWorkerRole: {
for (int i = 0; i < m_issuedConfigsInfo.size(); i++) {
QJsonObject issuedConfigObject = m_issuedConfigsInfo.at(i).toObject();
auto lastDownloaded = QDateTime::fromString(issuedConfigObject.value(apiDefs::key::lastDownloaded).toString());
auto workerLastUpdated = QDateTime::fromString(issuedConfigObject.value(apiDefs::key::workerLastUpdated).toString());
if (lastDownloaded < workerLastUpdated) {
return true;
}
}
return false;
}
} }
return QVariant(); return QVariant();
@@ -124,6 +137,7 @@ QHash<int, QByteArray> ApiAccountInfoModel::roleNames() const
roles[ConnectedDevicesRole] = "connectedDevices"; roles[ConnectedDevicesRole] = "connectedDevices";
roles[ServiceDescriptionRole] = "serviceDescription"; roles[ServiceDescriptionRole] = "serviceDescription";
roles[IsComponentVisibleRole] = "isComponentVisible"; roles[IsComponentVisibleRole] = "isComponentVisible";
roles[HasExpiredWorkerRole] = "hasExpiredWorker";
return roles; return roles;
} }
+2 -1
View File
@@ -17,7 +17,8 @@ public:
ConnectedDevicesRole, ConnectedDevicesRole,
ServiceDescriptionRole, ServiceDescriptionRole,
EndDateRole, EndDateRole,
IsComponentVisibleRole IsComponentVisibleRole,
HasExpiredWorkerRole
}; };
explicit ApiAccountInfoModel(QObject *parent = nullptr); explicit ApiAccountInfoModel(QObject *parent = nullptr);
+4
View File
@@ -44,6 +44,9 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const
case IsIssuedRole: { case IsIssuedRole: {
return isIssued; return isIssued;
} }
case IsWorkerExpiredRole: {
return issuedConfigInfo.lastDownloaded < issuedConfigInfo.workerLastUpdated;
}
} }
return QVariant(); return QVariant();
@@ -114,5 +117,6 @@ QHash<int, QByteArray> ApiCountryModel::roleNames() const
roles[CountryCodeRole] = "countryCode"; roles[CountryCodeRole] = "countryCode";
roles[CountryImageCodeRole] = "countryImageCode"; roles[CountryImageCodeRole] = "countryImageCode";
roles[IsIssuedRole] = "isIssued"; roles[IsIssuedRole] = "isIssued";
roles[IsWorkerExpiredRole] = "isWorkerExpired";
return roles; return roles;
} }
+2 -1
View File
@@ -14,7 +14,8 @@ public:
CountryNameRole = Qt::UserRole + 1, CountryNameRole = Qt::UserRole + 1,
CountryCodeRole, CountryCodeRole,
CountryImageCodeRole, CountryImageCodeRole,
IsIssuedRole IsIssuedRole,
IsWorkerExpiredRole
}; };
explicit ApiCountryModel(QObject *parent = nullptr); explicit ApiCountryModel(QObject *parent = nullptr);
+90
View File
@@ -0,0 +1,90 @@
#include "apiDevicesModel.h"
#include <QJsonObject>
#include "core/api/apiDefs.h"
#include "logger.h"
namespace
{
Logger logger("ApiDevicesModel");
constexpr QLatin1String gatewayAccount("gateway_account");
}
ApiDevicesModel::ApiDevicesModel(std::shared_ptr<Settings> settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent)
{
}
int ApiDevicesModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_issuedConfigs.size();
}
QVariant ApiDevicesModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount()))
return QVariant();
IssuedConfigInfo issuedConfigInfo = m_issuedConfigs.at(index.row());
switch (role) {
case OsVersionRole: {
return issuedConfigInfo.osVersion;
}
case SupportTagRole: {
return issuedConfigInfo.installationUuid;
}
case CountryCodeRole: {
return issuedConfigInfo.countryCode;
}
case LastUpdateRole: {
return QDateTime::fromString(issuedConfigInfo.lastDownloaded, Qt::ISODate).toLocalTime().toString("d MMM yyyy");
}
case IsCurrentDeviceRole: {
return issuedConfigInfo.installationUuid == m_settings->getInstallationUuid(false);
}
}
return QVariant();
}
void ApiDevicesModel::updateModel(const QJsonArray &issuedConfigs)
{
beginResetModel();
m_issuedConfigs.clear();
for (int i = 0; i < issuedConfigs.size(); i++) {
IssuedConfigInfo issuedConfigInfo;
QJsonObject issuedConfigObject = issuedConfigs.at(i).toObject();
if (issuedConfigObject.value(apiDefs::key::sourceType).toString() != gatewayAccount) {
continue;
}
issuedConfigInfo.installationUuid = issuedConfigObject.value(apiDefs::key::installationUuid).toString();
issuedConfigInfo.workerLastUpdated = issuedConfigObject.value(apiDefs::key::workerLastUpdated).toString();
issuedConfigInfo.lastDownloaded = issuedConfigObject.value(apiDefs::key::lastDownloaded).toString();
issuedConfigInfo.sourceType = issuedConfigObject.value(apiDefs::key::sourceType).toString();
issuedConfigInfo.osVersion = issuedConfigObject.value(apiDefs::key::osVersion).toString();
issuedConfigInfo.countryName = issuedConfigObject.value(apiDefs::key::serverCountryName).toString();
issuedConfigInfo.countryCode = issuedConfigObject.value(apiDefs::key::serverCountryCode).toString();
m_issuedConfigs.push_back(issuedConfigInfo);
}
endResetModel();
}
QHash<int, QByteArray> ApiDevicesModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[OsVersionRole] = "osVersion";
roles[SupportTagRole] = "supportTag";
roles[CountryCodeRole] = "countryCode";
roles[LastUpdateRole] = "lastUpdate";
roles[IsCurrentDeviceRole] = "isCurrentDevice";
return roles;
}
+52
View File
@@ -0,0 +1,52 @@
#ifndef APIDEVICESMODEL_H
#define APIDEVICESMODEL_H
#include <QAbstractListModel>
#include <QJsonArray>
#include <QVector>
#include "settings.h"
class ApiDevicesModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
OsVersionRole = Qt::UserRole + 1,
SupportTagRole,
CountryCodeRole,
LastUpdateRole,
IsCurrentDeviceRole
};
explicit ApiDevicesModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
public slots:
void updateModel(const QJsonArray &issuedConfigs);
protected:
QHash<int, QByteArray> roleNames() const override;
private:
struct IssuedConfigInfo
{
QString installationUuid;
QString workerLastUpdated;
QString lastDownloaded;
QString sourceType;
QString osVersion;
QString countryName;
QString countryCode;
};
QVector<IssuedConfigInfo> m_issuedConfigs;
std::shared_ptr<Settings> m_settings;
};
#endif // APIDEVICESMODEL_H
+4 -4
View File
@@ -65,8 +65,8 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
case CardDescriptionRole: { case CardDescriptionRole: {
auto speed = apiServiceData.serviceInfo.speed; auto speed = apiServiceData.serviceInfo.speed;
if (serviceType == serviceType::amneziaPremium) { if (serviceType == serviceType::amneziaPremium) {
return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. " return tr("Amnezia Premium is classic VPN for seamless work, downloading large files, and watching videos. "
"Works for any sites with no restrictions. Speed up to %1 MBit/s. Unlimited traffic.") "Access all websites and online resources. Speeds up to %1 Mbps.")
.arg(speed); .arg(speed);
} else if (serviceType == serviceType::amneziaFree) { } else if (serviceType == serviceType::amneziaFree) {
QString description = tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan."); QString description = tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
@@ -79,8 +79,8 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
} }
case ServiceDescriptionRole: { case ServiceDescriptionRole: {
if (serviceType == serviceType::amneziaPremium) { if (serviceType == serviceType::amneziaPremium) {
return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. " return tr("Amnezia Premium is classic VPN for for seamless work, downloading large files, and watching videos. "
"Works for any sites with no restrictions."); "Access all websites and online resources.");
} else { } else {
return tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan."); return tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
} }
+1 -1
View File
@@ -54,7 +54,7 @@ Rectangle {
Layout.rightMargin: 10 Layout.rightMargin: 10
Layout.leftMargin: 10 Layout.leftMargin: 10
text: qsTr("Amnezia Premium - for access to any website") text: qsTr("Amnezia Premium - for access to all websites and online resources")
color: AmneziaStyle.color.pearlGray color: AmneziaStyle.color.pearlGray
lineHeight: 18 lineHeight: 18
@@ -27,5 +27,6 @@ QtObject {
readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8) readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8)
readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65) readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
readonly property color pearlGray: '#EAEAEC' readonly property color pearlGray: '#EAEAEC'
readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26)
} }
} }
@@ -81,7 +81,7 @@ PageType {
actionButtonImage: "qrc:/images/controls/settings.svg" actionButtonImage: "qrc:/images/controls/settings.svg"
headerText: root.processedServer.name headerText: root.processedServer.name
descriptionText: qsTr("Locations for connection") descriptionText: qsTr("Location for connection")
actionButtonFunction: function() { actionButtonFunction: function() {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
@@ -0,0 +1,105 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import QtCore
import SortFilterProxyModel 0.2
import PageEnum 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
ListViewType {
id: listView
anchors.fill: parent
anchors.topMargin: 20
anchors.bottomMargin: 24
model: ApiDevicesModel
header: ColumnLayout {
width: listView.width
BackButtonType {
id: backButton
}
HeaderType {
id: header
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Active Devices")
descriptionText: qsTr("Manage currently connected devices")
}
WarningType {
Layout.topMargin: 16
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.fillWidth: true
textString: qsTr("You can find the identifier on the Support tab or, for older versions of the app, "
+ "by tapping '+' and then the three dots at the top of the page.")
iconPath: "qrc:/images/controls/alert-circle.svg"
}
}
delegate: ColumnLayout {
width: listView.width
LabelWithButtonType {
Layout.fillWidth: true
Layout.topMargin: 6
text: osVersion + (isCurrentDevice ? qsTr(" (current device)") : "")
descriptionText: qsTr("Support tag: ") + "\n" + supportTag + "\n" + qsTr("Last updated: ") + lastUpdate
rightImageSource: "qrc:/images/controls/trash.svg"
clickedFunction: function() {
if (isCurrentDevice && ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Cannot unlink device during active connection"))
return
}
var headerText = qsTr("Are you sure you want to unlink this device?")
var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect.")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
Qt.callLater(deactivateExternalDevice, supportTag, countryCode)
}
var noButtonFunction = function() {
}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
}
DividerType {}
}
}
function deactivateExternalDevice(supportTag, countryCode) {
PageController.showBusyIndicator(true)
if (ApiConfigsController.deactivateExternalDevice(supportTag, countryCode)) {
ApiSettingsController.getAccountInfo(true)
}
PageController.showBusyIndicator(false)
}
}
@@ -99,7 +99,7 @@ PageType {
Layout.leftMargin: 16 Layout.leftMargin: 16
headerText: qsTr("How to connect on another device") headerText: qsTr("How to connect on another device")
descriptionText: qsTr("Instructions on the Amnezia website") descriptionText: qsTr("Setup guides on the Amnezia website")
} }
} }
@@ -107,7 +107,6 @@ PageType {
width: listView.width width: listView.width
LabelWithButtonType { LabelWithButtonType {
id: telegramButton
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 6 Layout.topMargin: 6
@@ -45,8 +45,8 @@ PageType {
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
headerText: qsTr("Configuration files") headerText: qsTr("Configuration Files")
descriptionText: qsTr("To connect a router or AmneziaWG application") descriptionText: qsTr("For router setup or the AmneziaWG app")
} }
} }
@@ -58,6 +58,9 @@ PageType {
Layout.topMargin: 6 Layout.topMargin: 6
text: countryName text: countryName
descriptionText: isWorkerExpired ? qsTr("The configuration needs to be reissued") : ""
descriptionColor: AmneziaStyle.color.vibrantRed
leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg" rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg"
@@ -120,13 +123,13 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 16 Layout.margins: 16
headerText: qsTr("Configuration file ") + moreOptionsDrawer.countryName headerText: moreOptionsDrawer.countryName + qsTr(" configuration file")
} }
LabelWithButtonType { LabelWithButtonType {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Create a new") text: qsTr("Generate a new configuration file")
descriptionText: qsTr("The previously created one will stop working") descriptionText: qsTr("The previously created one will stop working")
clickedFunction: function() { clickedFunction: function() {
@@ -190,9 +193,15 @@ PageType {
} }
function showQuestion(isConfigIssue, countryCode, countryName) { function showQuestion(isConfigIssue, countryCode, countryName) {
var headerText = qsTr("Revoke the actual %1 configuration file?").arg(countryName) var headerText
var descriptionText = qsTr("The previously created file will no longer be valid. It will not be possible to connect using it.") if (isConfigIssue) {
var yesButtonText = qsTr("Continue") headerText = qsTr("Generate a new %1 configuration file?").arg(countryName)
} else {
headerText = qsTr("Revoke the current %1 configuration file?").arg(countryName)
}
var descriptionText = qsTr("Your previous configuration file will no longer work, and it will not be possible to connect using it")
var yesButtonText = isConfigIssue ? qsTr("Download") : qsTr("Continue")
var noButtonText = qsTr("Cancel") var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() { var yesButtonFunction = function() {
@@ -26,15 +26,15 @@ PageType {
QtObject { QtObject {
id: statusObject id: statusObject
readonly property string title: qsTr("Subscription status") readonly property string title: qsTr("Subscription Status")
readonly property string contentKey: "subscriptionStatus" readonly property string contentKey: "subscriptionStatus"
readonly property string objectImageSource: "qrc:/images/controls/map-pin.svg" readonly property string objectImageSource: "qrc:/images/controls/info.svg"
} }
QtObject { QtObject {
id: endDateObject id: endDateObject
readonly property string title: qsTr("Valid until") readonly property string title: qsTr("Valid Until")
readonly property string contentKey: "endDate" readonly property string contentKey: "endDate"
readonly property string objectImageSource: "qrc:/images/controls/history.svg" readonly property string objectImageSource: "qrc:/images/controls/history.svg"
} }
@@ -42,9 +42,9 @@ PageType {
QtObject { QtObject {
id: deviceCountObject id: deviceCountObject
readonly property string title: qsTr("Connected devices") readonly property string title: qsTr("Active Connections")
readonly property string contentKey: "connectedDevices" readonly property string contentKey: "connectedDevices"
readonly property string objectImageSource: "qrc:/images/controls/gauge.svg" readonly property string objectImageSource: "qrc:/images/controls/monitor.svg"
} }
property var processedServer property var processedServer
@@ -158,15 +158,32 @@ PageType {
readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible") readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible")
WarningType {
id: warning
Layout.topMargin: 32
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.fillWidth: true
backGroundColor: AmneziaStyle.color.translucentRichBrown
textString: qsTr("Configurations have been updated for some countries. Download and install the updated configuration files")
iconPath: "qrc:/images/controls/alert-circle.svg"
visible: ApiAccountInfoModel.data("hasExpiredWorker")
}
LabelWithButtonType { LabelWithButtonType {
id: vpnKey id: vpnKey
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 32 Layout.topMargin: warning.visible ? 16 : 32
visible: false //footer.isVisibleForAmneziaFree visible: false //footer.isVisibleForAmneziaFree
text: qsTr("Subscription key") text: qsTr("Subscription Key")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
@@ -174,7 +191,7 @@ PageType {
shareConnectionDrawer.openTriggered() shareConnectionDrawer.openTriggered()
shareConnectionDrawer.isSelfHostedConfig = false; shareConnectionDrawer.isSelfHostedConfig = false;
shareConnectionDrawer.shareButtonText = qsTr("Save VPN key to file") shareConnectionDrawer.shareButtonText = qsTr("Save VPN key as a file")
shareConnectionDrawer.copyButtonText = qsTr("Copy VPN key") shareConnectionDrawer.copyButtonText = qsTr("Copy VPN key")
@@ -192,13 +209,13 @@ PageType {
LabelWithButtonType { LabelWithButtonType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 32 Layout.topMargin: warning.visible ? 16 : 32
visible: footer.isVisibleForAmneziaFree visible: footer.isVisibleForAmneziaFree
text: qsTr("Configuration files") text: qsTr("Configuration Files")
descriptionText: qsTr("To connect a router or AmneziaWG application") descriptionText: qsTr("Manage configuration files")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
@@ -211,6 +228,26 @@ PageType {
visible: footer.isVisibleForAmneziaFree visible: footer.isVisibleForAmneziaFree
} }
LabelWithButtonType {
Layout.fillWidth: true
visible: footer.isVisibleForAmneziaFree
text: qsTr("Active Devices")
descriptionText: qsTr("Manage currently connected devices")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
ApiSettingsController.updateApiDevicesModel()
PageController.goToPage(PageEnum.PageSettingsApiDevices)
}
}
DividerType {
visible: footer.isVisibleForAmneziaFree
}
LabelWithButtonType { LabelWithButtonType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: footer.isVisibleForAmneziaFree ? 0 : 32 Layout.topMargin: footer.isVisibleForAmneziaFree ? 0 : 32
@@ -228,6 +265,8 @@ PageType {
LabelWithButtonType { LabelWithButtonType {
Layout.fillWidth: true Layout.fillWidth: true
visible: footer.isVisibleForAmneziaFree
text: qsTr("How to connect on another device") text: qsTr("How to connect on another device")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
@@ -236,7 +275,9 @@ PageType {
} }
} }
DividerType {} DividerType {
visible: footer.isVisibleForAmneziaFree
}
BasicButtonType { BasicButtonType {
id: resetButton id: resetButton
@@ -288,16 +329,17 @@ PageType {
pressedColor: AmneziaStyle.color.sheerWhite pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.vibrantRed textColor: AmneziaStyle.color.vibrantRed
text: qsTr("Deactivate the subscription on this device") text: qsTr("Unlink this device")
clickedFunc: function() { clickedFunc: function() {
var headerText = qsTr("Deactivate the subscription on this device?") var headerText = qsTr("Are you sure you want to unlink this device?")
var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect.")
var yesButtonText = qsTr("Continue") var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel") var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() { var yesButtonFunction = function() {
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("The next time the “Connect” button is pressed, the device will be activated again")) PageController.showNotificationMessage(qsTr("Cannot unlink device during active connection"))
} else { } else {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
if (ApiConfigsController.deactivateDevice()) { if (ApiConfigsController.deactivateDevice()) {
@@ -309,7 +351,7 @@ PageType {
var noButtonFunction = function() { var noButtonFunction = function() {
} }
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
} }
} }
+82 -64
View File
@@ -16,92 +16,110 @@ import "../Components"
PageType { PageType {
id: root id: root
ColumnLayout { QtObject {
id: backButtonLayout id: telegram
anchors.top: parent.top readonly property string title: qsTr("Telegram")
anchors.left: parent.left readonly property string description: "@" + ApiAccountInfoModel.getTelegramBotLink()
anchors.right: parent.right readonly property string link: "https://t.me/" + ApiAccountInfoModel.getTelegramBotLink()
}
QtObject {
id: techSupport
readonly property string title: qsTr("Email")
readonly property string description: qsTr("support@amnezia.org")
readonly property string link: "mailto:support@amnezia.org"
}
QtObject {
id: paymentSupport
readonly property string title: qsTr("Email Billing & Orders")
readonly property string description: qsTr("help@vpnpay.io")
readonly property string link: "mailto:help@vpnpay.io"
}
QtObject {
id: site
readonly property string title: qsTr("Website")
readonly property string description: qsTr("amnezia.org")
readonly property string link: LanguageModel.getCurrentSiteUrl()
}
property list<QtObject> supportModel: [
telegram,
techSupport,
paymentSupport,
site
]
ListViewType {
id: listView
anchors.fill: parent
anchors.topMargin: 20 anchors.topMargin: 20
anchors.bottomMargin: 24
BackButtonType { model: supportModel
id: backButton
}
header: ColumnLayout {
width: listView.width
HeaderType { BackButtonType {
id: header id: backButton
}
Layout.fillWidth: true HeaderType {
Layout.rightMargin: 16 id: header
Layout.leftMargin: 16
headerText: qsTr("Support") Layout.fillWidth: true
descriptionText: qsTr("Our technical support specialists are ready to help you at any time") Layout.rightMargin: 16
} Layout.leftMargin: 16
LabelWithButtonType { headerText: qsTr("Support")
Layout.fillWidth: true descriptionText: qsTr("Our technical support specialists are available to assist you at any time")
readonly property string telegramBotLink: ApiAccountInfoModel.getTelegramBotLink()
text: qsTr("Telegram")
descriptionText: "@" + telegramBotLink
rightImageSource: "qrc:/images/controls/external-link.svg"
clickedFunction: function() {
Qt.openUrlExternally("https://t.me/" + telegramBotLink)
} }
} }
DividerType {} delegate: ColumnLayout {
width: listView.width
LabelWithButtonType { LabelWithButtonType {
Layout.fillWidth: true Layout.fillWidth: true
text: title
text: qsTr("Mail") descriptionText: description
descriptionText: qsTr("support@amnezia.org") rightImageSource: "qrc:/images/controls/external-link.svg"
rightImageSource: "qrc:/images/controls/external-link.svg" clickedFunction: function() {
Qt.openUrlExternally(link)
clickedFunction: function() { }
Qt.openUrlExternally(qsTr("mailto:support@amnezia.org"))
} }
DividerType {}
} }
DividerType {}
LabelWithButtonType { footer: ColumnLayout {
Layout.fillWidth: true width: listView.width
text: qsTr("Site") LabelWithButtonType {
descriptionText: qsTr("amnezia.org") id: supportUuid
rightImageSource: "qrc:/images/controls/external-link.svg" Layout.fillWidth: true
clickedFunction: function() { text: qsTr("Support tag")
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl()) descriptionText: SettingsController.getInstallationUuid()
}
}
DividerType {} descriptionOnTop: true
LabelWithButtonType { rightImageSource: "qrc:/images/controls/copy.svg"
id: supportUuid rightImageColor: AmneziaStyle.color.paleGray
Layout.fillWidth: true
text: qsTr("Support tag") clickedFunction: function() {
descriptionText: SettingsController.getInstallationUuid() GC.copyToClipBoard(descriptionText)
PageController.showNotificationMessage(qsTr("Copied"))
descriptionOnTop: true if (!GC.isMobile()) {
this.rightButton.forceActiveFocus()
rightImageSource: "qrc:/images/controls/copy.svg" }
rightImageColor: AmneziaStyle.color.paleGray
clickedFunction: function() {
GC.copyToClipBoard(descriptionText)
PageController.showNotificationMessage(qsTr("Copied"))
if (!GC.isMobile()) {
this.rightButton.forceActiveFocus()
} }
} }
} }
@@ -140,7 +140,7 @@ PageType {
} }
onClicked: { onClicked: {
if (!checkable) { if (!checkable) {
PageController.showNotificationMessage(qsTr("Cannot change killSwitch settings during active connection")) PageController.showNotificationMessage(qsTr("Cannot change KillSwitch settings during active connection"))
} }
} }
} }