Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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: 73 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# Release workflow (macOS desktop + CLI)
# Release workflow (macOS + Linux desktop + CLI)
#
# Builds and publishes the macOS Electron app via GitHub Releases.
# Builds and publishes Electron desktop artifacts via GitHub Releases.
# electron-updater reads latest-mac.yml from the release assets to deliver
# auto-updates. Linux desktop packaging is intentionally disabled until the
# native packaged runtime and bundled agent CLIs are staged for Linux.
# auto-updates.
#
# Trigger: GitHub Actions UI → "Run workflow" → pick bump type
# Or CLI: gh workflow run release.yml -f bump=patch
Expand Down Expand Up @@ -320,9 +319,76 @@ jobs:
dist-electron/latest-mac.yml
if-no-files-found: error

# ── Step 2b: Build Linux (x64) ─────────────────────────────────────
build-linux:
needs: validate-and-bump
runs-on: ubuntu-latest
timeout-minutes: 35
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.dry_run == false && needs.validate-and-bump.outputs.tag || github.ref }}

- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}

- name: Verify Bun version pin
run: |
package_manager="$(sed -nE 's/.*"packageManager": "bun@([^"]+)".*/\1/p' package.json)"
if [[ "$package_manager" != "$BUN_VERSION" ]]; then
echo "::error::release workflow BUN_VERSION=$BUN_VERSION but package.json packageManager=bun@$package_manager"
exit 1
fi

- uses: actions/setup-node@v4
with:
node-version: 22

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Typecheck runtime surfaces
run: |
bun run typecheck
bun run typecheck:backend
bun run typecheck:agent-server

- name: Package Linux (x64)
run: |
bun run build:all
bun run validate:runtime
bun run smoke:runtime-source

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fix Linux source-runtime CLI discovery before gating release

On the new Ubuntu release job this smoke runs after staging the Linux CLIs, but the source runtime only treats darwin-arm64/darwin-x64 as dev-staged runtime keys (apps/runtime/index.ts and shared/lib/cli-path.ts never return linux-x64). As a result smoke:runtime-source starts agent-server without DEUS_BUNDLED_BIN_DIR, cannot emit the required bundled claude/codex paths, and the Linux release job times out/fails before packaging can be uploaded.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 92e6cc4. Source-runtime CLI discovery now recognizes linux-x64 in both apps/runtime/index.ts and shared/lib/cli-path.ts; the shared CLI path test covers staged Linux dev binaries.

bunx electron-builder --linux --publish never

Comment thread
coderabbitai[bot] marked this conversation as resolved.
- name: Verify Linux artifacts
run: |
set -euo pipefail
test -f dist-electron/Deus-*.AppImage
test -f dist-electron/deus_*_amd64.deb
test -x dist-electron/linux-unpacked/resources/bin/deus-runtime
test -x dist-electron/linux-unpacked/resources/bin/codex
test -x dist-electron/linux-unpacked/resources/bin/claude
test -x dist-electron/linux-unpacked/resources/bin/gh
test -x dist-electron/linux-unpacked/resources/bin/rg
test -x dist-electron/linux-unpacked/resources/bin/agent-browser
file dist-electron/linux-unpacked/resources/bin/deus-runtime | grep 'ELF 64-bit'

- name: Upload Linux artifacts
uses: actions/upload-artifact@v4
with:
name: linux-artifacts
path: |
dist-electron/*.AppImage
dist-electron/*.deb
dist-electron/latest-linux.yml
if-no-files-found: error

# ── Step 3: Stage a draft GitHub Release with all artifacts ─────────
create-release:
needs: [validate-and-bump, build-macos]
needs: [validate-and-bump, build-macos, build-linux]
if: ${{ inputs.dry_run == false }}
runs-on: ubuntu-latest
steps:
Expand All @@ -334,7 +400,7 @@ jobs:
run: |
mkdir -p release
find artifacts -type f \( \
-name "*.dmg" -o -name "*.zip" -o \
-name "*.dmg" -o -name "*.zip" -o -name "*.AppImage" -o -name "*.deb" -o \
-name "*.blockmap" -o -name "*.yml" \
\) -exec cp {} release/ \;
echo "=== Release files ==="
Expand Down Expand Up @@ -402,7 +468,7 @@ jobs:

- name: Publish to npm
working-directory: apps/cli
run: npm publish --access public --provenance
run: bun publish --access public

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve npm provenance when publishing the CLI

This replaces the previous npm publish --provenance with bun publish --access public, so releases from this workflow will no longer publish the CLI package with npm provenance even though the job still requests id-token: write. I checked bun publish --help; it lists publish flags like --access, --tag, --otp, and --auth-type, but no provenance flag, so this command drops the supply-chain attestation that the old workflow produced.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declined intentionally. The repo instructions require Bun for package operations and explicitly say never npm/yarn; local bun publish --help does not support --provenance, so keeping npm publish --provenance would violate the project command policy. The workflow still uses the pinned Bun version for release consistency.

env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

Expand Down
41 changes: 41 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,47 @@ jobs:
- name: Run desktop/runtime unit tests
run: bun run test:desktop-runtime

runtime-linux:
name: Native Runtime Smoke (Linux)
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.19

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Build Electron desktop outputs
run: |
bun run build:all

- name: Package Linux app
run: bunx electron-builder --linux dir --publish never
env:
CSC_IDENTITY_AUTO_DISCOVERY: "false"

- name: Verify packaged Linux runtime resources
run: |
set -euo pipefail
resources_dir="dist-electron/linux-unpacked/resources"
test -x "$resources_dir/bin/deus-runtime"
test -x "$resources_dir/bin/codex"
test -x "$resources_dir/bin/claude"
test -x "$resources_dir/bin/gh"
test -x "$resources_dir/bin/rg"
test -x "$resources_dir/bin/agent-browser"
file "$resources_dir/bin/deus-runtime" | grep 'ELF 64-bit'

backend-tests:
name: Backend Tests
runs-on: ubuntu-latest
Expand Down
19 changes: 19 additions & 0 deletions electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,25 @@ linux:
arch: [x64]
category: Development
icon: resources/icons/icon.png
extraResources:
- from: "dist/runtime/electron/bin/deus-runtime.json"
to: "bin/deus-runtime.json"
- from: "dist/runtime/electron/bin/agent-clis.json"
to: "bin/agent-clis.json"
- from: "dist/runtime/electron/bin/gh-cli.json"
to: "bin/gh-cli.json"
- from: "dist/runtime/electron/bin/linux-${arch}/gh"
to: "bin/gh"
- from: "dist/runtime/electron/bin/linux-${arch}/deus-runtime"
to: "bin/deus-runtime"
- from: "dist/runtime/electron/bin/linux-${arch}/codex"
to: "bin/codex"
- from: "dist/runtime/electron/bin/linux-${arch}/claude"
to: "bin/claude"
- from: "dist/runtime/electron/bin/linux-${arch}/rg"
to: "bin/rg"
- from: "dist/runtime/electron/bin/linux-${arch}/agent-browser"
to: "bin/agent-browser"
publish:
provider: github
releaseType: release
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"package:mac": "bun run build:all && electron-builder --mac",
"package:mac:dir": "node scripts/runtime/package-mac-dir.cjs",
"package:win": "node scripts/runtime/unsupported-packaged-platform.cjs Windows",
"package:linux": "node scripts/runtime/unsupported-packaged-platform.cjs Linux",
"package:linux": "bun run build:all && electron-builder --linux",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allow packaged Linux startup before publishing artifacts

Enabling package:linux produces AppImage/deb files, but the packaged desktop still refuses to start on Linux: resolveRuntimeEntries() in apps/desktop/main/backend-process.ts throws whenever app.isPackaged and process.platform !== "darwin". In any packaged Linux build from this script or the new release job, the app will abort before spawning the backend, so the Linux artifacts are not usable until that guard/runtime path is updated.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 92e6cc4. Packaged startup now permits darwin and linux, and the backend spawn test covers packaged Linux launching through Resources/bin/deus-runtime with the bundled bin directory on PATH.

"postinstall": "bun run prepare:device-use",
"native:electron": "electron-builder install-app-deps",
"native:node": "cd node_modules/better-sqlite3 && node ../node-gyp/bin/node-gyp.js rebuild",
Expand Down
26 changes: 20 additions & 6 deletions scripts/prepare-gh-cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ const GH_VERSION = ghContract.ghVersion;
const GH_RELEASE_BASE_URL = `https://github.com/cli/cli/releases/download/v${GH_VERSION}`;
const TARGETS = ghContract.targets.map((target) => ({
...target,
archiveName: `gh_${GH_VERSION}_${target.archivePlatform}.zip`,
archiveExtension: target.archiveExtension || "zip",
fileFormat: target.fileFormat || "Mach-O 64-bit executable",
archiveName: `gh_${GH_VERSION}_${target.archivePlatform}.${target.archiveExtension || "zip"}`,
archiveRoot: `gh_${GH_VERSION}_${target.archivePlatform}`,
sha256: target.archiveSha256,
}));
Expand Down Expand Up @@ -61,7 +63,7 @@ function clearMacExtendedAttributes(filePath) {
}

function verifyGhBinary(filePath, runtimeKey) {
if (process.platform !== "darwin") return;
if (process.platform !== "darwin" || !runtimeKey.startsWith("darwin-")) return;
execFileSync("codesign", ["--verify", "--verbose=2", filePath], {
timeout: VERIFY_TIMEOUT_MS,
stdio: ["ignore", "ignore", "pipe"],
Expand All @@ -76,7 +78,11 @@ function inspectGhBinary(filePath, target) {
timeout: VERIFY_TIMEOUT_MS,
stdio: ["ignore", "pipe", "pipe"],
}).trim();
if (!fileOutput.includes("Mach-O 64-bit executable") || !fileOutput.includes(target.fileArch)) {
const hasExpectedFormat =
target.fileFormat === "Mach-O 64-bit executable"
? fileOutput.includes("Mach-O 64-bit") && fileOutput.includes("executable")
: fileOutput.includes(target.fileFormat);
if (!hasExpectedFormat || !fileOutput.includes(target.fileArch)) {
throw new Error(`Unexpected gh architecture for ${target.runtimeKey}: ${fileOutput}`);
}

Expand Down Expand Up @@ -133,9 +139,17 @@ function stageGhBinary(target, archivePath) {

const tempDir = mkdtempSync(join(tmpdir(), "deus-gh-"));
try {
execFileSync("unzip", ["-q", "-o", archivePath, "-d", tempDir], {
stdio: ["ignore", "pipe", "pipe"],
});
if (target.archiveExtension === "zip") {
execFileSync("unzip", ["-q", "-o", archivePath, "-d", tempDir], {
stdio: ["ignore", "pipe", "pipe"],
});
} else if (target.archiveExtension === "tar.gz") {
execFileSync("tar", ["-xzf", archivePath, "-C", tempDir], {
stdio: ["ignore", "pipe", "pipe"],
});
} else {
throw new Error(`Unsupported gh archive extension: ${target.archiveExtension}`);
}

const sourcePath = join(tempDir, target.archiveRoot, "bin", "gh");
if (!existsSync(sourcePath)) {
Expand Down
Loading
Loading