Compare commits

...

17 Commits

Author SHA1 Message Date
irvinklause 8f5e42dd61 chore: add sending of release_date to s3 2026-02-04 07:38:44 +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
vkamn f73792844c chore: revoke #2148 (#2160) 2026-01-26 19:39:47 +08:00
Yaroslav Gurov a7199ca6f5 fix: add +x permissions to wireguard-go on linux (#2159) 2026-01-26 19:16:39 +08:00
vkamn 5e757cdd3b chore: bump qt version for linux build (#2157) 2026-01-25 21:35:16 +08:00
vkamn 92af1f3268 chore: runners (#2150)
* chore: change runner for linux and android

* chore: add libsecret to linux build

* chore: bump version
2026-01-23 12:05:31 +08:00
Yaroslav Gurov aad9d6dae2 chore: remove redundant gateway (#2148) 2026-01-22 18:21:15 +08:00
Yaroslav Gurov 423fe3fd4f fix: remove redundant gateway from xrayprotocol (#2147) 2026-01-22 18:03:36 +08:00
26 changed files with 271 additions and 82 deletions
+4 -4
View File
@@ -10,10 +10,10 @@ env:
jobs: jobs:
Build-Linux-Ubuntu: Build-Linux-Ubuntu:
runs-on: 4-core runs-on: android-runner
env: env:
QT_VERSION: 6.8.3 QT_VERSION: 6.10.1
QIF_VERSION: 4.7 QIF_VERSION: 4.7
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -58,7 +58,7 @@ jobs:
- name: 'Build project' - name: 'Build project'
run: | run: |
sudo apt-get install libxkbcommon-x11-0 sudo apt-get install libxkbcommon-x11-0 libsecret-1-dev
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64/bin export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64/bin
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
bash deploy/build_linux.sh bash deploy/build_linux.sh
@@ -537,7 +537,7 @@ jobs:
# ------------------------------------------------------ # ------------------------------------------------------
Build-Android: Build-Android:
runs-on: 4-core runs-on: android-runner
env: env:
ANDROID_BUILD_PLATFORM: android-36 ANDROID_BUILD_PLATFORM: android-36
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
- name: Verify git tag - name: Verify git tag
run: | run: |
TAG_NAME=${{ 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 if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)." echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
else else
+2 -2
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.12.8) set(AMNEZIAVPN_VERSION 4.8.12.9)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION} project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
DESCRIPTION "AmneziaVPN" DESCRIPTION "AmneziaVPN"
@@ -12,7 +12,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 2104) set(APP_ANDROID_VERSION_CODE 2105)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")
@@ -1,7 +1,10 @@
package org.amnezia.vpn package org.amnezia.vpn
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@@ -11,7 +14,25 @@ private const val TAG = "TvFilePicker"
class TvFilePicker : ComponentActivity() { 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 }) setResult(RESULT_OK, Intent().apply { data = it })
finish() finish()
} }
@@ -31,7 +52,7 @@ class TvFilePicker : ComponentActivity() {
private fun getFile() { private fun getFile() {
try { try {
Log.v(TAG, "getFile") Log.v(TAG, "getFile")
fileChooseResultLauncher.launch("*/*") fileChooseResultLauncher.launch(arrayOf("*/*"))
} catch (_: ActivityNotFoundException) { } catch (_: ActivityNotFoundException) {
Log.w(TAG, "Activity not found") Log.w(TAG, "Activity not found")
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) }) setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
@@ -419,6 +419,18 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
cbReadStdOut, cbReadStdErr); cbReadStdOut, cbReadStdErr);
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut; 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")) if (stdOut.contains("lock"))
return ErrorCode::ServerPacketManagerError; return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("command not found")) if (stdOut.contains("command not found"))
+1
View File
@@ -61,6 +61,7 @@ namespace amnezia
ServerDockerOnCgroupsV2 = 211, ServerDockerOnCgroupsV2 = 211,
ServerCgroupMountpoint = 212, ServerCgroupMountpoint = 212,
DockerPullRateLimit = 213, DockerPullRateLimit = 213,
ServerLinuxKernelTooOld = 214,
// Ssh connection errors // Ssh connection errors
SshRequestDeniedError = 300, 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::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::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::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 // Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break; 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;\ sleep 5; sudo systemctl start docker; sleep 5;\
fi;\ fi;\
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; 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); setValue("Conf/startMinimized", enabled);
} }
bool isNewsNotifications() const
{
return value("Conf/newsNotifications", true).toBool();
}
void setNewsNotifications(bool enabled)
{
setValue("Conf/newsNotifications", enabled);
}
bool isSaveLogs() const bool isSaveLogs() const
{ {
return value("Conf/saveLogs", false).toBool(); return value("Conf/saveLogs", false).toBool();
@@ -308,6 +308,15 @@ void SettingsController::toggleStartMinimized(bool enable)
emit startMinimizedChanged(); emit startMinimizedChanged();
} }
bool SettingsController::isNewsNotificationsEnabled()
{
return m_settings->isNewsNotifications();
}
void SettingsController::toggleNewsNotificationsEnabled(bool enable)
{
m_settings->setNewsNotifications(enable);
}
bool SettingsController::isScreenshotsEnabled() bool SettingsController::isScreenshotsEnabled()
{ {
return m_settings->isScreenshotsEnabled(); return m_settings->isScreenshotsEnabled();
@@ -73,6 +73,9 @@ public slots:
bool isStartMinimizedEnabled(); bool isStartMinimizedEnabled();
void toggleStartMinimized(bool enable); void toggleStartMinimized(bool enable);
bool isNewsNotificationsEnabled();
void toggleNewsNotificationsEnabled(bool enable);
bool isScreenshotsEnabled(); bool isScreenshotsEnabled();
void toggleScreenshotsEnabled(bool enable); void toggleScreenshotsEnabled(bool enable);
+34
View File
@@ -49,6 +49,36 @@ Item {
return drawerContent.state === stateName 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 { Connections {
target: Qt.application target: Qt.application
@@ -93,6 +123,8 @@ Item {
aboutToHide() aboutToHide()
setParentInteractive(true)
closed() closed()
} }
@@ -118,6 +150,8 @@ Item {
root.aboutToShow() root.aboutToShow()
setParentInteractive(false)
root.opened() root.opened()
} }
@@ -71,6 +71,8 @@ Item {
implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin
MouseArea { MouseArea {
id: mouseArea
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
hoverEnabled: root.enabled hoverEnabled: root.enabled
@@ -296,13 +298,13 @@ Item {
} }
Keys.onEnterPressed: { Keys.onEnterPressed: {
if (clickedFunction && typeof clickedFunction === "function") { if (!rightImageSource && clickedFunction && typeof clickedFunction === "function") {
clickedFunction() clickedFunction()
} }
} }
Keys.onReturnPressed: { Keys.onReturnPressed: {
if (clickedFunction && typeof clickedFunction === "function") { if (!rightImageSource && clickedFunction && typeof clickedFunction === "function") {
clickedFunction() clickedFunction()
} }
} }
@@ -140,6 +140,16 @@ PageType {
ListElement { name : "aes-128-gcm" } 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() { clickedFunction: function() {
cipherDropDown.text = selectedText cipherDropDown.text = selectedText
cipher = cipherDropDown.text cipher = cipherDropDown.text
@@ -147,13 +157,14 @@ PageType {
} }
Component.onCompleted: { Component.onCompleted: {
cipherDropDown.text = cipher updateSelectedIndex()
}
}
for (var i = 0; i < cipherListView.model.count; i++) { Connections {
if (cipherListView.model.get(i).name === cipherDropDown.text) { target: listView.model
selectedIndex = i function onDataChanged() {
} cipherListView.updateSelectedIndex()
}
} }
} }
} }
@@ -192,6 +192,16 @@ PageType {
ListElement { name : qsTr("SHA1") } 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() { clickedFunction: function() {
hashDropDown.text = selectedText hashDropDown.text = selectedText
hash = hashDropDown.text hash = hashDropDown.text
@@ -199,13 +209,14 @@ PageType {
} }
Component.onCompleted: { Component.onCompleted: {
hashDropDown.text = hash updateSelectedIndex()
}
}
for (var i = 0; i < hashListView.model.count; i++) { Connections {
if (hashListView.model.get(i).name === hashDropDown.text) { target: listView.model
currentIndex = i function onDataChanged() {
} hashListView.updateSelectedIndex()
}
} }
} }
} }
@@ -242,6 +253,16 @@ PageType {
ListElement { name : qsTr("none") } 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() { clickedFunction: function() {
cipherDropDown.text = selectedText cipherDropDown.text = selectedText
cipher = cipherDropDown.text cipher = cipherDropDown.text
@@ -249,13 +270,14 @@ PageType {
} }
Component.onCompleted: { Component.onCompleted: {
cipherDropDown.text = cipher updateSelectedIndex()
}
}
for (var i = 0; i < cipherListView.model.count; i++) { Connections {
if (cipherListView.model.get(i).name === cipherDropDown.text) { target: listView.model
currentIndex = i function onDataChanged() {
} cipherListView.updateSelectedIndex()
}
} }
} }
} }
@@ -109,6 +109,16 @@ PageType {
ListElement { name : "aes-128-gcm" } 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() { clickedFunction: function() {
cipherDropDown.text = selectedText cipherDropDown.text = selectedText
cipher = cipherDropDown.text cipher = cipherDropDown.text
@@ -116,13 +126,14 @@ PageType {
} }
Component.onCompleted: { Component.onCompleted: {
cipherDropDown.text = cipher updateSelectedIndex()
}
}
for (var i = 0; i < cipherListView.model.count; i++) { Connections {
if (cipherListView.model.get(i).name === cipherDropDown.text) { target: listView.model
currentIndex = i function onDataChanged() {
} cipherListView.updateSelectedIndex()
}
} }
} }
} }
+1 -1
View File
@@ -148,7 +148,7 @@ PageType {
id: news id: news
property string title: qsTr("News & Notifications") 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 property bool isVisible: ServersModel.hasServersFromGatewayApi
readonly property var clickedHandler: function() { readonly property var clickedHandler: function() {
if (!ServersModel.hasServersFromGatewayApi) { if (!ServersModel.hasServersFromGatewayApi) {
@@ -168,6 +168,29 @@ PageType {
DividerType { DividerType {
visible: !GC.isMobile() 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 { footer: ColumnLayout {
@@ -107,6 +107,7 @@ PageType {
onClicked: function() { onClicked: function() {
isEasySetup = true isEasySetup = true
checked = true
var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer)
listView.dockerContainer = dockerContainer listView.dockerContainer = dockerContainer
+1 -1
View File
@@ -383,7 +383,7 @@ PageType {
objectName: "settingsTabButton" objectName: "settingsTabButton"
isSelected: tabBar.currentIndex === 2 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 { Binding {
target: settingsTabButton target: settingsTabButton
property: "defaultColor" property: "defaultColor"
+1 -1
View File
@@ -499,7 +499,7 @@ bool VpnConnection::startNetworkCheckIfReady()
return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) { return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->startNetworkCheck(gateway, localAddress); QRemoteObjectPendingReply<bool> reply = iface->startNetworkCheck(gateway, localAddress);
return reply.waitForFinished() && reply.returnValue(); return reply.waitForFinished(1000) && reply.returnValue();
}); });
#else #else
return false; return false;
+2
View File
@@ -10,6 +10,8 @@
</array> </array>
<key>KeepAlive</key> <key>KeepAlive</key>
<true/> <true/>
<key>RunAtLoad</key>
<true/>
<key>Sockets</key> <key>Sockets</key>
<dict> <dict>
<key>Listeners</key> <key>Listeners</key>
+38 -26
View File
@@ -7,42 +7,54 @@ LOG_FOLDER=/var/log/$APP_NAME
LOG_FILE="$LOG_FOLDER/post-install.log" LOG_FILE="$LOG_FOLDER/post-install.log"
APP_PATH=/Applications/$APP_NAME.app 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 # Handle new installations unpacked into localized folder
if [ -d "/Applications/${APP_NAME}.localized" ]; then if [ -d "/Applications/${APP_NAME}.localized" ]; then
echo "`date` Detected ${APP_NAME}.localized, migrating to standard path" >> $LOG_FILE log "Detected ${APP_NAME}.localized, migrating to standard path"
sudo rm -rf "$APP_PATH" run_cmd sudo rm -rf "$APP_PATH"
sudo mv "/Applications/${APP_NAME}.localized/${APP_NAME}.app" "$APP_PATH" run_cmd sudo mv "/Applications/${APP_NAME}.localized/${APP_NAME}.app" "$APP_PATH"
sudo rm -rf "/Applications/${APP_NAME}.localized" run_cmd sudo rm -rf "/Applications/${APP_NAME}.localized"
fi fi
if launchctl list "$APP_NAME-service" &> /dev/null; then run_cmd launchctl bootout system "$LAUNCH_DAEMONS_PLIST_NAME" || run_cmd launchctl unload "$LAUNCH_DAEMONS_PLIST_NAME"
launchctl unload "$LAUNCH_DAEMONS_PLIST_NAME" run_cmd rm -f "$LAUNCH_DAEMONS_PLIST_NAME"
rm -f "$LAUNCH_DAEMONS_PLIST_NAME"
fi
sudo chmod -R a-w "$APP_PATH/" run_cmd sudo chmod -R a-w "$APP_PATH/"
sudo chown -R root "$APP_PATH/" run_cmd sudo chown -R root "$APP_PATH/"
sudo chgrp -R wheel "$APP_PATH/" run_cmd sudo chgrp -R wheel "$APP_PATH/"
rm -rf $LOG_FOLDER log "Requesting ${APP_NAME} to quit gracefully"
mkdir -p $LOG_FOLDER run_cmd osascript -e 'tell application "AmneziaVPN" to quit' || true
echo "`date` Script started" > $LOG_FILE
echo "Requesting ${APP_NAME} to quit gracefully" >> "$LOG_FILE"
osascript -e 'tell application "AmneziaVPN" to quit'
PLIST_SOURCE="$APP_PATH/Contents/Resources/$PLIST_NAME" PLIST_SOURCE="$APP_PATH/Contents/Resources/$PLIST_NAME"
if [ -f "$PLIST_SOURCE" ]; then 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 else
echo "`date` ERROR: service plist not found at $PLIST_SOURCE" >> $LOG_FILE log "ERROR: service plist not found at $PLIST_SOURCE"
fi fi
chown root:wheel "$LAUNCH_DAEMONS_PLIST_NAME" run_cmd chown root:wheel "$LAUNCH_DAEMONS_PLIST_NAME"
launchctl load "$LAUNCH_DAEMONS_PLIST_NAME" run_cmd chmod 644 "$LAUNCH_DAEMONS_PLIST_NAME"
echo "`date` Launching ${APP_NAME} application" >> $LOG_FILE run_cmd launchctl bootstrap system "$LAUNCH_DAEMONS_PLIST_NAME" || run_cmd launchctl load "$LAUNCH_DAEMONS_PLIST_NAME"
open -a "$APP_PATH" 2>> $LOG_FILE || true 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 log "Script finished"
echo "`date` Script finished" >> $LOG_FILE
+1 -1
View File
@@ -29,7 +29,7 @@ fi
# Unload the service if loaded and remove its plist file regardless # Unload the service if loaded and remove its plist file regardless
if launchctl list "${APP_NAME}-service" &> /dev/null; then 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 fi
sudo rm -f "$LAUNCH_DAEMONS_PLIST_NAME" sudo rm -f "$LAUNCH_DAEMONS_PLIST_NAME"
+29 -15
View File
@@ -1,9 +1,9 @@
#!/bin/sh #!/bin/bash
set -e set -e
VERSION=$1 VERSION=$1
if [[ $VERSION = '' ]]; then if [[ -z "$VERSION" ]]; then
echo '::error::VERSION does not set. Exiting with error...' echo '::error::VERSION does not set. Exiting with error...'
exit 1 exit 1
fi fi
@@ -14,25 +14,39 @@ cd dist
echo $VERSION >> VERSION 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 .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 if [[ $(cat CHANGELOG) = null ]]; then
echo '::error::Release does not exists. Exiting with error...' echo '::error::Release does not exists. Exiting with error...'
exit 1 exit 1
fi fi
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android8+_arm64-v8a.apk # Download files with error handling
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android8+_armeabi-v7a.apk download_file() {
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android8+_x86.apk local url=$1
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android8+_x86_64.apk local filename=$(basename "$url")
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android_7_arm64-v8a.apk echo "Downloading $filename..."
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android_7_armeabi-v7a.apk if ! wget -q "$url"; then
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android_7_x86.apk echo "::error::Failed to download $filename from $url"
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android_7_x86_64.apk exit 8
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_linux_x64.tar.zip fi
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_macos.dmg echo "Successfully downloaded $filename"
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_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 ../ 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!"