Linux port: shared core, Qt6 GUI, Velopack AppImage self-update, CI - #14
Open
Negative-Star-Innovators wants to merge 20 commits into
Open
Linux port: shared core, Qt6 GUI, Velopack AppImage self-update, CI#14Negative-Star-Innovators wants to merge 20 commits into
Negative-Star-Innovators wants to merge 20 commits into
Conversation
added 20 commits
August 18, 2026 07:51
Top-level linux/CMakeLists.txt driver building the core static library (engine and gui subdirectories slot in later), per-OS source selection in core/CMakeLists.txt with curl/OpenSSL/libsecret/Threads on Linux, and build instructions in linux/README.md.
- platform_compat.h: MSVC/POSIX shims (_snwprintf, _wtof, localtime_s, _wgetenv, SOCKET typedef, Winsock constants), keeping the public SOCKET-based http_server surface unchanged - http_server: POSIX sockets (select nfds fix, socklen_t); behavior (loopbackOnly, dual-stack, streaming SendChunk) identical - utils: UTF-8<->wstring via codecvt, XDG config path fallback (~/.config/agentredactor, AGENTREDACTOR_CONFIG_DIR still wins), /proc/self/exe, RFC4122 v4 UUID, libcurl implementations of HttpGetString/HttpDownloadFile/HttpDownloadFileSegmented - proxy_engine: libcurl upstream client (streaming SSE, credential header substitution, gzip/deflate) behind #ifdef; WinHTTP branch untouched - control_server: token via RAND_bytes, control.json with 0600 perms - secure_storage: shared interface moves to core/include; Windows impl (DPAPI/CNG/Hello) unchanged; new Linux impl with OpenSSL AES-256-GCM, libsecret machine key with /etc/machine-id PBKDF2 fallback, and typed-master-password protection (PBKDF2-HMAC-SHA256 wrapped session key) - settings_manager: per-OS password entry points - localization.h/constants.h/log_manager/model_downloader/pii_detector: de-Windows-ified headers, XDG model fallback dir, CPU-only EP on Linux, UTF-8 model path for ORT - core-smoke dev target verifies SettingsManager + RegexEngine against a temp config dir
- Move EngineApp into core (engine_app.h/engine_app.cpp) shared by both
platforms; Windows vcxproj and main.cpp include paths updated.
- Linux engine main: SIGPIPE ignore, flock-based single-instance lock,
config dir chmod 700, --console mode, termios no-echo password reads.
- Linux control API client over libcurl (control.json bearer auth).
- CLI gains typed master-password flow on Linux: 'password enable' prompts
twice, gated commands prompt once, /unlock accepts {"password": ...}
(Linux-only control API extension; Windows Hello path unchanged).
- fix(http_server): shutdown listen socket before joining listener thread
in Stop() — closing first raced with select() and aborted on glibc
(FD_SET bit out of range) when restarting listeners; latent on all
platforms.
- engine version reported from windows/version.txt via global
AR_VERSION_STRING compile definition.
- tests/cli/conftest.py: engine binary resolves to linux/build/engine/ agentredactor off Windows (AGENTREDACTOR_ENGINE_BIN overrides); run_cli gains input= to pipe a typed master password. - tests/cli/test_cli.py: skip the two Windows-Hello consent tests off Windows; branch the 'protection is not enabled' message per platform. - tests/cli/test_cli_linux_password.py (new): typed-master-password gate mirroring the Hello consent gate — enable/disable round-trip, locked session after restart, EOF/wrong/correct password behavior. - tests/linux/test_engine_smoke.py (new): engine start/stop, control.json 0600, config dir 0700, single-instance lock keeps the first engine's control.json (second instance exits 0 quietly, mirroring Windows). - tests/migration: run --selftest-migrate-settings against the Linux engine binary (AGENTREDACTOR_EXE still overrides). - tests/gui: ignore the whole directory off Windows (no GUI there yet); kill list matches the extensionless Linux engine binary. - READMEs: per-suite pytest invocations (the suites share the 'conftest' module name and cannot be collected in one process) and the python3-pytest-asyncio dependency.
…mplate - linux/gui/: Qt6 Widgets app (agentredactor-gui) mirroring the Windows MainWindow/HomePage surface — profile sidebar, profile/regex/keywords/ detection/password/statistics/session-redactions/logs/settings cards, lock overlay with typed master password (Linux has no Windows Hello), blocking model-download dialog, close-to-tray with control-panel fallback when no system tray is available, 10-minute inactivity re-lock, quit confirmation. - AppState mirror: engine spawn via QProcess::startDetached when the control API is unreachable, 1 s /status + /settings poll on a worker thread (profilesRevision diff triggers profile reload; full settings diff refreshes cards), engine stopped on quit only when the GUI spawned it, otherwise PUT /settings/lock when protected. - HTTP stack: reuses the tested curl ControlApiClient from linux/engine/ (one HTTP stack across the Linux product); ControlApiClient gains LastStatus() so the GUI can distinguish 403-while-locked. - XDG autostart: the Start-on-boot toggle and GUI startup reconcile $XDG_CONFIG_HOME/autostart/agentredactor.desktop (Exec=<gui> --tray-only) with the persisted setting, so CLI changes apply too. - Localization structure without translations yet: all strings via tr(), TranslatorLoader installs agentredactor_<tag>.qm from appLanguage (empty = system locale) and flips RTL via core IsLanguageRtl — the Windows Strings resw files can be converted to .ts later with no code changes. - SIGTERM/SIGINT self-pipe bridge -> graceful quit so the engine stop/lock decision also runs under systemd and loginctl. - linux/systemd/agentredactor.service: user unit template for headless boot-time startup. - tests/linux/test_gui_smoke.py: offscreen (QT_QPA_PLATFORM=offscreen) end-to-end checks — engine spawn/stop ownership, lock-on-quit when protected, autostart reconciliation in both directions.
- buildUi added the cards widget to the QSplitter instead of the QScrollArea, leaving the splitter managing three widgets (sidebar, empty scroll area, cards) and squeezing the cards into a narrow strip. - prevProfilesRevision_ initialized to 0 matched a fresh engine's revision, so the first settings snapshot never triggered the initial profile load and the sidebar stayed empty until the first mutation.
- AppUpdateManager (Velopack C/C++ lib, pinned 1.2.0 via linux/fetch-velopack.sh) behind a new AR_SELFRELEASE CMake option, mirroring AGENTREDACTOR_SELFRELEASE: startup check + Settings 'Check for updates' button, restart now/later prompt, WaitExitThenApplyUpdates + restart. Same loopback-only AGENTREDACTOR_UPDATE_FEED and AGENTREDACTOR_UPDATE_AUTOAPPLY test hooks as Windows. - GUI restarts the engine on version mismatch after a self-update (engine ships inside the AppImage next to the GUI); VelopackApp::Run first in main(). - linux/build-release.sh: Release build, AppDir staging (Qt libs/plugins bundled dynamically + LGPL-Qt-notice.txt, $ORIGIN rpaths), vpk pack channel linux. - First run symlinks ~/.local/bin/agentredactor to the bundled CLI. - Cloudflare worker: allow the linux channel and *.AppImage files. - tests/linux/test_update_feed.py: packs a vNext feed and asserts the shipped AppImage self-updates against a loopback feed (real apply + restart verified).
ubuntu-24.04: build core+engine+GUI, run cli/migration/linux suites (separate pytest processes), pack the AppImage on every PR (artifact dry-run), publish channel 'linux' to R2 only on v* tags or manual dispatch with publish checked — mirroring release-selfrelease.yml gating and secrets.
ARM64 (mirrors the win-arm64 split): - fetch-velopack.sh also extracts the linux_arm64 velopack_libc .so; the GUI CMake picks x64/arm64 by CMAKE_SYSTEM_PROCESSOR. - build-release.sh derives vpk runtime/channel from the host arch: x64 keeps channel 'linux', aarch64 packs -r linux-arm64 -c linux-arm64 and uses the aarch64 Qt plugin dir. Updater feed URL is arch-aware via __aarch64__. - build-linux.yml gains an arm64 leg on ubuntu-24.04-arm (aarch64 onnxruntime tarball); publish uploads both channels and prunes both prefixes. - Feed worker allowlist gains the linux-arm64 channel. CI fixes surfaced by the first PR run: - platform_compat.h: include winsock2.h/ws2tcpip.h before windows.h on _WIN32 (core headers use SOCKET and no longer rely on the includer's pch ordering; fixes the Windows vcxproj build). - build-linux.yml: stage the NER model into ~/.local/share/agentredactor/models (companions from windows/models/, weights from the R2 models endpoint, cached) — the engine keeps proxy ports closed until weights exist, and the cli/linux suites spawn the real engine. - Run the AppImage self-update E2E in CI after packing.
platform_compat.h now includes winsock2.h on _WIN32 (needed since core headers like http_server.h use SOCKET without relying on the includer). windows/include/system_tray.h included bare <windows.h> before any core header, compiling winsock v1 first and clashing with winsock2 in the non-pch batch — apply the same _WINSOCKAPI_ guard pch.h uses so v1 stays out and winsock2 (from platform_compat.h) provides SOCKET.
proxy_engine.h used to include <winhttp.h> directly; the port swapped it for platform_compat.h, which didn't pull winhttp — breaking the Windows build of proxy_engine.cpp. platform_compat.h now includes winhttp.h on _WIN32.
…abled macro windows.h (winevent.h) maps IsLoggingEnabled to IsLoggingEnabledW under UNICODE. On main the header self-included windows.h so declaration, definition and all call sites were mangled consistently; the port dropped that include, leaving the class declaration clean while log_manager.cpp's definition (after utils.h pulls windows.h) was renamed. Restoring the platform include in the header makes the macro visible at the declaration again, matching every other TU.
- Tray Language submenu and Settings language combo, both applying live
via the settings poll + QEvent::LanguageChange (no restart, unlike
Windows); RTL layout flip included. CLI 'set app-language' already worked
engine-side and now retranslates the running GUI too.
- retranslateUi now covers the menu bar, form labels, PII grid and the
dynamic regex/keyword rows; the tray menu retranslates via TrayIcon.
- English wording aligned to the Windows resw values where semantics
matched (updater, model download, validation, remove-profile/quit/clear
dialogs, PII_Type_* labels) so those translations are reused for free.
- i18n/sync_ts.py scans tr() sources and fills agentredactor_<locale>.ts
from windows/Strings/<tag>/Resources.resw (normalizing '&' accelerators
and {0} <-> %1 placeholders): 61/100 strings per language reused.
TrayMenu_StartOnBoot is excluded (de/fr/pt translations say 'with
Windows').
- i18n/bootstrap_translations.py machine-translates the 39 Linux-only
strings (typed master password flow, tray/quit wording, UI labels) with
Google Translate, same bootstrap convention as Windows; placeholders are
verified to survive. Native review still needed.
- CMake compiles the .ts catalogs into :/i18n with lrelease
(qt6-l10n-tools; English-only fallback with a warning when absent).
- New tests: every CLI-supported language has a catalog; live language
switching (de/ar/zh-CN/en) while the GUI runs.
…Velopack state Velopack keeps downloaded packages in a machine-wide /var/tmp/velopack/<packId> dir that the test's HOME/XDG isolation does not cover. The vNext package staged by the self-update E2E survived the test, and the next real AppImage launch applied it in place — silently reverting the user's freshly packed AppImage to the older test build. The fixture now purges that state dir before and after the test. Also: skip CLI symlink creation under AppImage runs (applicationDirPath is an ephemeral /tmp/.mount_* there, so ~/.local/bin/agentredactor dangled after exit) and clean up such dangling links.
… immediately - build-release.sh stages the small model files (tokenizer/config/ calibration/model graph) next to the binaries, mirroring the Windows self-release split (build.ps1 /XF *.onnx_data). Without them EnsureModelFiles could never start the first-run weight download, so AppImage installs on clean machines stayed stuck with no detector. - GUI applies a newly selected language (Settings combo and tray menu) immediately instead of waiting for the settings-poll round-trip.
…dows The engine and the UI both append to the shared log. The CRT append stream seeks to EOF and then writes, so racing processes overwrite each other's lines — seen in the Self-Release E2E as a mangled '[UpdateManager] Up to date' line (only '.5, latest 1.1.5)' survived), which the updater health check polls for. Open the log with FILE_APPEND_DATA only (no FILE_WRITE_DATA) so every WriteFile lands atomically at EOF regardless of the file pointer, and write UTF-8 (previously the CRT 'C' locale mangled non-ASCII to '?', matching the Linux side which already writes UTF-8 under O_APPEND).
…sers Running the AppImage once now drops a two-line wrapper at ~/.local/bin/agentredactor that re-launches the AppImage file with --cli; the GUI binary (before Velopack/Qt startup) execs the bundled dual-mode engine/CLI binary with the remaining args. A symlink cannot reach inside the ephemeral /tmp/.mount_* AppImage mount, which is why the shim was previously skipped entirely for AppImage runs. The wrapper is rewritten on every launch, so moving the AppImage self-heals on the next run. Installed layouts keep the direct symlink to the binary. The update E2E now asserts the shim is created and drives the full chain (wrapper -> AppImage -> --cli -> usage output) end-to-end.
Stops app processes, unmounts stale AppImage mounts, and removes config, logs, downloaded model weights, the CLI shim, the autostart entry and Velopack staging so a machine can be returned to a first-run state.
- The window hard-coded 1000x900; on a 1280x800 screen that opens taller than the display and, since Wayland apps cannot reposition themselves, the title bar can be lost off the top edge for good. Clamp the initial size to the available work area (content already scrolls). - Mirror the Windows GUI (HomePage::LoadProfileList): when the engine reports zero profiles, seed one named 'Default' on the first free port from 8080 instead of landing the user on an empty form. The alias rides the existing Windows resw translations (sync_ts.py reuse, 52 catalogs).
GNOME does not provide server-side decorations; without the bundled adwaita/bradient decoration plugins Qt logs 'No decoration plugins available' and the window runs with no title bar at all (no minimize/ maximize/close).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Linux port of Agent Redactor: shared C++ core, a thin Linux engine/CLI binary, and a Qt6 Widgets GUI with tray, autostart and Velopack self-update via AppImage. Also adds the
build-linuxCI workflow.Architecture: all backend logic lives in the shared
core/library (now compiled by both platforms); each OS has a thin engine stub (windows/engine/main.cpp,linux/engine/main.cpp) and a native GUI frontend (WinUI 3 on Windows, Qt6 on Linux). This structure is what a future macOS port (SwiftUI over the same core) will plug into.What's in here
e318562): POSIX sockets, libcurl, OpenSSL, libsecret and XDG paths behind platform abstractions. Every#ifdef _WIN32branch is the original code path; Windows-visible changes are additive only (e.g.engineVersionin/status,LastStatus()on the control client). Windows project files got one-line updates for two headers/sources that moved intocore/.06fdfb0): same CLI surface and control API as Windows. Protection on Linux is a typed in-app master password (PBKDF2-HMAC-SHA256 → AES-256-GCM), not the OS login password.c48caf8):tests/cli,tests/migrationand a newtests/linuxsuite all run green on Ubuntu (37p/2s, 5p/2s, 8p).b1e2493,e1141e4): profile sidebar + settings cards mirroring the Windows Home surface, lock overlay, StatusNotifier tray (verified on Ubuntu GNOME), XDG autostart with two-way settings sync, systemd user unit template. English-only UI but structured for translation (tr()+TranslatorLoaderdriven by the existingappLanguagesetting, RTL-aware).ec55708): Velopack C/C++ lib (pinned 1.2.0, fetched bylinux/fetch-velopack.sh, not vendored in git) behind anAR_SELFRELEASECMake option mirroringAGENTREDACTOR_SELFRELEASE. Startup + manual update check, restart-now/later prompt, engine respawn on version mismatch.linux/build-release.shpacks the AppImage (channellinux, same R2 bucket). Same loopback-onlyAGENTREDACTOR_UPDATE_FEED/AGENTREDACTOR_UPDATE_AUTOAPPLYtest hooks as Windows, plus an update E2E that verifies a real AppImage self-update against a local feed.f341ca9):.github/workflows/build-linux.yml— builds, runs all three suites, packs the AppImage as a PR artifact; publishes thelinuxchannel to R2 only onv*tags or manual publish dispatch (same gating/secrets asrelease-selfrelease.yml).cloudflare/src/routes/updates.jsallowlist extended with thelinuxchannel and*.AppImage(additive; deploy stays manual).Testing
_WIN32branches); the existing Windows workflows are untouched and run on this PR for verification.Not in scope (per plan)
macOS, Linux ARM64, Linux localization beyond English, any control-API shape changes.