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

on:
workflow_dispatch:
inputs:
upload_release:
description: 'Upload as a GitHub Release (requires a version tag)'
type: boolean
default: false
push:
branches:
- windows-offline-installer
tags:
- 'linux-cli-v*'

jobs:
build:
name: Build installer (${{ matrix.arch }})
strategy:
fail-fast: false
matrix:
include:
- arch: x64
runner: windows-latest
- arch: arm64
runner: windows-11-arm
runs-on: ${{ matrix.runner }}
continue-on-error: ${{ matrix.arch == 'arm64' }}

steps:
- uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
architecture: ${{ matrix.arch }}

- name: Install dependencies and compile TypeScript
shell: pwsh
run: |
npm ci
npm run build

- name: Stage installer bundle
shell: pwsh
run: |
& windows-installer/stage.ps1 `
-Arch "${{ matrix.arch }}" `
-NodeVersion "22.15.1"
if ($LASTEXITCODE -gt 1) { exit $LASTEXITCODE }
exit 0

- name: Install NSIS
shell: pwsh
run: choco install nsis --no-progress -y

- name: Install ImageMagick
shell: pwsh
run: choco install imagemagick --no-progress -y

- name: Generate installer branding assets
shell: pwsh
run: windows-installer/prepare-branding.ps1

- name: Build NSIS installer
shell: pwsh
run: |
$version = (Get-Content package.json | ConvertFrom-Json).version
Set-Location windows-installer
New-Item -ItemType Directory -Force output | Out-Null

$makeNsis = $null
$candidates = @(
"$env:ChocolateyInstall\bin\makensis.exe",
"$env:ProgramData\chocolatey\bin\makensis.exe",
"$env:ProgramFiles\NSIS\makensis.exe",
"C:\Program Files (x86)\NSIS\makensis.exe"
)

foreach ($candidate in $candidates) {
if (Test-Path $candidate) {
$makeNsis = $candidate
break
}
}

if (-not $makeNsis) {
$cmd = Get-Command makensis -ErrorAction SilentlyContinue
if ($cmd) { $makeNsis = $cmd.Source }
}

if (-not $makeNsis) {
throw "makensis.exe not found after NSIS install"
}

& $makeNsis /DARCH=${{ matrix.arch }} /DPRODUCT_VERSION=$version installer.nsi

- name: Upload installer artifact
uses: actions/upload-artifact@v4
with:
name: edge-impulse-linux-cli-windows-${{ matrix.arch }}
path: windows-installer/output/edge-impulse-linux-cli-windows-${{ matrix.arch }}-setup.exe
if-no-files-found: error

- name: Upload to GitHub Release
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && inputs.upload_release)
uses: softprops/action-gh-release@v2
with:
files: windows-installer/output/edge-impulse-linux-cli-windows-${{ matrix.arch }}-setup.exe
tag_name: ${{ github.ref_name }}
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,48 @@ Add the library to your application via:
$ npm install edge-impulse-linux
```

## Windows offline installer (for locked-down environments)

For corporate-managed Windows devices where `npm install` is blocked (TLS interception, no build tools, restricted package access), this repo also supports a prebuilt Windows installer artifact via GitHub Actions.

### What this installer includes

* Bundled `node.exe` runtime (no separate Node.js install required)
* Prebuilt `node_modules` from CI (no local `node-gyp` / Python toolchain required)
* Installed CLI shims in PATH:
* `edge-impulse-linux`
* `edge-impulse-linux-runner`
* `edge-impulse-camera-debug`

### End-user requirements

* Windows 10/11 (`x64` or `arm64` artifact)
* Administrator rights to install (writes to `Program Files` and system PATH)
* WSL is not required for installation, but recommended for full Linux CLI behavior

### Important runtime note

This is still the Linux CLI package, packaged for Windows installation. Some commands or hardware flows that depend on Linux-specific behavior or drivers may still require Linux/WSL at runtime.

For full functionality, install WSL first from an elevated Command Prompt:

```
wsl --install
```

### Build and download installer artifacts

Use the workflow in this repository:

* **Actions** → **Build Windows Linux-CLI Installer**

Artifacts produced:

* `edge-impulse-linux-cli-windows-x64`
* `edge-impulse-linux-cli-windows-arm64`

Each artifact zip contains a `.exe` installer.

## Collecting data

Before you can classify data you'll first need to collect it. If you want to collect data from the camera or microphone on your system you can use the Edge Impulse CLI, and if you want to collect data from different sensors (like accelerometers or proprietary control systems) you can do so in a few lines of code.
Expand Down
3 changes: 3 additions & 0 deletions windows-installer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
staging/
output/
branding/
131 changes: 131 additions & 0 deletions windows-installer/installer.nsi
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
; Edge Impulse Linux CLI – Windows Installer

Unicode true
SetCompressor /SOLID lzma

!ifndef PRODUCT_VERSION
!define PRODUCT_VERSION "0.0.0"
!endif
!ifndef ARCH
!define ARCH "x64"
!endif

!define PRODUCT_NAME "Edge Impulse Linux CLI"
!define PRODUCT_PUBLISHER "EdgeImpulse Inc."
!define PRODUCT_URL "https://edgeimpulse.com"
!define UNINSTALL_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\EdgeImpulseLinuxCLI"
!define STAGING_DIR "staging"
!define OUTPUT_DIR "output"
!define BRAND_HEADER_BMP "branding\header.bmp"
!define BRAND_WELCOME_BMP "branding\welcome.bmp"

!include "MUI2.nsh"
!include "x64.nsh"
!include "WinMessages.nsh"
!include "FileFunc.nsh"

!define MUI_ABORTWARNING
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_RIGHT
!define MUI_HEADERIMAGE_BITMAP "${BRAND_HEADER_BMP}"
!define MUI_WELCOMEFINISHPAGE_BITMAP "${BRAND_WELCOME_BMP}"

!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "${STAGING_DIR}\LICENSE.txt"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH

!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES

!insertmacro MUI_LANGUAGE "English"

Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
OutFile "${OUTPUT_DIR}\edge-impulse-linux-cli-windows-${ARCH}-setup.exe"
InstallDir "$PROGRAMFILES64\EdgeImpulse Linux CLI"
InstallDirRegKey HKLM "${UNINSTALL_KEY}" "InstallLocation"
RequestExecutionLevel admin
ShowInstDetails show
ShowUnInstDetails show

VIProductVersion "${PRODUCT_VERSION}.0"
VIAddVersionKey "ProductName" "${PRODUCT_NAME}"
VIAddVersionKey "CompanyName" "${PRODUCT_PUBLISHER}"
VIAddVersionKey "FileVersion" "${PRODUCT_VERSION}"
VIAddVersionKey "ProductVersion" "${PRODUCT_VERSION}"
VIAddVersionKey "FileDescription" "${PRODUCT_NAME} Installer"

Section "Edge Impulse Linux CLI (required)" SecMain
SectionIn RO

SetOutPath "$INSTDIR"
File "${STAGING_DIR}\node.exe"
File "${STAGING_DIR}\LICENSE.txt"
File "${STAGING_DIR}\package.json"
File /r "${STAGING_DIR}\build"
File /r "${STAGING_DIR}\node_modules"

SetOutPath "$INSTDIR\bin"
File "${STAGING_DIR}\bin\edge-impulse-linux.cmd"
File "${STAGING_DIR}\bin\edge-impulse-linux-runner.cmd"
File "${STAGING_DIR}\bin\edge-impulse-camera-debug.cmd"

FileOpen $R0 "$TEMP\_ei_addpath.ps1" w
FileWrite $R0 "$$binDir = '$INSTDIR\bin'$\r$\n"
FileWrite $R0 "$$key = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'$\r$\n"
FileWrite $R0 "$$cur = (Get-ItemProperty -Path $$key -Name Path).Path$\r$\n"
FileWrite $R0 "$$parts = $$cur -split ';' | Where-Object { $$_ -ne '' }$\r$\n"
FileWrite $R0 "if ($$parts -notcontains $$binDir) {$\r$\n"
FileWrite $R0 " Set-ItemProperty -Path $$key -Name Path -Value (($$parts + $$binDir) -join ';')$\r$\n"
FileWrite $R0 "}$\r$\n"
FileClose $R0

nsExec::ExecToLog "powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File $\"$TEMP\_ei_addpath.ps1$\""
Pop $R1
Delete "$TEMP\_ei_addpath.ps1"

SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000

WriteRegStr HKLM "${UNINSTALL_KEY}" "DisplayName" "${PRODUCT_NAME}"
WriteRegStr HKLM "${UNINSTALL_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "${UNINSTALL_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
WriteRegStr HKLM "${UNINSTALL_KEY}" "URLInfoAbout" "${PRODUCT_URL}"
WriteRegStr HKLM "${UNINSTALL_KEY}" "InstallLocation" "$INSTDIR"
WriteRegStr HKLM "${UNINSTALL_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegDWORD HKLM "${UNINSTALL_KEY}" "NoModify" 1
WriteRegDWORD HKLM "${UNINSTALL_KEY}" "NoRepair" 1

${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
WriteRegDWORD HKLM "${UNINSTALL_KEY}" "EstimatedSize" "$0"

WriteUninstaller "$INSTDIR\uninstall.exe"
SectionEnd

Section "Uninstall"
FileOpen $R0 "$TEMP\_ei_rmpath.ps1" w
FileWrite $R0 "$$binDir = '$INSTDIR\bin'$\r$\n"
FileWrite $R0 "$$key = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'$\r$\n"
FileWrite $R0 "$$cur = (Get-ItemProperty -Path $$key -Name Path).Path$\r$\n"
FileWrite $R0 "$$parts = $$cur -split ';' | Where-Object { $$_ -ne '' -and $$_ -ne $$binDir }$\r$\n"
FileWrite $R0 "Set-ItemProperty -Path $$key -Name Path -Value ($$parts -join ';')$\r$\n"
FileClose $R0

nsExec::ExecToLog "powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File $\"$TEMP\_ei_rmpath.ps1$\""
Pop $R1
Delete "$TEMP\_ei_rmpath.ps1"

SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000

RMDir /r "$INSTDIR\build"
RMDir /r "$INSTDIR\node_modules"
RMDir /r "$INSTDIR\bin"
Delete "$INSTDIR\node.exe"
Delete "$INSTDIR\LICENSE.txt"
Delete "$INSTDIR\package.json"
Delete "$INSTDIR\uninstall.exe"
RMDir "$INSTDIR"

DeleteRegKey HKLM "${UNINSTALL_KEY}"
SectionEnd
45 changes: 45 additions & 0 deletions windows-installer/prepare-branding.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[CmdletBinding()]
param()

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$repoRoot = Resolve-Path "$PSScriptRoot\.."
$sourceImage = Join-Path $repoRoot "img\linux-collection.png"
$brandingDir = Join-Path $PSScriptRoot "branding"
$headerBmp = Join-Path $brandingDir "header.bmp"
$welcomeBmp = Join-Path $brandingDir "welcome.bmp"

if (-not (Test-Path $sourceImage)) {
throw "Branding source image not found: $sourceImage"
}

New-Item -ItemType Directory -Force -Path $brandingDir | Out-Null

$magick = Get-Command magick -ErrorAction SilentlyContinue
if (-not $magick) {
throw "ImageMagick (magick) is required to generate NSIS branding bitmaps"
}

Write-Host "Generating NSIS branding bitmaps from $sourceImage"

# NSIS header image: 150x57
& $magick.Source "$sourceImage" `
-background white -gravity center `
-resize 150x57 `
-extent 150x57 `
BMP3:"$headerBmp"

# NSIS welcome/finish side image: 164x314
& $magick.Source "$sourceImage" `
-background white -gravity center `
-resize 164x314 `
-extent 164x314 `
BMP3:"$welcomeBmp"

if (-not (Test-Path $headerBmp) -or -not (Test-Path $welcomeBmp)) {
throw "Failed to generate branding assets"
}

Write-Host "Branding assets generated:"
Get-ChildItem $brandingDir | Format-Table Name, Length -AutoSize
Loading
Loading