Compare commits

...

11 Commits

Author SHA1 Message Date
irvinklause 2acf199755 fix: temp change of deploy_to_s3 cript location 2026-02-04 09:24:17 +00:00
ik a983d0504e fix: add checks for script components to find out where it can fall (#2169) 2026-01-30 14:43:30 +08:00
vkamn d0b8535395 fix: update tag deploy (#2168) 2026-01-30 13:15:50 +08:00
dpamnezia f84480cf56 chore: fix artifacts upload (#1961) 2026-01-30 12:43:21 +08:00
MrMirDan de7a026ec1 fix: change drawer parents interactivity (#2004)
* fix: change drawer parents interactivity

* update: better vars names
2026-01-30 12:42:53 +08:00
MrMirDan a128c7d247 fix: keyboard navigation (#2023)
* fix: self-hosted easy install card

* fix: label double click when enter/return pressed
2026-01-30 12:42:29 +08:00
MrMirDan f316f0e25a feat: news notifications switch (#2126)
* feat: news notifications switch

* update: text changes

* fix: notifications enabled by default
2026-01-30 12:19:50 +08:00
NickVs2015 ea5242e29b fix: fixed cipher selection (#2110) 2026-01-30 12:18:54 +08:00
NickVs2015 b31a62c55f feat: add support open files by atv (#2082) 2026-01-30 12:11:26 +08:00
yyy-amnezia 02e3107a23 feat: implement service kickstart and improve macos post install script (#2131) 2026-01-30 12:05:20 +08:00
lunardunno 1862850108 feat: checking linux kernel version when installing amneziawg-go (#2098)
* Checking Linux kernel version when installing amneziawg-go

print the Linux kernel version to stdOut for subsequent checking by the server controller.

* Add error for old linux kernel

Add error 214 ServerLinuxKernelTooOld

* Add case for old linux kernel

Add case for error 214 ServerLinuxKernelTooOld

* Added kernel check for Awg2

Added Linux kernel version check and introduced corresponding ServerLinuxKernelTooOld error for Awg2.
2026-01-30 12:04:27 +08:00
22 changed files with 293 additions and 82 deletions
+31 -8
View File
@@ -12,19 +12,16 @@ jobs:
Upload-S3:
runs-on: ubuntu-latest
steps:
- name: Checkout
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.RELEASE_VERSION }}
sparse-checkout: |
CMakeLists.txt
deploy/deploy_s3.sh
sparse-checkout-cone-mode: false
fetch-depth: 0 # Fetch full history to access default branch
- name: Verify git tag
- name: Checkout tag and verify version
run: |
git checkout ${{ inputs.RELEASE_VERSION }}
TAG_NAME=${{ inputs.RELEASE_VERSION }}
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
CMAKE_TAG=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/')
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
else
@@ -32,6 +29,32 @@ jobs:
exit 1
fi
- name: Get deployment script from default branch
run: |
# Detect default branch (dev, main, or master)
DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "")
if [[ -z "$DEFAULT_BRANCH" ]]; then
# Try to detect default branch (prioritize dev)
if git show-ref --verify --quiet refs/remotes/origin/dev; then
DEFAULT_BRANCH="dev"
elif git show-ref --verify --quiet refs/remotes/origin/main; then
DEFAULT_BRANCH="main"
elif git show-ref --verify --quiet refs/remotes/origin/master; then
DEFAULT_BRANCH="master"
else
echo "::error::Could not determine default branch"
exit 1
fi
fi
echo "Default branch: $DEFAULT_BRANCH"
# Get the script from default branch
mkdir -p deploy
git show origin/$DEFAULT_BRANCH:deploy/deploy_s3.sh > deploy/deploy_s3.sh || \
(echo "::error::Could not fetch deploy_s3.sh from default branch ($DEFAULT_BRANCH)" && exit 1)
chmod +x deploy/deploy_s3.sh
echo "✓ Using deployment script from $DEFAULT_BRANCH branch"
- name: Setup Rclone
uses: AnimMouse/setup-rclone@v1
with:
@@ -1,7 +1,10 @@
package org.amnezia.vpn
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
@@ -11,7 +14,25 @@ private const val TAG = "TvFilePicker"
class TvFilePicker : ComponentActivity() {
private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {
private val fileChooseResultLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
override fun createIntent(context: Context, input: Array<String>): Intent {
val intent = super.createIntent(context, input)
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
} else {
@Suppress("DEPRECATION")
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
}
if (activitiesToResolveIntent.all {
val name = it.activityInfo.packageName
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
}) {
throw ActivityNotFoundException()
}
return intent
}
}) {
setResult(RESULT_OK, Intent().apply { data = it })
finish()
}
@@ -31,7 +52,7 @@ class TvFilePicker : ComponentActivity() {
private fun getFile() {
try {
Log.v(TAG, "getFile")
fileChooseResultLauncher.launch("*/*")
fileChooseResultLauncher.launch(arrayOf("*/*"))
} catch (_: ActivityNotFoundException) {
Log.w(TAG, "Activity not found")
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
@@ -419,6 +419,18 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
cbReadStdOut, cbReadStdErr);
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
if (container == DockerContainer::Awg2) {
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
QRegularExpressionMatch match = regex.match(stdOut);
if (match.hasMatch()) {
int majorVersion = match.captured(1).toInt();
int minorVersion = match.captured(2).toInt();
if (majorVersion < 4 || (majorVersion == 4 && minorVersion < 14)) {
return ErrorCode::ServerLinuxKernelTooOld;
}
}
}
if (stdOut.contains("lock"))
return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("command not found"))
+1
View File
@@ -61,6 +61,7 @@ namespace amnezia
ServerDockerOnCgroupsV2 = 211,
ServerCgroupMountpoint = 212,
DockerPullRateLimit = 213,
ServerLinuxKernelTooOld = 214,
// Ssh connection errors
SshRequestDeniedError = 300,
+1
View File
@@ -29,6 +29,7 @@ QString errorString(ErrorCode code) {
case(ErrorCode::ServerDockerOnCgroupsV2): errorMessage = QObject::tr("Docker error: runc doesn't work on cgroups v2"); break;
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
case(ErrorCode::ServerLinuxKernelTooOld): errorMessage = QObject::tr("Server error: Linux kernel is too old"); break;
// Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
+2 -1
View File
@@ -21,4 +21,5 @@ if [ "$(systemctl is-active docker)" != "active" ]; then \
sleep 5; sudo systemctl start docker; sleep 5;\
fi;\
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; fi;\
docker --version
docker --version;\
uname -sr
+9
View File
@@ -94,6 +94,15 @@ public:
setValue("Conf/startMinimized", enabled);
}
bool isNewsNotifications() const
{
return value("Conf/newsNotifications", true).toBool();
}
void setNewsNotifications(bool enabled)
{
setValue("Conf/newsNotifications", enabled);
}
bool isSaveLogs() const
{
return value("Conf/saveLogs", false).toBool();
@@ -308,6 +308,15 @@ void SettingsController::toggleStartMinimized(bool enable)
emit startMinimizedChanged();
}
bool SettingsController::isNewsNotificationsEnabled()
{
return m_settings->isNewsNotifications();
}
void SettingsController::toggleNewsNotificationsEnabled(bool enable)
{
m_settings->setNewsNotifications(enable);
}
bool SettingsController::isScreenshotsEnabled()
{
return m_settings->isScreenshotsEnabled();
@@ -73,6 +73,9 @@ public slots:
bool isStartMinimizedEnabled();
void toggleStartMinimized(bool enable);
bool isNewsNotificationsEnabled();
void toggleNewsNotificationsEnabled(bool enable);
bool isScreenshotsEnabled();
void toggleScreenshotsEnabled(bool enable);
+34
View File
@@ -49,6 +49,36 @@ Item {
return drawerContent.state === stateName
}
function findComponent(obj, typeCtor) {
if (!obj)
return null
if (obj instanceof typeCtor)
return obj
if (obj.children && obj.children.length > 0) {
for (var i = 0; i < obj.children.length; i++) {
var matchingChildren = findComponent(obj.children[i], typeCtor)
if (matchingChildren) return matchingChildren
}
}
if (obj.contentItem) {
var matchingContentItem = findComponent(obj.contentItem, typeCtor)
if (matchingContentItem) return matchingContentItem
}
return null
}
function setParentInteractive(value) {
var flickableType = findComponent(root.parent, Flickable)
var listViewType = findComponent(root.parent, ListView)
if (flickableType) flickableType.interactive = value
if (listViewType) listViewType.interactive = value
}
Connections {
target: Qt.application
@@ -93,6 +123,8 @@ Item {
aboutToHide()
setParentInteractive(true)
closed()
}
@@ -118,6 +150,8 @@ Item {
root.aboutToShow()
setParentInteractive(false)
root.opened()
}
@@ -71,6 +71,8 @@ Item {
implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin
MouseArea {
id: mouseArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: root.enabled
@@ -296,13 +298,13 @@ Item {
}
Keys.onEnterPressed: {
if (clickedFunction && typeof clickedFunction === "function") {
if (!rightImageSource && clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
Keys.onReturnPressed: {
if (clickedFunction && typeof clickedFunction === "function") {
if (!rightImageSource && clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
@@ -140,6 +140,16 @@ PageType {
ListElement { name : "aes-128-gcm" }
}
function updateSelectedIndex() {
cipherDropDown.text = cipher
for (var i = 0; i < cipherListView.model.count; i++) {
if (cipherListView.model.get(i).name === cipher) {
selectedIndex = i
break
}
}
}
clickedFunction: function() {
cipherDropDown.text = selectedText
cipher = cipherDropDown.text
@@ -147,13 +157,14 @@ PageType {
}
Component.onCompleted: {
cipherDropDown.text = cipher
updateSelectedIndex()
}
}
for (var i = 0; i < cipherListView.model.count; i++) {
if (cipherListView.model.get(i).name === cipherDropDown.text) {
selectedIndex = i
}
}
Connections {
target: listView.model
function onDataChanged() {
cipherListView.updateSelectedIndex()
}
}
}
@@ -192,6 +192,16 @@ PageType {
ListElement { name : qsTr("SHA1") }
}
function updateSelectedIndex() {
hashDropDown.text = hash
for (var i = 0; i < hashListView.model.count; i++) {
if (hashListView.model.get(i).name === hash) {
selectedIndex = i
break
}
}
}
clickedFunction: function() {
hashDropDown.text = selectedText
hash = hashDropDown.text
@@ -199,13 +209,14 @@ PageType {
}
Component.onCompleted: {
hashDropDown.text = hash
updateSelectedIndex()
}
}
for (var i = 0; i < hashListView.model.count; i++) {
if (hashListView.model.get(i).name === hashDropDown.text) {
currentIndex = i
}
}
Connections {
target: listView.model
function onDataChanged() {
hashListView.updateSelectedIndex()
}
}
}
@@ -242,6 +253,16 @@ PageType {
ListElement { name : qsTr("none") }
}
function updateSelectedIndex() {
cipherDropDown.text = cipher
for (var i = 0; i < cipherListView.model.count; i++) {
if (cipherListView.model.get(i).name === cipher) {
selectedIndex = i
break
}
}
}
clickedFunction: function() {
cipherDropDown.text = selectedText
cipher = cipherDropDown.text
@@ -249,13 +270,14 @@ PageType {
}
Component.onCompleted: {
cipherDropDown.text = cipher
updateSelectedIndex()
}
}
for (var i = 0; i < cipherListView.model.count; i++) {
if (cipherListView.model.get(i).name === cipherDropDown.text) {
currentIndex = i
}
}
Connections {
target: listView.model
function onDataChanged() {
cipherListView.updateSelectedIndex()
}
}
}
@@ -109,6 +109,16 @@ PageType {
ListElement { name : "aes-128-gcm" }
}
function updateSelectedIndex() {
cipherDropDown.text = cipher
for (var i = 0; i < cipherListView.model.count; i++) {
if (cipherListView.model.get(i).name === cipher) {
selectedIndex = i
break
}
}
}
clickedFunction: function() {
cipherDropDown.text = selectedText
cipher = cipherDropDown.text
@@ -116,13 +126,14 @@ PageType {
}
Component.onCompleted: {
cipherDropDown.text = cipher
updateSelectedIndex()
}
}
for (var i = 0; i < cipherListView.model.count; i++) {
if (cipherListView.model.get(i).name === cipherDropDown.text) {
currentIndex = i
}
}
Connections {
target: listView.model
function onDataChanged() {
cipherListView.updateSelectedIndex()
}
}
}
+1 -1
View File
@@ -148,7 +148,7 @@ PageType {
id: news
property string title: qsTr("News & Notifications")
readonly property string leftImagePath: NewsModel.hasUnread ? "qrc:/images/controls/news-unread.svg" : "qrc:/images/controls/news.svg"
readonly property string leftImagePath: NewsModel.hasUnread && SettingsController.isNewsNotificationsEnabled() ? "qrc:/images/controls/news-unread.svg" : "qrc:/images/controls/news.svg"
property bool isVisible: ServersModel.hasServersFromGatewayApi
readonly property var clickedHandler: function() {
if (!ServersModel.hasServersFromGatewayApi) {
@@ -168,6 +168,29 @@ PageType {
DividerType {
visible: !GC.isMobile()
}
SwitcherType {
id: switcherNewsNotificationEnabled
visible: ServersModel.hasServersFromGatewayApi
Layout.fillWidth: true
Layout.margins: 16
text: qsTr("News Notification")
descriptionText: qsTr("Show notification icon when has unread news")
checked: SettingsController.isNewsNotificationsEnabled()
onToggled: function() {
if (checked !== SettingsController.isNewsNotificationsEnabled()) {
SettingsController.toggleNewsNotificationsEnabled(checked)
}
}
}
DividerType {
visible: !GC.isMobile()
}
}
footer: ColumnLayout {
@@ -107,6 +107,7 @@ PageType {
onClicked: function() {
isEasySetup = true
checked = true
var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer)
listView.dockerContainer = dockerContainer
+1 -1
View File
@@ -383,7 +383,7 @@ PageType {
objectName: "settingsTabButton"
isSelected: tabBar.currentIndex === 2
image: (ServersModel.hasServersFromGatewayApi && NewsModel.hasUnread) ? "qrc:/images/controls/settings-news.svg" : "qrc:/images/controls/settings.svg"
image: (ServersModel.hasServersFromGatewayApi && NewsModel.hasUnread && SettingsController.isNewsNotificationsEnabled()) ? "qrc:/images/controls/settings-news.svg" : "qrc:/images/controls/settings.svg"
Binding {
target: settingsTabButton
property: "defaultColor"
+2
View File
@@ -10,6 +10,8 @@
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>Sockets</key>
<dict>
<key>Listeners</key>
+38 -26
View File
@@ -7,42 +7,54 @@ LOG_FOLDER=/var/log/$APP_NAME
LOG_FILE="$LOG_FOLDER/post-install.log"
APP_PATH=/Applications/$APP_NAME.app
rm -rf "$LOG_FOLDER"
mkdir -p "$LOG_FOLDER"
echo "`date` Script started" > "$LOG_FILE"
log() {
echo "`date` $*" >> "$LOG_FILE"
}
run_cmd() {
log "CMD: $*"
"$@" >> "$LOG_FILE" 2>&1
local ec=$?
log "EXIT: $ec"
return $ec
}
# Handle new installations unpacked into localized folder
if [ -d "/Applications/${APP_NAME}.localized" ]; then
echo "`date` Detected ${APP_NAME}.localized, migrating to standard path" >> $LOG_FILE
sudo rm -rf "$APP_PATH"
sudo mv "/Applications/${APP_NAME}.localized/${APP_NAME}.app" "$APP_PATH"
sudo rm -rf "/Applications/${APP_NAME}.localized"
log "Detected ${APP_NAME}.localized, migrating to standard path"
run_cmd sudo rm -rf "$APP_PATH"
run_cmd sudo mv "/Applications/${APP_NAME}.localized/${APP_NAME}.app" "$APP_PATH"
run_cmd sudo rm -rf "/Applications/${APP_NAME}.localized"
fi
if launchctl list "$APP_NAME-service" &> /dev/null; then
launchctl unload "$LAUNCH_DAEMONS_PLIST_NAME"
rm -f "$LAUNCH_DAEMONS_PLIST_NAME"
fi
run_cmd launchctl bootout system "$LAUNCH_DAEMONS_PLIST_NAME" || run_cmd launchctl unload "$LAUNCH_DAEMONS_PLIST_NAME"
run_cmd rm -f "$LAUNCH_DAEMONS_PLIST_NAME"
sudo chmod -R a-w "$APP_PATH/"
sudo chown -R root "$APP_PATH/"
sudo chgrp -R wheel "$APP_PATH/"
run_cmd sudo chmod -R a-w "$APP_PATH/"
run_cmd sudo chown -R root "$APP_PATH/"
run_cmd sudo chgrp -R wheel "$APP_PATH/"
rm -rf $LOG_FOLDER
mkdir -p $LOG_FOLDER
echo "`date` Script started" > $LOG_FILE
echo "Requesting ${APP_NAME} to quit gracefully" >> "$LOG_FILE"
osascript -e 'tell application "AmneziaVPN" to quit'
log "Requesting ${APP_NAME} to quit gracefully"
run_cmd osascript -e 'tell application "AmneziaVPN" to quit' || true
PLIST_SOURCE="$APP_PATH/Contents/Resources/$PLIST_NAME"
if [ -f "$PLIST_SOURCE" ]; then
mv -f "$PLIST_SOURCE" "$LAUNCH_DAEMONS_PLIST_NAME" 2>> $LOG_FILE
run_cmd mv -f "$PLIST_SOURCE" "$LAUNCH_DAEMONS_PLIST_NAME"
else
echo "`date` ERROR: service plist not found at $PLIST_SOURCE" >> $LOG_FILE
log "ERROR: service plist not found at $PLIST_SOURCE"
fi
chown root:wheel "$LAUNCH_DAEMONS_PLIST_NAME"
launchctl load "$LAUNCH_DAEMONS_PLIST_NAME"
echo "`date` Launching ${APP_NAME} application" >> $LOG_FILE
open -a "$APP_PATH" 2>> $LOG_FILE || true
run_cmd chown root:wheel "$LAUNCH_DAEMONS_PLIST_NAME"
run_cmd chmod 644 "$LAUNCH_DAEMONS_PLIST_NAME"
run_cmd launchctl bootstrap system "$LAUNCH_DAEMONS_PLIST_NAME" || run_cmd launchctl load "$LAUNCH_DAEMONS_PLIST_NAME"
run_cmd launchctl enable "system/$APP_NAME-service" || true
run_cmd launchctl kickstart -k "system/$APP_NAME-service" || true
run_cmd launchctl print "system/$APP_NAME-service" || true
log "Launching ${APP_NAME} application"
run_cmd open -a "$APP_PATH" || true
echo "`date` Service status: $?" >> $LOG_FILE
echo "`date` Script finished" >> $LOG_FILE
log "Script finished"
+1 -1
View File
@@ -29,7 +29,7 @@ fi
# Unload the service if loaded and remove its plist file regardless
if launchctl list "${APP_NAME}-service" &> /dev/null; then
sudo launchctl unload "$LAUNCH_DAEMONS_PLIST_NAME"
sudo launchctl bootout system "$LAUNCH_DAEMONS_PLIST_NAME" || sudo launchctl unload "$LAUNCH_DAEMONS_PLIST_NAME"
fi
sudo rm -f "$LAUNCH_DAEMONS_PLIST_NAME"
+29 -16
View File
@@ -1,9 +1,9 @@
#!/bin/sh
#!/bin/bash
set -e
VERSION=$1
if [[ $VERSION = '' ]]; then
if [[ -z "$VERSION" ]]; then
echo '::error::VERSION does not set. Exiting with error...'
exit 1
fi
@@ -14,25 +14,38 @@ cd dist
echo $VERSION >> VERSION
curl -s https://api.github.com/repos/amnezia-vpn/amnezia-client/releases/tags/$VERSION | jq -r .body | tr -d '\r' > CHANGELOG
curl -s https://api.github.com/repos/amnezia-vpn/amnezia-client/releases/tags/$VERSION | jq -r .published_at > RELEASE_DATE
if [[ $(cat CHANGELOG) = null ]]; then
echo '::error::Release does not exists. Exiting with error...'
exit 1
fi
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android8+_arm64-v8a.apk
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android8+_armeabi-v7a.apk
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android8+_x86.apk
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android8+_x86_64.apk
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android_7_arm64-v8a.apk
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android_7_armeabi-v7a.apk
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android_7_x86.apk
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android_7_x86_64.apk
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_linux_x64.tar.zip
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_macos.dmg
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_macos_old.dmg
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_windows_x64.exe
# Download files with error handling
download_file() {
local url=$1
local filename=$(basename "$url")
echo "Downloading $filename..."
if ! wget -q "$url"; then
echo "::error::Failed to download $filename from $url"
exit 8
fi
echo "Successfully downloaded $filename"
}
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android9+_arm64-v8a.apk
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android9+_armeabi-v7a.apk
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android9+_x86.apk
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android9+_x86_64.apk
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_linux_x64.tar
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_macos.pkg
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_x64.exe
cd ../
rclone sync ./dist/ r2:/updates/
echo "Syncing to R2..."
if ! rclone sync ./dist/ r2:/updates/; then
echo "::error::Failed to sync files to R2"
exit 8
fi
echo "Deployment completed successfully!"