Skip to content

v2.3.2 — bump version, rebuild all PDFs at 2.3.2 #124

v2.3.2 — bump version, rebuild all PDFs at 2.3.2

v2.3.2 — bump version, rebuild all PDFs at 2.3.2 #124

Workflow file for this run

# ELI v2 — cross-platform release pipeline.
#
# Trigger: push a tag `v<version>` (must match [project].version in
# pyproject.toml — the guard job fails the run with a clear message if not).
#
# git tag v2.1.0 && git push origin v2.1.0
#
# Builds in parallel and uploads to the GitHub Release for that tag:
# Windows : ELI-Setup-<v>.exe (Inno Setup) + ELI_v2-<v>-windows-x64.zip (portable, ELI.exe inside)
# macOS : ELI_v2-<v>-macos-arm64.dmg (drag-to-Applications, ad-hoc signed)
# Linux : ELI_v2-<v>-x86_64.AppImage
# plus SHA256SUMS.txt.
#
# Piper TTS voices ARE bundled (pulled from the project's local-assets-v2.1
# release — same set previous full tarballs shipped). GGUF model weights are
# runtime downloads (too big to bundle). Binaries are unsigned; see
# docs/RELEASE_PIPELINE.md for optional Windows/Apple signing steps.
name: release
on:
push:
tags: ["v*"]
workflow_dispatch: {} # manual dry-run: builds artifacts, skips the Release
permissions:
contents: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
env:
PYTHON_VERSION: "3.11" # newest version with binary wheels for the FULL stack (piper-phonemize, tflite-runtime cap at 3.11)
PYTHONHASHSEED: "0"
PIP_DISABLE_PIP_VERSION_CHECK: "1"
PIP_NO_CACHE_DIR: "1"
# Extras that MUST be in the frozen app; the build fails if any is missing.
REQUIRED_EXTRAS: "gui llm server docs analysis extras"
# Extras bundled when their wheels resolve on the runner; skipped (loudly)
# otherwise — every optional import in ELI degrades cleanly by design.
# NOTE: this list is consumed by ALL THREE build jobs. The XTTS-v2 neural voice engine
# (`natural`) is deliberately NOT here — it is added as a job-level override on
# build-linux only, which is the one job that installs the CPU torch stack first and
# verifies no CUDA wheels crept in. Adding it globally makes windows/macos resolve
# torch from PyPI, and their bundles then fail the 2 GiB asset guard below.
OPTIONAL_EXTRAS: "tts asr image vision web wakeword"
jobs:
guard:
name: version guard
runs-on: ubuntu-latest
outputs:
version: ${{ steps.ver.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Whole-tree syntax gate at the BUILD python version
# The v2.1.0 exe shipped broken because one file used py3.12-only
# f-string syntax while builds run py3.11: PyInstaller only WARNS on
# uncompilable modules. Fail the release up front instead.
run: |
python -m compileall -q eli api || {
echo "::error::source does not compile under the release Python ($PYTHON_VERSION) — fix the file above (requires-python is >=3.10, so no 3.12-only syntax)"
exit 1
}
- name: Read version from pyproject.toml and check the tag matches
id: ver
run: |
VERSION=$(python3 -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "pyproject version: $VERSION"
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
TAG="${GITHUB_REF#refs/tags/}"
if [[ "$TAG" != "v$VERSION" ]]; then
echo "::error::Tag '$TAG' does not match pyproject.toml version '$VERSION'. Bump [project].version (and eli/kernel/self_upgrade.py + tests/test_pyproject_packaging.py) or re-tag as v$VERSION."
exit 1
fi
fi
build-windows:
env:
ELI_REQUIRE_VOICES: "1"
ELI_REQUIRE_EMBEDDER: "1"
GH_TOKEN: ${{ github.token }}
name: windows (exe + installer)
needs: guard
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set SOURCE_DATE_EPOCH (reproducible timestamps)
shell: bash
run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> "$GITHUB_ENV"
- name: Install dependencies (core hard-fails; optional extras degrade loudly)
shell: bash
run: |
set -euo pipefail
python -m pip install --upgrade pip wheel
pip install -r requirements-build.txt
pip install ".[$(echo $REQUIRED_EXTRAS | tr ' ' ',')]"
pip install llama-cpp-python --prefer-binary \
--extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu \
|| pip install llama-cpp-python
for extra in $OPTIONAL_EXTRAS; do
pip install ".[$extra]" \
|| echo "::warning::optional extra '$extra' has no installable wheels on windows — shipped build degrades cleanly without it"
done
- name: Generate capability manifests (bundled introspection data)
shell: bash
run: |
python -m eli.core.init_data
python tools/bootstrap_claims_artifacts.py
- name: Bundle Piper voices (from the project's local-assets release)
shell: bash
run: |
mkdir -p tts_piper/piper
gh release download local-assets-v2.1 --repo "$GITHUB_REPOSITORY" --clobber \
--pattern "*.onnx" --pattern "*.onnx.json" --dir tts_piper/piper \
|| { echo "::error::could not download voices from the local-assets-v2.1 release"; exit 1; }
count=$(ls tts_piper/piper/*.onnx 2>/dev/null | wc -l)
echo "voices downloaded: $count"
[ "$count" -ge 1 ] || { echo "::error::no .onnx voices present after download"; exit 1; }
- name: Bundle text embedder (nomic-embed, required for memory/RAG)
shell: bash
run: |
# Ship the ~84 MB embedder so the packaged app has semantic memory
# offline instead of degrading to keyword-only recall. Download straight
# to the repo-relative path ELI.spec bundles from — NOT via model_download,
# whose models_dir() resolves to a runtime data dir that differs per OS.
set -euo pipefail
dest="models/embeddings/nomic-embed-text-v1.5.Q4_K_M.gguf"
url="https://huggingface.co/nomic-ai/nomic-embed-text-v1.5-GGUF/resolve/main/nomic-embed-text-v1.5.Q4_K_M.gguf"
mkdir -p models/embeddings
curl -fL --retry 3 --retry-delay 5 -o "$dest" "$url" \
|| { echo "::error::embedder download failed from $url"; exit 1; }
# Guard against an HTML error page saved as the .gguf (size sanity: >50 MB).
bytes=$(wc -c < "$dest")
[ "$bytes" -gt 52428800 ] \
|| { echo "::error::embedder is only $bytes bytes — download did not return the model"; exit 1; }
echo "embedder: $(( bytes / 1048576 )) MiB at $dest"
- name: PyInstaller build (ELI.spec)
shell: bash
run: pyinstaller --noconfirm --clean ELI.spec
- name: Smoke check — bundle structure + REAL launch selftest
shell: bash
run: |
test -f dist/ELI/ELI.exe || { echo "::error::dist/ELI/ELI.exe missing"; exit 1; }
test -f dist/ELI/ELI-Server.exe || { echo "::error::dist/ELI/ELI-Server.exe missing"; exit 1; }
test -d dist/ELI/_internal/eli || { echo "::error::eli source tree missing from bundle"; exit 1; }
test -f dist/ELI/_internal/config/settings.example.json || { echo "::error::config seeds missing from bundle"; exit 1; }
# Boot the frozen app's import stack for real — catches missing
# hidden imports / syntax errors that only appear at runtime.
if ! ./dist/ELI/ELI.exe --selftest; then
echo "::error::frozen app failed its launch selftest:"
cat eli_selftest_error.log 2>/dev/null || true
exit 1
fi
- name: Reclaim disk before packaging
shell: bash
run: rm -rf build/ELI tts_piper
- name: Portable zip
shell: bash
run: |
V=${{ needs.guard.outputs.version }}
7z a -tzip "dist/ELI_v2-$V-windows-x64.zip" ./dist/ELI
- name: Download VC++ redistributable (bundled into installer)
shell: bash
run: |
mkdir -p build
# aka.ms is a redirect to a Microsoft CDN that occasionally 5xx/times out —
# retry so a transient hiccup can't fail an otherwise-green release (it did
# once, skipping publish while Linux/macOS built fine).
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors --connect-timeout 30 \
-o build/vc_redist.x64.exe https://aka.ms/vs/17/release/vc_redist.x64.exe
test -s build/vc_redist.x64.exe || { echo "::error::vc_redist download failed after retries"; exit 1; }
- name: Inno Setup installer
shell: pwsh
run: |
# The licence pane reads a generated UTF-8+BOM copy of LICENSE; Inno
# decodes a BOM-less UTF-8 file as ANSI and mangles it.
python scripts\make_installer_license.py
if ($LASTEXITCODE -ne 0) { Write-Error "could not generate the installer licence file"; exit 1 }
$iscc = "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe"
if (-not (Test-Path $iscc)) { choco install innosetup -y --no-progress; $iscc = "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe" }
if (-not (Test-Path $iscc)) { Write-Error "Inno Setup 6 (ISCC.exe) not found and choco install failed"; exit 1 }
& $iscc packaging\windows\installer.iss /DMyAppVersion=${{ needs.guard.outputs.version }}
if ($LASTEXITCODE -ne 0) { Write-Error "ISCC failed with exit code $LASTEXITCODE"; exit 1 }
- name: Checksums + GitHub 2 GiB asset-size guard
shell: bash
run: |
cd dist
rm -rf ELI
for f in *.zip *.exe; do
[ -e "$f" ] || continue
sha256sum "$f" >> SHA256SUMS-windows.txt
size=$(stat -c%s "$f")
if [ "$size" -ge 2147483648 ]; then
echo "::error::$f is $size bytes — over GitHub's 2 GiB release-asset limit. Trim OPTIONAL_EXTRAS in release.yml."
exit 1
fi
done
cat SHA256SUMS-windows.txt
- uses: actions/upload-artifact@v4
with:
name: eli-windows
retention-days: 3 # build intermediates; the publish job consumes them immediately
path: |
dist/*.zip
dist/*.exe
dist/SHA256SUMS-windows.txt
if-no-files-found: error
build-macos:
env:
ELI_REQUIRE_VOICES: "1"
ELI_REQUIRE_EMBEDDER: "1"
GH_TOKEN: ${{ github.token }}
name: macos (app + dmg, arm64)
needs: guard
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set SOURCE_DATE_EPOCH (reproducible timestamps)
run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> "$GITHUB_ENV"
- name: System build deps (portaudio for PyAudio)
run: brew install portaudio
- name: Install dependencies (core hard-fails; optional extras degrade loudly)
run: |
set -euo pipefail
python -m pip install --upgrade pip wheel
pip install -r requirements-build.txt
pip install ".[$(echo $REQUIRED_EXTRAS | tr ' ' ',')]"
pip install llama-cpp-python --prefer-binary \
--extra-index-url https://abetlen.github.io/llama-cpp-python/whl/metal \
|| pip install llama-cpp-python
for extra in $OPTIONAL_EXTRAS; do
pip install ".[$extra]" \
|| echo "::warning::optional extra '$extra' has no installable wheels on macos-arm64 — shipped build degrades cleanly without it"
done
- name: Generate capability manifests (bundled introspection data)
run: |
python -m eli.core.init_data
python tools/bootstrap_claims_artifacts.py
- name: Bundle Piper voices (from the project's local-assets release)
run: |
mkdir -p tts_piper/piper
gh release download local-assets-v2.1 --repo "$GITHUB_REPOSITORY" --clobber \
--pattern "*.onnx" --pattern "*.onnx.json" --dir tts_piper/piper \
|| { echo "::error::could not download voices from the local-assets-v2.1 release"; exit 1; }
count=$(ls tts_piper/piper/*.onnx 2>/dev/null | wc -l)
echo "voices downloaded: $count"
[ "$count" -ge 1 ] || { echo "::error::no .onnx voices present after download"; exit 1; }
- name: Bundle text embedder (nomic-embed, required for memory/RAG)
shell: bash
run: |
# Ship the ~84 MB embedder so the packaged app has semantic memory
# offline instead of degrading to keyword-only recall. Download straight
# to the repo-relative path ELI.spec bundles from — NOT via model_download,
# whose models_dir() resolves to a runtime data dir that differs per OS.
set -euo pipefail
dest="models/embeddings/nomic-embed-text-v1.5.Q4_K_M.gguf"
url="https://huggingface.co/nomic-ai/nomic-embed-text-v1.5-GGUF/resolve/main/nomic-embed-text-v1.5.Q4_K_M.gguf"
mkdir -p models/embeddings
curl -fL --retry 3 --retry-delay 5 -o "$dest" "$url" \
|| { echo "::error::embedder download failed from $url"; exit 1; }
bytes=$(wc -c < "$dest")
[ "$bytes" -gt 52428800 ] \
|| { echo "::error::embedder is only $bytes bytes — download did not return the model"; exit 1; }
echo "embedder: $(( bytes / 1048576 )) MiB at $dest"
- name: PyInstaller build (ELI.spec)
run: pyinstaller --noconfirm --clean ELI.spec
- name: Smoke check — .app structure + REAL launch selftest
run: |
test -x dist/ELI.app/Contents/MacOS/ELI || { echo "::error::ELI.app executable missing"; exit 1; }
test -d dist/ELI.app/Contents/Frameworks/eli -o -d dist/ELI.app/Contents/Resources/eli || { echo "::error::eli source tree missing from .app"; exit 1; }
if ! ./dist/ELI.app/Contents/MacOS/ELI --selftest; then
echo "::error::frozen app failed its launch selftest:"
cat eli_selftest_error.log 2>/dev/null || true
exit 1
fi
- name: Build .dmg (ad-hoc signed)
run: bash packaging/macos/build-dmg.sh
- name: Checksums + GitHub 2 GiB asset-size guard
run: |
cd dist
rm -rf ELI ELI.app
for f in *.dmg; do
shasum -a 256 "$f" >> SHA256SUMS-macos.txt
size=$(stat -f%z "$f")
if [ "$size" -ge 2147483648 ]; then
echo "::error::$f is $size bytes — over GitHub's 2 GiB release-asset limit. Trim OPTIONAL_EXTRAS in release.yml."
exit 1
fi
done
cat SHA256SUMS-macos.txt
- uses: actions/upload-artifact@v4
with:
name: eli-macos
retention-days: 3 # build intermediates; the publish job consumes them immediately
path: |
dist/*.dmg
dist/SHA256SUMS-macos.txt
if-no-files-found: error
build-linux:
env:
ELI_REQUIRE_VOICES: "1"
ELI_REQUIRE_EMBEDDER: "1"
GH_TOKEN: ${{ github.token }}
# The XTTS-v2 neural engine is deliberately NOT bundled. It shipped briefly in
# v2.1.72 and was pulled: the CPU-only torch the bundle must use (a CUDA build
# blows GitHub's 2 GiB asset limit) means XTTS runs on CPU, and on a GPU machine
# the startup picker pinned ELI_XTTS_DEVICE=cuda, so it crashed with "Torch not
# compiled with CUDA enabled" — and even working, it costs GPU/CPU far out of
# proportion to a voice. Piper is the shipped engine. Cloning still works from a
# source install (`pip install -e ".[natural]"`), where torch matches the host.
name: linux (AppImage)
needs: guard
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set SOURCE_DATE_EPOCH (reproducible timestamps)
run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> "$GITHUB_ENV"
- name: Free runner disk (full dependency stack + staging needs it)
run: |
sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc /usr/local/.ghcup
df -h /
- name: System build deps (portaudio, espeak, Qt/GL runtime libs)
run: |
sudo apt-get update
# xcb-util family: Qt 6.5+'s xcb platform plugin (libqxcb.so) links the
# whole set at runtime (libxcb-cursor/icccm/image/keysyms/render-util/util).
# PyInstaller only bundles what is present at build time, so these MUST be
# installed or the AppImage fails on lean end-user distros — Qt reports it
# misleadingly as "libxcb-cursor0 is needed" whichever member is missing.
# xvfb: virtual X server for the real-xcb GUI selftest in the smoke check.
sudo apt-get install -y --no-install-recommends \
portaudio19-dev libasound2-dev libegl1 libgl1 libxkbcommon-x11-0 \
libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 \
libxcb-render-util0 libxcb-util1 espeak-ng xvfb
- name: Install dependencies (core hard-fails; optional extras degrade loudly)
run: |
set -euo pipefail
python -m pip install --upgrade pip wheel
pip install -r requirements-build.txt
# CPU torch stack FIRST — default PyPI torch drags ~6 GB of CUDA libs into the
# bundle. torchaudio is included here for the same reason: the `natural`
# (XTTS-v2) extra requires it, and resolving it from PyPI afterwards pulls the
# CUDA build of torch straight back in.
pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu
pip install ".[$(echo $REQUIRED_EXTRAS | tr ' ' ',')]"
pip install llama-cpp-python --prefer-binary \
--extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu \
|| pip install llama-cpp-python
for extra in $OPTIONAL_EXTRAS; do
pip install ".[$extra]" \
|| echo "::warning::optional extra '$extra' has no installable wheels on linux — shipped build degrades cleanly without it"
done
# An extra that quietly re-resolved torch from PyPI would pull the CUDA build
# (~6 GB of nvidia-* wheels) and push the AppImage past GitHub's 2 GB per-asset
# limit — the exact failure that kept `natural` out of the bundle before. Fail
# here, where the cause is obvious, rather than at upload.
if pip list --format=freeze | grep -q '^nvidia-'; then
echo "::error::CUDA wheels were pulled in (nvidia-*) — the torch stack is no longer CPU-only:"
pip list --format=freeze | grep '^nvidia-' | sed 's/^/ /'
exit 1
fi
python - <<'PY'
import importlib
for mod, why in (("torch", "neural voice engine"), ("TTS", "XTTS-v2 (coqui-tts)")):
try:
importlib.import_module(mod)
print(f" {mod}: present ({why})")
except Exception as e:
print(f"::warning::{mod} unavailable — {why} will degrade to Piper: {e}")
PY
- name: Generate capability manifests (bundled introspection data)
run: |
python -m eli.core.init_data
python tools/bootstrap_claims_artifacts.py
- name: Bundle Piper voices (from the project's local-assets release)
run: |
mkdir -p tts_piper/piper
gh release download local-assets-v2.1 --repo "$GITHUB_REPOSITORY" --clobber \
--pattern "*.onnx" --pattern "*.onnx.json" --dir tts_piper/piper \
|| { echo "::error::could not download voices from the local-assets-v2.1 release"; exit 1; }
count=$(ls tts_piper/piper/*.onnx 2>/dev/null | wc -l)
echo "voices downloaded: $count"
[ "$count" -ge 1 ] || { echo "::error::no .onnx voices present after download"; exit 1; }
- name: Bundle text embedder (nomic-embed, required for memory/RAG)
shell: bash
run: |
# Ship the ~84 MB embedder so the packaged app has semantic memory
# offline instead of degrading to keyword-only recall. Download straight
# to the repo-relative path ELI.spec bundles from — NOT via model_download,
# whose models_dir() resolves to a runtime data dir that differs per OS.
set -euo pipefail
dest="models/embeddings/nomic-embed-text-v1.5.Q4_K_M.gguf"
url="https://huggingface.co/nomic-ai/nomic-embed-text-v1.5-GGUF/resolve/main/nomic-embed-text-v1.5.Q4_K_M.gguf"
mkdir -p models/embeddings
curl -fL --retry 3 --retry-delay 5 -o "$dest" "$url" \
|| { echo "::error::embedder download failed from $url"; exit 1; }
bytes=$(wc -c < "$dest")
[ "$bytes" -gt 52428800 ] \
|| { echo "::error::embedder is only $bytes bytes — download did not return the model"; exit 1; }
echo "embedder: $(( bytes / 1048576 )) MiB at $dest"
- name: PyInstaller build (ELI.spec)
run: |
pyinstaller --noconfirm --clean ELI.spec 2>&1 | tee /tmp/pyi.log
# collect_submodules imports the package in an ISOLATED SUBPROCESS, so a
# package that only imports with an in-process shim fails collection and
# PyInstaller merely WARNS — buried in ~250k log lines. That is how four
# releases shipped an AppImage whose neural engine could not load.
if grep -q "Failed to collect submodules for 'TTS'" /tmp/pyi.log; then
echo "::error::PyInstaller could not collect TTS — the neural engine will not load:"
grep -m1 -A2 "Failed to collect submodules for 'TTS'" /tmp/pyi.log
exit 1
fi
- name: Smoke check — bundle structure + REAL launch selftest (headless + xcb)
run: |
test -x dist/ELI/ELI || { echo "::error::dist/ELI/ELI missing"; exit 1; }
test -x dist/ELI/ELI-Server || { echo "::error::dist/ELI/ELI-Server missing"; exit 1; }
test -d dist/ELI/_internal/eli || { echo "::error::eli source tree missing from bundle"; exit 1; }
# The neural voice engine must be COLLECTED, not merely pip-installed.
# v2.1.65 and v2.1.66 both shipped coqui_tts-*.dist-info with no TTS/
# package: the spec's import probe raised on a transformers incompatibility
# and silently skipped collection, so every cloned voice fell back to Piper
# with the extra sitting unused in the bundle. Metadata is not the package.
if ls dist/ELI/_internal/coqui_tts-*.dist-info >/dev/null 2>&1; then
if [ ! -d dist/ELI/_internal/TTS ]; then
echo "::error::coqui-tts is installed but the TTS/ package was NOT collected — voice cloning would silently fall back to Piper"
exit 1
fi
echo "TTS/ package collected: $(du -sh dist/ELI/_internal/TTS | cut -f1)"
fi
# Self-containment guard: the Qt xcb plugin (libqxcb.so) links the whole
# xcb-util family at runtime and resolves them via its RUNPATH from
# PySide6/Qt/lib. Assert EACH member is bundled IN THAT EXACT DIRECTORY —
# v2.1.20 bundled libxcb-cursor into the bundle root instead, which the
# plugin never searches, so the GUI still failed on lean distros. Checking
# the precise dir (not just "present somewhere") is what closes that gap.
QTLIBDIR=dist/ELI/_internal/PySide6/Qt/lib
for _lib in libxcb-cursor.so libxcb-icccm.so libxcb-image.so \
libxcb-keysyms.so libxcb-render-util.so libxcb-util.so; do
if ! ls "$QTLIBDIR/$_lib"* >/dev/null 2>&1; then
echo "::error::$_lib is NOT bundled in $QTLIBDIR — Qt xcb GUI will fail on minimal distros"
exit 1
fi
done
# Headless engine selftest (offscreen Qt platform).
if ! QT_QPA_PLATFORM=offscreen ./dist/ELI/ELI --selftest; then
echo "::error::frozen app failed its headless launch selftest:"
cat eli_selftest_error.log 2>/dev/null || true
exit 1
fi
# Real-X GUI selftest under a virtual display — exercises the xcb platform
# plugin the offscreen path never touches. THIS is the check that would have
# caught the missing-libxcb-cursor gap; keep it so it can't regress.
if ! xvfb-run -a env QT_QPA_PLATFORM=xcb ./dist/ELI/ELI --selftest; then
echo "::error::frozen app failed the xcb (real X) launch selftest — a Qt GUI dependency is missing from the bundle:"
cat eli_selftest_error.log 2>/dev/null || true
exit 1
fi
- name: Build AppImage
run: bash packaging/linux/build-appimage-pyinstaller.sh
- name: Classic portable tarball (source + voices — parity with previous releases)
run: |
pip install build
SKIP_TESTS=1 bash scripts/package_desktop_app.sh --with-assets
mv dist/app_packages/ELI_v2-*-linux-portable.tar.gz dist/
ls -lh dist/*.tar.gz
- name: Checksums + GitHub 2 GiB asset-size guard
run: |
cd dist
rm -rf ELI
for f in *.AppImage *.tar.gz; do
[ -e "$f" ] || continue
sha256sum "$f" >> SHA256SUMS-linux.txt
size=$(stat -c%s "$f")
if [ "$size" -ge 2147483648 ]; then
echo "::error::$f is $size bytes — over GitHub's 2 GiB release-asset limit. Trim OPTIONAL_EXTRAS in release.yml."
exit 1
fi
done
cat SHA256SUMS-linux.txt
- uses: actions/upload-artifact@v4
with:
name: eli-linux
retention-days: 3 # build intermediates; the publish job consumes them immediately
path: |
dist/*.AppImage
dist/*.tar.gz
dist/SHA256SUMS-linux.txt
if-no-files-found: error
release:
name: publish GitHub Release
if: startsWith(github.ref, 'refs/tags/')
needs: [guard, build-windows, build-macos, build-linux]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Merge checksums
run: |
cd dist
cat SHA256SUMS-*.txt > SHA256SUMS.txt
rm SHA256SUMS-*.txt
ls -lh
- name: Create/update GitHub Release and upload artifacts
uses: softprops/action-gh-release@v2
with:
name: ELI v${{ needs.guard.outputs.version }}
generate_release_notes: true
files: |
dist/*.zip
dist/*.exe
dist/*.dmg
dist/*.AppImage
dist/*.tar.gz
dist/SHA256SUMS.txt
fail_on_unmatched_files: true