Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,5 @@ if(COMMAND qt_finalize_executable)
else()
qt_finalize_target(${PROJECT})
endif()

add_subdirectory(cli)
72 changes: 72 additions & 0 deletions client/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
set(CLI_PROJECT amnezia-cli)

set(CLI_SOURCES ${SOURCES})
list(FILTER CLI_SOURCES EXCLUDE REGEX "/main\\.cpp$")
list(FILTER CLI_SOURCES EXCLUDE REGEX "/amnezia_application\\.cpp$")
list(FILTER CLI_SOURCES EXCLUDE REGEX "/core/controllers/coreController\\.cpp$")
list(FILTER CLI_SOURCES EXCLUDE REGEX "/ui/controllers/settingsController\\.cpp$")
list(FILTER CLI_SOURCES EXCLUDE REGEX "/core/osSignalHandler\\.cpp$")

set(CLI_HEADERS ${HEADERS})
list(FILTER CLI_HEADERS EXCLUDE REGEX "/amnezia_application\\.h$")
list(FILTER CLI_HEADERS EXCLUDE REGEX "/core/controllers/coreController\\.h$")
list(FILTER CLI_HEADERS EXCLUDE REGEX "/ui/controllers/settingsController\\.h$")
list(FILTER CLI_HEADERS EXCLUDE REGEX "/core/osSignalHandler\\.h$")

list(APPEND CLI_SOURCES
${CMAKE_CURRENT_LIST_DIR}/main.cpp
${CMAKE_CURRENT_LIST_DIR}/cli_context.cpp
${CMAKE_CURRENT_LIST_DIR}/cli_ipc.cpp
)

qt_add_executable(${CLI_PROJECT} MANUAL_FINALIZATION)
target_include_directories(${CLI_PROJECT} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/..>
)

if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
qt_add_repc_replicas(${CLI_PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_interface.rep)
qt_add_repc_replicas(${CLI_PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_process_interface.rep)
endif()

target_link_libraries(${CLI_PROJECT} PRIVATE ${LIBS})
target_compile_definitions(${CLI_PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")

if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
target_compile_definitions(${CLI_PROJECT} PRIVATE AMNEZIA_DESKTOP)
endif()

if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(${CLI_PROJECT} PRIVATE "MZ_DEBUG")
endif()

target_sources(${CLI_PROJECT} PRIVATE ${CLI_SOURCES} ${CLI_HEADERS})

if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_custom_command(
TARGET ${CLI_PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
${CMAKE_SOURCE_DIR}/deploy/data/${DEPLOY_PLATFORM_PATH}
$<TARGET_FILE_DIR:${CLI_PROJECT}>
COMMAND_EXPAND_LISTS
)
if(EXISTS "${CMAKE_SOURCE_DIR}/client/3rd-prebuilt/deploy-prebuilt/${DEPLOY_PLATFORM_PATH}")
add_custom_command(
TARGET ${CLI_PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
${CMAKE_SOURCE_DIR}/client/3rd-prebuilt/deploy-prebuilt/${DEPLOY_PLATFORM_PATH}
$<TARGET_FILE_DIR:${CLI_PROJECT}>
COMMAND_EXPAND_LISTS
)
endif()
endif()

if(COMMAND qt_import_qml_plugins)
qt_import_qml_plugins(${CLI_PROJECT})
endif()
if(COMMAND qt_finalize_executable)
qt_finalize_executable(${CLI_PROJECT})
else()
qt_finalize_target(${CLI_PROJECT})
endif()
58 changes: 58 additions & 0 deletions client/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# amnezia-cli

`amnezia-cli` is a headless command-line client built on top of the same core models, controllers, and VPN runtime used by the desktop application.

## Build

From the repository root:

```bash
cmake -S . -B build -G Ninja
cmake --build build --target amnezia-cli -j4
```

The binary is produced at:

```bash
./build/client/cli/amnezia-cli
```

## Command Model

- `connect` and `disconnect` talk to a local CLI daemon over `QLocalSocket`.
- `status` queries the daemon when it is running; otherwise it returns a local disconnected snapshot.
- The daemon keeps VPN state and allows the CLI process itself to stay short-lived.
- Management commands such as `servers`, `countries`, `containers`, `install`, and `logs cleanup` can run directly or through the daemon.

## Common Commands

```bash
./build/client/cli/amnezia-cli status
./build/client/cli/amnezia-cli daemon start
./build/client/cli/amnezia-cli servers list
./build/client/cli/amnezia-cli countries list --index 0
./build/client/cli/amnezia-cli containers list --index 0
./build/client/cli/amnezia-cli connect --index 0
./build/client/cli/amnezia-cli disconnect
```

JSON output is available for automation on any command:

```bash
./build/client/cli/amnezia-cli status --json
```

## Supported Operations

- Inspect VPN state: `status`
- Control VPN connection: `connect`, `disconnect`
- Control the daemon: `daemon start|stop|status`
- Manage saved servers: `servers list|show|add|import|remove|set-default|scan`
- Manage available countries for API-backed configs: `countries list|set`
- Manage containers: `containers list|set-default|remove`
- Install a new server or container: `install server`, `install container`
- Cleanup local logs: `logs cleanup`

## Notes

- The daemon redirects its own stdout and stderr to avoid breaking `--json` output from the foreground CLI process.
50 changes: 50 additions & 0 deletions client/cli/cli_common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#ifndef CLI_COMMON_H
#define CLI_COMMON_H

#include <QJsonObject>
#include <QString>

namespace cli
{

struct Result
{
bool ok = false;
int exitCode = 1;
QString message;
QJsonObject data;

static Result success(const QString &message = {}, const QJsonObject &data = {})
{
return { true, 0, message, data };
}

static Result failure(const QString &message, int exitCode = 1, const QJsonObject &data = {})
{
return { false, exitCode, message, data };
}
};

inline QJsonObject resultToJson(const Result &result)
{
QJsonObject json;
json["ok"] = result.ok;
json["exit_code"] = result.exitCode;
json["message"] = result.message;
json["data"] = result.data;
return json;
}

inline Result resultFromJson(const QJsonObject &json)
{
Result result;
result.ok = json.value("ok").toBool(false);
result.exitCode = json.value("exit_code").toInt(result.ok ? 0 : 1);
result.message = json.value("message").toString();
result.data = json.value("data").toObject();
return result;
}

} // namespace cli

#endif // CLI_COMMON_H
Loading