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
163 changes: 163 additions & 0 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
name: CI tests
on:
push:
branches: [develop]
pull_request:
branches: [develop, main]
permissions:
contents: read
concurrency:
group: pr-tests-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
contracts:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
submodules: recursive
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '24.11.0'
- run: node --test scripts/release/*.test.mjs scripts/run-build-command.test.js

desktop:
runs-on: ubuntu-24.04
env:
PUPPETEER_SKIP_DOWNLOAD: 'true'
BUN_INSTALL_CACHE_DIR: ${{ github.workspace }}/.data/pr-bun-cache
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
fetch-depth: 0
submodules: recursive
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '24.11.0'
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: '1.3.9'
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: ${{ env.BUN_INSTALL_CACHE_DIR }}
key: pr-bun-ubuntu24-1.3.9-${{ hashFiles('aionui/bun.lock') }}
- uses: ./.github/actions/install-desktop-dependencies
- name: Prepare the pinned Chrome DevTools dependency and browser MCP bundle
run: node scripts/prepare-chrome-devtools-mcp.js
env:
NODE_OPTIONS: --max-old-space-size=4096
- run: bun run test
working-directory: aionui

rust:
strategy:
fail-fast: false
matrix:
include:
- { component: aioncore, toolchain: '1.95.0' }
- { component: aionrs, toolchain: '1.96.1' }
runs-on: ubuntu-24.04
env:
CARGO_INCREMENTAL: '0'
CARGO_PROFILE_TEST_DEBUG: '0'
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
submodules: recursive
persist-credentials: false
- run: rustup toolchain install ${{ matrix.toolchain }} --profile minimal
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: |
~/.cargo/registry
~/.cargo/git
${{ matrix.component }}/target
key: pr-ubuntu24-${{ matrix.component }}-${{ matrix.toolchain }}-${{ hashFiles('aioncore/Cargo.lock', 'aionrs/Cargo.lock') }}-${{ github.sha }}
restore-keys: pr-ubuntu24-${{ matrix.component }}-${{ matrix.toolchain }}-${{ hashFiles('aioncore/Cargo.lock', 'aionrs/Cargo.lock') }}-
- run: cargo +${{ matrix.toolchain }} test --locked --workspace --lib
working-directory: ${{ matrix.component }}

secrets-vault:
name: Secrets vault (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
env:
CARGO_BUILD_JOBS: '2'
CARGO_PROFILE_TEST_DEBUG: '0'
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
submodules: recursive
persist-credentials: false
- run: rustup toolchain install 1.95.0 --profile minimal
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: |
~/.cargo/registry
~/.cargo/git
aioncore/target
key: secrets-${{ runner.os }}-1.95-${{ hashFiles('aioncore/Cargo.lock', 'aionrs/Cargo.lock') }}-${{ github.sha }}
restore-keys: secrets-${{ runner.os }}-1.95-${{ hashFiles('aioncore/Cargo.lock', 'aionrs/Cargo.lock') }}-
- name: Install Linux native prerequisites
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install --yes --no-install-recommends build-essential cmake pkg-config libssl-dev libasound2-dev libudev-dev
- name: Verify encrypted vault and history masking
working-directory: aioncore
run: cargo +1.95.0 test --locked -p aionui-secrets
- name: Verify human transport boundaries
working-directory: aioncore
run: cargo +1.95.0 test --locked -p aionui-app --lib secrets::access_tests
- name: Verify scoped Secrets HTTP integration
working-directory: aioncore
run: cargo +1.95.0 test --locked -p aionui-app --test secrets_e2e
- name: Verify sensitive event projections
working-directory: aioncore
run: cargo +1.95.0 test --locked -p aionui-ai-agent --lib capability::sensitive
- name: Verify ACP recovered Secrets confirmations
working-directory: aioncore
run: cargo +1.95.0 test --locked -p aionui-ai-agent --lib secrets_sdk_permission_roundtrip
- name: Verify Codex Secrets approval wire
working-directory: aioncore
run: cargo +1.95.0 test --locked -p aionui-session --lib secrets_wire_
- name: Select native Rust toolchain
run: rustup toolchain install 1.96.1 --profile minimal
- name: Verify native MCP approvals and raw delivery
working-directory: aionrs
run: cargo +1.96.1 test --locked -p aion-agent sensitive_

secrets-headless:
name: Secrets passphrase (headless container)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
submodules: recursive
persist-credentials: false
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: |
.data/secrets-container-cargo
.data/secrets-container-target
key: secrets-headless-1.95-${{ hashFiles('aioncore/Cargo.lock', 'aionrs/Cargo.lock') }}-${{ github.sha }}
restore-keys: secrets-headless-1.95-${{ hashFiles('aioncore/Cargo.lock', 'aionrs/Cargo.lock') }}-
- name: Verify passphrase recovery without a desktop key store
run: |
mkdir -p .data/secrets-container-cargo .data/secrets-container-target
docker run --rm \
--volume "$PWD:/workspace" \
--volume "$PWD/.data/secrets-container-cargo:/cache" \
--volume "$PWD/.data/secrets-container-target:/target" \
--workdir /workspace/aioncore \
--env CARGO_HOME=/cache \
--env CARGO_TARGET_DIR=/target \
--env CARGO_BUILD_JOBS=2 \
--env CARGO_PROFILE_TEST_DEBUG=0 \
rust:1.95.0-bookworm \
bash -ec 'apt-get update && apt-get install --yes --no-install-recommends cmake pkg-config libssl-dev libasound2-dev libudev-dev && cargo test --locked -p aionui-secrets'
42 changes: 23 additions & 19 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ on:
workflow_dispatch:
inputs:
macos:
description: 'Build signed/notarized macOS arm64 installers.'
description: 'Build Developer ID signed and notarized macOS arm64 installers.'
type: boolean
default: true
windows:
description: 'Build the unsigned Windows x64 installer.'
type: boolean
default: true
linux:
description: 'Build unsigned Linux x64 desktop packages.'
linux-flatpak:
description: 'Build the Linux x64 Flatpak bundle.'
type: boolean
default: true
linux-deb:
description: 'Build the Linux x64 Debian package.'
type: boolean
default: true
linux-appimage:
description: 'Build the Linux x64 AppImage in a container.'
type: boolean
default: true
linux_packages:
description: 'Linux formats for manual verification; publication requires all.'
type: choice
options: [all, flatpak, deb]
default: all
docker:
description: 'Build and smoke-test all Linux VPS images.'
type: boolean
Expand All @@ -35,13 +38,13 @@ permissions:
contents: read

concurrency:
group: release-${{ github.ref }}
group: release-${{ github.ref }}-${{ github.event_name == 'workflow_dispatch' && !inputs.publish && github.run_id || 'publication' }}
cancel-in-progress: false

jobs:
preflight:
runs-on: ubuntu-24.04
environment: ${{ (github.event_name == 'push' || inputs.macos || inputs.publish) && 'release' || 'ci' }}
environment: ${{ (github.event_name == 'push' || inputs.macos) && 'release' || 'ci' }}
outputs:
version: ${{ steps.source.outputs.version }}
tag: ${{ steps.source.outputs.tag }}
Expand Down Expand Up @@ -73,8 +76,9 @@ jobs:
RELEASE_PUBLISH: ${{ github.event_name == 'push' || inputs.publish }}
RELEASE_BUILD_MACOS: ${{ github.event_name == 'push' || inputs.macos }}
RELEASE_BUILD_WINDOWS: ${{ github.event_name == 'push' || inputs.windows }}
RELEASE_BUILD_LINUX: ${{ github.event_name == 'push' || inputs.linux }}
RELEASE_LINUX_PACKAGES: ${{ github.event_name == 'push' && 'all' || inputs.linux_packages }}
RELEASE_LINUX_FLATPAK: ${{ github.event_name == 'push' || inputs['linux-flatpak'] }}
RELEASE_LINUX_DEB: ${{ github.event_name == 'push' || inputs['linux-deb'] }}
RELEASE_LINUX_APPIMAGE: ${{ github.event_name == 'push' || inputs['linux-appimage'] }}
RELEASE_BUILD_DOCKER: ${{ github.event_name == 'push' || inputs.docker }}
REQUIRE_SIGNING_SECRETS: 'true'
MAC_SIGNING_READY: ${{ secrets.MAC_CERTIFICATE_P12_BASE64 != '' && secrets.MAC_CERTIFICATE_PASSWORD != '' && secrets.MAC_SIGNING_IDENTITY != '' && secrets.MAC_TEAM_ID != '' && secrets.MAC_NOTARY_APPLE_ID != '' && secrets.MAC_NOTARY_APP_PASSWORD != '' }}
Expand Down Expand Up @@ -168,7 +172,7 @@ jobs:
desktop-${{ matrix.target }}-rust1.95.0-node24.11.0-bun1.3.9-${{ hashFiles('aioncore/Cargo.lock', 'aionrs/Cargo.lock', 'aionui/bun.lock', 'aionui/scripts/memory/locks/*.json') }}-
desktop-${{ matrix.target }}-rust1.95.0-node24.11.0-bun1.3.9-
- uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
if: runner.os == 'Linux' && needs.preflight.outputs.linux_packages != 'deb'
if: runner.os == 'Linux' && (needs.preflight.outputs.linux_packages == 'all' || contains(needs.preflight.outputs.linux_packages, 'flatpak'))
id: flatpak-cache
with:
path: ${{ runner.temp }}/flatpak-user
Expand All @@ -182,16 +186,16 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install --yes --no-install-recommends build-essential cmake pkg-config libssl-dev
if [ "$RELEASE_LINUX_PACKAGES" != deb ]; then
if [[ "$RELEASE_LINUX_PACKAGES" == all || "$RELEASE_LINUX_PACKAGES" == *flatpak* ]]; then
sudo apt-get install --yes --no-install-recommends flatpak flatpak-builder dbus-x11
fi
- name: Install locked dependencies
uses: ./.github/actions/install-desktop-dependencies
- name: Prepare configured Flatpak runtimes and verify sandbox
if: runner.os == 'Linux' && needs.preflight.outputs.linux_packages != 'deb'
if: runner.os == 'Linux' && (needs.preflight.outputs.linux_packages == 'all' || contains(needs.preflight.outputs.linux_packages, 'flatpak'))
run: dbus-run-session -- node scripts/release/flatpak-preflight.mjs --install
- name: Prepare ephemeral Developer ID keychain and notarization
if: runner.os == 'macOS'
if: runner.os == 'macOS' && matrix.environment == 'release'
env:
MAC_CERTIFICATE_P12_BASE64: ${{ secrets.MAC_CERTIFICATE_P12_BASE64 }}
MAC_CERTIFICATE_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }}
Expand All @@ -201,7 +205,7 @@ jobs:
MAC_NOTARY_APP_PASSWORD: ${{ secrets.MAC_NOTARY_APP_PASSWORD }}
run: node scripts/release/ci-macos-signing.mjs setup
- name: Build macOS installers
if: runner.os == 'macOS'
if: runner.os == 'macOS' && matrix.environment == 'release'
run: |
node scripts/release/build.mjs --signed
node scripts/release/ci-macos-signing.mjs validate aionui/out/mac-arm64/eLLMsha.app
Expand All @@ -218,7 +222,7 @@ jobs:
if: runner.os == 'Linux'
run: |
test "$(node -p process.arch)" = x64
if [ "$RELEASE_LINUX_PACKAGES" = deb ]; then
if [[ "$RELEASE_LINUX_PACKAGES" != all && "$RELEASE_LINUX_PACKAGES" != *flatpak* ]]; then
node scripts/release/build.mjs --unsigned
else
dbus-run-session -- node scripts/release/build.mjs --unsigned
Expand Down Expand Up @@ -256,7 +260,7 @@ jobs:
path: ${{ runner.temp }}/flatpak-user
key: ${{ steps.flatpak-cache.outputs.cache-primary-key }}
- name: Remove temporary signing material
if: always() && runner.os == 'macOS'
if: always() && runner.os == 'macOS' && matrix.environment == 'release'
run: node scripts/release/ci-macos-signing.mjs cleanup

images:
Expand Down
45 changes: 45 additions & 0 deletions CI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Building eLLMsha with GitHub Actions

Use the root repository's **Release installers and VPS images** workflow for development builds as well as releases. A development build produces downloadable run artifacts. A release publishes installers to GitHub Releases and images to GHCR. All branches use the same production packaging and verification; only the source ref, platform selection and publication choice differ. Test profiles and synthetic credentials are created separately during QA and are never embedded in installers.

## Build just what you need

1. Open [the build workflow](https://github.com/ShiftHackZ/ellmsha/actions/workflows/release.yml) and choose **Run workflow**.
2. Select the branch to build. Use `develop` for the integration build or select your pushed feature branch to test its exact source. The selected ref must contain this workflow version.
3. Enable the outputs you want in the table below. All six default to **on**. Leave **publish** off.
4. Open the finished run and download the matching archive under **Artifacts**. Desktop archives include the installer, SHA-256 sums and a JSON manifest with the root and component commits. Run artifacts are retained for 14 days.

| Input | Output |
| --- | --- |
| `macos` | Apple silicon DMG and ZIP. Every branch uses the same Developer ID signing and notarization as main. |
| `windows` | Unsigned Windows x64 installer |
| `linux-flatpak` | Linux x64 Flatpak bundle |
| `linux-deb` | Linux x64 Debian package |
| `linux-appimage` | Linux x64 portable AppImage, packaged inside a pinned Node container |
| `docker` | Four Linux/amd64 VPS images and their smoke checks |

The three Linux switches are independent of each other and of `docker`. A combined Linux build compiles the desktop payload once. Only Flatpak requests install the Flatpak SDK. The AppImage container wraps the unpacked application and reuses the electron-builder download cache.

For example, this builds **only AppImage** on a pushed feature branch:

```sh
gh workflow run release.yml --repo ShiftHackZ/ellmsha --ref feature/my-change \
-f macos=false -f windows=false -f docker=false \
-f linux-flatpak=false -f linux-deb=false -f linux-appimage=true -f publish=false
```

To test Flatpak alone, swap the Flatpak and AppImage values. Use `gh run list --repo ShiftHackZ/ellmsha --workflow release.yml --branch feature/my-change` to find the run.

## What runs on a pull request or develop push

Every direct push to `develop` and root PR targeting `develop` or `main` runs release contracts, desktop UI tests and Rust workspace library tests against the pushed commit or PR merge result and its pinned submodules. Changes to the shared-terminal paths also trigger the existing cross-platform PTY suite. PR jobs use read-only repository permissions. Packaging is available separately through manual dispatch.

Desktop builds cache compiled Rust dependencies, package-manager downloads, Electron and electron-builder downloads, and the Flatpak installation when selected. Docker maintains its own build cache. GitHub scopes cache reuse by ref. A cache miss on a new branch is expected. Inspect the earliest failed step and retry failed jobs after resolving transient runner issues.

## Publish a release

1. Validate the desired root and component commits on `main`. Build the candidate with **publish off** and test its installers.
2. Check the release notes and the package/About version. Push the matching immutable `vYYYY.MM.DD-patch.N` tag at that exact commit. The tag push selects **all six outputs** and starts publication automatically.
3. Let that run finish. Its publish job checks the exact tag, Kyiv release date, source manifests, installer hashes and tested images before publishing. Verify the distributed assets from the [release page](https://github.com/ShiftHackZ/ellmsha/releases) because publication rebuilds them.

Manual publication is reserved for the exact tagged commit on main or the matching tag and requires all six outputs. Avoid dispatching a second publication for a tag that already triggered a run. Keep signing secrets in GitHub's **release** environment. Set its deployment branch policy to **All branches** so manually selected refs use the same signing path; retain any required reviewer protections. Only repository collaborators can dispatch these builds. Signing failure stops the build—there is no unsigned fallback. PR test workflows do not receive signing secrets. See [signing and build prerequisites](scripts/release/README.md) and [branching strategy](GIT.md).
Loading
Loading