Add native installers (DMG/EXE/AppImage) to releases

This commit is contained in:
2026-09-01 16:27:07 -05:00
parent 40f15bc45a
commit 247daa6460
9 changed files with 1015 additions and 4 deletions
+91 -4
View File
@@ -64,9 +64,89 @@ jobs:
name: bindings-${{ matrix.target }} name: bindings-${{ matrix.target }}
path: gelectron_core.*.node path: gelectron_core.*.node
runtime:
name: Runtime ${{ matrix.target }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
target: aarch64-apple-darwin
platform: darwin
arch: arm64
- os: macos-latest
target: x86_64-apple-darwin
platform: darwin
arch: x64
- os: windows-latest
target: x86_64-pc-windows-msvc
platform: win32
arch: x64
- os: windows-latest
target: aarch64-pc-windows-msvc
platform: win32
arch: arm64
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
platform: linux
arch: x64
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install Linux system dependencies
if: matrix.platform == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev libsoup-3.0-dev
- name: Build gelectron binary
run: cargo build --release -p gelectron --target ${{ matrix.target }}
- name: Package release archive
run: |
bash scripts/make-release.sh \
-v "${{ github.ref_name }}" \
-p ${{ matrix.platform }} \
-a ${{ matrix.arch }} \
-b "target/${{ matrix.target }}/release/gelectron" \
--no-build
- name: Build macOS installer (DMG)
if: matrix.platform == 'darwin'
run: bash scripts/pkg/make-dmg.sh -v "${{ github.ref_name }}" -a ${{ matrix.arch }} -b "target/${{ matrix.target }}/release/gelectron" -o dist
- name: Build Windows installer (EXE)
if: matrix.platform == 'win32' && matrix.arch == 'x64'
shell: pwsh
run: |
choco install nsis -y --no-progress
powershell -File scripts/pkg/make-exe.ps1 -Bin "target/${{ matrix.target }}/release/gelectron.exe" -Compat src/electron -Version "${{ github.ref_name }}" -Arch x64 -Out dist
- name: Build Linux installer (AppImage)
if: matrix.platform == 'linux'
run: bash scripts/pkg/make-appimage.sh -v "${{ github.ref_name }}" -b "target/${{ matrix.target }}/release/gelectron" -o dist
- name: Upload installer artifacts
uses: actions/upload-artifact@v4
with:
name: installers-${{ matrix.target }}
path: |
dist/gelectron-*
dist/Gelectron-*
if-no-files-found: error
release: release:
name: Attach binaries to GitHub Release name: Attach binaries to GitHub Release
needs: build needs: [build, runtime]
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
@@ -79,10 +159,17 @@ jobs:
pattern: bindings-* pattern: bindings-*
merge-multiple: true merge-multiple: true
- name: List binaries - name: Download all installers
uses: actions/download-artifact@v4
with:
path: artifacts
pattern: installers-*
merge-multiple: true
- name: List artifacts
run: ls -la artifacts/ run: ls -la artifacts/
- name: Upload binaries to GitHub Release - name: Upload assets to GitHub Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
files: artifacts/*.node files: artifacts/**
+47
View File
@@ -38,8 +38,55 @@ Once installed, run any Electron app with:
gelectron-core /path/to/electron-app gelectron-core /path/to/electron-app
``` ```
### Download a native installer (double-click, no tools needed)
Every new release tag (pushed as `v*`) is pre-compiled in CI and published as
platform-native, double-clickable installers on the [Releases page]:
| Platform | Installer | What it does |
|---|---|---|
| macOS | `Gelectron-<version>-arm64.dmg` / `-x64.dmg` | Contains `Install gelectron.pkg`, which installs the runtime + compat layer to `/usr/local/bin` |
| Windows | `Gelectron-<version>-x64.exe` | NSIS installer → `C:\Program Files\Gelectron`, adds it to the system PATH, Start Menu + uninstaller |
| Linux | `Gelectron-<version>-x86_64.AppImage` | Self-contained bundle (includes Node.js + compat), just run it |
After installing, `gelectron /path/to/electron-app` works from anywhere.
> macOS installers are ad-hoc signed (no Developer ID), so on another Mac the
> first launch shows a Gatekeeper "unidentified developer" warning — right-click
> → Open to run it.
### Install from GitHub Releases (no npm)
Every new release tag (pushed as `v*`) is pre-compiled in CI and published as
a self-contained installer archive. Install the latest pre-built runtime with
a single command — no Rust toolchain, no npm:
```bash
# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/mileswolfallen2/gelectron/main/scripts/install-release.sh | bash
# Windows (PowerShell)
irm https://raw.githubusercontent.com/mileswolfallen2/gelectron/main/scripts/install-release.ps1 | iex
```
The installer downloads `gelectron-<version>-<platform>-<arch>.tar.gz` (or
`.zip`) from the latest GitHub Release and puts the `gelectron` binary plus
the JS compatibility layer into `~/.local/bin` (macOS/Linux) or
`%LOCALAPPDATA%\gelectron\bin` (Windows). Customize with:
```bash
bash scripts/install-release.sh --version v0.1.1 # specific tag
bash scripts/install-release.sh --prefix ~/bin # custom location
bash scripts/install-release.sh --uninstall # remove
```
Archives can also be downloaded directly from the [Releases page] and
unpacked manually — the binary just needs the `compat/` folder next to it.
> The `gelectron-core` npm package is the recommended distribution channel. Building from source (below) is only needed if you're developing Gelectron itself or want the bleeding-edge version. > The `gelectron-core` npm package is the recommended distribution channel. Building from source (below) is only needed if you're developing Gelectron itself or want the bleeding-edge version.
[Releases page]: https://github.com/mileswolfallen2/gelectron/releases
### Prerequisites (for building from source) ### Prerequisites (for building from source)
- Rust 1.75+ (`rustup.rs`) - Rust 1.75+ (`rustup.rs`)
+119
View File
@@ -0,0 +1,119 @@
# gelectron installer (PowerShell / Windows)
#
# Downloads the pre-built gelectron binary + Electron compatibility layer from
# GitHub Releases and installs it into PREFIX so `gelectron <app>` works from
# anywhere. No Rust toolchain or npm required.
#
# Install layout:
# <PREFIX>/gelectron.exe native binary
# <PREFIX>/compat/*.js Electron API compat layer
#
# Usage:
# .\scripts\install-release.ps1 latest release
# .\scripts\install-release.ps1 -Version v0.1.1
# .\scripts\install-release.ps1 -File C:\path\to\gelectron-0.1.1-win32-x64.zip
# .\scripts\install-release.ps1 -Prefix "$HOME\bin"
# .\scripts\install-release.ps1 -Uninstall
param(
[string]$Repo = "mileswolfallen2/gelectron",
[string]$Prefix = "",
[string]$Version = "",
[string]$File = "",
[switch]$Uninstall,
[switch]$Help
)
$ErrorActionPreference = "Stop"
function Show-Help {
@"
gelectron installer - install pre-built gelectron from GitHub Releases
Usage:
install-release.ps1 [options]
Options:
-Repo owner/repo GitHub repo (default: mileswolfallen2/gelectron)
-Prefix DIR Install directory (default: %LOCALAPPDATA%\gelectron\bin)
-Version TAG Install a specific release tag (default: latest)
-File PATH Install from a local archive instead of downloading
-Uninstall Remove previously installed files
-Help Show this help
"@ | Write-Host
}
if ($Help) { Show-Help; exit 0 }
if (-not $Prefix) { $Prefix = Join-Path $env:LOCALAPPDATA "gelectron\bin" }
# ── Detect platform / arch ──────────────────────────────────────────────────
$Platform = "win32"
switch ($env:PROCESSOR_ARCHITECTURE) {
"ARM64" { $Arch = "arm64" }
default { $Arch = "x64" }
}
$ArchiveExt = "zip"
# ── Uninstall ───────────────────────────────────────────────────────────────
if ($Uninstall) {
foreach ($p in @((Join-Path $Prefix "gelectron.exe"), (Join-Path $Prefix "compat"))) {
if (Test-Path $p) { Remove-Item -Recurse -Force $p }
}
Write-Host "Removed $Prefix\gelectron.exe and $Prefix\compat"
exit 0
}
# ── Resolve version + archive ───────────────────────────────────────────────
$ArchiveFile = $File
if (-not $ArchiveFile) {
if ($Version) {
$Tag = "v" + ($Version.TrimStart("v"))
} else {
Write-Host "==> Resolving latest release from $Repo..."
$latest = Invoke-RestMethod -Uri "https://api.github.com/repos/$Repo/releases/latest" -Headers @{ "User-Agent" = "gelectron-installer" }
$Tag = $latest.tag_name
}
$Ver = $Tag.TrimStart("v")
$Asset = "gelectron-$Ver-$Platform-$Arch.$ArchiveExt"
$Url = "https://github.com/$Repo/releases/download/$Tag/$Asset"
Write-Host "==> Downloading $Asset ..."
$ArchiveFile = Join-Path $env:TEMP "gelectron-install-$([Guid]::NewGuid().ToString('N')).$ArchiveExt"
Invoke-WebRequest -Uri $Url -OutFile $ArchiveFile -UseBasicParsing
}
# ── Extract ─────────────────────────────────────────────────────────────────
$Stage = Join-Path $env:TEMP ("gelectron-install-stage-" + [Guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $Stage | Out-Null
try {
Write-Host "==> Extracting archive..."
Expand-Archive -Path $ArchiveFile -DestinationPath $Stage -Force
$Bin = Join-Path $Stage "gelectron.exe"
if (-not (Test-Path $Bin)) {
throw "archive does not contain gelectron.exe"
}
# ── Install ─────────────────────────────────────────────────────────────
Write-Host "==> Installing to $Prefix"
New-Item -ItemType Directory -Path $Prefix -Force | Out-Null
Copy-Item $Bin (Join-Path $Prefix "gelectron.exe") -Force
if (Test-Path (Join-Path $Stage "compat")) {
Copy-Item -Recurse -Force (Join-Path $Stage "compat") $Prefix
}
Write-Host ""
Write-Host " OK Installed: $Prefix\gelectron.exe"
Write-Host " OK Installed: $Prefix\compat\"
Write-Host ""
Write-Host " Add $Prefix to your PATH, then run:"
Write-Host ' gelectron C:\path\to\electron-app'
}
finally {
Remove-Item -Recurse -Force $Stage -ErrorAction SilentlyContinue
if (-not $File) { Remove-Item -Force $ArchiveFile -ErrorAction SilentlyContinue }
}
+174
View File
@@ -0,0 +1,174 @@
#!/usr/bin/env bash
#
# gelectron installer
#
# Downloads the pre-built gelectron binary + Electron compatibility layer from
# GitHub Releases and installs it into PREFIX so `gelectron <app>` works from
# anywhere. No Rust toolchain or Node dependency manager required.
#
# Install layout:
# <PREFIX>/gelectron native binary
# <PREFIX>/compat/*.js Electron API compat layer
#
# Usage:
# curl -fsSL https://raw.githubusercontent.com/mileswolfallen2/gelectron/main/scripts/install-release.sh | bash
# ./scripts/install-release.sh latest release
# ./scripts/install-release.sh --version v0.1.1
# ./scripts/install-release.sh --file /path/to/gelectron-0.1.1-darwin-arm64.tar.gz
# PREFIX=~/bin ./scripts/install-release.sh custom prefix
# ./scripts/install-release.sh --uninstall remove installed files
set -euo pipefail
DEFAULT_REPO="mileswolfallen2/gelectron"
DEFAULT_PREFIX="${HOME}/.local/bin"
REPO="$DEFAULT_REPO"
PREFIX="${PREFIX:-$DEFAULT_PREFIX}"
VERSION=""
FILE=""
UNINSTALL=0
usage() {
cat <<'EOF'
gelectron installer — install pre-built gelectron from GitHub Releases
Usage:
install-release.sh [options]
Options:
--repo owner/repo GitHub repo to fetch releases from (default: mileswolfallen2/gelectron)
--prefix DIR Install directory (env: PREFIX, default: ~/.local/bin)
--version TAG Install a specific release tag (default: latest)
--file PATH Install from a local release archive instead of downloading
--uninstall Remove previously installed files
-h, --help Show this help
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--repo) REPO="${2:-}"; shift 2 ;;
--prefix) PREFIX="${2:-}"; shift 2 ;;
--version) VERSION="${2:-}"; shift 2 ;;
--file) FILE="${2:-}"; shift 2 ;;
--uninstall) UNINSTALL=1; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "error: unknown option: $1" >&2; usage; exit 1 ;;
esac
done
# ── Detect platform / arch ──────────────────────────────────────────────────
case "$(uname -s)" in
Darwin) PLATFORM="darwin" ;;
MINGW*|MSYS*|CYGWIN*) PLATFORM="win32" ;;
Linux) PLATFORM="linux" ;;
*) echo "error: unsupported platform: $(uname -s)" >&2; exit 1 ;;
esac
case "$(uname -m)" in
arm64|aarch64) ARCH="arm64" ;;
x86_64|amd64) ARCH="x64" ;;
*) echo "error: unsupported arch: $(uname -m)" >&2; exit 1 ;;
esac
[[ "$PLATFORM" == "win32" ]] && ARCHIVE_EXT="zip" || ARCHIVE_EXT="tar.gz"
# ── Uninstall ───────────────────────────────────────────────────────────────
if [[ "$UNINSTALL" == "1" ]]; then
rm -f "$PREFIX/gelectron"
rm -f "$PREFIX/gelectron.exe"
rm -rf "$PREFIX/compat"
echo "Removed $PREFIX/gelectron and $PREFIX/compat"
exit 0
fi
# ── Resolve version + archive ───────────────────────────────────────────────
if [[ -n "$FILE" ]]; then
ARCHIVE_FILE="$FILE"
else
if [[ -n "$VERSION" ]]; then
TAG="${VERSION#v}"
TAG="v${TAG}"
else
echo "==> Resolving latest release from $REPO..."
if ! command -v curl >/dev/null 2>&1; then
echo "error: curl is required" >&2
exit 1
fi
API_URL="https://api.github.com/repos/$REPO/releases/latest"
TAG="$(curl -fsSL "$API_URL" | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -n1)"
if [[ -z "$TAG" ]]; then
echo "error: could not resolve latest release from $API_URL" >&2
exit 1
fi
fi
VER="${TAG#v}"
ASSET="gelectron-$VER-$PLATFORM-$ARCH.$ARCHIVE_EXT"
URL="https://github.com/$REPO/releases/download/$TAG/$ASSET"
echo "==> Downloading $ASSET ..."
ARCHIVE_FILE="$(mktemp /tmp/gelectron-install.XXXXXX.$ARCHIVE_EXT)"
trap 'rm -f "$ARCHIVE_FILE"' EXIT
curl -fsSL -L "$URL" -o "$ARCHIVE_FILE"
fi
# ── Extract ─────────────────────────────────────────────────────────────────
STAGE="$(mktemp -d "${TMPDIR:-/tmp}/gelectron-install-stage.XXXXXX")"
trap 'rm -rf "$STAGE" "${ARCHIVE_FILE:-}"' EXIT
echo "==> Extracting archive..."
case "$ARCHIVE_EXT" in
zip) unzip -qo "$ARCHIVE_FILE" -d "$STAGE" ;;
*) tar -xzf "$ARCHIVE_FILE" -C "$STAGE" ;;
esac
BIN_ABS="$STAGE/gelectron"
[[ -f "$STAGE/gelectron.exe" ]] && BIN_ABS="$STAGE/gelectron.exe"
if [[ ! -f "$BIN_ABS" ]]; then
echo "error: archive does not contain gelectron" >&2
ls -la "$STAGE" >&2
exit 1
fi
# ── Install ─────────────────────────────────────────────────────────────────
echo "==> Installing to $PREFIX"
mkdir -p "$PREFIX"
install -m 755 "$BIN_ABS" "$PREFIX/$(basename "$BIN_ABS")"
if [[ -d "$STAGE/compat" ]]; then
mkdir -p "$PREFIX/compat"
install -m 644 "$STAGE"/compat/*.js "$PREFIX/compat/"
fi
# Downloaded-from-GitHub binaries carry a quarantine attribute on macOS and
# Gatekeeper will block the first launch. Clear it — the binary is ad-hoc
# signed, so this is the only thing standing in the way.
if [[ "$PLATFORM" == "darwin" ]]; then
xattr -dr com.apple.quarantine "$PREFIX/$(basename "$BIN_ABS")" 2>/dev/null || true
fi
echo
echo " ✓ Installed: $PREFIX/$(basename "$BIN_ABS")"
echo " ✓ Installed: $PREFIX/compat/"
if ! command -v gelectron >/dev/null 2>&1 || [[ "$(command -v gelectron)" != "$PREFIX/gelectron" ]]; then
echo
echo " warning: $PREFIX is not on your PATH."
case "$SHELL" in
*zsh) RC="$HOME/.zshrc" ;;
*bash) RC="$HOME/.bashrc" ;;
*) RC="$HOME/.profile" ;;
esac
echo " Add this line to $RC:"
echo " export PATH=\"$PREFIX:\$PATH\""
fi
echo
"$PREFIX/$(basename "$BIN_ABS")" --version >/dev/null 2>&1 && \
echo " Run: gelectron /path/to/electron-app" || \
echo " Hint: $PREFIX/$(basename "$BIN_ABS") --version"
+180
View File
@@ -0,0 +1,180 @@
#!/usr/bin/env bash
#
# gelectron release builder
#
# Pre-compiles the gelectron native binary and bundles it with the Electron
# compatibility layer (src/electron/*.js) into a single, self-contained
# installer archive per platform/arch:
#
# gelectron-<version>-darwin-arm64.tar.gz
# gelectron-<version>-darwin-x64.tar.gz
# gelectron-<version>-win32-arm64.zip
# gelectron-<version>-win32-x64.zip
# gelectron-<version>-linux-arm64.tar.gz
# gelectron-<version>-linux-x64.tar.gz
#
# Archive layout (the install scripts unpack this into PREFIX):
# gelectron | gelectron.exe native binary
# compat/*.js Electron API compatibility layer
#
# This script is what the GitHub Actions workflow runs on every new release
# tag; it can also be run locally for testing or manual distributions.
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
VERSION=""
PLATFORM=""
ARCH=""
BINARY=""
OUT_DIR="$REPO_DIR/dist"
BUILD=1
usage() {
cat <<'EOF'
gelectron release builder
Usage:
scripts/make-release.sh [options]
Options:
-v, --version VER Release version (default: from package.json, minus v)
-p, --platform P Target OS: darwin, win32, linux (default: current)
-a, --arch A Target arch: x64, arm64 (default: current)
-b, --binary PATH Use an already-built gelectron binary (skips cargo build)
-o, --out DIR Output directory (default: <repo>/dist)
-n, --no-build Do not compile (requires --binary or an existing build)
-h, --help Show this help
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
-v|--version) VERSION="${2:-}"; shift 2 ;;
-p|--platform) PLATFORM="${2:-}"; shift 2 ;;
-a|--arch) ARCH="${2:-}"; shift 2 ;;
-b|--binary) BINARY="${2:-}"; shift 2 ;;
-o|--out) OUT_DIR="${2:-}"; shift 2 ;;
-n|--no-build) BUILD=0; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "error: unknown option: $1" >&2; usage; exit 1 ;;
esac
done
# ── Resolve defaults ────────────────────────────────────────────────────────
if [[ -z "$VERSION" ]]; then
VERSION="$(node -p "require('$REPO_DIR/package.json').version" 2>/dev/null || echo '0.0.0')"
fi
VERSION="${VERSION#v}"
if [[ -z "$PLATFORM" ]]; then
case "$(uname -s)" in
Darwin) PLATFORM="darwin" ;;
MINGW*|MSYS*|CYGWIN*) PLATFORM="win32" ;;
Linux) PLATFORM="linux" ;;
*) echo "error: cannot detect platform" >&2; exit 1 ;;
esac
fi
if [[ -z "$ARCH" ]]; then
case "$(uname -m)" in
arm64|aarch64) ARCH="arm64" ;;
x86_64|amd64) ARCH="x64" ;;
*) echo "error: cannot detect arch" >&2; exit 1 ;;
esac
fi
case "$PLATFORM-$ARCH" in
darwin-arm64|darwin-x64|win32-arm64|win32-x64|linux-arm64|linux-x64) ;;
*) echo "error: unsupported platform/arch: $PLATFORM/$ARCH" >&2; exit 1 ;;
esac
EXE_SUFFIX=""
[[ "$PLATFORM" == "win32" ]] && EXE_SUFFIX=".exe"
[[ "$PLATFORM" == "win32" ]] && ARCHIVE_EXT="zip" || ARCHIVE_EXT="tar.gz"
log() { echo "==> $*"; }
# ── Locate or build the gelectron binary ────────────────────────────────────
BIN="$BINARY"
if [[ -z "$BIN" && "$BUILD" == "1" ]]; then
CANDIDATES=(
"$REPO_DIR/target/release/gelectron$EXE_SUFFIX"
"$REPO_DIR/target/debug/gelectron$EXE_SUFFIX"
)
for c in "${CANDIDATES[@]}"; do
if [[ -f "$c" ]]; then BIN="$c"; break; fi
done
fi
if [[ -z "$BIN" && "$BUILD" == "1" ]]; then
log "Building gelectron (release)..."
cargo build --release --manifest-path "$REPO_DIR/Cargo.toml" -p gelectron
BIN="$REPO_DIR/target/release/gelectron$EXE_SUFFIX"
fi
# A --binary path may omit the platform extension (e.g. CI passes the target
# path before the .exe suffix is appended).
if [[ -n "$BIN" && ! -f "$BIN" && -n "$EXE_SUFFIX" && -f "$BIN$EXE_SUFFIX" ]]; then
BIN="$BIN$EXE_SUFFIX"
fi
if [[ -z "$BIN" || ! -f "$BIN" ]]; then
echo "error: gelectron binary not found. Build it first (cargo build --release -p gelectron) or pass --binary." >&2
exit 1
fi
BIN="$(cd "$(dirname "$BIN")" && pwd)/$(basename "$BIN")"
# ── Stage the payload ───────────────────────────────────────────────────────
STAGE="$(mktemp -d "${TMPDIR:-/tmp}/gelectron-release.XXXXXX")"
trap 'rm -rf "$STAGE"' EXIT
PAYLOAD="$STAGE/payload"
mkdir -p "$PAYLOAD"
log "Copying binary ($(basename "$BIN"))..."
install -m 755 "$BIN" "$PAYLOAD/gelectron$EXE_SUFFIX"
COMPAT_SRC="$REPO_DIR/src/electron"
if [[ ! -d "$COMPAT_SRC" || -z "$(ls "$COMPAT_SRC"/*.js 2>/dev/null)" ]]; then
echo "error: compat layer not found in $COMPAT_SRC" >&2
exit 1
fi
log "Copying compat layer..."
mkdir -p "$PAYLOAD/compat"
install -m 644 "$COMPAT_SRC"/*.js "$PAYLOAD/compat/"
# Ad-hoc sign so the binary actually runs on arm64 Macs (unsigned binaries
# are killed by the kernel). No certificate required.
if [[ "$PLATFORM" == "darwin" ]]; then
if command -v codesign >/dev/null 2>&1; then
log "Ad-hoc signing binary..."
codesign --force --sign - "$PAYLOAD/gelectron" 2>/dev/null || echo " (warning: codesign failed)"
fi
fi
# ── Archive ─────────────────────────────────────────────────────────────────
mkdir -p "$OUT_DIR"
ARCHIVE_NAME="gelectron-$VERSION-$PLATFORM-$ARCH.$ARCHIVE_EXT"
ARCHIVE_PATH="$OUT_DIR/$ARCHIVE_NAME"
if [[ "$ARCHIVE_EXT" == "zip" ]]; then
log "Creating $ARCHIVE_NAME..."
if command -v powershell >/dev/null 2>&1; then
powershell -NoProfile -Command "Compress-Archive -Path '$PAYLOAD/*' -DestinationPath '$ARCHIVE_PATH' -Force"
else
(cd "$PAYLOAD" && zip -qr "$ARCHIVE_PATH" .)
fi
else
log "Creating $ARCHIVE_NAME..."
(cd "$PAYLOAD" && tar -czf "$ARCHIVE_PATH" .)
fi
echo
log "Created installer: $ARCHIVE_PATH"
sha256sum "$ARCHIVE_PATH" 2>/dev/null || shasum -a 256 "$ARCHIVE_PATH" 2>/dev/null || true
+90
View File
@@ -0,0 +1,90 @@
; Gelectron Windows installer (NSIS)
;
; Build with makensis from a staging directory containing:
; gelectron.exe
; compat\*.js
;
; makensis /DVERSION=0.1.1 /DARCH=x64 Gelectron.nsi
;
; Installs to $PROGRAMFILES64\Gelectron, adds it to the machine PATH, creates
; Start Menu + Add/Remove Programs entries, and writes an uninstaller.
!include "MUI2.nsh"
!include "WordFunc.nsh"
!ifndef VERSION
!define VERSION "0.0.0"
!endif
!ifndef ARCH
!define ARCH "x64"
!endif
!define APPNAME "Gelectron"
!define COMPANY "gelectron"
!define ENV_KEY 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}"
Name "${APPNAME}"
OutFile "Gelectron-${VERSION}-${ARCH}.exe"
InstallDir "$PROGRAMFILES64\${APPNAME}"
InstallDirRegKey HKLM "Software\${APPNAME}" "InstallDir"
RequestExecutionLevel admin
; ── Modern UI ──────────────────────────────────────────────────────────────
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
; ── Install section ─────────────────────────────────────────────────────────
Section "Install"
SetShellVarContext all
SetOutPath "$INSTDIR"
File "gelectron.exe"
File /r "compat"
WriteUninstaller "$INSTDIR\Uninstall.exe"
CreateDirectory "$SMPROGRAMS\${APPNAME}"
CreateShortcut "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" "$INSTDIR\gelectron.exe"
WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${APPNAME}"
WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${VERSION}"
WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${COMPANY}"
WriteRegStr HKLM "${UNINST_KEY}" "InstallLocation" "$INSTDIR"
WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" '"$INSTDIR\Uninstall.exe"'
WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\gelectron.exe"
; Add $INSTDIR to the machine PATH (add-if-not-present via WordFunc)
ReadRegStr $0 ${ENV_KEY} "Path"
${WordAdd} $0 ";" "+$INSTDIR" $1
WriteRegExpandStr ${ENV_KEY} "Path" $1
SendMessage ${HWND_BROADCAST} ${WM_SETTINGCHANGE} 0 "STR:Environment"
SectionEnd
; ── Uninstall section ───────────────────────────────────────────────────────
Section "Uninstall"
SetShellVarContext all
; Remove $INSTDIR from the machine PATH
ReadRegStr $0 ${ENV_KEY} "Path"
${WordAdd} $0 ";" "-$INSTDIR" $1
WriteRegExpandStr ${ENV_KEY} "Path" $1
SendMessage ${HWND_BROADCAST} ${WM_SETTINGCHANGE} 0 "STR:Environment"
Delete "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk"
RMDir "$SMPROGRAMS\${APPNAME}"
DeleteRegKey HKLM "${UNINST_KEY}"
DeleteRegKey HKLM "Software\${APPNAME}"
RMDir /r "$INSTDIR"
SectionEnd
+142
View File
@@ -0,0 +1,142 @@
#!/usr/bin/env bash
#
# Build the Linux AppImage: bundles the gelectron binary, the compat layer
# and a Node.js runtime into a single self-contained, downloadable AppImage.
#
# Gelectron-<version>-x86_64.AppImage
#
# Requires: the gelectron binary (built against webkit2gtk-4.1), compat layer,
# and network access (downloads Node.js + appimagetool).
#
# Usage:
# scripts/pkg/make-appimage.sh -v VERSION [--binary PATH] [--compat DIR] [-o DIR]
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
NODE_VERSION="20.18.1"
VERSION=""
BINARY=""
COMPAT="$REPO_DIR/src/electron"
OUT_DIR="$REPO_DIR/dist"
usage() {
cat <<'EOF'
Linux AppImage builder
Usage:
scripts/pkg/make-appimage.sh -v VERSION [--binary PATH] [--compat DIR] [-o DIR]
Options:
-v, --version VER Version string (e.g. 0.1.1)
-b, --binary PATH Path to the gelectron binary
-c, --compat DIR Path to the compat layer (default: <repo>/src/electron)
-o, --out DIR Output directory (default: <repo>/dist)
-h, --help Show this help
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
-v|--version) VERSION="${2:-}"; shift 2 ;;
-b|--binary) BINARY="${2:-}"; shift 2 ;;
-c|--compat) COMPAT="${2:-}"; shift 2 ;;
-o|--out) OUT_DIR="${2:-}"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "error: unknown option: $1" >&2; usage; exit 1 ;;
esac
done
VERSION="${VERSION#v}"
[[ -n "$VERSION" ]] || { echo "error: --version required" >&2; exit 1; }
if [[ -z "$BINARY" ]]; then
BINARY="$REPO_DIR/target/release/gelectron"
fi
[[ -f "$BINARY" ]] || { echo "error: binary not found: $BINARY" >&2; exit 1; }
[[ -d "$COMPAT" ]] || { echo "error: compat dir not found: $COMPAT" >&2; exit 1; }
log() { echo "==> $*"; }
STAGE="$(mktemp -d "${TMPDIR:-/tmp}/gelectron-appimage.XXXXXX")"
trap 'rm -rf "$STAGE"' EXIT
APPDIR="$STAGE/Gelectron.AppDir"
mkdir -p "$APPDIR/usr/bin"
# ── Binaries ────────────────────────────────────────────────────────────────
log "Staging binary..."
install -m 755 "$BINARY" "$APPDIR/usr/bin/gelectron"
log "Staging compat layer (usr/bin/compat, resolved next to the binary)..."
mkdir -p "$APPDIR/usr/bin/compat"
install -m 644 "$COMPAT"/*.js "$APPDIR/usr/bin/compat/"
# Node.js runtime (gelectron's Node mode requires node on PATH)
NODE_DIR="$STAGE/node"
if [[ ! -d "$NODE_DIR" ]]; then
log "Downloading Node.js v$NODE_VERSION..."
mkdir -p "$NODE_DIR"
NODE_ARCHIVE="$STAGE/node.tar.xz"
curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" -o "$NODE_ARCHIVE"
tar -xJf "$NODE_ARCHIVE" -C "$NODE_DIR"
fi
install -m 755 "$NODE_DIR"/node-v${NODE_VERSION}-linux-x64/bin/node "$APPDIR/usr/bin/node"
install -m 644 "$NODE_DIR"/node-v${NODE_VERSION}-linux-x64/lib/libnode.so* "$APPDIR/usr/lib/" 2>/dev/null || true
mkdir -p "$APPDIR/usr/lib"
cp "$NODE_DIR"/node-v${NODE_VERSION}-linux-x64/lib/libnode.so* "$APPDIR/usr/lib/" 2>/dev/null || true
# ── AppImage metadata ───────────────────────────────────────────────────────
cat > "$APPDIR/AppRun" <<'EOF'
#!/bin/sh
SELF="$(dirname "$(readlink -f "$0")")"
export PATH="$SELF/usr/bin:$PATH"
export LD_LIBRARY_PATH="$SELF/usr/lib:${LD_LIBRARY_PATH:-}"
export GELECTRON_NATIVE=1
exec "$SELF/usr/bin/gelectron" "$@"
EOF
chmod +x "$APPDIR/AppRun"
cat > "$APPDIR/gelectron.desktop" <<EOF
[Desktop Entry]
Type=Application
Name=Gelectron
Comment=Firefox-engine alternative to Electron, powered by Servo
Exec=gelectron
Icon=gelectron
Terminal=false
Categories=Development;
EOF
if [[ -f "$REPO_DIR/logo.png" ]]; then
install -m 644 "$REPO_DIR/logo.png" "$APPDIR/gelectron.png"
fi
# ── appimagetool ────────────────────────────────────────────────────────────
TOOL="$STAGE/appimagetool"
if [[ ! -f "$TOOL" ]]; then
log "Downloading appimagetool..."
curl -fsSL "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage" -o "$TOOL"
fi
chmod +x "$TOOL"
# ── Build ───────────────────────────────────────────────────────────────────
mkdir -p "$OUT_DIR"
APPIMAGE_NAME="Gelectron-$VERSION-x86_64.AppImage"
APPIMAGE_PATH="$OUT_DIR/$APPIMAGE_NAME"
export VERSION="$VERSION"
export ARCH="x86_64"
# Run appimagetool without FUSE (Ubuntu 24.04+ runners don't ship libfuse2)
export APPIMAGE_EXTRACT_AND_RUN=1
log "Creating $APPIMAGE_NAME ..."
"$TOOL" "$APPDIR" "$APPIMAGE_PATH" >/dev/null
echo
log "Created installer: $APPIMAGE_PATH"
sha256sum "$APPIMAGE_PATH" 2>/dev/null || shasum -a 256 "$APPIMAGE_PATH" 2>/dev/null || true
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env bash
#
# Build the macOS installer: a .pkg (installs the gelectron runtime +
# compat layer to /usr/local/bin) wrapped in a .dmg for distribution.
#
# Gelectron-<version>-<arch>.dmg
# └── Install gelectron.pkg (double-click, runs macOS Installer as root)
#
# Requires macOS (pkgbuild + hdiutil). Ad-hoc signing only — no Developer ID
# certificate, so first launch shows a Gatekeeper warning on other machines.
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
VERSION=""
ARCH=""
BINARY=""
COMPAT="$REPO_DIR/src/electron"
OUT_DIR="$REPO_DIR/dist"
usage() {
cat <<'EOF'
macOS installer builder (pkg inside DMG)
Usage:
scripts/pkg/make-dmg.sh -v VERSION -a ARCH [--binary PATH] [--compat DIR] [-o DIR]
Options:
-v, --version VER Version string (e.g. 0.1.1)
-a, --arch A arm64 | x64
-b, --binary PATH Path to the gelectron binary
-c, --compat DIR Path to the compat layer (default: <repo>/src/electron)
-o, --out DIR Output directory (default: <repo>/dist)
-h, --help Show this help
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
-v|--version) VERSION="${2:-}"; shift 2 ;;
-a|--arch) ARCH="${2:-}"; shift 2 ;;
-b|--binary) BINARY="${2:-}"; shift 2 ;;
-c|--compat) COMPAT="${2:-}"; shift 2 ;;
-o|--out) OUT_DIR="${2:-}"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "error: unknown option: $1" >&2; usage; exit 1 ;;
esac
done
[[ -n "$VERSION" ]] || { echo "error: --version required" >&2; exit 1; }
VERSION="${VERSION#v}"
[[ -n "$ARCH" ]] || { echo "error: --arch required" >&2; exit 1; }
case "$ARCH" in arm64|x64) ;; *) echo "error: unsupported arch: $ARCH" >&2; exit 1 ;; esac
if [[ -z "$BINARY" ]]; then
BINARY="$REPO_DIR/target/release/gelectron"
fi
[[ -f "$BINARY" ]] || { echo "error: binary not found: $BINARY" >&2; exit 1; }
[[ -d "$COMPAT" ]] || { echo "error: compat dir not found: $COMPAT" >&2; exit 1; }
log() { echo "==> $*"; }
STAGE="$(mktemp -d "${TMPDIR:-/tmp}/gelectron-dmg.XXXXXX")"
trap 'rm -rf "$STAGE"' EXIT
# ── Payload root (what pkgbuild installs into /usr/local/bin) ─────────────
PAYLOAD="$STAGE/gelectron"
mkdir -p "$PAYLOAD"
log "Staging payload..."
install -m 755 "$BINARY" "$PAYLOAD/gelectron"
mkdir -p "$PAYLOAD/compat"
install -m 644 "$COMPAT"/*.js "$PAYLOAD/compat/"
if command -v codesign >/dev/null 2>&1; then
log "Ad-hoc signing binary..."
codesign --force --sign - "$PAYLOAD/gelectron" 2>/dev/null || echo " (warning: codesign failed)"
fi
# ── Component package ───────────────────────────────────────────────────────
PKG="$STAGE/Install gelectron.pkg"
log "Building package (installs to /usr/local/bin)..."
pkgbuild \
--root "$PAYLOAD" \
--identifier "com.gelectron.runtime.$ARCH" \
--version "$VERSION" \
--ownership recommended \
--install-location /usr/local/bin \
"$PKG"
# ── DMG ────────────────────────────────────────────────────────────────────
mkdir -p "$OUT_DIR"
DMG_NAME="Gelectron-$VERSION-$ARCH.dmg"
DMG_PATH="$OUT_DIR/$DMG_NAME"
DMG_STAGE="$STAGE/dmg"
mkdir -p "$DMG_STAGE"
cp "$PKG" "$DMG_STAGE/"
log "Creating $DMG_NAME ..."
hdiutil create \
-volname "Gelectron $VERSION" \
-srcfolder "$DMG_STAGE" \
-format UDZO \
-ov \
"$DMG_PATH" >/dev/null
echo
log "Created installer: $DMG_PATH"
shasum -a 256 "$DMG_PATH"
+58
View File
@@ -0,0 +1,58 @@
# Gelectron Windows installer builder (NSIS)
#
# Stages the payload and compiles the installer EXE with makensis:
# Gelectron-<version>-<arch>.exe
#
# Usage:
# powershell -File scripts/pkg/make-exe.ps1 `
# -Bin target\release\gelectron.exe `
# -Compat src\electron `
# -Version 0.1.1 -Arch x64 -Out dist
#
# Requires makensis on PATH (install via: choco install nsis -y)
param(
[Parameter(Mandatory = $true)][string]$Bin,
[Parameter(Mandatory = $true)][string]$Compat,
[Parameter(Mandatory = $true)][string]$Version,
[string]$Arch = "x64",
[string]$Out = "dist",
[string]$NsiPath = "scripts/pkg/gelectron.nsi"
)
$ErrorActionPreference = "Stop"
if (-not (Get-Command makensis -ErrorAction SilentlyContinue)) {
throw "makensis not found. Install NSIS first (choco install nsis -y)."
}
$RealityVersion = $Version.TrimStart("v")
$Stage = Join-Path $env:TEMP ("gelectron-nsis-" + [Guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $Stage -Force | Out-Null
try {
Copy-Item $Bin (Join-Path $Stage "gelectron.exe") -Force
Copy-Item -Recurse -Force $Compat (Join-Path $Stage "compat")
Copy-Item $NsiPath (Join-Path $Stage "gelectron.nsi") -Force
Write-Host "==> Compiling installer (makensis)..."
Push-Location $Stage
try {
& makensis "-DVERSION=$RealityVersion" "-DARCH=$Arch" gelectron.nsi
if ($LASTEXITCODE -ne 0) { throw "makensis failed with exit code $LASTEXITCODE" }
}
finally {
Pop-Location
}
$Installer = Get-ChildItem $Stage -Filter "Gelectron-*.exe" | Select-Object -First 1
if (-not $Installer) { throw "makensis produced no installer" }
New-Item -ItemType Directory -Path $Out -Force | Out-Null
Copy-Item $Installer.FullName (Join-Path $Out $Installer.Name) -Force
Write-Host ""
Write-Host "==> Created installer: $(Join-Path $Out $Installer.Name)"
}
finally {
Remove-Item -Recurse -Force $Stage -ErrorAction SilentlyContinue
}