Skip to content
Closed
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
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ OpenWorker is local-first. Everything lives on your machine: the agent loop, you

Prerequisites: Python 3.10+, Node 20+, and (for the desktop shell) the Rust toolchain via [rustup](https://rustup.rs/).

Linux desktop builds also need WebKit/Tauri native packages. On Ubuntu 24.04:

```shell
bash packaging/install_linux_desktop_deps.sh
```

```shell
git clone https://github.com/andrewyng/openworker
cd openworker
Expand All @@ -111,9 +117,9 @@ The standalone server creates a per-launch token at
For direct API calls, send its value in the `X-OpenWorker-Token` header. The
desktop app uses an in-memory launch token instead and never writes it to disk.

To run the full desktop app instead of the browser UI, replace step 3 with `npm run tauri dev` (from `surfaces/gui/`) - the Tauri shell launches the window and supervises the server itself.
To run the full desktop app instead of the browser UI, replace step 3 with `npm run tauri dev` (from `surfaces/gui/`) - the Tauri shell launches the window and supervises the server itself. On Linux, run `packaging/install_linux_desktop_deps.sh` first.

Tests: `.venv/bin/pytest` (server), `npm test` and `npm run e2e` in `surfaces/gui` (GUI unit + hermetic end-to-end). Desktop bundles are built with `packaging/build_dmg.sh` / `packaging/build_windows.ps1`.
Tests: `.venv/bin/pytest` (server), `npm test` and `npm run e2e` in `surfaces/gui` (GUI unit + hermetic end-to-end). Desktop bundles are built with `packaging/build_dmg.sh` / `packaging/build_windows.ps1` / `packaging/build_linux.sh`.

## Repository layout

Expand All @@ -122,7 +128,7 @@ Tests: `.venv/bin/pytest` (server), `npm test` and `npm run e2e` in `surfaces/gu
| `coworker/` | Python backend - agent engine, model providers, connectors, MCP client, memory, automations |
| `surfaces/gui/` | Desktop app - React UI + Tauri shell that supervises the server |
| `stt/` | Speech-to-text sidecar (Rust) for voice input |
| `packaging/` | Installer builds (macOS DMG, Windows), auto-update manifest, dev bootstrap |
| `packaging/` | Installer builds (macOS DMG, Windows, Linux), auto-update manifest, dev bootstrap |
| `docs/` | Design specs and decision logs |
| `tests/` | Backend test suite |

Expand Down
84 changes: 84 additions & 0 deletions packaging/build_linux.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# Build the Linux desktop app from source.
#
# Steps:
# 1. PyInstaller-bundle the Python server into a standalone onedir sidecar.
# 2. Stage that sidecar under the Tauri resources directory.
# 3. Run `tauri build` for Linux bundles.
#
# Prerequisites:
# - Ubuntu/Debian native packages from `packaging/install_linux_desktop_deps.sh`.
# - Rust via rustup.
# - Node/npm and GUI dependencies (`npm ci` or `npm install` in `surfaces/gui`).
# - The repo venv created by `packaging/setup_dev_env.sh`.
#
# Default bundles are `deb,appimage`. Override with:
# packaging/build_linux.sh rpm
set -euo pipefail

HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/.." && pwd)"
GUI="$ROOT/surfaces/gui"
VENV="$ROOT/.venv"
BUNDLES="${1:-deb,appimage}"

require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "Required command '$1' not found on PATH." >&2
exit 1
fi
}

require_pkg_config() {
if ! pkg-config --exists "$1"; then
echo "Missing pkg-config package '$1'. Run: packaging/install_linux_desktop_deps.sh" >&2
exit 1
fi
}

require_cmd rustc
require_cmd npm
require_cmd pkg-config
require_pkg_config libsoup-3.0
require_pkg_config webkit2gtk-4.1

if [ ! -x "$VENV/bin/pyinstaller" ]; then
echo "PyInstaller not found at $VENV/bin/pyinstaller. Run: bash packaging/setup_dev_env.sh" >&2
exit 1
fi

TRIPLE="$(rustc -vV | sed -n 's/host: //p')"

# Build the bundled whisper.cpp CPU backend for distribution, not for the
# build host. Some VMs expose AVX2 without FMA, which breaks ggml's AVX2 path,
# and native CPU flags can also produce binaries that fail on older Linux hosts.
export GGML_NATIVE="${GGML_NATIVE:-OFF}"
export GGML_AVX="${GGML_AVX:-OFF}"
export GGML_AVX2="${GGML_AVX2:-OFF}"
export GGML_AVX_VNNI="${GGML_AVX_VNNI:-OFF}"
export GGML_AVX512="${GGML_AVX512:-OFF}"
export GGML_AVX512_VBMI="${GGML_AVX512_VBMI:-OFF}"
export GGML_AVX512_VNNI="${GGML_AVX512_VNNI:-OFF}"
export GGML_AVX512_BF16="${GGML_AVX512_BF16:-OFF}"
export GGML_BMI2="${GGML_BMI2:-OFF}"
export GGML_F16C="${GGML_F16C:-OFF}"
export GGML_FMA="${GGML_FMA:-OFF}"
export GGML_SSE42="${GGML_SSE42:-OFF}"

echo "==> [1/3] PyInstaller: bundling openworker-server ($TRIPLE)"
"$VENV/bin/pyinstaller" --noconfirm --clean \
--distpath "$HERE/dist" --workpath "$HERE/build" "$HERE/openworker-server.spec"

echo "==> [2/3] staging sidecar resources"
mkdir -p "$GUI/src-tauri/binaries"
rm -rf "$GUI/src-tauri/binaries/sidecar" "$GUI/src-tauri/binaries/openworker-server-$TRIPLE"
cp -R "$HERE/dist/openworker-server" "$GUI/src-tauri/binaries/sidecar"
chmod +x "$GUI/src-tauri/binaries/sidecar/openworker-server"

echo "==> [3/3] tauri build (--bundles $BUNDLES)"
( cd "$GUI" && npm run tauri build -- --bundles "$BUNDLES" )

BUNDLE="$GUI/src-tauri/target/release/bundle"
echo
echo "Done. Linux bundles under: $BUNDLE"
find "$BUNDLE" -type f \( -name '*.deb' -o -name '*.AppImage' -o -name '*.rpm' \) -print 2>/dev/null || true
28 changes: 28 additions & 0 deletions packaging/install_linux_desktop_deps.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Install Ubuntu packages required to build/run the Tauri desktop shell.
#
# Tested on Ubuntu 24.04. The browser/dev UI does not need these native packages,
# but `npm run tauri dev` and `packaging/build_linux.sh` do.
set -euo pipefail

if ! command -v apt-get >/dev/null 2>&1; then
echo "This helper supports apt-based Ubuntu/Debian systems only." >&2
exit 1
fi

sudo apt-get update
sudo apt-get install -y \
build-essential \
cmake \
curl \
file \
libasound2-dev \
libayatana-appindicator3-dev \
libclang-dev \
librsvg2-dev \
libsoup-3.0-dev \
libssl-dev \
libwebkit2gtk-4.1-dev \
libxdo-dev \
pkg-config \
wget
6 changes: 4 additions & 2 deletions packaging/setup_dev_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
VENV="$ROOT/.venv"

python3 -m venv "$VENV"
# The coworker package (server, engine, connectors) + inbound-messaging extras.
# The coworker package (server, engine, connectors) + extras needed by the
# documented local test/build flows. Keep this aligned with CI: a fresh venv
# should not rely on optional packages already installed on the machine.
# aisuite comes in as a regular dependency (git-pinned in pyproject.toml until
# the next PyPI release).
"$VENV/bin/pip" install --quiet --upgrade pip
"$VENV/bin/pip" install --quiet -e "$ROOT[messaging,dev]"
"$VENV/bin/pip" install --quiet -e "$ROOT[messaging,dev,bedrock]" pyinstaller typer tzdata

"$VENV/bin/python" -c 'import aisuite, coworker' # fail loudly if the wiring broke
echo "Ready: $VENV"
Expand Down
158 changes: 158 additions & 0 deletions reports/linux-local-smoke-2026-08-25.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Linux Local Smoke Test - 2026-08-25

Environment:
- OS: Ubuntu 24.04.4 LTS (Noble)
- Python: 3.11.9
- Node: 22.22.0
- npm: 10.9.4
- Rust: rustc 1.95.0-nightly

## Result

OpenWorker works locally on Linux for the Python server and browser GUI from source.
The initial Linux desktop `.deb` build also works from a fresh Ubuntu 24.04 VM
after installing the package prerequisites added in this PR.

Verified:
- `bash packaging/setup_dev_env.sh` completed successfully.
- `npm install` completed successfully in `surfaces/gui`.
- Server started with `.venv/bin/openworker-server --cwd /home/luis.lobo@epicio.com/dev/luislobo/openworker --port 8765`.
- Server endpoints responded with valid JSON using the sidecar token:
- `/v1/health`
- `/v1/settings`
- `/v1/sessions`
- `/v1/personas`
- Browser UI started with `npm run dev -- --host 127.0.0.1` and served `http://127.0.0.1:1420/`.
- GUI production build passed with `npm run build`.
- GUI unit tests passed: 134 tests.
- GUI E2E tests passed on Linux Chromium: 221 tests.
- Backend tests passed after installing Bedrock optional dependencies: 1,861 passed, 1 skipped.
- Barebones Ubuntu 24.04 VM smoke passed from a fresh `.venv` and fresh
`node_modules`:
- `bash packaging/setup_dev_env.sh`
- `.venv/bin/pytest tests/test_config.py tests/test_environment.py tests/test_bedrock_provider.py -q`
- `npm install`
- `npm test -- --run`
- `npm run build`
- `packaging/build_linux.sh deb`
- VM Linux bundle produced:
`surfaces/gui/src-tauri/target/release/bundle/deb/OpenWorker_0.2.1_amd64.deb`
- Local `.deb` install passed with `sudo apt-get install -y ./surfaces/gui/src-tauri/target/release/bundle/deb/OpenWorker_0.2.1_amd64.deb`.
- Installed app launch passed from `/tmp`: `/usr/bin/openworker-desktop`
spawned `/usr/lib/OpenWorker/sidecar/openworker-server` and the GUI loaded
`/v1/health`, `/v1/settings`, `/v1/sessions`, `/v1/personas`, and websocket
endpoints successfully.

## Issues Found

### 1. README test command fails after the documented bootstrap

The README says backend tests can be run with `.venv/bin/pytest`. After running the documented bootstrap (`bash packaging/setup_dev_env.sh`), the full suite failed only in `tests/test_bedrock_provider.py` because `boto3`/`botocore` were not installed.

Repro:

```shell
bash packaging/setup_dev_env.sh
.venv/bin/pytest -q
```

Observed failure class:

```text
ModuleNotFoundError: No module named 'boto3'
ModuleNotFoundError: No module named 'botocore'
```

Workaround verified:

```shell
.venv/bin/pip install -e '.[bedrock]'
.venv/bin/pytest tests/test_bedrock_provider.py -q
.venv/bin/pytest -q
```

PR fix:
- Include the `bedrock` extra in `packaging/setup_dev_env.sh` for developer/test environments.

### 2. Tauri desktop build fails without Linux system packages

`npm run tauri -- build` failed on this Ubuntu 24.04 machine because native Tauri/WebKit dependencies are not installed.

Repro:

```shell
cd surfaces/gui
npm run tauri -- build
```

Observed failure:

```text
The system library `libsoup-3.0` required by crate `soup3-sys` was not found.
The file `libsoup-3.0.pc` needs to be installed and the PKG_CONFIG_PATH environment variable must contain its parent directory.
```

Confirmed missing:

```shell
pkg-config --modversion libsoup-3.0
pkg-config --modversion webkit2gtk-4.1
```

Both commands failed on this machine.

Ubuntu 24.04 package candidates:

```shell
build-essential
cmake
curl
file
libasound2-dev
libsoup-3.0-dev
libwebkit2gtk-4.1-dev
libayatana-appindicator3-dev
libclang-dev
librsvg2-dev
libssl-dev
libxdo-dev
pkg-config
wget
```

PR fixes:
- Add Linux desktop prerequisites to the README.
- Add a Linux packaging/build script alongside `build_dmg.sh` and `build_windows.ps1`.
- Consider adding Linux artifacts to `packaging/make_update_manifest.py` if official Linux desktop releases are intended.

### 3. Linux desktop build needs portable `whisper.cpp` CPU flags

The clean Ubuntu 24.04 VM exposed `avx2` but not `fma` in `/proc/cpuinfo`.
The bundled `whisper.cpp` build enabled an AVX2 path that calls FMA intrinsics,
which failed during `whisper-rs-sys` compilation:

```text
error: inlining failed in call to 'always_inline' '_mm256_fmadd_ps': target specific option mismatch
```

PR fix:
- `packaging/build_linux.sh` defaults the `GGML_*` CPU feature flags to a
portable distribution build. Builders can still override those environment
variables for host-optimized local packages.

### 4. Installed Linux `.deb` app must resolve bundled sidecar under `/usr/lib`

The initial local install launched, but when started from the source checkout it
fell back to the repo `.venv` sidecar. Starting from `/tmp` confirmed that the
Linux `.deb` executable lives at `/usr/bin/openworker-desktop` while Tauri
resources are installed under `/usr/lib/OpenWorker/sidecar`.

PR fix:
- Add the Linux `.deb` resource path to the sidecar resolver before the dev
`.venv` fallback.

## Known Linux Feature Limits From Source Inspection

- Voice Input is explicitly unsupported on Linux in `surfaces/gui/src-tauri/src/lib.rs`.
- Keep-awake is a no-op on Linux in `surfaces/gui/src-tauri/src/lib.rs`.
- Official downloads/update manifest currently cover macOS and Windows, not Linux.
13 changes: 13 additions & 0 deletions surfaces/gui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ A fresh checkout has no server to run — create the venv both flows below expec
bash packaging/setup_dev_env.sh # → .venv (server + aisuite)
```

On Linux, install the native WebKit/Tauri packages before running the desktop
shell:

```bash
bash packaging/install_linux_desktop_deps.sh
```

## Run it (browser, two terminals)

1. **Start the server** (needs a model key, e.g. `OPENAI_API_KEY`, in the environment —
Expand Down Expand Up @@ -44,6 +51,12 @@ npm install # first time
npm run tauri dev # builds the shell, launches the window, starts the server
```

Linux release-style bundles can be built from the repo root with:

```bash
packaging/build_linux.sh
```

## Tests

```bash
Expand Down
3 changes: 3 additions & 0 deletions surfaces/gui/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ fn server_bin() -> PathBuf {
if let Some(contents) = dir.parent() {
candidates.push(contents.join("Resources").join("sidecar").join(exe_name));
}
if cfg!(target_os = "linux") {
candidates.push(PathBuf::from("/usr/lib/OpenWorker/sidecar").join(exe_name));
}
candidates.push(dir.join(exe_name)); // legacy onefile externalBin slot
for c in candidates {
if c.exists() {
Expand Down