Files
logos-liblogos/docs/project.md

33 KiB

Project Description

Project Structure

logos-liblogos/
├── CMakeLists.txt                       # Root CMake configuration
├── README.md                            # Project overview and build instructions
├── flake.nix                            # Nix flake configuration
├── flake.lock                           # Nix flake lock file
├── docs/
│   ├── index.md                         # Documentation index
│   ├── spec.md                          # High-level specification
│   └── project.md                       # This document
├── src/
│   ├── CMakeLists.txt                   # Source build configuration
│   ├── path_safety.h                    # logos::isSafePathSegment — validate untrusted names used as path segments
│   ├── logos_core/                      # Core library implementation
│   │   ├── logos_core.h                 # C API header (public)
│   │   ├── logos_core.cpp               # C API implementation
│   │   ├── module_manager.h/cpp         # Facade: orchestrates registry, runtime registry, resolver
│   │   ├── module_registry.h/cpp        # In-memory registry of discovered/loaded modules
│   │   ├── module_name_validation.h     # Qt-free allowlist validator for untrusted module names
│   │   ├── dependency_resolver.h/cpp    # Topological sort with circular dependency detection
│   │   ├── module_runtime.h             # Abstract ModuleRuntime interface (Qt-free)
│   │   ├── module_container.h           # Abstract ModuleContainer interface (where/how a module runs)
│   │   ├── module_loader.h              # Abstract ModuleLoader interface (what type of module)
│   │   ├── composite_runtime.h/cpp      # Pairs a container + loader into a ModuleRuntime
│   │   └── runtime_registry.h/cpp       # Registry of ModuleRuntime implementations
│   ├── containers/                      # Container implementations (process isolation strategies)
│   │   └── subprocess/                  # Subprocess container (Boost.Process v2)
│   │       ├── subprocess_container.h/cpp   # ModuleContainer impl: process spawn, pipes, kill, token send
│   │       ├── subprocess_manager.h         # Backward-compat shim for SubprocessManager name
│   │       ├── peer_credentials.h           # Qt-free peer-uid auth for the token socket (SO_PEERCRED/getpeereuid), shared by both handoff sides
│   │       ├── unix_socket_path.h           # Qt-free token socket path resolver (shared by both handoff sides)
│   │       └── token_receiver.h/cpp         # Child-side auth token receipt via an owner-only, peer-authenticated Unix socket (validates module name against the allowlist before deriving the socket path)
│   └── runtimes/                        # Module runtime/loader implementations
│       └── runtime_qt/                  # Qt plugin runtime
│           ├── qt_plugin_runtime.h/cpp      # ModuleLoader impl: resolve logos_host_qt, build CLI args
│           └── host/                        # Child-side code (builds logos_host_qt binary)
│               ├── logos_host.cpp               # Host entry point (logos_host_qt binary)
│               ├── command_line_parser.h/cpp    # CLI argument parsing (--name, --path)
│               ├── module_initializer.h/cpp     # Module loading and LogosAPI init (takes authToken param)
│               └── qt/
│                   └── qt_app.h/cpp             # Qt application setup for host
├── tests/                               # Google Test suite
│   ├── CMakeLists.txt                   # Test build configuration
│   ├── test_app_lifecycle.cpp           # C API lifecycle tests (init, exec, cleanup, processEvents)
│   ├── test_module_manager.cpp          # ModuleManager + ModuleRegistry tests
│   ├── test_subprocess_manager.cpp      # SubprocessManager lifecycle and subprocess tests
│   ├── test_runtime_registry.cpp        # RuntimeRegistry selection and fan-out tests
│   ├── test_module_runtime_abstraction.cpp  # End-to-end runtime abstraction tests (FakeRuntime)
│   ├── test_dependency_resolver.cpp     # DependencyResolver tests
│   ├── test_process_stats.cpp           # ProcessStats tests (external process-stats lib)
│   ├── test_token_exchange.cpp          # Token exchange via Unix domain socket tests (incl. F-011 path-traversal module-name rejection and F-012 socket-permission & peer-uid hardening)
│   ├── test_module_name_validation.cpp  # Module-name allowlist + token-socket path-traversal regression (F-030)
│   └── qt_test_adapter.h               # Qt test utilities/adapter header
├── nix/                                 # Nix build modules
│   ├── default.nix                      # Common configuration (deps, flags, metadata)
│   ├── build.nix                        # Shared build derivation
│   ├── bin.nix                          # Binary extraction (logos_host_qt + runtime libs)
│   ├── lib.nix                          # Library extraction (liblogos_core)
│   ├── include.nix                      # Header installation
│   ├── modules.nix                      # Bundled built-in modules
│   └── tests.nix                        # Test suite build
└── .github/
    └── workflows/
        └── ci.yml                       # GitHub Actions CI workflow

Stack, Frameworks & Dependencies

Component Purpose
C++17 Implementation language
CMake 3.14+ Build system
Qt 6 (Core, RemoteObjects) Event loop, module system, IPC, meta-object system
Boost (Process, Asio, Uuid) Subprocess management, async I/O, and UUID generation
nlohmann_json JSON parsing/serialization (replaces Qt JSON internally)
OpenSSL Required transitively by the SDK's plain-C++ TCP+TLS transport
CLI11 Command-line argument parsing (logos_host)
zstd Compression (build dependency)
spdlog Structured logging
Google Test Unit testing framework
Nix Package management and reproducible builds

External Logos Dependencies

Dependency Purpose
logos-cpp-sdk C++ client library (LogosAPI, LogosAPIClient, TokenManager, PluginInterface)
logos-module Module library (metadata extraction, module loading utilities)
logos-capability-module Built-in capability authorization module
process-stats CPU and memory monitoring for module processes
logos-package-manager Package management library for installed module discovery
logos-nix Common Nix tooling and nixpkgs pin

Core Modules

ModuleManager

Files: src/logos_core/module_manager.h, src/logos_core/module_manager.cpp

Purpose: Thin facade that orchestrates ModuleRegistry, RuntimeRegistry, and DependencyResolver. Provides the C++-level API for module lifecycle management. Each module runs in a separate subprocess managed by the selected ModuleRuntime implementation (default: SubprocessManager, which spawns logos_host_qt processes).

Thread safety: loadModule, loadModuleWithDependencies, and unloadModule are serialised by a static loadMutex() (one load/unload at a time). discoverInstalledModules delegates to ModuleRegistry which has its own reader-writer lock.

API (namespace ModuleManager):

Method Description
registry() → ModuleRegistry& Access the shared module registry
runtimes() → RuntimeRegistry& Access the shared runtime registry
setModulesDir(path) Set the primary module directory (clears existing)
addModulesDir(path) Add an additional module directory
setPersistenceBasePath(path) Set base directory for module instance persistence
setModuleTransports(name, json) Register a per-module LogosTransportSet (serialized JSON). Threaded through to the child subprocess on load so its provider binds every listener instead of only the global default. Empty clears the entry. Read+write protected by loadMutex()
discoverInstalledModules() Scan all module directories and register discovered modules
processModule(path) → std::string Extract metadata from a module file, register as known
processModuleCStr(path) → char* C-string variant of processModule
loadModule(name) → bool Load a module (selects a runtime via RuntimeRegistry, spawns subprocess, sends auth token)
loadModuleWithDependencies(name) → bool Resolve dependency tree, load in topological order. Returns false if any dependency is unknown or a cycle is detected (hard failure on !ResolveResult::ok())
initializeCapabilityModule() → bool Load the built-in capability module if available
unloadModule(name) → bool Terminate module process and update registry
unloadModuleWithDependents(name) → bool Cascade unload: terminate the named module together with every currently loaded module that transitively depends on it, leaves-first
terminateAll() Terminate all running module processes
clear() Clear registry and reset all state
resolveDependencies(modules) → std::vector<std::string> Topological sort with circular dependency detection
getDependencies(name, recursive) → std::vector<std::string> Declared dependencies of name among known modules; walks the forward graph transitively when recursive=true. Cycle- and diamond-safe BFS
getDependents(name, recursive) → std::vector<std::string> Declared dependents of name among known modules; walks the reverse graph transitively when recursive=true. Reads from the in-process registry, no disk query
getDependenciesCStr(name, recursive) → char** C-string variant backing logos_core_get_module_dependencies
getDependentsCStr(name, recursive) → char** C-string variant backing logos_core_get_module_dependents
getLoadedModulesCStr() → char** Return loaded module names as null-terminated C string array
getKnownModulesCStr() → char** Return known module names as null-terminated C string array
isModuleLoaded(name) → bool Check if a module is currently loaded
getModuleProcessIds() → std::unordered_map<std::string, int64_t> Return module name → process ID mappings

ModuleRegistry

Files: src/logos_core/module_registry.h, src/logos_core/module_registry.cpp

Purpose: In-memory registry of discovered and loaded modules. Single source of truth for the dependency graph: stores module paths, forward dependencies, and the derived reverse edges (dependents). All public methods are thread-safe: mutating methods acquire a std::unique_lock on an internal std::shared_mutex; read-only methods acquire a std::shared_lock, allowing concurrent reads.

Trust boundary — module name validation: A module's name comes verbatim from its (untrusted) embedded plugin metadata and is later used as a filesystem path segment for the token-handoff socket, as this map's key, and as the RPC target. Prevents this attack (CWE-22): a malicious installed module declares name='../<x>' (or a name colliding with another module's socket); the / or .. escapes the temp dir, so a local attacker pre-binding the resolved socket path captures the per-module auth token and gains the module's RPC privileges. processModuleInternal() validates the name with logos::isValidModuleName (src/logos_core/module_name_validation.h, allowlist [A-Za-z0-9_-], ≤64 bytes, rejects /, .., etc.) and skips any module whose name is unsafe. The socket sinks (SubprocessContainer::sendTokenToProcess, SubprocessTokenReceiver::receive) re-validate defensively.

Data:

  • ModuleInfo struct — holds path, dependencies (std::vector<std::string>), dependents (std::vector<std::string>, reverse-edge cache), loaded flag, runtime (std::shared_ptr<ModuleRuntime>), handle (LoadedModuleHandle)
  • std::unordered_map<std::string, ModuleInfo> m_modules — module database keyed by name
  • std::vector<std::string> m_modulesDirs — configured module directories
  • std::shared_mutex m_mutex — reader-writer lock protecting all fields

Dependency graph invariant: ModuleInfo::dependents mirrors the inverse of dependencies across all known modules. ModuleRegistry owns this invariant and maintains it by calling the private recomputeDependentsLocked() at the tail of every forward-edge mutation (discoverInstalledModules, processModule, registerModule when deps are passed, registerDependencies). Callers never populate dependents directly. This replaces the previous pattern of querying PackageManagerLib::resolveDependents() on disk — the registry is now the single authority for reverse-dep lookups, and ModuleManager::getDependents / unloadModuleWithDependents read straight from it.

API (class ModuleRegistry):

Method Description
setModulesDir(dir) Clear and set single module directory
addModulesDir(dir) Add to module directory list
modulesDirs() → std::vector<std::string> Return configured directories
discoverInstalledModules() Scan directories, parse manifest.json files; recomputes dependents at end
processModule(path) → std::string Extract metadata from module file, register as known; recomputes dependents at end. Rejects (returns "", no registry entry) a module whose name — taken from untrusted plugin JSON — is not a single safe path segment (logos::isSafePathSegment), since the name later becomes a token-socket / persistence path component
registerModule(name, path, deps) Manually register a module; recomputes dependents when deps are passed
registerDependencies(name, deps) Set dependencies for a known module; recomputes dependents
isKnown(name) → bool Module exists in registry
modulePath(name) → std::string Get file path for a known module
moduleDependencies(name, recursive) → std::vector<std::string> Forward-edge lookup. recursive=false returns direct dependencies from ModuleInfo; recursive=true walks the forward graph breadth-first (cycle/diamond safe)
moduleDependents(name, recursive) → std::vector<std::string> Reverse-edge lookup. recursive=false returns direct dependents from ModuleInfo; recursive=true walks the reverse graph breadth-first (cycle/diamond safe)
knownModuleNames() → std::vector<std::string> All discovered module names
isLoaded(name) → bool Module is currently running
markLoaded(name) / markUnloaded(name) Update load state
markLoaded(name, runtime, handle) Mark loaded with associated runtime and handle
runtimeFor(name) → std::shared_ptr<ModuleRuntime> Get the runtime that loaded a given module
loadedModuleNames() → std::vector<std::string> Currently running module names
clearLoaded() Clear all loaded state
clear() Reset entire registry

ModuleRuntime (interface)

Files: src/logos_core/module_runtime.h

Purpose: Abstract interface for module loading strategies. Decouples the core from any specific subprocess or module-loading mechanism (Qt, WASM, in-process, etc.). Each implementation handles a particular module format.

Supporting types:

  • ModuleDescriptor — describes a module to load: name, path, format, modulesDirs, instancePersistencePath, transportSetJson (per-module LogosTransportSet serialized as JSON; empty = inherit the global default LocalSocket), onTerminated callback
  • LoadedModuleHandle — opaque handle returned by load(): pid and runtimeData (std::any)

API (class ModuleRuntime):

Method Description
id() → std::string Unique runtime identifier (e.g. "qt-subprocess")
canHandle(desc) → bool Whether this runtime can load the given module descriptor
load(desc) → std::optional<LoadedModuleHandle> Load a module, return a handle on success
sendToken(handle, token) → bool Send auth token to the loaded module
terminate(handle) Terminate a specific loaded module
terminateAll() Terminate all modules managed by this runtime
getAllPids() → std::unordered_map<std::string, int64_t> Map module names to process IDs

RuntimeRegistry

Files: src/logos_core/runtime_registry.h, src/logos_core/runtime_registry.cpp

Purpose: Central registry of ModuleRuntime implementations. Selects the appropriate runtime for a given ModuleDescriptor by iterating registered runtimes and calling canHandle(). Fans out terminateAll() and getAllPids() across all registered runtimes.

API (class RuntimeRegistry):

Method Description
add(runtime) Register a ModuleRuntime implementation
select(desc) → std::shared_ptr<ModuleRuntime> Find the first runtime that can handle the descriptor
terminateAll() Terminate all modules across all runtimes
getAllPids() → std::unordered_map<std::string, int64_t> Aggregate PIDs from all runtimes

DependencyResolver

Files: src/logos_core/dependency_resolver.h, src/logos_core/dependency_resolver.cpp

Purpose: Compute topological sort of module dependencies using Kahn's algorithm. Detects circular dependencies and missing modules.

API (namespace DependencyResolver):

API (namespace DependencyResolver):

Type / Method Description
ResolveResult Result struct: order (topological load order), missing (unknown dependency names), hasCycle (cycle detected). ok() returns true when missing is empty and hasCycle is false
resolve(requested, isKnown, getDependencies) → ResolveResult Resolves dependencies via Kahn's algorithm. Returns the reachable, known modules in load order plus diagnostic info about missing deps and cycles. Callers decide policy: load paths treat !ok() as a hard failure; teardown paths use .order only

Takes callback functions (IsKnownFn, GetDependenciesFn) so it has no coupling to the registry implementation.

SubprocessContainer

Files: src/containers/subprocess/subprocess_container.h, src/containers/subprocess/subprocess_container.cpp

Purpose: Concrete ModuleContainer implementation for subprocess-based module isolation using Boost.Process v2 and Boost.Asio. Handles process lifecycle (spawn, monitor, kill) and credential delivery via Unix domain sockets. Knows nothing about what type of module runs inside the subprocess.

  • Uses boost::process::v2::process for subprocess spawning and boost::asio::io_context for async I/O
  • Background io_context thread with work guard for non-blocking async read and wait callbacks
  • Async read loop for stdout/stderr with line buffering
  • Synchronous kill with graceful SIGTERM → SIGKILL escalation (5s timeout)
  • Unix domain socket for token delivery (scoped by LOGOS_INSTANCE_ID)
  • Token-listener authentication (CWE-940 / F-010, F-012): the socket path is predictable and world-writable, so before writing the auth token sendTokenToProcess() verifies the connected peer's credentials. The peer uid must match ours and, when the child pid is known, the peer pid must equal the spawned child — read via SO_PEERCRED on Linux and via getpeereid() + getsockopt(SOL_LOCAL, LOCAL_PEERPID) on macOS, so both platforms enforce the uid + pid gate (peerIsTrusted()). It additionally refuses to deliver the credential to a listener owned by a different uid via the shared socketPeerIsSameUid() helper (peer_credentials.h) — the mirror of the receiver's owner-only (0600) socket node (F-012). A mismatched peer is treated like a failed connect: the token is never written and the send fails closed, so a co-tenant pre-binding the path cannot intercept the secret. The named-path race is closed completely only by a future socketpair()-fd handoff.
  • A std::mutex (s_processesMutex) protects the s_processes map against concurrent access

ModuleContainer interface: id()"subprocess", canHandle(), launch(), sendToken(), terminate(), terminateAll(), hasModule(), pid(), getAllPids()

Static process management API (used by tests):

Method Description
startProcess(name, executable, arguments, callbacks) → bool Launch a subprocess with async output monitoring
sendTokenToProcess(name, token) → bool Send auth token via Unix domain socket. Re-validates name (logos::isValidModuleName) before deriving the socket path (refuses and tears down the entry for an invalid name), and authenticates the listener's uid before sending
terminateProcess(name) Gracefully terminate a specific process
terminateAllProcesses() Terminate all managed processes
hasProcess(name) → bool Check if a process entry exists
getProcessId(name) → int64_t Get PID for a named process
getAllProcessIds() → unordered_map Map all process names to PIDs
registerProcess(name) Register a placeholder process entry
clearAll() Clear all process entries

A backward-compat SubprocessManager header (src/containers/subprocess/subprocess_manager.h) inherits from CompositeRuntime and forwards static methods to SubprocessContainer, so test code that references the old name keeps compiling.

QtPluginRuntime

Files: src/runtimes/runtime_qt/qt_plugin_runtime.h, src/runtimes/runtime_qt/qt_plugin_runtime.cpp

Purpose: Concrete ModuleLoader implementation for the Qt plugin module format. Resolves the logos_host_qt binary path and builds the CLI arguments (--name, --path, --instance-persistence-path) the host binary expects. id() returns "qt-plugin".

CompositeRuntime

Files: src/logos_core/composite_runtime.h, src/logos_core/composite_runtime.cpp

Purpose: Implements the ModuleRuntime interface by pairing a ModuleContainer (where/how to run) with a ModuleLoader (what to load). The default registration in ModuleManager creates CompositeRuntime(SubprocessContainer, QtPluginRuntime). id() returns "qt-plugin+subprocess".

ProcessStats (external dependency)

Source: process-stats library (linked as a static dependency)

Purpose: CPU and memory monitoring for loaded module processes.

API (namespace ProcessStats):

Method Description
getProcessStats(pid) → ProcessStatsData Return CPU %, CPU time, and memory usage for a process
getModuleStats(processIds) → char* Return JSON array of stats for all provided module processes

LogosHost

Files: src/runtimes/runtime_qt/host/logos_host.cpp, src/runtimes/runtime_qt/host/command_line_parser.h/cpp, src/runtimes/runtime_qt/host/module_initializer.h/cpp, src/runtimes/runtime_qt/host/qt/qt_app.h/cpp, src/containers/subprocess/token_receiver.h/cpp

Purpose: Lightweight subprocess (logos_host_qt) that loads a single Qt module. Parses --name, --path, optional --instance-persistence-path, and optional --transport-set (per-module LogosTransportSet JSON; empty = global-default LocalSocket only) arguments. On startup, token receipt (container concern) is separated from module loading (runtime concern): the host first receives its auth token via subprocess container IPC (an owner-only Unix socket that authenticates the connecting parent's uid before accepting the token — see Token-handoff socket hardening), then loads the Qt plugin and constructs the LogosAPI with the parsed transport set (explicit-transport ctor when provided, single-arg ctor otherwise). Registers the module with the remote object registry and runs the Qt event loop. A logos_host compatibility symlink is installed for backward compatibility with downstream consumers.

Because a directly-invoked host receives --name straight from its command line (not via the registry, which validates names at discovery time), SubprocessTokenReceiver::receive independently re-validates the module name — and the LOGOS_INSTANCE_ID it appends — as a single safe path segment (logos::isSafePathSegment) before building the logos_token_<name> socket path. Without this defense-in-depth check, a name like seg/../victim would make QLocalServer::removeServer() unlink an attacker-chosen file outside the temp directory.

C API

The public C API (logos_core.h) is the only exported interface. All functions use LOGOS_CORE_EXPORT for shared library visibility.

Lifecycle:

Function Description
logos_core_init(argc, argv) Initialize the library
logos_core_start() Discover modules and initialize capability module
logos_core_cleanup() Terminate all modules and clean up

Module Management:

Function Description
logos_core_add_modules_dir(dir) Add a module directory to scan (duplicates ignored)
logos_core_set_persistence_base_path(path) Set base directory for module instance persistence
logos_core_set_module_transports(name, json) Register a per-module transport set (JSON, see logos-cpp-sdk shape). Forwarded to the child via --transport-set so its LogosAPIProvider binds every listener instead of only the global default LocalSocket. Must be called before the module is loaded; empty clears the entry
logos_core_set_access_policy(json) Install the inter-module access policy (version + mode + per-target allowedCallers allowlists). Core parses it and registers the per-target restrictions with capability_module, which denies token issuance (and thus calls) for disallowed callers when mode is enforce. Under enforce, restrictions are also auto-derived from the dependency graph (a module may only call its declared dependencies; allowed callers = loaded dependents + trusted core/core_service, re-pushed on load/unload); an explicit entry overrides the derived set for that target. Call before modules load; NULL/empty clears it
logos_core_load_module(name, with_dependencies) → int Load a module (1 = success, 0 = failure). When with_dependencies is true, resolves the dependency tree and loads in topological order
logos_core_unload_module(name, with_dependents) → int Unload a module. When with_dependents is true, cascade unloads every loaded transitive dependent leaves-first. Returns 1 only if every step succeeded
logos_core_get_module_dependencies(name, recursive) → char** Modules that name depends on (forward edges). recursive=true walks the forward graph transitively. Unknown names yield an empty array. Caller frees
logos_core_get_module_dependents(name, recursive) → char** Modules that depend on name (reverse edges). recursive=true walks transitively. Unknown names yield an empty array. Caller frees
logos_core_process_module(path) → char* Process module file, return name (caller frees)
logos_core_refresh_modules() Re-scan module directories

Queries:

Function Description
logos_core_get_loaded_modules() → char** Null-terminated array of loaded names (caller frees)
logos_core_get_known_modules() → char** Null-terminated array of known names (caller frees)
logos_core_get_module_stats() → char* JSON array of CPU/memory stats (caller frees)
logos_core_get_token(key) → char* Get auth token by key (caller frees)

Thread Safety

Category Guarantee
logos_core_load_module, logos_core_unload_module Serialised by a single internal mutex — safe to call concurrently from multiple threads. The cascade variant (with_dependents=true) holds the lock for the entire leaves-first teardown so a late-arriving load can't interleave between tearing down the dependents and the target
logos_core_get_known_modules, logos_core_get_loaded_modules Protected by a shared reader-writer lock — safe to call concurrently with each other and with the mutating functions above
logos_core_refresh_modules Protected by ModuleRegistry's reader-writer lock (write side) — safe for concurrent registry access but not serialised against load/unload
logos_core_init, logos_core_start, logos_core_cleanup Not thread-safe — must be called from a single thread during startup/shutdown

Build Artifacts

Artifact Description
liblogos_core.{so,dylib,dll} Core shared library (C API)
logos_host_qt Qt module subprocess host binary
logos_host Compatibility symlink → logos_host_qt
logos_core_tests Google Test suite

Operational

Nix provides reproducible builds with all dependencies managed automatically.

Build everything (binaries + libraries + headers):

nix build

The result includes:

  • result/bin/logos_host_qt — Qt module host binary
  • result/bin/logos_host — Compatibility symlink → logos_host_qt
  • result/lib/liblogos_core.{so,dylib} — Core library
  • result/include/ — Headers (logos_core.h, interface.h)

Build individual components:

nix build '.#logos-liblogos-bin'       # Binaries only
nix build '.#logos-liblogos-lib'       # Libraries only
nix build '.#logos-liblogos-include'   # Headers only
nix build '.#logos-liblogos-tests'     # Test suite
nix build '.#logos-liblogos-modules'   # Built-in modules
nix build '.#portable'                 # Portable variant (LOGOS_PORTABLE_BUILD=ON)

Run tests:

nix build '.#logos-liblogos-tests'
./result/bin/logos_core_tests

# Run specific tests
./result/bin/logos_core_tests --gtest_filter=AppLifecycleTest.*

# List all tests
./result/bin/logos_core_tests --gtest_list_tests

Development shell:

nix develop

Override local dependencies:

nix build --override-input logos-cpp-sdk path:../logos-cpp-sdk

CMake

Prerequisites:

  • CMake 3.14+
  • C++17 compatible compiler
  • Qt 6 with Core and RemoteObjects modules
  • Boost (with Process component)
  • nlohmann_json
  • OpenSSL (transitive: SDK's plain-C++ TCP+TLS transport)
  • CLI11
  • Google Test (fetched via FetchContent if not system-installed)

Build:

mkdir -p build && cd build
cmake ..
make -j$(nproc)

The logos_host_qt binary will be in build/bin/ and liblogos_core in build/lib/.

Run tests:

make -j$(nproc)
ctest --output-on-failure

Note: CMake expects the logos-cpp-sdk and logos-module libraries to be available. For a pre-built SDK it uses find_package(logos-cpp-sdk) (the package config carries OpenSSL / Boost / nlohmann_json / Qt as transitive deps); otherwise it falls back to the source tree (cpp/CMakeLists.txt).

Dev vs Portable Builds

Controlled by the LOGOS_PORTABLE_BUILD CMake flag:

  • Dev (default): Looks for LGX variants with -dev suffix (e.g., linux-amd64-dev)
  • Portable (-DLOGOS_PORTABLE_BUILD=ON): Looks for variants without suffix (e.g., linux-amd64)

Build portable with Nix: nix build '.#portable'

Consumers

logos-liblogos is a library consumed by two frontends:

Examples

Basic C API Usage

#include "logos_core.h"

int main(int argc, char *argv[]) {
    logos_core_init(argc, argv);
    logos_core_add_modules_dir("/path/to/modules");

    logos_core_start();
    logos_core_load_module("chat", false);

    char** loaded = logos_core_get_loaded_modules();
    for (int i = 0; loaded[i] != NULL; i++) {
        printf("Loaded: %s\n", loaded[i]);
    }
    free(loaded);

    logos_core_cleanup();
    return 0;
}

Loading with Dependencies

// Resolves the dependency tree and loads in correct order
logos_core_load_module("my_module", true);

Continuous Integration

GitHub Actions workflow (.github/workflows/ci.yml) runs on every push/PR to master:

  1. Checkout code
  2. Install Nix with flakes enabled
  3. Use cachix cache (logos-co)
  4. Build: nix build .#logos-liblogos-tests
  5. Run: ./result/bin/logos_core_tests

Supported Platforms

  • macOS (aarch64-darwin, x86_64-darwin)
  • Linux (aarch64-linux, x86_64-linux)