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
51 changes: 29 additions & 22 deletions docs/dev-getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ This guide covers everything you need to build, test, and contribute to Lemonade
- [Usage](#usage)
- [lemonade-router (Server Only)](#lemonade-router-server-only)
- [lemonade-server.exe (Console CLI Client)](#lemonade-serverexe-console-cli-client)
- [lemonade-tray.exe (GUI Tray Launcher)](#lemonade-trayexe-gui-tray-launcher---windows-only)
- [lemonade-tray (GUI Tray Application)](#lemonade-tray-gui-tray-application---windows-and-linux)
- [Logging and Console Output](#logging-and-console-output)
- [Testing](#testing)
- [Basic Functionality Tests](#basic-functionality-tests)
Expand All @@ -40,6 +40,7 @@ Lemonade consists of these main executables:
- **lemonade-router.exe** - Core HTTP server executable that handles requests and LLM backend orchestration
- **lemonade-server.exe** - Console CLI client for terminal users that manages server lifecycle, executes commands via HTTP API
- **lemonade-tray.exe** (Windows only) - GUI tray launcher for desktop users, automatically starts `lemonade-server.exe serve`
- **lemonade-tray** (Linux, when compiled with GTK3+AppIndicator3) - System tray launcher using AppIndicator3
- **lemonade-log-viewer.exe** (Windows only) - Log file viewer with live tail support and installer-friendly file sharing

## Building from Source
Expand Down Expand Up @@ -95,7 +96,8 @@ cmake --build --preset windows
- `build/Release/lemonade-log-viewer.exe` - Log file viewer
- **Linux/macOS:**
- `build/lemonade-router` - HTTP server
- `build/lemonade-server` - Console CLI client
- `build/lemonade-server` - Console CLI client (always headless on Linux)
- `build/lemonade-tray` - GUI tray application (Linux only, built when GTK3+AppIndicator3 are found)
- **Resources:** Automatically copied to `build/Release/resources/` on Windows, `build/resources/` on Linux/macOS (web UI files, model registry, backend version configuration)

### Building the Electron Desktop App (Optional)
Expand Down Expand Up @@ -160,9 +162,10 @@ chmod +x build/app-appimage/Lemonade-*.AppImage
- Security features enabled: Control Flow Guard, ASLR, DEP

**Linux:**
- Linux builds are headless-only (no tray application) by default
- This avoids LGPL dependencies (GTK3, libappindicator3, libnotify)
- Run server using: `lemonade-server serve` (headless mode is automatic)
- `lemonade-server` is always headless on Linux (GTK-free, daemon-friendly); use `lemonade-server serve` to start the server
- `lemonade-tray` is a separate binary for the system tray, auto-detected at build time: built if GTK3 and AppIndicator3 (ayatana or upstream) are found
- To require tray support (fail if deps missing): `-DENABLE_LINUX_TRAY=ON`
- Optional tray dependencies: `gtk3-devel` and one of `ayatana-appindicator-glib-devel`, `ayatana-appindicator3-devel`, or `libappindicator-gtk3-devel`
- Fully functional for server operations and model management
- Uses permissively licensed dependencies only (MIT, Apache 2.0, BSD, curl license)
- Clean .deb package with only runtime files (no development headers)
Expand Down Expand Up @@ -584,19 +587,20 @@ A console application for terminal users:
- Manages server lifecycle (start/stop persistent or ephemeral servers)
- Communicates with `lemonade-router` via HTTP endpoints
- Starts `lemonade-router` with appropriate options
- Provides optional system tray interface via `serve` command
- Provides optional system tray interface via `serve` command (Windows/macOS; on Linux the tray is provided by the separate `lemonade-tray` binary)

**Command Types:**
- **serve:** Starts a persistent server (with optional tray interface)
- **run:** Starts persistent server, loads model, opens browser
- **Other commands:** Use existing server or start ephemeral server, execute command via API, auto-cleanup

#### lemonade-tray (GUI Launcher - Windows Only)
#### lemonade-tray (GUI Tray Application - Windows and Linux)

A minimal WIN32 GUI application for desktop users:
- Simple launcher that starts `lemonade-server.exe serve`
A GUI application for desktop users that exposes the server via a system tray icon:
- **Windows:** Minimal launcher — finds `lemonade-server.exe`, launches it with the `serve` command, then exits. The server process owns the tray icon.
- **Linux:** Tray application (requires GTK3 + AppIndicator3). Connects to an already-running server if one is found; otherwise starts one (via systemd if a unit is installed, or by spawning `lemonade-router` directly).
- Zero console output or CLI interface
- Used by Start Menu, Desktop shortcuts, and autostart
- Used by application launchers, desktop shortcuts, and autostart entries
- Provides seamless GUI experience for non-technical users

### Client-Server Communication
Expand Down Expand Up @@ -694,7 +698,7 @@ The `lemonade-server` executable is the command-line interface for terminal user
# Run a model (starts persistent server with tray and opens browser)
./lemonade-server run Llama-3.2-1B-Instruct-CPU

# Start persistent server (with tray on Windows/macOS, headless on Linux)
# Start persistent server (with tray on Windows/macOS; always headless on Linux — use lemonade-tray for tray)
./lemonade-server serve

# Start persistent server without tray (headless mode, explicit on all platforms)
Expand All @@ -716,24 +720,27 @@ The `lemonade-server` executable is the command-line interface for terminal user

**Note:** `lemonade-router` is always launched with `--log-level debug` for optimal troubleshooting. Use `--log-level debug` on `lemonade-server` commands to see client-side debug output.

### lemonade-tray.exe (GUI Tray Launcher - Windows Only)
### lemonade-tray (GUI Tray Application - Windows and Linux)

The `lemonade-tray` executable is a simple GUI launcher for desktop users:
- Double-click from Start Menu or Desktop to start server
- Automatically runs `lemonade-server.exe serve` in tray mode
- Zero console windows or CLI interface
The `lemonade-tray` executable provides a system tray icon for desktop users:
- Double-click from Start Menu, application launcher, or Desktop to start server
- Zero console windows or CLI interface — always starts the tray directly
- Perfect for non-technical users
- Single-instance protection: shows friendly message if already running

**What it does:**
1. Finds `lemonade-server.exe` in the same directory
2. Launches it with the `serve` command
3. Exits immediately (server continues running with tray icon)
**Platform support:**
- **Windows:** Always available; uses Win32 notification area APIs. Acts as a minimal launcher: finds `lemonade-server.exe` in the same directory, launches it with the `serve` command, then exits (the server process owns the tray icon).
- **Linux:** Available when compiled with GTK3 + AppIndicator3 support (auto-detected at build time). Connects to an already-running server if one is found; otherwise starts one (via systemd if a unit is installed, or by spawning `lemonade-router` directly).

**What it does (Linux):**
1. Starts immediately in tray mode (no subcommand needed)
2. Connects to an already-running server via the PID file, or starts one (via systemd if a unit is installed, otherwise spawns `lemonade-router` directly)
3. Shows a system tray icon connected to the server

**When to use:**
- Launching from Start Menu
- Launching from Start Menu (Windows) or application launcher (Linux)
- Desktop shortcuts
- Windows startup
- Windows startup / Linux autostart
- Any GUI/point-and-click scenario

**System Tray Features (when running):**
Expand Down
2 changes: 1 addition & 1 deletion docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@

### 1. **What if I encounter installation or runtime errors?**

Check the Lemonade Server logs via the App (all supported OSes) or tray icon (Windows only). Common issues include model compatibility or outdated versions.
Check the Lemonade Server logs via the App or tray icon. Common issues include model compatibility or outdated versions.

👉 [Open an Issue on GitHub](https://github.com/lemonade-sdk/lemonade/issues)

Expand Down
2 changes: 1 addition & 1 deletion docs/server/server_integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ By default, the server runs on port 8000. Optionally, you can specify a custom p
lemonade-server serve --port 8123
```

You can also prevent the server from showing a system tray icon by using the `--no-tray` flag (Windows and macOS):
You can also prevent the server from showing a system tray icon by using the `--no-tray` flag:

```bash
lemonade-server serve --no-tray
Expand Down
117 changes: 117 additions & 0 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,123 @@ else
fi
fi

# Check optional Linux tray dependencies (AppIndicator3 + libnotify [+ GTK3 if not using glib variant])
# These are optional - lemonade-tray is only built when they are present.
# lemonade-server always builds without them (headless, daemon-friendly).
if [ "$OS" = "linux" ] && command_exists pkg-config; then
print_info "Checking optional Linux tray dependencies (AppIndicator3)..."
missing_tray_packages=()
appindicator_glib_found=false

# Check AppIndicator first — the glib variant is GTK-free and preferred.
if pkg-config --exists ayatana-appindicator-glib-0.1 2>/dev/null || \
pkg-config --exists ayatana-appindicator-glib 2>/dev/null; then
print_success "AppIndicator3 glib variant is installed (tray support, GTK-free)"
appindicator_glib_found=true
elif pkg-config --exists ayatana-appindicator3-0.1 2>/dev/null || \
pkg-config --exists appindicator3-0.1 2>/dev/null; then
print_success "AppIndicator3 is installed (tray support)"
else
print_warning "AppIndicator3 not found (optional, needed for lemonade-tray)"
if command_exists apt; then
missing_tray_packages+=("libayatana-appindicator3-dev")
elif command_exists pacman; then
missing_tray_packages+=("libayatana-appindicator")
elif command_exists dnf; then
missing_tray_packages+=("libayatana-appindicator-gtk3-devel")
fi
fi

# dbusmenu-glib is required alongside the glib appindicator variant so that
# GNOME Shell can find com.canonical.dbusmenu (it does not speak org.gtk.Menus).
if [ "$appindicator_glib_found" = true ]; then
if pkg-config --exists dbusmenu-glib-0.4 2>/dev/null; then
print_success "dbusmenu-glib is installed (GNOME Shell tray menu support)"
else
print_warning "dbusmenu-glib not found (optional, needed for tray menus on GNOME Shell)"
if command_exists apt; then
missing_tray_packages+=("libdbusmenu-glib-dev")
elif command_exists pacman; then
missing_tray_packages+=("libdbusmenu-glib")
elif command_exists dnf; then
missing_tray_packages+=("dbusmenu-glib-devel")
fi
fi
fi

# GTK3 is only required when NOT using the glib appindicator variant.
if [ "$appindicator_glib_found" = false ]; then
if pkg-config --exists gtk+-3.0 2>/dev/null; then
print_success "gtk3 is installed (tray support)"
else
print_warning "gtk3 not found (optional, needed for lemonade-tray)"
if command_exists apt; then
missing_tray_packages+=("libgtk-3-dev")
elif command_exists pacman; then
missing_tray_packages+=("gtk3")
elif command_exists dnf; then
missing_tray_packages+=("gtk3-devel")
fi
fi
fi

if pkg-config --exists libnotify 2>/dev/null; then
print_success "libnotify is installed (tray notifications)"
else
print_warning "libnotify not found (optional, enables tray notifications)"
if command_exists apt; then
missing_tray_packages+=("libnotify-dev")
elif command_exists pacman; then
missing_tray_packages+=("libnotify")
elif command_exists dnf; then
missing_tray_packages+=("libnotify-devel")
fi
fi

if [ ${#missing_tray_packages[@]} -gt 0 ]; then
echo ""
print_warning "Optional tray packages missing (lemonade-tray will not be built):"
for pkg in "${missing_tray_packages[@]}"; do
echo " - $pkg"
done
echo ""

# Build install command for display
if command_exists apt; then
tray_install_cmd="sudo apt install -y ${missing_tray_packages[*]}"
elif command_exists pacman; then
tray_install_cmd="sudo pacman -S --needed --noconfirm ${missing_tray_packages[*]}"
elif command_exists dnf; then
tray_install_cmd="sudo dnf install -y ${missing_tray_packages[*]}"
fi

print_info "To enable tray support, run: $tray_install_cmd"

if [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; then
print_info "CI environment detected, skipping optional tray dependencies."
else
read -p "Install optional tray dependencies now? (y/N): " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
print_info "Installing optional tray dependencies..."
if command_exists apt; then
maybe_sudo apt install -y "${missing_tray_packages[@]}"
elif command_exists pacman; then
maybe_sudo pacman -S --needed --noconfirm "${missing_tray_packages[@]}"
elif command_exists dnf; then
maybe_sudo dnf install -y "${missing_tray_packages[@]}"
fi
print_success "Optional tray dependencies installed"
else
print_info "Skipping optional tray dependencies (lemonade-tray will not be built)"
fi
fi
else
print_success "All optional tray dependencies are installed (lemonade-tray will be built)"
fi
echo ""
fi

# Check Node.js and npm
print_info "Checking Node.js and npm installation..."

Expand Down
27 changes: 27 additions & 0 deletions src/cpp/BUILD_OPTIONS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# Lemonade Build Options

## Linux Tray Configuration

### Build Options

#### `ENABLE_LINUX_TRAY` (Default: **OFF** / auto-detect)
Enable system tray support on Linux via GTK3 + AppIndicator3.

- When **OFF** (default): Tray support is auto-detected at configure time. If GTK3 and one of the AppIndicator3 libraries are found, `lemonade-tray` is built as a separate executable. `lemonade-server` on Linux is always headless regardless. If dependencies are missing, only `lemonade-server` is built (headless mode).
- When **ON**: Tray support is required — the build will fail if the dependencies are not found.

Optional runtime dependencies (for tray support):
- `gtk3-devel` (or `libgtk-3-dev` on Debian/Ubuntu)
- One of:
- `ayatana-appindicator-glib-devel` (recommended successor, GLib-only)
- `ayatana-appindicator3-devel` (Ayatana GTK3 variant)
- `libappindicator-gtk3-devel` (upstream libappindicator3)

```bash
# Auto-detect (default): tray enabled if deps are found
cmake ../src/cpp

# Explicitly require tray support (fail if deps missing)
cmake -DENABLE_LINUX_TRAY=ON ../src/cpp
```

---

## React App Build Configuration

The CMake build system allows you to control whether the React web app and/or Electron desktop app are built and included in the server.
Expand Down
4 changes: 2 additions & 2 deletions src/cpp/include/lemon/cli_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ struct ServerConfig {

struct TrayConfig {
std::string command; // No default - must be explicitly specified
// Default to headless mode on Linux (no tray support), tray mode on other platforms
#if defined(__linux__) && !defined(__ANDROID__)
// Default to headless mode on Linux only when tray support is not available (no GTK/AppIndicator)
#if defined(__linux__) && !defined(__ANDROID__) && !defined(HAVE_APPINDICATOR)
bool no_tray = true;
#else
bool no_tray = false;
Expand Down
42 changes: 40 additions & 2 deletions src/cpp/include/lemon_tray/platform/linux_tray.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@

#include "tray_interface.h"

#ifdef HAVE_APPINDICATOR
#ifdef HAVE_AYATANA_APPINDICATOR_GLIB
#include <libayatana-appindicator-glib/ayatana-appindicator.h> // glib variant (GTK-free, GIO only)
#ifdef HAVE_DBUSMENU_GLIB
#include <libdbusmenu-glib/server.h>
#include <libdbusmenu-glib/menuitem.h>
#endif
#else
#include <gtk/gtk.h>
#ifdef HAVE_AYATANA_APPINDICATOR
#include <libayatana-appindicator/app-indicator.h> // GTK3 Ayatana variant
#else
#include <libappindicator/app-indicator.h> // upstream fallback
#endif // HAVE_AYATANA_APPINDICATOR
#endif // HAVE_AYATANA_APPINDICATOR_GLIB
#ifdef HAVE_LIBNOTIFY
#include <libnotify/notify.h>
#endif // HAVE_LIBNOTIFY
#endif // HAVE_APPINDICATOR

namespace lemon_tray {

class LinuxTray : public TrayInterface {
Expand All @@ -27,8 +47,26 @@ class LinuxTray : public TrayInterface {
void set_ready_callback(std::function<void()> callback) override;

private:
// Headless implementation - no GUI dependencies
// Linux tray support disabled to avoid LGPL dependencies
#ifdef HAVE_APPINDICATOR
AppIndicator* indicator_;
#ifdef HAVE_AYATANA_APPINDICATOR_GLIB
GMainLoop* main_loop_;
GMenu* g_menu_;
GSimpleActionGroup* action_group_;
std::vector<std::function<void()>*> callbacks_;
void build_g_menu(const Menu& menu, GMenu* parent, GSimpleActionGroup* actions,
int& action_id, std::vector<std::function<void()>*>& callbacks);
#ifdef HAVE_DBUSMENU_GLIB
DbusmenuServer* dbusmenu_server_;
void build_dbusmenu(const Menu& menu, DbusmenuMenuitem* parent,
std::vector<std::function<void()>*>& callbacks);
#endif
#else
GtkWidget* gtk_menu_;
void build_gtk_menu(const Menu& menu, GtkWidget* parent_menu);
#endif // HAVE_AYATANA_APPINDICATOR_GLIB
#endif // HAVE_APPINDICATOR

std::string app_name_;
std::string icon_path_;
std::function<void()> ready_callback_;
Expand Down
2 changes: 2 additions & 0 deletions src/cpp/include/lemon_tray/platform/tray_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct MenuItem {
MenuCallback callback;
bool enabled = true;
bool checked = false;
bool is_checkable = false;
bool is_separator = false;
std::shared_ptr<Menu> submenu = nullptr;
int id = -1; // Platform-specific menu item ID
Expand All @@ -53,6 +54,7 @@ struct MenuItem {
item.text = text;
item.callback = callback;
item.checked = checked;
item.is_checkable = true;
item.enabled = enabled;
return item;
}
Expand Down
9 changes: 9 additions & 0 deletions src/cpp/include/lemon_tray/tray_app.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ class TrayApp {
void on_open_documentation();
void on_upgrade();
void on_quit();
#ifndef _WIN32
void on_start_server();
void on_stop_server();
void on_stop_external_server();
#endif

// Helpers
void open_url(const std::string& url);
Expand Down Expand Up @@ -122,6 +127,10 @@ class TrayApp {
std::vector<ModelInfo> downloaded_models_;
bool should_exit_;
bool process_owns_server_ = false;
#ifndef _WIN32
int external_server_pid_ = 0; // PID of external server we didn't spawn (for SIGTERM)
#endif
bool last_menu_server_reachable_ = false; // For refresh detection

// Model loading state
std::atomic<bool> is_loading_model_{false};
Expand Down
Loading