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
80 changes: 80 additions & 0 deletions .github/workflows/build-linux.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Build - Linux

on:
push:
tags:
- 'v*'
workflow_dispatch:

permissions:
contents: write

jobs:
build-linux:
name: Build Linux (Ubuntu)
runs-on: ubuntu-latest

Comment thread
coderabbitai[bot] marked this conversation as resolved.
strategy:
matrix:
arch: [x64]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Install Linux build dependencies
run: |
sudo apt-get update
sudo apt-get install -y libarchive-tools

- name: Build Electron app for Linux
run: npm run electron:build:linux
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Verify build artifacts
run: |
echo "=== Checking deb ==="
shopt -s nullglob
DEBS=(release/*.deb)
if [ ${#DEBS[@]} -eq 0 ]; then echo "ERROR: No .deb found"; exit 1; fi
for DEB in "${DEBS[@]}"; do
echo "deb: $DEB ($(stat --format=%s "$DEB") bytes)"

echo "=== Checking deb metadata ==="
dpkg-deb --info "$DEB"

echo "=== Checking deb contains dorothy binary ==="
MAIN_BIN=$(dpkg-deb --contents "$DEB" | grep -m1 "opt/Dorothy/dorothy$")
if [ -z "$MAIN_BIN" ]; then echo "ERROR: dorothy binary not found in deb"; exit 1; fi
BIN_SIZE=$(echo "$MAIN_BIN" | awk '{print $3}')
echo "Main binary: $(echo "$MAIN_BIN" | awk '{print $NF}') ($BIN_SIZE bytes)"
if [ "$BIN_SIZE" -eq 0 ]; then echo "ERROR: dorothy binary is zero-size"; exit 1; fi
done

echo "=== All checks passed ==="

- name: Upload deb artifact
uses: actions/upload-artifact@v4
with:
name: Dorothy-Linux-deb-${{ matrix.arch }}
path: release/*.deb
if-no-files-found: warn

- name: Upload to GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v2
with:
files: |
release/*.deb
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
/.next/
/out/

# production
/build
# production (Next.js output)
/build/*
!/build/linux/

# misc
.DS_Store
Expand Down
13 changes: 13 additions & 0 deletions build/linux/after-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
# Set SUID bit on chrome-sandbox for Electron's sandbox to work.
# Required for Chromium's SUID sandbox on Linux (avoids --no-sandbox).
# Idempotent: safe to run multiple times.
chown root:root /opt/Dorothy/chrome-sandbox
chmod 4755 /opt/Dorothy/chrome-sandbox

# Warn if SUID bit didn't stick (e.g. /opt mounted with nosuid)
if [ ! -u /opt/Dorothy/chrome-sandbox ]; then
echo "WARNING: Failed to set SUID bit on /opt/Dorothy/chrome-sandbox." >&2
echo "If /opt is mounted with 'nosuid', the Electron sandbox will not work." >&2
echo "You may need to run Dorothy with --no-sandbox as a workaround." >&2
fi
6 changes: 6 additions & 0 deletions build/linux/before-remove.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
# Remove SUID bit from chrome-sandbox before package removal.
# Ensures no world-executable SUID root binary is left behind.
if [ -f /opt/Dorothy/chrome-sandbox ]; then
chmod 0755 /opt/Dorothy/chrome-sandbox
fi
12 changes: 12 additions & 0 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';

if (process.platform === 'linux') {
// Ignore EPIPE errors on stdout/stderr (happens when launched from desktop entries)
process.stdout.on('error', (err: NodeJS.ErrnoException) => {
if (err.code === 'EPIPE') return;
throw err;
});
process.stderr.on('error', (err: NodeJS.ErrnoException) => {
if (err.code === 'EPIPE') return;
throw err;
});
}

// Types
import type { AppSettings, AgentStatus } from './types';

Expand Down
36 changes: 34 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"name": "dorothy",
"version": "1.2.7",
"description": "Agent Control Center - manage AI coding agents from a single desktop app",
"author": "Charlie85270",
"private": true,
"main": "electron/dist/main.js",
"scripts": {
Expand All @@ -14,8 +16,11 @@
"test:coverage": "vitest run --coverage",
"electron:dev": "concurrently \"npm run dev\" \"npm run electron:start\"",
"electron:start": "wait-on http://localhost:3000 && tsc -p electron/tsconfig.json && NODE_ENV=development electron .",
"electron:build": "bash -c 'set -e; mv src/app/api src/app/_api_backup; mv src/app/icon.tsx src/app/_icon_backup.tsx 2>/dev/null || true; trap \"mv src/app/_api_backup src/app/api 2>/dev/null; mv src/app/_icon_backup.tsx src/app/icon.tsx 2>/dev/null\" EXIT; ELECTRON_BUILD=1 next build; tsc -p electron/tsconfig.json; cd mcp-orchestrator && npm install && npm run build && cd ..; cd mcp-telegram && npm install && npm run build && cd ..; cd mcp-kanban && npm install && npm run build && cd ..; cd mcp-vault && npm install && npm run build && cd ..; cd mcp-socialdata && npm install && npm run build && cd ..; cd mcp-x && npm install && npm run build && cd ..; cd mcp-world && npm install && npm run build && cd ..; electron-builder --mac'",
"electron:pack": "tsc -p electron/tsconfig.json && cd mcp-orchestrator && npm install && npm run build && cd .. && cd mcp-telegram && npm install && npm run build && cd .. && cd mcp-kanban && npm install && npm run build && cd .. && cd mcp-vault && npm install && npm run build && cd .. && cd mcp-socialdata && npm install && npm run build && cd .. && cd mcp-x && npm install && npm run build && cd .. && cd mcp-world && npm install && npm run build && cd .. && electron-builder --dir --mac"
"electron:prepare": "bash scripts/electron-prepare.sh",
"electron:build": "npm run electron:prepare && electron-builder --mac",
"electron:build:linux": "npm run electron:prepare && electron-builder --linux",
"electron:pack": "npm run electron:prepare && electron-builder --dir --mac",
"electron:pack:linux": "npm run electron:prepare && electron-builder --dir --linux"
},
"build": {
"appId": "io.dorothy.app",
Expand Down Expand Up @@ -116,6 +121,33 @@
"zip"
]
},
"linux": {
"category": "Development",
"icon": "public/icon-512.png",
"target": [
"deb"
],
"desktop": {
"StartupNotify": "true",
"Categories": "Development;IDE;"
}
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"deb": {
"maintainer": "Charlie85270",
"fpm": ["--after-install", "build/linux/after-install.sh", "--before-remove", "build/linux/before-remove.sh"],
"depends": [
"libgtk-3-0 | libgtk-3-0t64",
"libnotify4",
"libnss3",
"libxss1",
"libxtst6",
"xdg-utils",
"libatspi2.0-0 | libatspi2.0-0t64",
"libuuid1",
"libsecret-1-0"
],
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"artifactName": "Dorothy-${version}-${arch}.deb"
},
"dmg": {
"title": "Dorothy",
"contents": [
Expand Down
29 changes: 29 additions & 0 deletions scripts/electron-prepare.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash
# Shared preparation steps for all Electron build/pack scripts.
# Builds Next.js (with API route backup), compiles TypeScript, and builds all MCP sub-packages.
set -e

REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"

# Backup API routes and icon that are incompatible with static export
mv "$REPO_ROOT/src/app/api" "$REPO_ROOT/src/app/_api_backup"
mv "$REPO_ROOT/src/app/icon.tsx" "$REPO_ROOT/src/app/_icon_backup.tsx" 2>/dev/null || true

cleanup() {
mv "$REPO_ROOT/src/app/_api_backup" "$REPO_ROOT/src/app/api" 2>/dev/null || true
mv "$REPO_ROOT/src/app/_icon_backup.tsx" "$REPO_ROOT/src/app/icon.tsx" 2>/dev/null || true
}
trap cleanup EXIT

# Build Next.js static export
ELECTRON_BUILD=1 npx next build

# Compile Electron TypeScript
tsc -p electron/tsconfig.json

# Build all MCP sub-packages
for pkg in mcp-orchestrator mcp-telegram mcp-kanban mcp-vault mcp-socialdata mcp-x mcp-world; do
pushd "$pkg" > /dev/null
npm install && npm run build
popd > /dev/null
done