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
37 changes: 33 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Desktop release builds — macOS (.dmg, arm64 + Intel) and Windows (.msi + NSIS .exe).
# Desktop release builds — macOS (.dmg, arm64 + Intel), Windows (.msi + NSIS .exe), and
# Linux x64 (.AppImage + .deb).
#
# CI calls the SAME scripts developers run locally (packaging/build_dmg.sh and
# build_windows.ps1); this file only provisions the toolchain (Node, Rust, a Python venv at
# .venv with PyInstaller) and publishes the results.
# CI calls the SAME scripts developers run locally (packaging/build_dmg.sh,
# build_windows.ps1, build_linux.sh); this file only provisions the toolchain (Node, Rust, a
# Python venv at .venv with PyInstaller) and publishes the results.
#
# Triggers:
# - tag push `v*` → builds all targets and attaches them to a DRAFT GitHub Release
Expand Down Expand Up @@ -50,6 +51,8 @@ jobs:
slug: macos-arm64
- os: windows-latest
slug: windows
- os: ubuntu-latest # x64
slug: linux-x64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
Expand All @@ -66,6 +69,16 @@ jobs:
with:
workspaces: surfaces/gui/src-tauri

- name: Install Linux bundler dependencies
if: runner.os == 'Linux'
# Tauri's AppImage/.deb bundlers shell out to these; webkit2gtk is the embedded
# webview, the rest is the standard Tauri Linux prerequisite set.
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential libssl-dev libgtk-3-dev libwebkit2gtk-4.1-dev \
libappindicator3-dev librsvg2-dev patchelf libfuse2 fakeroot dpkg desktop-file-utils

- uses: actions/setup-python@v5
with:
python-version: "3.12"
Expand Down Expand Up @@ -120,6 +133,13 @@ jobs:
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: ./packaging/build_windows.ps1

- name: Build AppImage + .deb (Linux)
if: runner.os == 'Linux'
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: bash packaging/build_linux.sh

- name: Stage artifacts (versioned + stable names)
run: |
mkdir -p out
Expand All @@ -133,6 +153,15 @@ jobs:
# secret is configured). The .sig signs CONTENT, so the stable rename is safe.
SIG=$(ls "$BUNDLE"/nsis/*.exe.sig 2>/dev/null | head -1 || true)
[ -n "$SIG" ] && cp "$SIG" out/OpenWorker-windows-setup.exe.sig
elif [ "$RUNNER_OS" = "Linux" ]; then
cp "$BUNDLE"/appimage/*.AppImage out/
cp "$BUNDLE"/appimage/*.AppImage out/OpenWorker-${{ matrix.slug }}.AppImage
cp "$BUNDLE"/deb/*.deb out/
cp "$BUNDLE"/deb/*.deb out/OpenWorker-${{ matrix.slug }}.deb
# Updater signature for the AppImage (present only when the updater key secret
# is configured). The .sig signs CONTENT, so the stable rename is safe.
SIG=$(ls "$BUNDLE"/appimage/*.AppImage.sig 2>/dev/null | head -1 || true)
[ -n "$SIG" ] && cp "$SIG" out/OpenWorker-${{ matrix.slug }}.AppImage.sig
else
cp "$BUNDLE"/dmg/*.dmg out/
cp "$BUNDLE"/dmg/*.dmg out/OpenWorker-${{ matrix.slug }}.dmg
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ 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.

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` (AppImage + .deb).

## Repository layout

Expand All @@ -98,7 +98,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 AppImage/.deb), auto-update manifest, dev bootstrap |
| `docs/` | Design specs and decision logs |
| `tests/` | Backend test suite |

Expand Down
68 changes: 68 additions & 0 deletions packaging/build_linux.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
# Build the Linux desktop app + AppImage and .deb package.
#
# The Linux counterpart to build_dmg.sh / build_windows.ps1:
# 1. PyInstaller-bundle the server into a standalone onedir folder (no venv at runtime).
# 2. Stage it at binaries/sidecar/ for Tauri's `resources` slot.
# 3. `tauri build --bundles appimage,deb` -> OpenWorker .AppImage + .deb (resources copied in).
#
# Prerequisites (mirrors build_windows.ps1's header):
# - Rust (rustup) + Node/npm, and the GUI deps installed (npm ci in surfaces/gui).
# - A Python venv at .venv (repo root) with this package installed editable, plus the
# build-only deps:
# python3 -m venv .venv
# .venv/bin/pip install -e . pyinstaller tzdata typer
# `typer` is needed only at BUILD time: PyInstaller walks the `mcp` package and
# `mcp.cli` calls sys.exit() at import if typer is absent, which aborts the freeze.
# (aisuite installs like any other dependency - git-pinned in pyproject.toml.)
# - Linux system deps for the Tauri bundlers (Debian/Ubuntu package names):
# build-essential libssl-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev
# librsvg2-dev patchelf libfuse2 fakeroot dpkg desktop-file-utils
#
# The result is UNSIGNED - Tauri's updater signing (minisign) still applies if
# TAURI_SIGNING_PRIVATE_KEY is set, same as the other platform scripts; there is no OS-level
# code signing on Linux for AppImage/.deb.
#
# Experimental (use-at-your-own-risk) connectors are EXCLUDED from this build by default -
# the spec strips coworker.connectors.experimental. Self-builders can opt in with:
# COWORKER_EXPERIMENTAL=1 ./build_linux.sh
set -euo pipefail

HERE="$(cd "$(dirname "$0")" && pwd)"
PLATFORM="$(cd "$HERE/.." && pwd)"
GUI="$PLATFORM/surfaces/gui"
APP="OpenWorker"
VPY="$PLATFORM/.venv/bin/python"
TRIPLE="$(rustc -vV | sed -n 's/host: //p')" # e.g. x86_64-unknown-linux-gnu

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

echo "==> [2/3] staging sidecar resources"
# Onedir bundle (exe + _internal/) ships via Tauri `resources`, landing at sidecar/ next to
# the app binary - onefile's per-launch self-extraction cost seconds of boot splash.
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 appimage,deb)"
# Auto-update artifact (.AppImage + minisign .sig): produced only when the updater signing
# key is available (CI secret TAURI_SIGNING_PRIVATE_KEY). Keyless builds skip the overlay so
# dev/fork builds keep working; keyless RELEASES strand Linux installs without auto-update.
UPDATER_OVERLAY=()
if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
UPDATER_OVERLAY=(--config '{"bundle":{"createUpdaterArtifacts":true}}')
else
echo " WARNING: no updater signing key - building WITHOUT auto-update artifacts (not releasable)."
fi
# ${arr[@]+…} guard: plain "${arr[@]}" on an EMPTY array is an "unbound variable" under
# set -u on bash < 4.4 (see build_dmg.sh) - keep the same guard for consistency.
( cd "$GUI" && npm run tauri build -- --bundles appimage,deb ${UPDATER_OVERLAY[@]+"${UPDATER_OVERLAY[@]}"} )

BUNDLE="$GUI/src-tauri/target/release/bundle"
echo ""
echo "Done."
echo "AppImage: $BUNDLE/appimage/"
echo ".deb: $BUNDLE/deb/"
2 changes: 2 additions & 0 deletions packaging/make_update_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

OpenWorker-macos-arm64.app.tar.gz(.sig) -> platforms["darwin-aarch64"]
OpenWorker-windows-setup.exe(.sig) -> platforms["windows-x86_64"]
OpenWorker-linux-x64.AppImage(.sig) -> platforms["linux-x86_64"]

URLs point at the TAG-pinned GitHub download path (releases/download/<tag>/<asset>),
never at `latest/` — a manifest must reference exactly the artifacts it shipped with,
Expand All @@ -35,6 +36,7 @@
ARTIFACTS = {
"OpenWorker-macos-arm64.app.tar.gz": "darwin-aarch64",
"OpenWorker-windows-setup.exe": "windows-x86_64",
"OpenWorker-linux-x64.AppImage": "linux-x86_64",
}


Expand Down